├── .commitlintrc.json ├── .editorconfig ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── commitlint.yml │ ├── nodejs.yml │ └── npm-publish.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .markdownlint.json ├── .mocharc.js ├── .npmignore ├── .npmrc ├── .prettierrc.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── SECURITY.md ├── examples ├── repository-mode │ ├── index.ts │ ├── lib │ │ ├── app.ts │ │ ├── database │ │ │ └── sequelize.ts │ │ ├── posts │ │ │ ├── Post.ts │ │ │ └── postRouterFactory.ts │ │ ├── server.ts │ │ └── users │ │ │ ├── User.ts │ │ │ └── userRouterFactory.ts │ ├── package-lock.json │ └── package.json ├── simple │ ├── index.ts │ ├── lib │ │ ├── app.ts │ │ ├── database │ │ │ └── sequelize.ts │ │ ├── posts │ │ │ ├── Post.ts │ │ │ └── postRouterFactory.ts │ │ ├── server.ts │ │ └── users │ │ │ ├── User.ts │ │ │ └── userRouterFactory.ts │ ├── package-lock.json │ └── package.json └── tsconfig.json ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── associations │ ├── alias-inference │ │ └── alias-inference-service.ts │ ├── belongs-to-many │ │ ├── belongs-to-many-association.ts │ │ ├── belongs-to-many-options.ts │ │ └── belongs-to-many.ts │ ├── belongs-to │ │ ├── belongs-to-association.ts │ │ └── belongs-to.ts │ ├── foreign-key │ │ ├── foreign-key-meta.ts │ │ ├── foreign-key-service.ts │ │ └── foreign-key.ts │ ├── has │ │ ├── has-association.ts │ │ ├── has-many.ts │ │ └── has-one.ts │ ├── shared │ │ ├── association-service.ts │ │ ├── association.ts │ │ ├── base-association.ts │ │ └── union-association-options.ts │ └── through │ │ └── through-options.ts ├── browser │ └── index.ts ├── hooks │ ├── bulk │ │ ├── after │ │ │ ├── after-bulk-create.ts │ │ │ ├── after-bulk-destroy.ts │ │ │ ├── after-bulk-restore.ts │ │ │ ├── after-bulk-sync.ts │ │ │ └── after-bulk-update.ts │ │ └── before │ │ │ ├── before-bulk-create.ts │ │ │ ├── before-bulk-destroy.ts │ │ │ ├── before-bulk-restore.ts │ │ │ ├── before-bulk-sync.ts │ │ │ └── before-bulk-update.ts │ ├── shared │ │ ├── hook-meta.ts │ │ ├── hook-options.ts │ │ ├── hooks-service.ts │ │ └── validation-failed.ts │ └── single │ │ ├── after │ │ ├── after-connect.ts │ │ ├── after-create.ts │ │ ├── after-define.ts │ │ ├── after-destroy.ts │ │ ├── after-find.ts │ │ ├── after-init.ts │ │ ├── after-restore.ts │ │ ├── after-save.ts │ │ ├── after-sync.ts │ │ ├── after-update.ts │ │ ├── after-upsert.ts │ │ └── after-validate.ts │ │ └── before │ │ ├── before-connect.ts │ │ ├── before-count.ts │ │ ├── before-create.ts │ │ ├── before-define.ts │ │ ├── before-destroy.ts │ │ ├── before-find-after-expand-include-all.ts │ │ ├── before-find-after-options.ts │ │ ├── before-find.ts │ │ ├── before-init.ts │ │ ├── before-restore.ts │ │ ├── before-save.ts │ │ ├── before-sync.ts │ │ ├── before-update.ts │ │ ├── before-upsert.ts │ │ └── before-validate.ts ├── index.ts ├── model │ ├── column │ │ ├── attribute-service.ts │ │ ├── column-options │ │ │ ├── allow-null.ts │ │ │ ├── comment.ts │ │ │ ├── default.ts │ │ │ └── unique.ts │ │ ├── column.ts │ │ ├── primary-key │ │ │ ├── auto-increment.ts │ │ │ └── primary-key.ts │ │ └── timestamps │ │ │ ├── created-at.ts │ │ │ ├── deleted-at.ts │ │ │ └── updated-at.ts │ ├── index │ │ ├── create-index-decorator.ts │ │ ├── index-decorator.ts │ │ └── index-service.ts │ ├── model │ │ ├── association │ │ │ ├── association-action-options.ts │ │ │ ├── association-count-options.ts │ │ │ ├── association-create-options.ts │ │ │ └── association-get-options.ts │ │ └── model.ts │ ├── shared │ │ ├── model-class-getter.ts │ │ ├── model-not-initialized-error.ts │ │ └── model-service.ts │ └── table │ │ ├── table-options.ts │ │ └── table.ts ├── scopes │ ├── default-scope.ts │ ├── scope-find-options.ts │ ├── scope-options.ts │ ├── scope-service.ts │ ├── scope-table-options.ts │ └── scopes.ts ├── sequelize │ ├── data-type │ │ ├── data-type-service.ts │ │ └── data-type.ts │ ├── repository │ │ └── repository.ts │ ├── sequelize │ │ ├── sequelize-options.ts │ │ ├── sequelize-service.ts │ │ └── sequelize.ts │ └── validation-only │ │ └── db-dialect-dummy.ts ├── shared │ ├── array.ts │ ├── object.ts │ ├── string.ts │ └── types.ts └── validation │ ├── contains.ts │ ├── equals.ts │ ├── is-after.ts │ ├── is-alpha.ts │ ├── is-alphanumeric.ts │ ├── is-array.ts │ ├── is-before.ts │ ├── is-credit-card.ts │ ├── is-date.ts │ ├── is-decimal.ts │ ├── is-email.ts │ ├── is-float.ts │ ├── is-in.ts │ ├── is-int.ts │ ├── is-ip-v4.ts │ ├── is-ip-v6.ts │ ├── is-ip.ts │ ├── is-lowercase.ts │ ├── is-null.ts │ ├── is-numeric.ts │ ├── is-uppercase.ts │ ├── is-url.ts │ ├── is-uuid.ts │ ├── is.ts │ ├── length.ts │ ├── max.ts │ ├── min.ts │ ├── not-contains.ts │ ├── not-empty.ts │ ├── not-in.ts │ ├── not-null.ts │ ├── not.ts │ ├── validate.ts │ └── validator.ts ├── test ├── models │ ├── Book.ts │ ├── Box.ts │ ├── Hook.ts │ ├── Manufacturer.ts │ ├── Page.ts │ ├── Person.ts │ ├── Player.ts │ ├── Shoe.ts │ ├── ShoeWithDeprecatedScopes.ts │ ├── ShoeWithScopes.ts │ ├── ShoeWithValidation.ts │ ├── Team.ts │ ├── TimeStampsUser.ts │ ├── User.ts │ ├── UserWithCreatedAtButWithoutUpdatedAt.ts │ ├── UserWithCustomUpdatedAt.ts │ ├── UserWithNoAutoIncrementation.ts │ ├── UserWithSwag.ts │ ├── UserWithValidation.ts │ ├── UserWithVersion.ts │ ├── exports │ │ ├── Game.ts │ │ ├── custom-match │ │ │ └── match.model.ts │ │ ├── gamer.model.ts │ │ └── throws │ │ │ └── _cheater.ts │ └── globs │ │ ├── match-dir-only │ │ ├── PlayerDir.ts │ │ ├── ShoeDir.ts │ │ └── TeamDir.ts │ │ ├── match-files │ │ ├── AddressDir.ts │ │ └── UserDir.ts │ │ └── match-sub-dir-files │ │ ├── players │ │ └── player.model.ts │ │ ├── shoes │ │ └── shoe.model.ts │ │ └── teams │ │ └── team.model.ts ├── specs │ ├── annotations │ │ ├── belongs-to-many.spec.ts │ │ ├── belongs-to.spec.ts │ │ ├── comment.spec.ts │ │ ├── has-many.spec.ts │ │ └── has-one.spec.ts │ ├── association.spec.ts │ ├── browser.spec.ts │ ├── hooks │ │ └── hooks.spec.ts │ ├── index.spec.ts │ ├── instance-methods.spec.ts │ ├── instance.spec.ts │ ├── model-methods.spec.ts │ ├── model.spec.ts │ ├── models │ │ ├── model.spec.ts │ │ └── sequelize.spec.ts │ ├── repository-mode.spec.ts │ ├── scopes.spec.ts │ ├── services │ │ ├── association.spec.ts │ │ └── models.spec.ts │ ├── table_column.spec.ts │ ├── unique.spec.ts │ ├── utils │ │ ├── array.spec.ts │ │ ├── data-type.spec.ts │ │ ├── object.spec.ts │ │ └── string.spec.ts │ └── validation.spec.ts ├── tsconfig.json ├── tsconfig.mocha.js ├── types │ ├── attributes.spec.ts │ ├── model.spec.ts │ └── scopes.spec.ts └── utils │ ├── association.ts │ ├── common.ts │ └── sequelize.ts └── tsconfig.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": { 4 | "body-max-line-length": [0], 5 | "type-enum": [ 6 | 2, 7 | "always", 8 | [ 9 | "build", 10 | "chore", 11 | "ci", 12 | "docs", 13 | "feat", 14 | "fix", 15 | "perf", 16 | "refactor", 17 | "revert", 18 | "style", 19 | "test", 20 | "meta" 21 | ] 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | node: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:prettier/recommended', 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2021, 13 | sourceType: 'module', 14 | }, 15 | plugins: ['prettier', '@typescript-eslint'], 16 | rules: { 17 | '@typescript-eslint/ban-types': 'off', 18 | '@typescript-eslint/no-explicit-any': 'off', 19 | '@typescript-eslint/explicit-module-boundary-types': [ 20 | 'error', 21 | { 22 | allowArgumentsExplicitlyTypedAsAny: true, 23 | }, 24 | ], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # For more information see: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: 'github-actions' 6 | directory: '/' 7 | schedule: 8 | interval: 'daily' 9 | 10 | - package-ecosystem: 'npm' 11 | directory: '/' 12 | schedule: 13 | interval: 'daily' 14 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | # For more information see: https://github.com/marketplace/actions/commit-linter 2 | 3 | name: 'Lint Commit Messages' 4 | 5 | on: 6 | push: 7 | branches: [master] 8 | pull_request: 9 | branches: [master] 10 | 11 | jobs: 12 | commitlint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: 'actions/checkout@v3.1.0' 16 | with: 17 | fetch-depth: 0 18 | - uses: 'wagoid/commitlint-github-action@v5.3.0' 19 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 2 | 3 | name: 'Node.js CI' 4 | 5 | on: 6 | push: 7 | branches: [master] 8 | pull_request: 9 | branches: [master] 10 | 11 | jobs: 12 | build: 13 | runs-on: 'ubuntu-latest' 14 | 15 | strategy: 16 | matrix: 17 | node-version: [10.x, 12.x, 14.x, 16.x] 18 | 19 | steps: 20 | - uses: 'actions/checkout@v3.1.0' 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: 'actions/setup-node@v3.5.1' 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - run: 'npm ci' 28 | - name: Lint 29 | if: ${{ matrix.node-version != '10.x' }} 30 | run: 'npm run lint' 31 | - name: Markdownlint 32 | if: ${{ matrix.node-version != '10.x' }} 33 | run: 'npm run markdownlint' 34 | - run: 'npm run build' 35 | - run: 'DISABLE_LOGGING=1 npm run cover' 36 | - run: 'npx nyc report --reporter=lcov > coverage.lcov && npx codecov' 37 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 2 | 3 | name: 'Node.js Package' 4 | 5 | on: 6 | release: 7 | types: [created] 8 | 9 | jobs: 10 | publish-npm: 11 | runs-on: 'ubuntu-latest' 12 | steps: 13 | - uses: 'actions/checkout@v3.1.0' 14 | 15 | - uses: 'actions/setup-node@v3.5.1' 16 | with: 17 | node-version: 16 18 | registry-url: 'https://registry.npmjs.org/' 19 | 20 | - run: 'npm ci' 21 | - run: 'npm run build' 22 | - run: 'npm publish' 23 | env: 24 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # mac 2 | *.DS_Store 3 | 4 | # ide 5 | .idea 6 | .vscode 7 | 8 | # libraries 9 | node_modules 10 | 11 | #logs 12 | npm-debug.log 13 | 14 | # build 15 | dist/**/*.js 16 | dist/**/*.js.map 17 | dist/**/*.d.ts 18 | 19 | # src 20 | !src/hooks/shared/hooks.d.ts 21 | !src/model/model/model.d.ts 22 | !src/model/model/model.js 23 | !src/sequelize/sequelize/sequelize.d.ts 24 | !src/sequelize/sequelize/sequelize.js 25 | 26 | # testing 27 | coverage 28 | .nyc_output 29 | !test/tsconfig.mocha.js 30 | !.mocharc.js 31 | 32 | 33 | # lockFile 34 | *.lock 35 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npm run markdownlint 6 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD013": false, 4 | "MD024": false 5 | } 6 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {MochaSetupOptions} 3 | */ 4 | module.exports = { 5 | extension: ['ts'], 6 | require: [ 7 | 'test/tsconfig.mocha.js' 8 | ], 9 | package: './package.json', 10 | 'watch-files': ['test/**/*.spec.ts'], 11 | // 'watch-ignore': ['lib/vendor'] 12 | }; 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .travis.yml 4 | .gitignore 5 | tsconfig.json 6 | test 7 | coverage 8 | .nyc_output 9 | examples 10 | .DS_Store 11 | .idea 12 | *.ts 13 | *.js.map 14 | !*.d.ts 15 | src 16 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | singleQuote: true 4 | }; 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [2.1.6](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.5...v2.1.6) (2023-11-24) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * deny modifying the object prototype ([#1698](https://github.com/RobinBuschmann/sequelize-typescript/issues/1698)) ([5ce8afd](https://github.com/RobinBuschmann/sequelize-typescript/commit/5ce8afdd1671b08c774ce106b000605ba8fccf78)) 9 | 10 | # Changelog 11 | 12 | ## [2.1.5](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.4...v2.1.5) (2022-10-17) 13 | 14 | ### Bug Fixes 15 | 16 | - **deps:** revert to glob@7.2.0 for sequelize@6 & node@10 compatibility ([#1479](https://github.com/RobinBuschmann/sequelize-typescript/issues/1479)) ([7c8eea7](https://github.com/RobinBuschmann/sequelize-typescript/commit/7c8eea7bb7f9de5fdb03fef56afb0654808a0d18)) 17 | 18 | ## [2.1.4](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.3...v2.1.4) (2022-10-15) 19 | 20 | ### Bug Fixes 21 | 22 | - **ci:** bump markdownlint ([#1470](https://github.com/RobinBuschmann/sequelize-typescript/issues/1470)) ([b24869b](https://github.com/RobinBuschmann/sequelize-typescript/commit/b24869bc770289c37b9b74f43630aa63eab706b4)) 23 | - **model:** compatible constructor with sequelize ([#1310](https://github.com/RobinBuschmann/sequelize-typescript/issues/1310)) ([4f03520](https://github.com/RobinBuschmann/sequelize-typescript/commit/4f03520c4c3076a3d7c6ed6fc4ed76f1c06f9ef7)) 24 | - update to TypeScript 4.8 ([#1453](https://github.com/RobinBuschmann/sequelize-typescript/issues/1453)) ([5ddfa61](https://github.com/RobinBuschmann/sequelize-typescript/commit/5ddfa612de51750f0f81e1d8c7e4fc2d03824713)) 25 | 26 | ## [2.1.3](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.2...v2.1.3) (2022-02-16) 27 | 28 | ### Bug Fixes 29 | 30 | - Fix sequelize/types/lib/hooks path ([#1202](https://github.com/RobinBuschmann/sequelize-typescript/issues/1198)) ([ab45c14](https://github.com/RobinBuschmann/sequelize-typescript/commit/ab45c14da8cbd388f7611c0703e1f198e1f4541b)) 31 | 32 | ## [2.1.2](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.1...v2.1.2) (2022-01-03) 33 | 34 | ### Bug Fixes 35 | 36 | - use custom decorator on column have a property descriptor ([#1070](https://github.com/RobinBuschmann/sequelize-typescript/issues/1070)) ([7ce03de](https://github.com/RobinBuschmann/sequelize-typescript/commit/7ce03de76b465172994f41a55058ea49f3ce27c3)) 37 | - **validators:** allow any values for isIn/notIn ([#1124](https://github.com/RobinBuschmann/sequelize-typescript/issues/1124)) ([d25b392](https://github.com/RobinBuschmann/sequelize-typescript/commit/d25b39282d2a49e4e5cf286100344e7d1fda3c84)) 38 | 39 | ## [2.1.1](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.1.0...v2.1.1) (2021-10-10) 40 | 41 | ### Bug Fixes 42 | 43 | - **model:** adjust init method to recently introduced sequelize type changes ([b60c011](https://github.com/RobinBuschmann/sequelize-typescript/commit/b60c011be2e971e56cb783d4ade994965faab916)) 44 | 45 | ## [2.1.0](https://github.com/RobinBuschmann/sequelize-typescript/compare/v2.0.0-beta.1...v2.1.0) (2021-02-14) 46 | 47 | Initial release with Changelog. 48 | 49 | ### Bug Fixes 50 | 51 | - allow $set null (remove association) ([#774](https://github.com/RobinBuschmann/sequelize-typescript/issues/774)) ([ffe1c78](https://github.com/RobinBuschmann/sequelize-typescript/commit/ffe1c78df73df7f287b8ce345d6ac0df30283723)) 52 | - model associations methods to reflect sequelize v6 ([#888](https://github.com/RobinBuschmann/sequelize-typescript/issues/888)) ([6b1e3ff](https://github.com/RobinBuschmann/sequelize-typescript/commit/6b1e3fffd974f087be2e18258306f81860923ba3)) 53 | - typeof Model errors by using typeof Model generics ([#900](https://github.com/RobinBuschmann/sequelize-typescript/issues/900)) ([b865840](https://github.com/RobinBuschmann/sequelize-typescript/commit/b8658404f12e7a44893c9b8652714473bb25f495)) 54 | 55 | ### Features 56 | 57 | - infer bigint data type ([#893](https://github.com/RobinBuschmann/sequelize-typescript/issues/893)) ([7c467d4](https://github.com/RobinBuschmann/sequelize-typescript/commit/7c467d404a200b3153cc7aa2605d1e542bef3da9)) 58 | 59 | ## Older versions 60 | 61 | ### ⚠️ sequelize@5 62 | 63 | `sequelize@5` requires `sequelize-typescript@1`. See 64 | [documentation](https://github.com/RobinBuschmann/sequelize-typescript/tree/1.0.0) for version `1.0`. 65 | 66 | ```sh 67 | npm install sequelize-typescript@1.0 68 | ``` 69 | 70 | #### V5 Model definition 71 | 72 | ```typescript 73 | import { Table, Model } from 'sequelize-typescript'; 74 | 75 | @Table 76 | class Person extends Model {} 77 | ``` 78 | 79 | ### ⚠️ sequelize@4 80 | 81 | `sequelize@4` requires `sequelize-typescript@0.6`. See 82 | [documentation](https://github.com/RobinBuschmann/sequelize-typescript/tree/0.6.X) for version `0.6`. 83 | 84 | ```sh 85 | npm install sequelize-typescript@0.6 86 | ``` 87 | 88 | ### Upgrade to `sequelize-typescript@2` 89 | 90 | - `sequelize-typescript@2` only works with `sequelize@6.2>=`. 91 | For `sequelize@5` use `sequelize-typescript@1.0`. 92 | 93 | #### Breaking Changes 94 | 95 | - All breaking changes of `sequelize@6` are also valid for `sequelize-typescript@2`. 96 | See [Upgrade to v6](https://sequelize.org/master/manual/upgrade-to-v6.html) for details. 97 | - `@types/bluebird` is no longer needed, `sequelize@6` removed usage of `bluebird` 98 | - Sequelize v6.2 introduced additional model attributes typings, which affects how the model is defined. 99 | - See below comparison between V5 and V6 model definition to show how to upgrade models. 100 | - For more details, see [sequelize typescript docs](https://sequelize.org/master/manual/typescript.html). 101 | 102 | ### Upgrade to `sequelize-typescript@1` 103 | 104 | `sequelize-typescript@1` only works with `sequelize@5>=`. 105 | For `sequelize@4` & `sequelize@3` use `sequelize-typescript@0.6`. 106 | 107 | #### Breaking Changes @5 108 | 109 | All breaking changes of `sequelize@5` are also valid for `sequelize-typescript@1`. 110 | See [Upgrade to v5](https://sequelize.org/v5/manual/upgrade-to-v5.html) for details. 111 | 112 | #### Official Sequelize Typings 113 | 114 | sequelize-typescript now uses the official typings bundled with sequelize 115 | (See [this](https://sequelize.org/v5/manual/upgrade-to-v5.html#typescript-support)). 116 | Please note the following details: 117 | 118 | - Most of the sequelize-typescript interfaces of the previous version are replaced by the official ones 119 | - `@types/sequelize` is no longer used 120 | - `@types/bluebird` is no longer an explicit dependency 121 | - The official typings are less strict than the former sequelize-typescript ones -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # To contribute you can 2 | 3 | - Open issues and participate in discussion of other issues. 4 | - Fork the project to open up PR's. 5 | - Update the [types of Sequelize](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/sequelize). 6 | - Anything else constructively helpful. 7 | 8 | In order to open a pull request please: 9 | 10 | - Create a new branch. 11 | - Run tests locally (`npm install && npm run build && npm run cover`) and ensure your commits don't break the tests. 12 | - Document your work well with commit messages, a good PR description, comments in code when necessary, etc. 13 | 14 | In order to update the types for sequelize please go to [the Definitely Typed repo](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/sequelize), it would also be a good 15 | idea to open a PR into [sequelize](https://github.com/sequelize/sequelize) so that Sequelize can maintain its own types, but that 16 | might be harder than getting updated types into microsoft's repo. The Typescript team is slowly trying to encourage 17 | npm package maintainers to maintain their own typings, but Microsoft still has dedicated and good people maintaining the DT repo, 18 | accepting PR's and keeping quality high. 19 | 20 | **Keep in mind `sequelize-typescript` does not provide typings for `sequelize`** - these are seperate things. 21 | A lot of the types in `sequelize-typescript` augment, refer to, or extends what sequelize already has. 22 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # Issue 6 | 7 | ## Versions 8 | 9 | 10 | 11 | - sequelize: 12 | - sequelize-typescript: 13 | - typescript: 14 | 15 | ## Issue type 16 | 17 | 18 | 19 | - [ ] bug report 20 | - [ ] feature request 21 | 22 | ## Actual behavior 23 | 24 | 25 | 26 | ## Expected behavior 27 | 28 | 29 | 30 | ## Steps to reproduce 31 | 32 | 33 | 34 | ## Related code 35 | 36 | 42 | 43 | ```ts 44 | insert short code snippets here 45 | ``` 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Robin Buschmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported versions 4 | 5 | The following table describes the versions of this project that are currently supported with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 2.x | :heavy_check_mark: | 10 | 11 | ## Responsible disclosure policy 12 | 13 | At Sequelize, we prioritize security issues and will try to fix them as soon as they are disclosed. 14 | 15 | If you discover a security vulnerability, please create a security advisory [here](https://github.com/sequelize/sequelize-typescript/security/advisories/new). 16 | Otherwise, contact the project maintainers privately. You can find related information in [CONTACT.md](https://github.com/sequelize/sequelize/blob/main/CONTACT.md) of the core sequelize repository. 17 | -------------------------------------------------------------------------------- /examples/repository-mode/index.ts: -------------------------------------------------------------------------------- 1 | import './lib/server'; 2 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as strongErrorHandler from 'strong-error-handler'; 3 | import {json} from 'body-parser'; 4 | 5 | import {sequelize} from './database/sequelize'; 6 | import {userRouterFactory} from './users/userRouterFactory'; 7 | import {postRouterFactory} from './posts/postRouterFactory'; 8 | import {User} from './users/User'; 9 | import {Post} from './posts/Post'; 10 | 11 | const userRepository = sequelize.getRepository(User); 12 | const postRepository = sequelize.getRepository(Post); 13 | 14 | export const app = express(); 15 | 16 | app.use(json()); 17 | 18 | app.use(userRouterFactory(userRepository, postRepository)); 19 | app.use(postRouterFactory(postRepository)); 20 | 21 | app.use(strongErrorHandler({ 22 | debug: true, 23 | })); 24 | 25 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/database/sequelize.ts: -------------------------------------------------------------------------------- 1 | import {Sequelize} from 'sequelize-typescript'; 2 | 3 | import {User} from '../users/User'; 4 | import {Post} from '../posts/Post'; 5 | 6 | export const sequelize = new Sequelize({ 7 | dialect: 'sqlite', 8 | storage: ':memory:', 9 | models: [User, Post], 10 | repositoryMode: true, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/posts/Post.ts: -------------------------------------------------------------------------------- 1 | import {Model, Table, Column, ForeignKey, BelongsTo} from 'sequelize-typescript'; 2 | 3 | import {User} from '../users/User'; 4 | 5 | @Table 6 | export class Post extends Model { 7 | 8 | @Column text!: string; 9 | @ForeignKey(() => User) @Column userId!: number; 10 | @BelongsTo(() => User) user: User; 11 | } 12 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/posts/postRouterFactory.ts: -------------------------------------------------------------------------------- 1 | import {Router} from "express"; 2 | import {Repository} from "sequelize-typescript"; 3 | import {Post} from "./Post"; 4 | 5 | export const postRouterFactory = ( 6 | postRepository: Repository, 7 | ) => Router() 8 | 9 | .get('/posts', (req, res, next) => 10 | postRepository.findAll() 11 | .then(posts => res.json(posts)) 12 | .catch(next) 13 | ) 14 | 15 | .get('/posts/:id', (req, res, next) => 16 | postRepository.findByPk(req.params.id) 17 | .then(post => post 18 | ? res.json(post) 19 | : next({statusCode: 404})) 20 | .catch(next) 21 | ) 22 | 23 | .post('/posts', (req, res, next) => 24 | postRepository.create(req.body) 25 | .then(post => res.json(post)) 26 | .catch(next) 27 | ) 28 | 29 | ; 30 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/server.ts: -------------------------------------------------------------------------------- 1 | import {createServer} from 'http'; 2 | import {app} from './app'; 3 | import {sequelize} from './database/sequelize'; 4 | 5 | const port = process.env.PORT || 5000; 6 | 7 | (async () => { 8 | 9 | await sequelize.sync({force: true}); 10 | 11 | createServer(app) 12 | .listen(port, () => console.log(`Server listen on port ${port}`)); 13 | 14 | })(); 15 | 16 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/users/User.ts: -------------------------------------------------------------------------------- 1 | import {Model, Table, Column, HasMany} from 'sequelize-typescript'; 2 | 3 | import {Post} from '../posts/Post'; 4 | 5 | @Table 6 | export class User extends Model { 7 | 8 | @Column name!: string; 9 | @HasMany(() => Post) posts: Post[]; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /examples/repository-mode/lib/users/userRouterFactory.ts: -------------------------------------------------------------------------------- 1 | import {Router} from 'express'; 2 | import {Repository} from 'sequelize-typescript'; 3 | 4 | import {User} from './User'; 5 | import {Post} from '../posts/Post'; 6 | 7 | export const userRouterFactory = ( 8 | userRepository: Repository, 9 | postRepository: Repository, 10 | ) => Router() 11 | 12 | .get('/users', (req, res, next) => 13 | userRepository.findAll({include: [postRepository]}) 14 | .then(users => res.json(users)) 15 | .catch(next) 16 | ) 17 | 18 | .get('/users/:id', (req, res, next) => 19 | userRepository.findByPk(req.params.id) 20 | .then(user => user 21 | ? res.json(user) 22 | : next({statusCode: 404})) 23 | .catch(next) 24 | ) 25 | 26 | .post('/users', (req, res, next) => 27 | userRepository.create(req.body) 28 | .then(user => res.json(user)) 29 | .catch(next) 30 | ) 31 | 32 | ; 33 | -------------------------------------------------------------------------------- /examples/repository-mode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repository-mode", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node -r ts-node/register index.ts" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/body-parser": "^1.17.0", 13 | "@types/express": "^4.16.0", 14 | "@types/strong-error-handler": "^2.3.0", 15 | "ts-node": "^7.0.1" 16 | }, 17 | "dependencies": { 18 | "body-parser": "^1.18.3", 19 | "express": "^4.16.4", 20 | "sequelize": "^5.22.5", 21 | "sequelize-typescript": "^1.0.0-alpha.9", 22 | "strong-error-handler": "^3.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple/index.ts: -------------------------------------------------------------------------------- 1 | import './lib/server'; 2 | -------------------------------------------------------------------------------- /examples/simple/lib/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as strongErrorHandler from 'strong-error-handler'; 3 | import {json} from 'body-parser'; 4 | 5 | import {userRouterFactory} from './users/userRouterFactory'; 6 | import {postRouterFactory} from './posts/postRouterFactory'; 7 | 8 | export const app = express(); 9 | 10 | app.use(json()); 11 | 12 | app.use(userRouterFactory()); 13 | app.use(postRouterFactory()); 14 | 15 | app.use(strongErrorHandler({ 16 | debug: true, 17 | })); 18 | 19 | -------------------------------------------------------------------------------- /examples/simple/lib/database/sequelize.ts: -------------------------------------------------------------------------------- 1 | import {Sequelize} from 'sequelize-typescript'; 2 | 3 | import {User} from '../users/User'; 4 | import {Post} from '../posts/Post'; 5 | 6 | export const sequelize = new Sequelize({ 7 | dialect: 'sqlite', 8 | storage: ':memory:', 9 | models: [User, Post], 10 | }); 11 | -------------------------------------------------------------------------------- /examples/simple/lib/posts/Post.ts: -------------------------------------------------------------------------------- 1 | import {Model, Table, Column, ForeignKey, BelongsTo} from 'sequelize-typescript'; 2 | 3 | import {User} from '../users/User'; 4 | 5 | @Table 6 | export class Post extends Model { 7 | 8 | @Column text!: string; 9 | @ForeignKey(() => User) @Column userId!: number; 10 | @BelongsTo(() => User) user: User; 11 | } 12 | -------------------------------------------------------------------------------- /examples/simple/lib/posts/postRouterFactory.ts: -------------------------------------------------------------------------------- 1 | import {Router} from "express"; 2 | import {Post} from "./Post"; 3 | 4 | export const postRouterFactory = () => Router() 5 | 6 | .get('/posts', (req, res, next) => 7 | Post.findAll() 8 | .then(posts => res.json(posts)) 9 | .catch(next) 10 | ) 11 | 12 | .get('/posts/:id', (req, res, next) => 13 | Post.findByPk(req.params.id) 14 | .then(post => post 15 | ? res.json(post) 16 | : next({statusCode: 404})) 17 | .catch(next) 18 | ) 19 | 20 | .post('/posts', (req, res, next) => 21 | Post.create(req.body) 22 | .then(post => res.json(post)) 23 | .catch(next) 24 | ) 25 | 26 | ; 27 | -------------------------------------------------------------------------------- /examples/simple/lib/server.ts: -------------------------------------------------------------------------------- 1 | import {createServer} from 'http'; 2 | import {app} from './app'; 3 | import {sequelize} from './database/sequelize'; 4 | 5 | const port = process.env.PORT || 5000; 6 | 7 | (async () => { 8 | 9 | await sequelize.sync({force: true}); 10 | 11 | createServer(app) 12 | .listen(port, () => console.log(`Server listen on port ${port}`)); 13 | 14 | })(); 15 | 16 | -------------------------------------------------------------------------------- /examples/simple/lib/users/User.ts: -------------------------------------------------------------------------------- 1 | import {Model, Table, Column, HasMany} from 'sequelize-typescript'; 2 | 3 | import {Post} from '../posts/Post'; 4 | 5 | @Table 6 | export class User extends Model { 7 | 8 | @Column name!: string; 9 | @HasMany(() => Post) posts: Post[]; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /examples/simple/lib/users/userRouterFactory.ts: -------------------------------------------------------------------------------- 1 | import {Router} from 'express'; 2 | 3 | import {User} from './User'; 4 | import {Post} from '../posts/Post'; 5 | 6 | export const userRouterFactory = () => Router() 7 | 8 | .get('/users', (req, res, next) => 9 | User.findAll({include: [Post]}) 10 | .then(users => res.json(users)) 11 | .catch(next) 12 | ) 13 | 14 | .get('/users/:id', (req, res, next) => 15 | User.findByPk(req.params.id) 16 | .then(user => user 17 | ? res.json(user) 18 | : next({statusCode: 404})) 19 | .catch(next) 20 | ) 21 | 22 | .post('/users', (req, res, next) => 23 | User.create(req.body) 24 | .then(user => res.json(user)) 25 | .catch(next) 26 | ) 27 | 28 | ; 29 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repository-mode", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node -r ts-node/register index.ts" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/body-parser": "^1.17.0", 13 | "@types/express": "^4.16.0", 14 | "@types/strong-error-handler": "^2.3.0", 15 | "ts-node": "^7.0.1" 16 | }, 17 | "dependencies": { 18 | "body-parser": "^1.18.3", 19 | "express": "^4.16.4", 20 | "sequelize": "^5.22.5", 21 | "sequelize-typescript": "^1.0.0-alpha.9", 22 | "strong-error-handler": "^3.5.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "sourceMap": true, 8 | "strictNullChecks": false, 9 | "noUnusedLocals": true, 10 | "pretty": true, 11 | "skipLibCheck": true, 12 | "lib": ["es2015"] 13 | }, 14 | "include": [ 15 | "repository-mode", 16 | "simple" 17 | ], 18 | "exclude": [ 19 | "../node_modules", 20 | "../src", 21 | "../src/index.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sequelize-typescript", 3 | "version": "2.1.6", 4 | "description": "Decorators and some other features for sequelize", 5 | "scripts": { 6 | "build": "tsc", 7 | "test": "mocha test/**/*.spec.ts", 8 | "cover": "nyc mocha test/**/*.spec.ts", 9 | "lint": "eslint --ext .ts src/ test/", 10 | "lint:fix": "npm run lint -- --fix", 11 | "markdownlint": "markdownlint '**/*.md' --ignore node_modules --ignore CHANGELOG.md", 12 | "release": "release-it", 13 | "postinstall": "husky install", 14 | "prepublishOnly": "pinst --disable && npm run build", 15 | "postpublish": "pinst --enable" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/RobinBuschmann/sequelize-typescript.git" 20 | }, 21 | "keywords": [ 22 | "orm", 23 | "object relational mapper", 24 | "sequelize", 25 | "typescript", 26 | "decorators", 27 | "mysql", 28 | "sqlite", 29 | "postgresql", 30 | "postgres", 31 | "mssql" 32 | ], 33 | "author": "Robin Buschmann", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/RobinBuschmann/sequelize-typescript/issues" 37 | }, 38 | "homepage": "https://github.com/RobinBuschmann/sequelize-typescript#readme", 39 | "main": "dist/index.js", 40 | "types": "dist/index.d.ts", 41 | "browser": "dist/browser/index.js", 42 | "engines": { 43 | "node": ">=10.0.0" 44 | }, 45 | "release-it": { 46 | "git": { 47 | "commit": false, 48 | "push": false, 49 | "tag": false 50 | }, 51 | "github": { 52 | "release": false 53 | }, 54 | "npm": { 55 | "publish": false 56 | }, 57 | "hooks": { 58 | "before:init": [ 59 | "npm run lint", 60 | "npm run markdownlint", 61 | "npm run build", 62 | "npm run test" 63 | ] 64 | }, 65 | "plugins": { 66 | "@release-it/conventional-changelog": { 67 | "preset": "angular", 68 | "infile": "CHANGELOG.md" 69 | } 70 | } 71 | }, 72 | "nyc": { 73 | "lines": 85, 74 | "statements": 85, 75 | "functions": 85, 76 | "branches": 85, 77 | "include": [ 78 | "src" 79 | ], 80 | "exclude": [ 81 | "test" 82 | ], 83 | "extension": [ 84 | ".ts" 85 | ], 86 | "reporter": [ 87 | "lcov", 88 | "text-summary" 89 | ], 90 | "cache": true, 91 | "all": true, 92 | "check-coverage": true, 93 | "report-dir": "./coverage" 94 | }, 95 | "dependencies": { 96 | "glob": "7.2.0" 97 | }, 98 | "devDependencies": { 99 | "@commitlint/cli": "17.2.0", 100 | "@commitlint/config-conventional": "17.2.0", 101 | "@release-it/conventional-changelog": "5.1.1", 102 | "@types/chai": "4.3.4", 103 | "@types/chai-as-promised": "7.1.5", 104 | "@types/chai-datetime": "0.0.37", 105 | "@types/lodash": "4.14.190", 106 | "@types/mocha": "9.1.0", 107 | "@types/node": "18.11.9", 108 | "@types/prettyjson": "0.0.30", 109 | "@types/sinon": "10.0.11", 110 | "@types/sinon-chai": "3.2.9", 111 | "@typescript-eslint/eslint-plugin": "5.44.0", 112 | "@typescript-eslint/parser": "5.42.0", 113 | "chai": "4.3.7", 114 | "chai-as-promised": "7.1.1", 115 | "chai-datetime": "1.8.0", 116 | "codecov": "3.8.3", 117 | "copyfiles": "2.4.1", 118 | "eslint": "8.27.0", 119 | "eslint-config-prettier": "8.5.0", 120 | "eslint-plugin-prettier": "4.2.1", 121 | "has-flag": "5.0.1", 122 | "husky": "8.0.2", 123 | "lodash": "4.17.21", 124 | "markdownlint-cli": "0.32.2", 125 | "mocha": "9.2.2", 126 | "moment": "2.29.4", 127 | "mysql2": "2.3.3", 128 | "nyc": "15.1.0", 129 | "pinst": "3.0.0", 130 | "prettier": "2.8.0", 131 | "prettyjson": "1.2.5", 132 | "reflect-metadata": "0.1.13", 133 | "release-it": "15.5.0", 134 | "sequelize": "6.29.0", 135 | "sinon": "13.0.1", 136 | "sinon-chai": "3.7.0", 137 | "source-map-support": "0.5.21", 138 | "sqlite3": "5.1.2", 139 | "ts-node": "10.9.1", 140 | "typescript": "4.8.4", 141 | "uuid-validate": "0.0.3" 142 | }, 143 | "peerDependencies": { 144 | "@types/node": "*", 145 | "@types/validator": "*", 146 | "reflect-metadata": "*", 147 | "sequelize": ">=6.20.1" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":maintainLockFilesWeekly", 5 | ":semanticCommitTypeAll(meta)", 6 | ":semanticCommitScopeDisabled" 7 | ], 8 | "automergeStrategy": "squash", 9 | "semanticCommitType": "meta", 10 | "packageRules": [ 11 | { 12 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 13 | "automerge": true 14 | }, 15 | { 16 | "matchPackageNames": ["mocha", "@types/mocha", "sinon", "@types/sinon"], 17 | "matchUpdateTypes": ["major"], 18 | "enabled": false 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/associations/alias-inference/alias-inference-service.ts: -------------------------------------------------------------------------------- 1 | import { getAssociationsByRelation } from '../shared/association-service'; 2 | 3 | /** 4 | * Pre conform includes, so that "as" value can be inferred from source 5 | */ 6 | export function inferAlias(options: any, source: any): any { 7 | options = { ...options }; 8 | 9 | if (!options.include) { 10 | return options; 11 | } 12 | // if include is not an array, wrap in an array 13 | if (!Array.isArray(options.include)) { 14 | options.include = [options.include]; 15 | } else if (!options.include.length) { 16 | delete options.include; 17 | return options; 18 | } 19 | 20 | // convert all included elements to { model: Model } form 21 | options.include = options.include.map((include) => { 22 | include = inferAliasForInclude(include, source); 23 | 24 | return include; 25 | }); 26 | 27 | return options; 28 | } 29 | 30 | /** 31 | * Pre conform include, so that alias ("as") value can be inferred from source class 32 | */ 33 | function inferAliasForInclude(include: any, source: any): any { 34 | const hasModelOptionWithoutAsOption = !!(include.model && !include.as); 35 | const hasIncludeOptions = !!include.include; 36 | const isConstructorFn = include instanceof Function; 37 | 38 | if (isConstructorFn || hasModelOptionWithoutAsOption) { 39 | if (isConstructorFn) { 40 | include = { model: include }; 41 | } 42 | 43 | const targetPrototype = source.prototype || source; 44 | const relatedClass = include.model; 45 | const associations = getAssociationsByRelation(targetPrototype, relatedClass); 46 | 47 | if (associations.length > 0) { 48 | if (associations.length > 1) { 49 | throw new Error( 50 | `Alias cannot be inferred: "${source.name}" has multiple ` + 51 | `relations with "${include.model.name}"` 52 | ); 53 | } 54 | include.as = associations[0].getAs(); 55 | } 56 | } 57 | 58 | if (!isConstructorFn && hasIncludeOptions) { 59 | include = inferAlias(include, include.model); 60 | } 61 | 62 | return include; 63 | } 64 | -------------------------------------------------------------------------------- /src/associations/belongs-to-many/belongs-to-many-association.ts: -------------------------------------------------------------------------------- 1 | import { BaseAssociation } from '../shared/base-association'; 2 | import { BelongsToManyOptions } from './belongs-to-many-options'; 3 | import { ModelNotInitializedError } from '../../model/shared/model-not-initialized-error'; 4 | import { getForeignKeyOptions } from '../foreign-key/foreign-key-service'; 5 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 6 | import { Association } from '../shared/association'; 7 | import { Sequelize } from '../../sequelize/sequelize/sequelize'; 8 | import { UnionAssociationOptions } from '../shared/union-association-options'; 9 | import { ModelType } from '../../model/model/model'; 10 | import { ThroughOptions } from '../through/through-options'; 11 | 12 | export class BelongsToManyAssociation< 13 | TCreationAttributes extends {}, 14 | TModelAttributes extends {}, 15 | TCreationAttributesThrough extends {}, 16 | TModelAttributesThrough extends {} 17 | > extends BaseAssociation { 18 | constructor( 19 | associatedClassGetter: ModelClassGetter, 20 | protected options: BelongsToManyOptions 21 | ) { 22 | super(associatedClassGetter, options); 23 | } 24 | 25 | getAssociation(): Association { 26 | return Association.BelongsToMany; 27 | } 28 | 29 | getSequelizeOptions( 30 | model: ModelType, 31 | sequelize: Sequelize 32 | ): UnionAssociationOptions { 33 | const options: BelongsToManyOptions = { 34 | ...this.options, 35 | }; 36 | const associatedClass = this.getAssociatedClass(); 37 | const throughOptions = this.getThroughOptions(sequelize); 38 | 39 | const throughModel = 40 | typeof throughOptions === 'object' && typeof throughOptions.model !== 'string' 41 | ? throughOptions.model 42 | : undefined; 43 | options.through = throughOptions; 44 | options.foreignKey = getForeignKeyOptions(model, throughModel as any, this.options.foreignKey); 45 | options.otherKey = getForeignKeyOptions( 46 | associatedClass, 47 | throughModel as any, 48 | this.options.otherKey 49 | ); 50 | 51 | return options; 52 | } 53 | 54 | private getThroughOptions( 55 | sequelize: Sequelize 56 | ): ThroughOptions | string { 57 | const through = this.options.through; 58 | const throughModel = typeof through === 'object' ? through.model : through; 59 | const throughOptions: ThroughOptions = 60 | typeof through === 'object' ? { ...through } : ({} as any); 61 | 62 | if (typeof throughModel === 'function') { 63 | const throughModelClass = sequelize.model(throughModel()); 64 | if (!throughModelClass.isInitialized) { 65 | throw new ModelNotInitializedError(throughModelClass, 'Association cannot be resolved.'); 66 | } 67 | throughOptions.model = throughModelClass as any; 68 | } else { 69 | return throughModel; 70 | } 71 | 72 | return throughOptions; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/associations/belongs-to-many/belongs-to-many-options.ts: -------------------------------------------------------------------------------- 1 | import { BelongsToManyOptions as OriginBelongsToManyOptions } from 'sequelize'; 2 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 3 | import { ThroughOptions } from '../through/through-options'; 4 | 5 | export type BelongsToManyOptions< 6 | TCreationAttributesThrough extends {}, 7 | TModelAttributesThrough extends {} 8 | > = { 9 | [K in keyof OriginBelongsToManyOptions]: K extends 'through' 10 | ? 11 | | ModelClassGetter 12 | | string 13 | | ThroughOptions 14 | : OriginBelongsToManyOptions[K]; 15 | }; 16 | -------------------------------------------------------------------------------- /src/associations/belongs-to-many/belongs-to-many.ts: -------------------------------------------------------------------------------- 1 | import { BelongsToManyOptions } from './belongs-to-many-options'; 2 | import { BelongsToManyAssociation } from './belongs-to-many-association'; 3 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 4 | import { addAssociation } from '../shared/association-service'; 5 | 6 | export function BelongsToMany< 7 | TCreationAttributes extends {}, 8 | TModelAttributes extends {}, 9 | TCreationAttributesThrough extends {}, 10 | TModelAttributesThrough extends {} 11 | >( 12 | associatedClassGetter: ModelClassGetter, 13 | through: ModelClassGetter | string, 14 | foreignKey?: string, 15 | otherKey?: string 16 | ): Function; 17 | export function BelongsToMany< 18 | TCreationAttributes extends {}, 19 | TModelAttributes extends {}, 20 | TCreationAttributesThrough extends {}, 21 | TModelAttributesThrough extends {} 22 | >( 23 | associatedClassGetter: ModelClassGetter, 24 | options: BelongsToManyOptions 25 | ): Function; 26 | export function BelongsToMany< 27 | TCreationAttributes extends {}, 28 | TModelAttributes extends {}, 29 | TCreationAttributesThrough extends {}, 30 | TModelAttributesThrough extends {} 31 | >( 32 | associatedClassGetter: ModelClassGetter, 33 | throughOrOptions: 34 | | ModelClassGetter 35 | | string 36 | | BelongsToManyOptions, 37 | foreignKey?: string, 38 | otherKey?: string 39 | ): Function { 40 | return (target: any, propertyName: string) => { 41 | let options: Partial< 42 | BelongsToManyOptions 43 | > = { foreignKey, otherKey }; 44 | 45 | if (typeof throughOrOptions === 'string' || typeof throughOrOptions === 'function') { 46 | options.through = throughOrOptions; 47 | } else { 48 | options = { ...throughOrOptions }; 49 | } 50 | 51 | if (!options.as) options.as = propertyName; 52 | 53 | addAssociation( 54 | target, 55 | new BelongsToManyAssociation( 56 | associatedClassGetter, 57 | options as BelongsToManyOptions 58 | ) 59 | ); 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/associations/belongs-to/belongs-to-association.ts: -------------------------------------------------------------------------------- 1 | import { BelongsToOptions } from 'sequelize'; 2 | 3 | import { BaseAssociation } from '../shared/base-association'; 4 | import { getForeignKeyOptions } from '../foreign-key/foreign-key-service'; 5 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 6 | import { Association } from '../shared/association'; 7 | import { ModelType } from '../../model/model/model'; 8 | import { UnionAssociationOptions } from '../shared/union-association-options'; 9 | 10 | export class BelongsToAssociation< 11 | TCreationAttributes extends {}, 12 | TModelAttributes extends {} 13 | > extends BaseAssociation { 14 | constructor( 15 | associatedClassGetter: ModelClassGetter, 16 | protected options: BelongsToOptions 17 | ) { 18 | super(associatedClassGetter, options); 19 | } 20 | 21 | getAssociation(): Association { 22 | return Association.BelongsTo; 23 | } 24 | 25 | getSequelizeOptions( 26 | model: ModelType 27 | ): UnionAssociationOptions { 28 | const associatedClass = this.getAssociatedClass(); 29 | const foreignKey = getForeignKeyOptions(associatedClass, model, this.options.foreignKey); 30 | 31 | return { 32 | ...this.options, 33 | foreignKey, 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/associations/belongs-to/belongs-to.ts: -------------------------------------------------------------------------------- 1 | import { BelongsToOptions } from 'sequelize'; 2 | 3 | import { BelongsToAssociation } from './belongs-to-association'; 4 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 5 | import { addAssociation, getPreparedAssociationOptions } from '../shared/association-service'; 6 | 7 | export function BelongsTo( 8 | associatedClassGetter: ModelClassGetter, 9 | foreignKey?: string 10 | ): Function; 11 | 12 | export function BelongsTo( 13 | associatedClassGetter: ModelClassGetter, 14 | options?: BelongsToOptions 15 | ): Function; 16 | 17 | export function BelongsTo( 18 | associatedClassGetter: ModelClassGetter, 19 | optionsOrForeignKey?: string | BelongsToOptions 20 | ): Function { 21 | return (target: any, propertyName: string) => { 22 | const options: BelongsToOptions = getPreparedAssociationOptions(optionsOrForeignKey); 23 | if (!options.as) options.as = propertyName; 24 | addAssociation(target, new BelongsToAssociation(associatedClassGetter, options)); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/associations/foreign-key/foreign-key-meta.ts: -------------------------------------------------------------------------------- 1 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 2 | 3 | export interface ForeignKeyMeta { 4 | relatedClassGetter: ModelClassGetter; 5 | foreignKey: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/associations/foreign-key/foreign-key-service.ts: -------------------------------------------------------------------------------- 1 | import { ForeignKeyOptions } from 'sequelize'; 2 | 3 | import { ForeignKeyMeta } from './foreign-key-meta'; 4 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 5 | import { ModelType } from '../../model/model/model'; 6 | 7 | const FOREIGN_KEYS_KEY = 'sequelize:foreignKeys'; 8 | 9 | export function getForeignKeyOptions< 10 | TCreationAttributes extends {}, 11 | TModelAttributes extends {}, 12 | TCreationAttributesThrough extends {}, 13 | TModelAttributesThrough extends {} 14 | >( 15 | relatedClass: ModelType, 16 | classWithForeignKey?: ModelType, 17 | foreignKey?: string | ForeignKeyOptions 18 | ): ForeignKeyOptions { 19 | let foreignKeyOptions: ForeignKeyOptions = {}; 20 | 21 | if (typeof foreignKey === 'string') { 22 | foreignKeyOptions.name = foreignKey; 23 | } else if (foreignKey && typeof foreignKey === 'object') { 24 | foreignKeyOptions = { ...foreignKey }; 25 | } 26 | if (!foreignKeyOptions.name && classWithForeignKey) { 27 | const foreignKeys = getForeignKeys(classWithForeignKey.prototype) || []; 28 | for (const key of foreignKeys) { 29 | if ( 30 | key.relatedClassGetter() === relatedClass || 31 | relatedClass.prototype instanceof key.relatedClassGetter() 32 | ) { 33 | foreignKeyOptions.name = key.foreignKey; 34 | break; 35 | } 36 | } 37 | } 38 | if (!foreignKeyOptions.name) { 39 | throw new Error( 40 | `Foreign key for "${(relatedClass as any).name}" is missing ` + 41 | `on "${(classWithForeignKey as any).name}".` 42 | ); 43 | } 44 | 45 | return foreignKeyOptions; 46 | } 47 | 48 | /** 49 | * Adds foreign key meta data for specified class 50 | */ 51 | export function addForeignKey( 52 | target: any, 53 | relatedClassGetter: ModelClassGetter, 54 | foreignKey: string 55 | ): void { 56 | let foreignKeys = getForeignKeys(target); 57 | if (!foreignKeys) { 58 | foreignKeys = []; 59 | } 60 | foreignKeys.push({ 61 | relatedClassGetter, 62 | foreignKey, 63 | }); 64 | setForeignKeys(target, foreignKeys); 65 | } 66 | 67 | /** 68 | * Returns foreign key meta data from specified class 69 | */ 70 | export function getForeignKeys( 71 | target: any 72 | ): ForeignKeyMeta[] | undefined { 73 | const foreignKeys = Reflect.getMetadata(FOREIGN_KEYS_KEY, target); 74 | if (foreignKeys) { 75 | return [...foreignKeys]; 76 | } 77 | } 78 | 79 | /** 80 | * Sets foreign key meta data 81 | */ 82 | function setForeignKeys(target: any, foreignKeys: any[]): void { 83 | Reflect.defineMetadata(FOREIGN_KEYS_KEY, foreignKeys, target); 84 | } 85 | -------------------------------------------------------------------------------- /src/associations/foreign-key/foreign-key.ts: -------------------------------------------------------------------------------- 1 | import { addForeignKey } from './foreign-key-service'; 2 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 3 | 4 | export function ForeignKey( 5 | relatedClassGetter: ModelClassGetter 6 | ): Function { 7 | return (target: any, propertyName: string) => { 8 | addForeignKey(target, relatedClassGetter, propertyName); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/associations/has/has-association.ts: -------------------------------------------------------------------------------- 1 | import { HasManyOptions, HasOneOptions } from 'sequelize'; 2 | 3 | import { BaseAssociation } from '../shared/base-association'; 4 | import { getForeignKeyOptions } from '../foreign-key/foreign-key-service'; 5 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 6 | import { Association } from '../shared/association'; 7 | import { UnionAssociationOptions } from '../shared/union-association-options'; 8 | import { ModelType } from '../../model/model/model'; 9 | 10 | export class HasAssociation< 11 | TCreationAttributes extends {}, 12 | TModelAttributes extends {} 13 | > extends BaseAssociation { 14 | constructor( 15 | associatedClassGetter: ModelClassGetter, 16 | protected options: HasManyOptions | HasOneOptions, 17 | private association: Association 18 | ) { 19 | super(associatedClassGetter, options); 20 | } 21 | 22 | getAssociation(): Association { 23 | return this.association; 24 | } 25 | 26 | getSequelizeOptions( 27 | model: ModelType 28 | ): UnionAssociationOptions { 29 | const options = { ...this.options }; 30 | const associatedClass = this.getAssociatedClass(); 31 | 32 | options.foreignKey = getForeignKeyOptions(model, associatedClass, options.foreignKey); 33 | 34 | return options; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/associations/has/has-many.ts: -------------------------------------------------------------------------------- 1 | import { HasManyOptions } from 'sequelize'; 2 | 3 | import { HasAssociation } from './has-association'; 4 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 5 | import { addAssociation, getPreparedAssociationOptions } from '../shared/association-service'; 6 | import { Association } from '../shared/association'; 7 | 8 | export function HasMany( 9 | associatedClassGetter: ModelClassGetter, 10 | foreignKey?: string 11 | ): Function; 12 | 13 | export function HasMany( 14 | associatedClassGetter: ModelClassGetter, 15 | options?: HasManyOptions 16 | ): Function; 17 | 18 | export function HasMany( 19 | associatedClassGetter: ModelClassGetter, 20 | optionsOrForeignKey?: string | HasManyOptions 21 | ): Function { 22 | return (target: any, propertyName: string) => { 23 | const options: HasManyOptions = getPreparedAssociationOptions(optionsOrForeignKey); 24 | if (!options.as) options.as = propertyName; 25 | addAssociation(target, new HasAssociation(associatedClassGetter, options, Association.HasMany)); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/associations/has/has-one.ts: -------------------------------------------------------------------------------- 1 | import { HasOneOptions } from 'sequelize'; 2 | 3 | import { HasAssociation } from './has-association'; 4 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 5 | import { addAssociation, getPreparedAssociationOptions } from '../shared/association-service'; 6 | import { Association } from '../shared/association'; 7 | 8 | export function HasOne( 9 | associatedClassGetter: ModelClassGetter, 10 | foreignKey?: string 11 | ): Function; 12 | 13 | export function HasOne( 14 | associatedClassGetter: ModelClassGetter, 15 | options?: HasOneOptions 16 | ): Function; 17 | 18 | export function HasOne( 19 | associatedClassGetter: ModelClassGetter, 20 | optionsOrForeignKey?: string | HasOneOptions 21 | ): Function { 22 | return (target: any, propertyName: string) => { 23 | const options: HasOneOptions = getPreparedAssociationOptions(optionsOrForeignKey); 24 | if (!options.as) options.as = propertyName; 25 | addAssociation(target, new HasAssociation(associatedClassGetter, options, Association.HasOne)); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/associations/shared/association-service.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { BelongsToOptions, HasOneOptions, HasManyOptions, ManyToManyOptions } from 'sequelize'; 3 | import { BaseAssociation } from './base-association'; 4 | 5 | const ASSOCIATIONS_KEY = 'sequelize:associations'; 6 | 7 | export type NonBelongsToManyAssociationOptions = 8 | | BelongsToOptions 9 | | HasManyOptions 10 | | HasOneOptions 11 | | ManyToManyOptions; 12 | 13 | export function getPreparedAssociationOptions( 14 | optionsOrForeignKey?: string | NonBelongsToManyAssociationOptions 15 | ): NonBelongsToManyAssociationOptions { 16 | let options: NonBelongsToManyAssociationOptions = {}; 17 | 18 | if (optionsOrForeignKey) { 19 | if (typeof optionsOrForeignKey === 'string') { 20 | options.foreignKey = optionsOrForeignKey; 21 | } else { 22 | options = { ...optionsOrForeignKey }; 23 | } 24 | } 25 | return options; 26 | } 27 | 28 | /** 29 | * Stores association meta data for specified class 30 | */ 31 | export function addAssociation( 32 | target: any, 33 | association: BaseAssociation 34 | ): void { 35 | let associations = getAssociations(target); 36 | 37 | if (!associations) { 38 | associations = []; 39 | } 40 | associations.push(association); 41 | setAssociations(target, associations); 42 | } 43 | 44 | /** 45 | * Returns association meta data from specified class 46 | */ 47 | export function getAssociations( 48 | target: any 49 | ): BaseAssociation[] | undefined { 50 | const associations = Reflect.getMetadata(ASSOCIATIONS_KEY, target); 51 | if (associations) { 52 | return [...associations]; 53 | } 54 | } 55 | 56 | export function setAssociations( 57 | target: any, 58 | associations: BaseAssociation[] 59 | ): void { 60 | Reflect.defineMetadata(ASSOCIATIONS_KEY, associations, target); 61 | } 62 | 63 | export function getAssociationsByRelation< 64 | TCreationAttributes extends {}, 65 | TModelAttributes extends {} 66 | >(target: any, relatedClass: any): BaseAssociation[] { 67 | const associations = getAssociations(target); 68 | return (associations || []).filter((association) => { 69 | const _relatedClass = association.getAssociatedClass(); 70 | return ( 71 | _relatedClass.prototype === relatedClass.prototype || 72 | relatedClass.prototype instanceof _relatedClass 73 | ); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/associations/shared/association.ts: -------------------------------------------------------------------------------- 1 | export enum Association { 2 | BelongsToMany = 'belongsToMany', 3 | BelongsTo = 'belongsTo', 4 | HasMany = 'hasMany', 5 | HasOne = 'hasOne', 6 | } 7 | -------------------------------------------------------------------------------- /src/associations/shared/base-association.ts: -------------------------------------------------------------------------------- 1 | import { UnionAssociationOptions } from './union-association-options'; 2 | import { Association } from './association'; 3 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 4 | import { ModelType } from '../../model/model/model'; 5 | import { Sequelize } from '../../sequelize/sequelize/sequelize'; 6 | 7 | export abstract class BaseAssociation { 8 | constructor( 9 | private associatedClassGetter: ModelClassGetter, 10 | protected options: UnionAssociationOptions 11 | ) {} 12 | 13 | abstract getAssociation(): Association; 14 | abstract getSequelizeOptions( 15 | model: ModelType, 16 | sequelize: Sequelize 17 | ): UnionAssociationOptions; 18 | 19 | getAssociatedClass(): ModelType { 20 | return this.associatedClassGetter(); 21 | } 22 | 23 | getAs(): string | { singular: string; plural: string } | undefined { 24 | return this.options.as; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/associations/shared/union-association-options.ts: -------------------------------------------------------------------------------- 1 | import { BelongsToOptions, HasManyOptions, HasOneOptions, ManyToManyOptions } from 'sequelize'; 2 | 3 | export type UnionAssociationOptions = 4 | | BelongsToOptions 5 | | HasManyOptions 6 | | HasOneOptions 7 | | ManyToManyOptions; 8 | -------------------------------------------------------------------------------- /src/associations/through/through-options.ts: -------------------------------------------------------------------------------- 1 | import { ThroughOptions as OriginThroughOptions } from 'sequelize'; 2 | import { ModelClassGetter } from '../../model/shared/model-class-getter'; 3 | 4 | export type ThroughOptions = { 5 | [K in keyof OriginThroughOptions]: K extends 'model' 6 | ? ModelClassGetter | string 7 | : OriginThroughOptions[K]; 8 | }; 9 | -------------------------------------------------------------------------------- /src/browser/index.ts: -------------------------------------------------------------------------------- 1 | function noop() { 2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 3 | return (target: Object, propertyName: string): void => { 4 | // noop 5 | }; 6 | } 7 | 8 | export class Model {} 9 | export const DataType = {}; 10 | 11 | export const AllowNull = noop; 12 | export const AutoIncrement = noop; 13 | export const Column = noop; 14 | export const Comment = noop; 15 | export const CreatedAt = noop; 16 | export const Default = noop; 17 | export const DefaultScope = noop; 18 | export const DeletedAt = noop; 19 | export const ForeignKey = noop; 20 | export const PrimaryKey = noop; 21 | export const Scopes = noop; 22 | export const Table = noop; 23 | export const Unique = noop; 24 | export const UpdatedAt = noop; 25 | export const BelongsTo = noop; 26 | export const BelongsToMany = noop; 27 | export const HasMany = noop; 28 | export const HasOne = noop; 29 | export const AfterBulkCreate = noop; 30 | export const AfterBulkDelete = noop; 31 | export const AfterBulkDestroy = noop; 32 | export const AfterBulkRestore = noop; 33 | export const AfterBulkSync = noop; 34 | export const AfterBulkUpdate = noop; 35 | export const AfterConnect = noop; 36 | export const AfterCreate = noop; 37 | export const AfterDefine = noop; 38 | export const AfterDelete = noop; 39 | export const AfterDestroy = noop; 40 | export const AfterFind = noop; 41 | export const AfterInit = noop; 42 | export const AfterRestore = noop; 43 | export const AfterSave = noop; 44 | export const AfterSync = noop; 45 | export const AfterUpdate = noop; 46 | export const AfterUpsert = noop; 47 | export const AfterValidate = noop; 48 | export const BeforeBulkCreate = noop; 49 | export const BeforeBulkDelete = noop; 50 | export const BeforeBulkDestroy = noop; 51 | export const BeforeBulkRestore = noop; 52 | export const BeforeBulkSync = noop; 53 | export const BeforeBulkUpdate = noop; 54 | export const BeforeConnect = noop; 55 | export const BeforeCount = noop; 56 | export const BeforeCreate = noop; 57 | export const BeforeDefine = noop; 58 | export const BeforeDelete = noop; 59 | export const BeforeDestroy = noop; 60 | export const BeforeFind = noop; 61 | export const BeforeFindAfterExpandIncludeAll = noop; 62 | export const BeforeFindAfterOptions = noop; 63 | export const BeforeInit = noop; 64 | export const BeforeRestore = noop; 65 | export const BeforeSave = noop; 66 | export const BeforeSync = noop; 67 | export const BeforeUpdate = noop; 68 | export const BeforeUpsert = noop; 69 | export const BeforeValidate = noop; 70 | export const ValidationFailed = noop; 71 | export const Contains = noop; 72 | export const Equals = noop; 73 | export const Is = noop; 74 | export const IsAfter = noop; 75 | export const IsAlpha = noop; 76 | export const IsAlphanumeric = noop; 77 | export const IsArray = noop; 78 | export const IsBefore = noop; 79 | export const IsCreditCard = noop; 80 | export const IsDate = noop; 81 | export const IsDecimal = noop; 82 | export const IsEmail = noop; 83 | export const IsFloat = noop; 84 | export const IsIn = noop; 85 | export const IsInt = noop; 86 | export const IsIP = noop; 87 | export const IsIPv4 = noop; 88 | export const IsIPv6 = noop; 89 | export const IsLowercase = noop; 90 | export const IsNull = noop; 91 | export const IsNumeric = noop; 92 | export const IsUppercase = noop; 93 | export const IsUrl = noop; 94 | export const IsUUID = noop; 95 | export const Length = noop; 96 | export const Max = noop; 97 | export const Min = noop; 98 | export const Not = noop; 99 | export const NotContains = noop; 100 | export const NotEmpty = noop; 101 | export const NotIn = noop; 102 | export const NotNull = noop; 103 | export const Validate = noop; 104 | export const Validator = noop; 105 | -------------------------------------------------------------------------------- /src/hooks/bulk/after/after-bulk-create.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterBulkCreate(target: any, propertyName: string): void; 5 | export function AfterBulkCreate(options: HookOptions): Function; 6 | export function AfterBulkCreate(...args: any[]): void | Function { 7 | return implementHookDecorator('afterBulkCreate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/after/after-bulk-destroy.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterBulkDestroy(target: any, propertyName: string): void; 5 | export function AfterBulkDestroy(options: HookOptions): Function; 6 | export function AfterBulkDestroy(...args: any[]): void | Function { 7 | return implementHookDecorator('afterBulkDestroy', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/after/after-bulk-restore.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterBulkRestore(target: any, propertyName: string): void; 5 | export function AfterBulkRestore(options: HookOptions): Function; 6 | export function AfterBulkRestore(...args: any[]): void | Function { 7 | return implementHookDecorator('afterBulkRestore' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/after/after-bulk-sync.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterBulkSync(target: any, propertyName: string): void; 5 | export function AfterBulkSync(options: HookOptions): Function; 6 | export function AfterBulkSync(...args: any[]): void | Function { 7 | return implementHookDecorator('afterBulkSync', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/after/after-bulk-update.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterBulkUpdate(target: any, propertyName: string): void; 5 | export function AfterBulkUpdate(options: HookOptions): Function; 6 | export function AfterBulkUpdate(...args: any[]): void | Function { 7 | return implementHookDecorator('afterBulkUpdate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/before/before-bulk-create.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeBulkCreate(target: any, propertyName: string): void; 5 | export function BeforeBulkCreate(options: HookOptions): Function; 6 | export function BeforeBulkCreate(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeBulkCreate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/before/before-bulk-destroy.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeBulkDestroy(target: any, propertyName: string): void; 5 | export function BeforeBulkDestroy(options: HookOptions): Function; 6 | export function BeforeBulkDestroy(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeBulkDestroy', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/before/before-bulk-restore.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeBulkRestore(target: any, propertyName: string): void; 5 | export function BeforeBulkRestore(options: HookOptions): Function; 6 | export function BeforeBulkRestore(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeBulkRestore' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/before/before-bulk-sync.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeBulkSync(target: any, propertyName: string): void; 5 | export function BeforeBulkSync(options: HookOptions): Function; 6 | export function BeforeBulkSync(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeBulkSync', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/bulk/before/before-bulk-update.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeBulkUpdate(target: any, propertyName: string): void; 5 | export function BeforeBulkUpdate(options: HookOptions): Function; 6 | export function BeforeBulkUpdate(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeBulkUpdate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/shared/hook-meta.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from './hook-options'; 2 | import { SequelizeHooks } from 'sequelize/types/hooks'; 3 | 4 | export interface HookMeta { 5 | hookType: keyof SequelizeHooks; 6 | methodName: string; 7 | options?: HookOptions; 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/shared/hook-options.ts: -------------------------------------------------------------------------------- 1 | export interface HookOptions { 2 | name?: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/shared/hooks-service.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { HookMeta } from './hook-meta'; 3 | import { HookOptions } from './hook-options'; 4 | import { SequelizeHooks } from 'sequelize/types/hooks'; 5 | import { ModelCtor } from '../../model/model/model'; 6 | 7 | const HOOKS_KEY = 'sequelize:hooks'; 8 | 9 | /** 10 | * Installs hooks on the specified models 11 | */ 12 | export function installHooks(models: ModelCtor[]): void { 13 | models.forEach((model) => { 14 | const hooks = getHooks(model); 15 | 16 | if (hooks) { 17 | hooks.forEach((hook) => { 18 | installHook(model, hook); 19 | }); 20 | } 21 | }); 22 | } 23 | 24 | /** 25 | * Implementation for hook decorator functions. These are polymorphic. When 26 | * called with a single argument (IHookOptions) they return a decorator 27 | * factory function. When called with multiple arguments, they add the hook 28 | * to the model’s metadata. 29 | */ 30 | export function implementHookDecorator( 31 | hookType: keyof SequelizeHooks, 32 | args: any[] 33 | ): Function | void { 34 | if (args.length === 1) { 35 | const options: HookOptions = args[0]; 36 | 37 | return (target: any, propertyName: string) => addHook(target, hookType, propertyName, options); 38 | } else { 39 | const target = args[0]; 40 | const propertyName = args[1]; 41 | 42 | addHook(target, hookType, propertyName); 43 | } 44 | } 45 | 46 | /** 47 | * Adds hook meta data for specified model 48 | * @throws if applied to a non-static method 49 | * @throws if the hook method name is reserved 50 | */ 51 | export function addHook( 52 | target: any, 53 | hookType: keyof SequelizeHooks, 54 | methodName: string, 55 | options: HookOptions = {} 56 | ): void { 57 | if (typeof target !== 'function') { 58 | throw new Error( 59 | `Hook method '${methodName}' is not a static method. ` + 60 | `Only static methods can be used for hooks` 61 | ); 62 | } 63 | 64 | // make sure the hook name doesn’t conflict with Sequelize’s existing methods 65 | if (methodName === hookType) { 66 | throw new Error( 67 | `Hook method cannot be named '${methodName}'. That name is ` + `reserved by Sequelize` 68 | ); 69 | } 70 | 71 | const hooks = getHooks(target) || []; 72 | 73 | hooks.push({ 74 | hookType, 75 | methodName, 76 | options, 77 | }); 78 | 79 | setHooks(target, hooks); 80 | } 81 | 82 | /** 83 | * Install a hook 84 | */ 85 | function installHook(model: ModelCtor, hook: HookMeta): void { 86 | if (hook.options && hook.options.name) { 87 | model.addHook(hook.hookType, hook.options.name, model[hook.methodName]); 88 | return; 89 | } 90 | 91 | model.addHook(hook.hookType, model[hook.methodName]); 92 | } 93 | 94 | /** 95 | * Returns hooks meta data from specified class 96 | */ 97 | export function getHooks(target: any): HookMeta[] | undefined { 98 | const hooks = Reflect.getMetadata(HOOKS_KEY, target); 99 | if (hooks) { 100 | return [...hooks]; 101 | } 102 | 103 | return; 104 | } 105 | 106 | /** 107 | * Saves hooks meta data for the specified class 108 | */ 109 | export function setHooks(target: any, hooks: HookMeta[]): void { 110 | Reflect.defineMetadata(HOOKS_KEY, hooks, target); 111 | } 112 | -------------------------------------------------------------------------------- /src/hooks/shared/validation-failed.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from './hook-options'; 2 | import { implementHookDecorator } from './hooks-service'; 3 | 4 | export function ValidationFailed(target: any, propertyName: string): void; 5 | export function ValidationFailed(options: HookOptions): Function; 6 | export function ValidationFailed(...args: any[]): void | Function { 7 | return implementHookDecorator('validationFailed' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-connect.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterConnect(target: any, propertyName: string): void; 5 | export function AfterConnect(options: HookOptions): Function; 6 | export function AfterConnect(...args: any[]): void | Function { 7 | return implementHookDecorator('afterConnect', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-create.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterCreate(target: any, propertyName: string): void; 5 | export function AfterCreate(options: HookOptions): Function; 6 | export function AfterCreate(...args: any[]): void | Function { 7 | return implementHookDecorator('afterCreate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-define.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterDefine(target: any, propertyName: string): void; 5 | export function AfterDefine(options: HookOptions): Function; 6 | export function AfterDefine(...args: any[]): void | Function { 7 | return implementHookDecorator('afterDefine', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-destroy.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterDestroy(target: any, propertyName: string): void; 5 | export function AfterDestroy(options: HookOptions): Function; 6 | export function AfterDestroy(...args: any[]): void | Function { 7 | return implementHookDecorator('afterDestroy', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-find.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterFind(target: any, propertyName: string): void; 5 | export function AfterFind(options: HookOptions): Function; 6 | export function AfterFind(...args: any[]): void | Function { 7 | return implementHookDecorator('afterFind', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-init.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterInit(target: any, propertyName: string): void; 5 | export function AfterInit(options: HookOptions): Function; 6 | export function AfterInit(...args: any[]): void | Function { 7 | return implementHookDecorator('afterInit', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-restore.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterRestore(target: any, propertyName: string): void; 5 | export function AfterRestore(options: HookOptions): Function; 6 | export function AfterRestore(...args: any[]): void | Function { 7 | return implementHookDecorator('afterRestore' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-save.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterSave(target: any, propertyName: string): void; 5 | export function AfterSave(options: HookOptions): Function; 6 | export function AfterSave(...args: any[]): void | Function { 7 | return implementHookDecorator('afterSave' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-sync.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterSync(target: any, propertyName: string): void; 5 | export function AfterSync(options: HookOptions): Function; 6 | export function AfterSync(...args: any[]): void | Function { 7 | return implementHookDecorator('afterSync', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-update.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterUpdate(target: any, propertyName: string): void; 5 | export function AfterUpdate(options: HookOptions): Function; 6 | export function AfterUpdate(...args: any[]): void | Function { 7 | return implementHookDecorator('afterUpdate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-upsert.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterUpsert(target: any, propertyName: string): void; 5 | export function AfterUpsert(options: HookOptions): Function; 6 | export function AfterUpsert(...args: any[]): void | Function { 7 | return implementHookDecorator('afterUpsert' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/after/after-validate.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function AfterValidate(target: any, propertyName: string): void; 5 | export function AfterValidate(options: HookOptions): Function; 6 | export function AfterValidate(...args: any[]): void | Function { 7 | return implementHookDecorator('afterValidate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-connect.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeConnect(target: any, propertyName: string): void; 5 | export function BeforeConnect(options: HookOptions): Function; 6 | export function BeforeConnect(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeConnect', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-count.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeCount(target: any, propertyName: string): void; 5 | export function BeforeCount(options: HookOptions): Function; 6 | export function BeforeCount(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeCount', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-create.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeCreate(target: any, propertyName: string): void; 5 | export function BeforeCreate(options: HookOptions): Function; 6 | export function BeforeCreate(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeCreate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-define.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeDefine(target: any, propertyName: string): void; 5 | export function BeforeDefine(options: HookOptions): Function; 6 | export function BeforeDefine(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeDefine', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-destroy.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeDestroy(target: any, propertyName: string): void; 5 | export function BeforeDestroy(options: HookOptions): Function; 6 | export function BeforeDestroy(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeDestroy', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-find-after-expand-include-all.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeFindAfterExpandIncludeAll(target: any, propertyName: string): void; 5 | export function BeforeFindAfterExpandIncludeAll(options: HookOptions): Function; 6 | export function BeforeFindAfterExpandIncludeAll(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeFindAfterExpandIncludeAll', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-find-after-options.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeFindAfterOptions(target: any, propertyName: string): void; 5 | export function BeforeFindAfterOptions(options: HookOptions): Function; 6 | export function BeforeFindAfterOptions(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeFindAfterOptions', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-find.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeFind(target: any, propertyName: string): void; 5 | export function BeforeFind(options: HookOptions): Function; 6 | export function BeforeFind(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeFind', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-init.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeInit(target: any, propertyName: string): void; 5 | export function BeforeInit(options: HookOptions): Function; 6 | export function BeforeInit(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeInit', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-restore.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeRestore(target: any, propertyName: string): void; 5 | export function BeforeRestore(options: HookOptions): Function; 6 | export function BeforeRestore(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeRestore' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-save.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeSave(target: any, propertyName: string): void; 5 | export function BeforeSave(options: HookOptions): Function; 6 | export function BeforeSave(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeSave' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-sync.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeSync(target: any, propertyName: string): void; 5 | export function BeforeSync(options: HookOptions): Function; 6 | export function BeforeSync(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeSync', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-update.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeUpdate(target: any, propertyName: string): void; 5 | export function BeforeUpdate(options: HookOptions): Function; 6 | export function BeforeUpdate(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeUpdate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-upsert.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeUpsert(target: any, propertyName: string): void; 5 | export function BeforeUpsert(options: HookOptions): Function; 6 | export function BeforeUpsert(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeUpsert' as any, args); 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/single/before/before-validate.ts: -------------------------------------------------------------------------------- 1 | import { HookOptions } from '../../shared/hook-options'; 2 | import { implementHookDecorator } from '../../shared/hooks-service'; 3 | 4 | export function BeforeValidate(target: any, propertyName: string): void; 5 | export function BeforeValidate(options: HookOptions): Function; 6 | export function BeforeValidate(...args: any[]): void | Function { 7 | return implementHookDecorator('beforeValidate', args); 8 | } 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './associations/belongs-to/belongs-to'; 2 | export * from './associations/belongs-to/belongs-to-association'; 3 | export * from './associations/belongs-to-many/belongs-to-many'; 4 | export * from './associations/belongs-to-many/belongs-to-many-association'; 5 | export * from './associations/belongs-to-many/belongs-to-many-options'; 6 | export * from './associations/foreign-key/foreign-key'; 7 | export * from './associations/has/has-association'; 8 | export * from './associations/has/has-one'; 9 | export * from './associations/has/has-many'; 10 | export * from './associations/shared/association'; 11 | export * from './associations/shared/base-association'; 12 | export * from './model/model/association/association-action-options'; 13 | export * from './associations/shared/union-association-options'; 14 | export * from './associations/shared/association-service'; 15 | export * from './associations/through/through-options'; 16 | 17 | export * from './hooks/bulk/after/after-bulk-create'; 18 | export * from './hooks/bulk/after/after-bulk-destroy'; 19 | export * from './hooks/bulk/after/after-bulk-restore'; 20 | export * from './hooks/bulk/after/after-bulk-sync'; 21 | export * from './hooks/bulk/after/after-bulk-update'; 22 | export * from './hooks/bulk/before/before-bulk-create'; 23 | export * from './hooks/bulk/before/before-bulk-destroy'; 24 | export * from './hooks/bulk/before/before-bulk-restore'; 25 | export * from './hooks/bulk/before/before-bulk-sync'; 26 | export * from './hooks/bulk/before/before-bulk-update'; 27 | export * from './hooks/single/after/after-connect'; 28 | export * from './hooks/single/after/after-create'; 29 | export * from './hooks/single/after/after-define'; 30 | export * from './hooks/single/after/after-destroy'; 31 | export * from './hooks/single/after/after-find'; 32 | export * from './hooks/single/after/after-init'; 33 | export * from './hooks/single/after/after-restore'; 34 | export * from './hooks/single/after/after-save'; 35 | export * from './hooks/single/after/after-sync'; 36 | export * from './hooks/single/after/after-update'; 37 | export * from './hooks/single/after/after-upsert'; 38 | export * from './hooks/single/after/after-validate'; 39 | export * from './hooks/single/before/before-connect'; 40 | export * from './hooks/single/before/before-count'; 41 | export * from './hooks/single/before/before-create'; 42 | export * from './hooks/single/before/before-define'; 43 | export * from './hooks/single/before/before-destroy'; 44 | export * from './hooks/single/before/before-find'; 45 | export * from './hooks/single/before/before-find-after-expand-include-all'; 46 | export * from './hooks/single/before/before-find-after-options'; 47 | export * from './hooks/single/before/before-init'; 48 | export * from './hooks/single/before/before-restore'; 49 | export * from './hooks/single/before/before-save'; 50 | export * from './hooks/single/before/before-sync'; 51 | export * from './hooks/single/before/before-update'; 52 | export * from './hooks/single/before/before-upsert'; 53 | export * from './hooks/single/before/before-validate'; 54 | export * from './hooks/shared/hook-options'; 55 | export * from './hooks/shared/hooks-service'; 56 | export * from './hooks/shared/validation-failed'; 57 | 58 | export * from './model/column/column-options/allow-null'; 59 | export * from './model/column/column-options/comment'; 60 | export * from './model/column/column-options/default'; 61 | export * from './model/column/column-options/unique'; 62 | export * from './model/column/primary-key/auto-increment'; 63 | export * from './model/column/primary-key/primary-key'; 64 | export * from './model/column/timestamps/created-at'; 65 | export * from './model/column/timestamps/deleted-at'; 66 | export * from './model/column/timestamps/updated-at'; 67 | export * from './model/column/attribute-service'; 68 | export * from './model/column/column'; 69 | export * from './model/model/association/association-count-options'; 70 | export * from './model/model/association/association-get-options'; 71 | export * from './model/model/model'; 72 | export * from './model/shared/model-class-getter'; 73 | export * from './model/shared/model-service'; 74 | export * from './model/table/table'; 75 | export * from './model/table/table-options'; 76 | export * from './model/index/create-index-decorator'; 77 | export * from './model/index/index-decorator'; 78 | export * from './model/index/index-service'; 79 | 80 | export * from './scopes/default-scope'; 81 | export * from './scopes/scope-options'; 82 | export * from './scopes/scope-service'; 83 | export * from './scopes/scope-table-options'; 84 | export * from './scopes/scopes'; 85 | 86 | export * from './sequelize/data-type/data-type'; 87 | export * from './sequelize/data-type/data-type-service'; 88 | export * from './sequelize/sequelize/sequelize-options'; 89 | export * from './sequelize/validation-only/db-dialect-dummy'; 90 | export * from './sequelize/sequelize/sequelize'; 91 | export * from './sequelize/sequelize/sequelize-service'; 92 | export * from './sequelize/repository/repository'; 93 | 94 | export * from './validation/contains'; 95 | export * from './validation/equals'; 96 | export * from './validation/is'; 97 | export * from './validation/is-after'; 98 | export * from './validation/is-alpha'; 99 | export * from './validation/is-alphanumeric'; 100 | export * from './validation/is-before'; 101 | export * from './validation/is-credit-card'; 102 | export * from './validation/is-date'; 103 | export * from './validation/is-decimal'; 104 | export * from './validation/is-email'; 105 | export * from './validation/is-float'; 106 | export * from './validation/is-in'; 107 | export * from './validation/is-int'; 108 | export * from './validation/is-ip'; 109 | export * from './validation/is-ip-v4'; 110 | export * from './validation/is-array'; 111 | export * from './validation/is-ip-v6'; 112 | export * from './validation/is-lowercase'; 113 | export * from './validation/is-null'; 114 | export * from './validation/is-numeric'; 115 | export * from './validation/is-uppercase'; 116 | export * from './validation/is-url'; 117 | export * from './validation/is-uuid'; 118 | export * from './validation/length'; 119 | export * from './validation/max'; 120 | export * from './validation/min'; 121 | export * from './validation/not'; 122 | export * from './validation/not-contains'; 123 | export * from './validation/not-empty'; 124 | export * from './validation/not-in'; 125 | export * from './validation/not-null'; 126 | export * from './validation/validate'; 127 | export * from './validation/validator'; 128 | -------------------------------------------------------------------------------- /src/model/column/attribute-service.ts: -------------------------------------------------------------------------------- 1 | import { deepAssign } from '../../shared/object'; 2 | import { ModelAttributeColumnOptions } from 'sequelize'; 3 | 4 | const ATTRIBUTES_KEY = 'sequelize:attributes'; 5 | 6 | /** 7 | * Returns model attributes from class by restoring this 8 | * information from reflect metadata 9 | */ 10 | export function getAttributes(target: any): any | undefined { 11 | const attributes = Reflect.getMetadata(ATTRIBUTES_KEY, target); 12 | 13 | if (attributes) { 14 | return Object.keys(attributes).reduce((copy, key) => { 15 | copy[key] = { ...attributes[key] }; 16 | 17 | return copy; 18 | }, {}); 19 | } 20 | } 21 | 22 | /** 23 | * Sets attributes 24 | */ 25 | export function setAttributes(target: any, attributes: any): void { 26 | Reflect.defineMetadata(ATTRIBUTES_KEY, { ...attributes }, target); 27 | } 28 | 29 | /** 30 | * Adds model attribute by specified property name and 31 | * sequelize attribute options and stores this information 32 | * through reflect metadata 33 | */ 34 | export function addAttribute(target: any, name: string, options: any): void { 35 | let attributes = getAttributes(target); 36 | 37 | if (!attributes) { 38 | attributes = {}; 39 | } 40 | attributes[name] = { ...options }; 41 | 42 | setAttributes(target, attributes); 43 | } 44 | 45 | /** 46 | * Adds attribute options for specific attribute 47 | */ 48 | export function addAttributeOptions( 49 | target: any, 50 | propertyName: string, 51 | options: Partial 52 | ): void { 53 | const attributes = getAttributes(target); 54 | 55 | if (!attributes || !attributes[propertyName]) { 56 | throw new Error( 57 | `@Column annotation is missing for "${propertyName}" of class "${target.constructor.name}"` + 58 | ` or annotation order is wrong.` 59 | ); 60 | } 61 | 62 | attributes[propertyName] = deepAssign(attributes[propertyName], options); 63 | 64 | setAttributes(target, attributes); 65 | } 66 | -------------------------------------------------------------------------------- /src/model/column/column-options/allow-null.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../attribute-service'; 2 | 3 | /** 4 | * Sets allowNull true for annotated property column. 5 | */ 6 | export function AllowNull(target: any, propertyName: string): void; 7 | export function AllowNull(allowNull: boolean): Function; 8 | export function AllowNull(...args: any[]): void | Function { 9 | if (args.length === 1) { 10 | const allowNull = args[0]; 11 | 12 | return (target: any, propertyName: string) => 13 | addAttributeOptions(target, propertyName, { allowNull }); 14 | } else { 15 | const target = args[0]; 16 | const propertyName = args[1]; 17 | 18 | addAttributeOptions(target, propertyName, { 19 | allowNull: true, 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/model/column/column-options/comment.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../attribute-service'; 2 | 3 | /** 4 | * Sets the specified comment value for the annotated field 5 | */ 6 | export function Comment(value: string): PropertyDecorator { 7 | return (target: any, propertyName: string) => { 8 | addAttributeOptions(target, propertyName, { 9 | comment: value, 10 | }); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/model/column/column-options/default.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../attribute-service'; 2 | 3 | /** 4 | * Sets the specified default value for the annotated field 5 | */ 6 | export function Default(value: any): Function { 7 | return (target: any, propertyName: string) => { 8 | addAttributeOptions(target, propertyName, { 9 | defaultValue: value, 10 | }); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/model/column/column-options/unique.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../attribute-service'; 2 | 3 | type UniqueOptions = boolean | string | { name: string; msg: string }; 4 | 5 | /** 6 | * Sets unique option as specified in options and returns decorator 7 | */ 8 | export function Unique(options: UniqueOptions): Function; 9 | 10 | /** 11 | * Decorator, which sets unique option true for annotated property. 12 | */ 13 | export function Unique(target: Object, propertyName: string): void; 14 | 15 | export function Unique(...args: any[]): void | Function { 16 | if (args.length === 1) { 17 | const [options] = args; 18 | return (_target, _propertyName) => { 19 | annotate(_target, _propertyName, options); 20 | }; 21 | } 22 | const [target, propertyName] = args; 23 | annotate(target, propertyName); 24 | } 25 | 26 | function annotate(target: Object, propertyName: string, option: UniqueOptions = true) { 27 | addAttributeOptions(target, propertyName, { 28 | unique: option, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/model/column/column.ts: -------------------------------------------------------------------------------- 1 | import { ModelAttributeColumnOptions, DataType } from 'sequelize'; 2 | 3 | import { addAttribute } from './attribute-service'; 4 | import { isDataType } from '../../sequelize/data-type/data-type-service'; 5 | import { getSequelizeTypeByDesignType } from '../shared/model-service'; 6 | 7 | export function Column(dataType: DataType): Function; 8 | export function Column(options: Partial): Function; 9 | export function Column( 10 | target: any, 11 | propertyName: string, 12 | propertyDescriptor?: PropertyDescriptor 13 | ): void; 14 | export function Column(...args: any[]): Function | void { 15 | // In case of no specified options, we infer the 16 | // sequelize data type by the type of the property 17 | if (args.length >= 2) { 18 | const target = args[0]; 19 | const propertyName = args[1]; 20 | const propertyDescriptor = args[2]; 21 | 22 | annotate(target, propertyName, propertyDescriptor); 23 | return; 24 | } 25 | 26 | return (target: any, propertyName: string, propertyDescriptor?: PropertyDescriptor) => { 27 | annotate( 28 | target, 29 | propertyName, 30 | propertyDescriptor ?? Object.getOwnPropertyDescriptor(target, propertyName), 31 | args[0] 32 | ); 33 | }; 34 | } 35 | 36 | function annotate( 37 | target: any, 38 | propertyName: string, 39 | propertyDescriptor?: PropertyDescriptor, 40 | optionsOrDataType: Partial | DataType = {} 41 | ): void { 42 | let options: Partial; 43 | 44 | if (isDataType(optionsOrDataType)) { 45 | options = { 46 | type: optionsOrDataType, 47 | }; 48 | } else { 49 | options = { ...(optionsOrDataType as ModelAttributeColumnOptions) }; 50 | 51 | if (!options.type) { 52 | options.type = getSequelizeTypeByDesignType(target, propertyName); 53 | } 54 | } 55 | 56 | if (propertyDescriptor) { 57 | if (propertyDescriptor.get) { 58 | options.get = propertyDescriptor.get; 59 | } 60 | if (propertyDescriptor.set) { 61 | options.set = propertyDescriptor.set; 62 | } 63 | } 64 | 65 | addAttribute(target, propertyName, options); 66 | } 67 | -------------------------------------------------------------------------------- /src/model/column/primary-key/auto-increment.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../attribute-service'; 2 | 3 | /** 4 | * Sets auto increment true for annotated field 5 | */ 6 | export function AutoIncrement(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | autoIncrement: true, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/model/column/primary-key/primary-key.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../attribute-service'; 2 | 3 | /** 4 | * Sets primary key option true for annotated property. 5 | */ 6 | export function PrimaryKey(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | primaryKey: true, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/model/column/timestamps/created-at.ts: -------------------------------------------------------------------------------- 1 | import { addOptions } from '../../shared/model-service'; 2 | 3 | export function CreatedAt(target: any, propertyName: string): void { 4 | addOptions(target, { 5 | createdAt: propertyName, 6 | timestamps: true, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /src/model/column/timestamps/deleted-at.ts: -------------------------------------------------------------------------------- 1 | import { addOptions } from '../../shared/model-service'; 2 | 3 | export function DeletedAt(target: any, propertyName: string): void { 4 | addOptions(target, { 5 | deletedAt: propertyName, 6 | timestamps: true, 7 | paranoid: true, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/model/column/timestamps/updated-at.ts: -------------------------------------------------------------------------------- 1 | import { addOptions } from '../../shared/model-service'; 2 | 3 | export function UpdatedAt(target: any, propertyName: string): void { 4 | addOptions(target, { 5 | updatedAt: propertyName, 6 | timestamps: true, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /src/model/index/create-index-decorator.ts: -------------------------------------------------------------------------------- 1 | import { addFieldToIndex, IndexOptions, IndexFieldOptions } from './index-service'; 2 | 3 | interface IndexDecorator { 4 | (fieldOptions: Pick>): Function; 5 | (target: any, propertyName: string, propertyDescriptor?: PropertyDescriptor): void; 6 | } 7 | 8 | export function createIndexDecorator(options: IndexOptions = {}): IndexDecorator { 9 | let indexId: string | number; 10 | return ((...args: any[]) => { 11 | if (args.length >= 2) { 12 | const [target, propertyName] = args; 13 | 14 | const fieldOptions = { name: propertyName }; 15 | indexId = addFieldToIndex(target, fieldOptions, options, indexId); 16 | return; 17 | } 18 | 19 | return (target: any, propertyName: string) => { 20 | const fieldOptions = { name: propertyName, ...args[0] }; 21 | indexId = addFieldToIndex(target, fieldOptions, options, indexId); 22 | }; 23 | }) as IndexDecorator; 24 | } 25 | -------------------------------------------------------------------------------- /src/model/index/index-decorator.ts: -------------------------------------------------------------------------------- 1 | import { addFieldToIndex, IndexOptions, IndexFieldOptions } from './index-service'; 2 | 3 | type IndexDecoratorOptions = IndexOptions & 4 | Pick>; 5 | 6 | export function Index(name: string): Function; 7 | export function Index(options: IndexDecoratorOptions): Function; 8 | export function Index( 9 | target: any, 10 | propertyName: string, 11 | propertyDescriptor?: PropertyDescriptor 12 | ): void; 13 | export function Index(...args: any[]): Function | void { 14 | if (args.length >= 2) { 15 | const [target, propertyName] = args; 16 | 17 | annotateModelWithIndex(target, propertyName); 18 | return; 19 | } 20 | 21 | return (target: any, propertyName: string) => { 22 | annotateModelWithIndex(target, propertyName, args[0]); 23 | }; 24 | } 25 | 26 | export function annotateModelWithIndex( 27 | target: any, 28 | propertyName: string, 29 | optionsOrName: IndexDecoratorOptions | string = {}, 30 | indexId?: string | number 31 | ): string | number { 32 | let indexOptions: IndexOptions; 33 | let fieldOptions: IndexFieldOptions; 34 | if (typeof optionsOrName === 'string') { 35 | indexOptions = { name: optionsOrName }; 36 | fieldOptions = { name: propertyName }; 37 | } else { 38 | const { length, order, collate, ...rest } = optionsOrName; 39 | indexOptions = rest; 40 | fieldOptions = { 41 | name: propertyName, 42 | length, 43 | order, 44 | collate, 45 | }; 46 | } 47 | 48 | return addFieldToIndex(target, fieldOptions, indexOptions, indexId); 49 | } 50 | -------------------------------------------------------------------------------- /src/model/index/index-service.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { IndexesOptions as SequelizeIndexOptions } from 'sequelize'; 3 | 4 | const INDEXES_KEY = 'sequelize:indexes'; 5 | 6 | // https://github.com/sequelize/sequelize/blob/beeff96f/types/lib/query-interface.d.ts#L169 7 | export interface IndexFieldOptions { 8 | name: string; 9 | length?: number; 10 | order?: 'ASC' | 'DESC'; 11 | collate?: string; 12 | } 13 | 14 | export interface IndexesMeta { 15 | named: { [name: string]: IndexOptions }; 16 | unnamed: IndexOptions[]; 17 | } 18 | 19 | export type IndexOptions = Pick< 20 | SequelizeIndexOptions, 21 | Exclude 22 | >; 23 | 24 | /** 25 | * Returns model indexes from class by restoring this 26 | * information from reflect metadata 27 | */ 28 | export function getIndexes(target: any): IndexesMeta { 29 | const { named = {}, unnamed = [] }: IndexesMeta = Reflect.getMetadata(INDEXES_KEY, target) || {}; 30 | 31 | return { named: { ...named }, unnamed: [...unnamed] }; 32 | } 33 | 34 | /** 35 | * Sets indexes 36 | */ 37 | export function setIndexes(target: any, indexes: IndexesMeta): void { 38 | Reflect.defineMetadata(INDEXES_KEY, indexes, target); 39 | } 40 | 41 | /** 42 | * Adds field to index by sequelize index and index field options, 43 | * and stores this information through reflect metadata. Returns index ID. 44 | */ 45 | export function addFieldToIndex( 46 | target: any, 47 | fieldOptions: IndexFieldOptions, 48 | indexOptions: IndexOptions, 49 | indexId?: string | number 50 | ): string | number { 51 | const indexes = getIndexes(target); 52 | 53 | const chosenId = 54 | typeof indexId !== 'undefined' ? indexId : indexOptions.name || indexes.unnamed.length; 55 | const indexStore = typeof chosenId === 'string' ? indexes.named : indexes.unnamed; 56 | if (!indexStore[chosenId]) indexStore[chosenId] = { ...indexOptions }; 57 | 58 | const index = indexStore[chosenId]; 59 | if (!index.fields) index.fields = []; 60 | index.fields.push(fieldOptions); 61 | 62 | setIndexes(target, indexes); 63 | 64 | return chosenId; 65 | } 66 | -------------------------------------------------------------------------------- /src/model/model/association/association-action-options.ts: -------------------------------------------------------------------------------- 1 | export interface AssociationActionOptions { 2 | through?: any; 3 | transaction?: any; 4 | validate?: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/model/model/association/association-count-options.ts: -------------------------------------------------------------------------------- 1 | import { FindOptions } from 'sequelize'; 2 | 3 | export type AssociationCountOptions = { 4 | scope?: string | boolean; 5 | } & FindOptions; 6 | -------------------------------------------------------------------------------- /src/model/model/association/association-create-options.ts: -------------------------------------------------------------------------------- 1 | import { CreateOptions } from 'sequelize'; 2 | 3 | export type AssociationCreateOptions = { 4 | through?: any; 5 | } & CreateOptions; 6 | -------------------------------------------------------------------------------- /src/model/model/association/association-get-options.ts: -------------------------------------------------------------------------------- 1 | import { FindOptions } from 'sequelize'; 2 | 3 | export type AssociationGetOptions = { 4 | scope?: string | boolean; 5 | schema?: string; 6 | } & FindOptions; 7 | -------------------------------------------------------------------------------- /src/model/shared/model-class-getter.ts: -------------------------------------------------------------------------------- 1 | import { ModelType } from '../model/model'; 2 | 3 | export type ModelClassGetter = ( 4 | returns?: void 5 | ) => ModelType; 6 | -------------------------------------------------------------------------------- /src/model/shared/model-not-initialized-error.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '../model/model'; 2 | 3 | export class ModelNotInitializedError extends Error { 4 | message: string; 5 | 6 | constructor(modelClass: typeof Model, additionalMessage: string) { 7 | super(); 8 | this.message = 9 | `Model not initialized: ${additionalMessage} "${modelClass.name}" ` + 10 | `needs to be added to a Sequelize instance.`; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/model/shared/model-service.ts: -------------------------------------------------------------------------------- 1 | import { DataTypeAbstract, ModelOptions } from 'sequelize'; 2 | 3 | import { Model } from '../model/model'; 4 | import { inferDataType } from '../../sequelize/data-type/data-type-service'; 5 | 6 | const MODEL_NAME_KEY = 'sequelize:modelName'; 7 | const OPTIONS_KEY = 'sequelize:options'; 8 | 9 | /** 10 | * Sets model name from class by storing this 11 | * information through reflect metadata 12 | */ 13 | export function setModelName(target: any, modelName: string): void { 14 | Reflect.defineMetadata(MODEL_NAME_KEY, modelName, target); 15 | } 16 | 17 | /** 18 | * Returns model name from class by restoring this 19 | * information from reflect metadata 20 | */ 21 | export function getModelName(target: any): string { 22 | return Reflect.getMetadata(MODEL_NAME_KEY, target); 23 | } 24 | 25 | /** 26 | * Returns sequelize define options from class prototype 27 | * by restoring this information from reflect metadata 28 | */ 29 | export function getOptions(target: any): ModelOptions | undefined { 30 | const options = Reflect.getMetadata(OPTIONS_KEY, target); 31 | 32 | if (options) { 33 | return { ...options }; 34 | } 35 | } 36 | 37 | /** 38 | * Sets seuqlize define options to class prototype 39 | */ 40 | export function setOptions(target: any, options: ModelOptions): void { 41 | Reflect.defineMetadata(OPTIONS_KEY, { ...options }, target); 42 | } 43 | 44 | /** 45 | * Adds options be assigning new options to old one 46 | */ 47 | export function addOptions(target: any, options: ModelOptions): void { 48 | let _options = getOptions(target); 49 | 50 | if (!_options) { 51 | _options = {}; 52 | } 53 | setOptions(target, { 54 | ..._options, 55 | ...options, 56 | validate: { 57 | ...(_options.validate || {}), 58 | ...(options.validate || {}), 59 | }, 60 | }); 61 | } 62 | 63 | /** 64 | * Maps design types to sequelize data types; 65 | * @throws if design type cannot be automatically mapped to 66 | * a sequelize data type 67 | */ 68 | export function getSequelizeTypeByDesignType(target: any, propertyName: string): DataTypeAbstract { 69 | const type = Reflect.getMetadata('design:type', target, propertyName); 70 | const dataType = inferDataType(type); 71 | 72 | if (dataType) { 73 | return dataType; 74 | } 75 | 76 | throw new Error(`Specified type of property '${propertyName}' 77 | cannot be automatically resolved to a sequelize data type. Please 78 | define the data type manually`); 79 | } 80 | 81 | /** 82 | * Resolves all model getters of specified options object 83 | * recursively. 84 | * So that {model: () => Person} will be converted to 85 | * {model: Person} 86 | */ 87 | export function resolveModelGetter(options: any): any { 88 | const maybeModelGetter = (value) => typeof value === 'function' && value.length === 0; 89 | const isModel = (value) => value && value.prototype && value.prototype instanceof Model; 90 | const isOptionObjectOrArray = (value) => value && typeof value === 'object'; 91 | 92 | return Object.keys(options).reduce( 93 | (acc, key) => { 94 | const value = options[key]; 95 | 96 | if (maybeModelGetter(value)) { 97 | const maybeModel = value(); 98 | 99 | if (isModel(maybeModel)) { 100 | acc[key] = maybeModel; 101 | } 102 | } else if (isOptionObjectOrArray(value)) { 103 | acc[key] = resolveModelGetter(value); 104 | } 105 | 106 | return acc; 107 | }, 108 | Array.isArray(options) ? [...options] : { ...options } 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/model/table/table-options.ts: -------------------------------------------------------------------------------- 1 | import { Model, ModelOptions } from 'sequelize'; 2 | 3 | export interface TableOptions extends ModelOptions { 4 | modelName?: string; 5 | 6 | /** 7 | * Enable optimistic locking. When enabled, sequelize will add a version count attribute 8 | * to the model and throw an OptimisticLockingError error when stale instances are saved. 9 | * Set to true or a string with the attribute name you want to use to enable. 10 | */ 11 | version?: boolean | string; 12 | } 13 | -------------------------------------------------------------------------------- /src/model/table/table.ts: -------------------------------------------------------------------------------- 1 | import { TableOptions } from './table-options'; 2 | import { Model } from '../model/model'; 3 | import { setModelName, addOptions } from '../shared/model-service'; 4 | 5 | export function Table(options: TableOptions): Function; 6 | export function Table(target: Function): void; 7 | export function Table(arg: any): void | Function { 8 | if (typeof arg === 'function') { 9 | annotate(arg); 10 | } else { 11 | const options: TableOptions = { ...arg }; 12 | return (target: any) => annotate(target, options); 13 | } 14 | } 15 | 16 | function annotate(target: typeof Model, options: TableOptions = {}): void { 17 | setModelName(target.prototype, options.modelName || target.name); 18 | addOptions(target.prototype, options); 19 | } 20 | -------------------------------------------------------------------------------- /src/scopes/default-scope.ts: -------------------------------------------------------------------------------- 1 | import { addScopeOptions, addScopeOptionsGetter } from './scope-service'; 2 | import { ScopeFindOptions } from './scope-find-options'; 3 | import { DefaultScopeGetter } from './scope-options'; 4 | 5 | /** 6 | * Decorator for defining default Model scope 7 | */ 8 | export function DefaultScope(scopeGetter: DefaultScopeGetter): Function; 9 | 10 | /** 11 | * Decorator for defining default Model scope 12 | * @deprecated 13 | */ 14 | export function DefaultScope( 15 | scope: ScopeFindOptions 16 | ): Function; 17 | 18 | /** 19 | * Decorator for defining default Model scope 20 | */ 21 | export function DefaultScope( 22 | scopeOrSsopeGetter: ScopeFindOptions | DefaultScopeGetter 23 | ): Function { 24 | return (target: any) => { 25 | if (typeof scopeOrSsopeGetter === 'function') { 26 | addScopeOptionsGetter(target.prototype, { getDefaultScope: scopeOrSsopeGetter }); 27 | } else { 28 | addScopeOptions(target.prototype, { defaultScope: scopeOrSsopeGetter }); 29 | } 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/scopes/scope-find-options.ts: -------------------------------------------------------------------------------- 1 | import { Association, FindOptions, IncludeOptions } from 'sequelize'; 2 | import { ModelClassGetter } from '../model/shared/model-class-getter'; 3 | 4 | export type ScopeIncludeOptions = { 5 | [K in keyof IncludeOptions]: K extends 'model' 6 | ? ModelClassGetter 7 | : K extends 'include' 8 | ? ScopeIncludeOptions 9 | : IncludeOptions[K]; 10 | }; 11 | 12 | export type ScopeFindOptions = { 13 | [K in keyof FindOptions]: K extends 'include' 14 | ? 15 | | ModelClassGetter[] 16 | | Association[] 17 | | ScopeIncludeOptions[] 18 | | { all: true } 19 | : FindOptions[K]; 20 | }; 21 | -------------------------------------------------------------------------------- /src/scopes/scope-options.ts: -------------------------------------------------------------------------------- 1 | import { ScopeTableOptions } from './scope-table-options'; 2 | import { ScopeFindOptions } from './scope-find-options'; 3 | import { FindOptions } from 'sequelize'; 4 | 5 | export interface ScopeOptions 6 | extends ScopeTableOptions { 7 | defaultScope?: ScopeFindOptions; 8 | } 9 | 10 | export interface ScopeOptionsGetters { 11 | getDefaultScope?: DefaultScopeGetter; 12 | getScopes?: ScopesOptionsGetter; 13 | } 14 | 15 | export type DefaultScopeGetter = () => FindOptions; 16 | export type ScopesOptionsGetter = () => { [sopeName: string]: ScopesOptions }; 17 | export type ScopesOptions = FindOptions | ((...args: any[]) => FindOptions); 18 | -------------------------------------------------------------------------------- /src/scopes/scope-service.ts: -------------------------------------------------------------------------------- 1 | import { FindOptions } from 'sequelize'; 2 | 3 | import { ModelCtor } from '../model/model/model'; 4 | import { deepAssign } from '../shared/object'; 5 | import { ScopeOptions, ScopeOptionsGetters, ScopesOptions } from './scope-options'; 6 | import { resolveModelGetter } from '../model/shared/model-service'; 7 | import { inferAlias } from '../associations/alias-inference/alias-inference-service'; 8 | import { ScopeFindOptions } from './scope-find-options'; 9 | 10 | const SCOPES_KEY = 'sequelize:scopes'; 11 | const SCOPES_OPTIONS_KEY = 'sequelize:scopes-options'; 12 | 13 | /** 14 | * Resolves scopes and adds them to the specified models 15 | */ 16 | export function resolveScopes(models: ModelCtor[]): void { 17 | models.forEach((model) => { 18 | resolvesDeprecatedScopes(model); 19 | const { getDefaultScope, getScopes } = getScopeOptionsGetters(model.prototype); 20 | let options = {}; 21 | if (getDefaultScope) { 22 | options = { ...options, defaultScope: getDefaultScope() }; 23 | } 24 | if (getScopes) { 25 | options = { ...options, ...getScopes() }; 26 | } 27 | Object.keys(options).forEach((key) => resolveScope(key, model, options[key])); 28 | }); 29 | } 30 | export const resolveScope = (scopeName: string, model: ModelCtor, options: ScopesOptions): void => { 31 | if (typeof options === 'function') { 32 | const fn = options; 33 | options = (...args: any[]) => inferAlias(fn(...args), model); 34 | } else { 35 | options = inferAlias(options, model); 36 | } 37 | model.addScope(scopeName, options as FindOptions, { override: true }); 38 | }; 39 | 40 | export const addScopeOptionsGetter = (target: any, options: ScopeOptionsGetters): void => { 41 | const currentOptions = getScopeOptionsGetters(target) || {}; 42 | setScopeOptionsGetters(target, { ...currentOptions, ...options }); 43 | }; 44 | 45 | export const getScopeOptionsGetters = (target: any): ScopeOptionsGetters => { 46 | const options = Reflect.getMetadata(SCOPES_OPTIONS_KEY, target); 47 | if (options) { 48 | return { ...options }; 49 | } 50 | return {}; 51 | }; 52 | 53 | export const setScopeOptionsGetters = (target: any, options: ScopeOptionsGetters): void => { 54 | Reflect.defineMetadata(SCOPES_OPTIONS_KEY, options, target); 55 | }; 56 | 57 | /** 58 | * @deprecated 59 | */ 60 | export const resolvesDeprecatedScopes = (model: ModelCtor): void => { 61 | const options = getScopeOptions(model.prototype) || {}; 62 | Object.keys(options).forEach((key) => resolveDeprecatedScope(key, model, options[key])); 63 | }; 64 | 65 | /** 66 | * Adds scope option meta data for specified prototype 67 | * @deprecated 68 | */ 69 | export function addScopeOptions( 70 | target: any, 71 | options: ScopeOptions 72 | ): void { 73 | const _options = getScopeOptions(target) || {}; 74 | setScopeOptions(target, deepAssign({}, _options, options)); 75 | } 76 | 77 | /** 78 | * Returns scope option meta data from specified target 79 | * @deprecated 80 | */ 81 | export function getScopeOptions( 82 | target: any 83 | ): ScopeOptions | undefined { 84 | const options = Reflect.getMetadata(SCOPES_KEY, target); 85 | if (options) { 86 | return deepAssign({}, options); 87 | } 88 | } 89 | 90 | /** 91 | * @deprecated 92 | */ 93 | function resolveDeprecatedScope( 94 | scopeName: string, 95 | model: ModelCtor, 96 | options: ScopeFindOptions | Function | undefined 97 | ): void { 98 | if (typeof options === 'function') { 99 | const fn: Function = options; 100 | options = (...args: any[]) => inferAlias(fn(...args), model); 101 | } else { 102 | options = inferAlias(resolveModelGetter(options), model); 103 | } 104 | model.addScope(scopeName, options as FindOptions, { override: true }); 105 | } 106 | 107 | /** 108 | * Set scope option meta data for specified prototype 109 | * @deprecated 110 | */ 111 | function setScopeOptions( 112 | target: any, 113 | options: ScopeOptions 114 | ): void { 115 | Reflect.defineMetadata(SCOPES_KEY, options, target); 116 | } 117 | -------------------------------------------------------------------------------- /src/scopes/scope-table-options.ts: -------------------------------------------------------------------------------- 1 | import { ScopeFindOptions } from './scope-find-options'; 2 | 3 | export interface ScopeTableOptions { 4 | [scopeName: string]: 5 | | ScopeFindOptions 6 | | Function 7 | | undefined; 8 | } 9 | -------------------------------------------------------------------------------- /src/scopes/scopes.ts: -------------------------------------------------------------------------------- 1 | import { addScopeOptions, addScopeOptionsGetter } from './scope-service'; 2 | import { ScopeTableOptions } from './scope-table-options'; 3 | import { ScopesOptionsGetter } from './scope-options'; 4 | 5 | /** 6 | * Decorator for defining Model scopes 7 | */ 8 | export function Scopes(scopesGetter: ScopesOptionsGetter): Function; 9 | 10 | /** 11 | * Decorator for defining Model scopes 12 | * @deprecated 13 | */ 14 | export function Scopes( 15 | scopes: ScopeTableOptions 16 | ): Function; 17 | 18 | /** 19 | * Decorator for defining Model scopes 20 | */ 21 | export function Scopes( 22 | scopesOrScopesGetter: 23 | | ScopeTableOptions 24 | | ScopesOptionsGetter 25 | ): Function { 26 | return (target: any) => { 27 | if (typeof scopesOrScopesGetter === 'function') { 28 | addScopeOptionsGetter(target.prototype, { 29 | getScopes: scopesOrScopesGetter, 30 | }); 31 | } else { 32 | addScopeOptions(target.prototype, scopesOrScopesGetter); 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/sequelize/data-type/data-type-service.ts: -------------------------------------------------------------------------------- 1 | import { DataType, DataTypeAbstract, DataTypes } from 'sequelize'; 2 | 3 | /* 4 | * Checks if specified value is a sequelize data type (ABSTRACT, STRING...) 5 | */ 6 | export function isDataType(value: any): value is DataType { 7 | return ( 8 | typeof value === 'string' || 9 | (typeof value === 'function' && value({}) instanceof (DataTypes.ABSTRACT as any)) || 10 | value instanceof (DataTypes.ABSTRACT as any) 11 | ); 12 | } 13 | 14 | /** 15 | * Infers sequelize data type by design type 16 | */ 17 | export function inferDataType(designType: any): DataTypeAbstract | undefined { 18 | switch (designType) { 19 | case String: 20 | return DataTypes.STRING; 21 | case BigInt: 22 | return DataTypes.BIGINT; 23 | case Number: 24 | return DataTypes.INTEGER; 25 | case Boolean: 26 | return DataTypes.BOOLEAN; 27 | case Date: 28 | return DataTypes.DATE; 29 | case Buffer: 30 | return DataTypes.BLOB; 31 | default: 32 | return void 0; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/sequelize/data-type/data-type.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | 3 | export const DataType: typeof DataTypes = DataTypes; 4 | -------------------------------------------------------------------------------- /src/sequelize/repository/repository.ts: -------------------------------------------------------------------------------- 1 | import { NonAbstract } from '../../shared/types'; 2 | import { Model } from '../..'; 3 | 4 | export type Repository = (new () => M) & NonAbstract; 5 | -------------------------------------------------------------------------------- /src/sequelize/sequelize/sequelize-options.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'sequelize'; 2 | import { ModelCtor } from '../../model/model/model'; 3 | 4 | export type ModelMatch = (filename: string, member: string) => boolean; 5 | 6 | export interface SequelizeOptions extends Options { 7 | /** 8 | * Path to models or actual models, 9 | * which should be loaded for sequelize instance 10 | */ 11 | models?: string[] | ModelCtor[]; 12 | 13 | /** 14 | * Path to models, which should be loaded 15 | * @deprecated 16 | */ 17 | modelPaths?: string[]; 18 | 19 | /** 20 | * Matches models by filename using a custom function. 21 | * @default (filename, member) => filename === member 22 | */ 23 | modelMatch?: ModelMatch; 24 | 25 | /** 26 | * If true enables repository mode when true 27 | */ 28 | repositoryMode?: boolean; 29 | 30 | /** 31 | * If true enables validate only mode 32 | */ 33 | validateOnly?: boolean; 34 | } 35 | -------------------------------------------------------------------------------- /src/sequelize/sequelize/sequelize-service.ts: -------------------------------------------------------------------------------- 1 | import { basename, extname, join, parse } from 'path'; 2 | import * as glob from 'glob'; 3 | import { uniqueFilter } from '../../shared/array'; 4 | import { ModelMatch, SequelizeOptions } from './sequelize-options'; 5 | import { ModelCtor } from '../../model/model/model'; 6 | 7 | /** 8 | * Prepares sequelize config passed to original sequelize constructor 9 | */ 10 | export function prepareOptions(options: SequelizeOptions): SequelizeOptions { 11 | if (options.validateOnly) { 12 | return getValidationOnlyOptions(options); 13 | } 14 | return { ...(options as SequelizeOptions) }; 15 | } 16 | 17 | export function prepareArgs(...args: any[]): { preparedArgs: any[]; options?: SequelizeOptions } { 18 | const lastArg = args[args.length - 1]; 19 | const options = lastArg && typeof lastArg === 'object' ? prepareOptions(lastArg) : undefined; 20 | 21 | if (options) { 22 | args[args.length - 1] = options; 23 | } 24 | return { preparedArgs: args, options }; 25 | } 26 | 27 | function getValidationOnlyOptions(options: SequelizeOptions): SequelizeOptions { 28 | return { 29 | ...options, 30 | dialect: 'sqlite', 31 | dialectModulePath: __dirname + '/../validation-only/db-dialect-dummy', 32 | }; 33 | } 34 | 35 | /** 36 | * Determines models from value 37 | */ 38 | export function getModels(arg: (ModelCtor | string)[], modelMatch: ModelMatch): ModelCtor[] { 39 | const hasSupportedExtension = (path) => ['.ts', '.js'].indexOf(extname(path)) !== -1; 40 | 41 | if (arg && typeof arg[0] === 'string') { 42 | return arg.reduce((models: any[], dir) => { 43 | if (!glob.hasMagic(dir) && !hasSupportedExtension(dir)) dir = join(dir as string, '/*'); 44 | const _models = glob 45 | .sync(dir as string) 46 | .filter(isImportable) 47 | .map(getFullfilepathWithoutExtension) 48 | .filter(uniqueFilter) 49 | .map((fullPath) => { 50 | // eslint-disable-next-line @typescript-eslint/no-var-requires 51 | const module = require(fullPath); 52 | const fileName = basename(fullPath); 53 | 54 | const matchedMemberKey = Object.keys(module).find((m) => modelMatch(fileName, m)); 55 | const matchedMember = matchedMemberKey ? module[matchedMemberKey] : undefined; 56 | 57 | if (!matchedMember && !module.default) { 58 | throw new Error( 59 | `No default export defined for file "${fileName}" or ` + 60 | `export does not satisfy filename.` 61 | ); 62 | } 63 | return matchedMember || module.default; 64 | }); 65 | 66 | models.push(..._models); 67 | 68 | return models; 69 | }, []); 70 | } 71 | 72 | return arg as ModelCtor[]; 73 | } 74 | 75 | /** 76 | * Checks if specified filename is importable or not; 77 | * Which means that, it needs to have a specific file extension 78 | */ 79 | function isImportable(file: string): boolean { 80 | const filePart = file.slice(-3); 81 | return filePart === '.js' || (filePart === '.ts' && file.slice(-5) !== '.d.ts'); 82 | } 83 | 84 | /** 85 | * Return the value of the full path with filename, without extension 86 | */ 87 | function getFullfilepathWithoutExtension(file: string): string { 88 | const parsedFile = parse(file); 89 | return join(parsedFile.dir, parsedFile.name); 90 | } 91 | -------------------------------------------------------------------------------- /src/sequelize/sequelize/sequelize.ts: -------------------------------------------------------------------------------- 1 | import { InitOptions, Sequelize as OriginSequelize } from 'sequelize'; 2 | import { ModelNotInitializedError } from '../../model/shared/model-not-initialized-error'; 3 | import { ModelMatch, SequelizeOptions } from './sequelize-options'; 4 | import { getModels, prepareArgs } from './sequelize-service'; 5 | import { Model, ModelCtor, ModelType } from '../../model/model/model'; 6 | import { getModelName, getOptions } from '../../model/shared/model-service'; 7 | import { resolveScopes } from '../../scopes/scope-service'; 8 | import { installHooks } from '../../hooks/shared/hooks-service'; 9 | import { getAssociations } from '../../associations/shared/association-service'; 10 | import { getAttributes } from '../../model/column/attribute-service'; 11 | import { getIndexes } from '../../model/index/index-service'; 12 | import { Repository } from '../..'; 13 | 14 | export class Sequelize extends OriginSequelize { 15 | options: SequelizeOptions; 16 | repositoryMode: boolean; 17 | 18 | constructor(database: string, username: string, password?: string, options?: SequelizeOptions); 19 | constructor(database: string, username: string, options?: SequelizeOptions); 20 | constructor(options?: SequelizeOptions); 21 | constructor(uri: string, options?: SequelizeOptions); 22 | constructor(...args: any[]) { 23 | const { preparedArgs, options } = prepareArgs(...args); 24 | super(...preparedArgs); 25 | 26 | if (options) { 27 | this.repositoryMode = !!options.repositoryMode; 28 | if (options.models) this.addModels(options.models); 29 | if (options.modelPaths) this.addModels(options.modelPaths); 30 | } else { 31 | this.repositoryMode = false; 32 | } 33 | } 34 | 35 | model( 36 | model: string | ModelType 37 | ): ModelCtor { 38 | if (typeof model !== 'string') { 39 | return super.model(getModelName(model.prototype)) as ModelCtor; 40 | } 41 | return super.model(model) as ModelCtor; 42 | } 43 | 44 | addModels(models: ModelCtor[]): void; 45 | addModels(modelPaths: string[]): void; 46 | addModels(modelPaths: string[], modelMatch?: ModelMatch): void; 47 | addModels(arg: (ModelCtor | string)[]): void; 48 | addModels(arg: (ModelCtor | string)[], modelMatch?: ModelMatch): void { 49 | const defaultModelMatch = (filename, member) => filename === member; 50 | const models = getModels(arg, modelMatch || this.options.modelMatch || defaultModelMatch); 51 | 52 | const definedModels = this.defineModels(models); 53 | this.associateModels(definedModels); 54 | resolveScopes(definedModels); 55 | installHooks(definedModels); 56 | } 57 | 58 | getRepository(modelClass: new () => M): Repository { 59 | return this.model(modelClass as any) as Repository; 60 | } 61 | 62 | private associateModels(models: ModelCtor[]): void { 63 | models.forEach((model) => { 64 | const associations = getAssociations(model.prototype); 65 | 66 | if (!associations) return; 67 | 68 | associations.forEach((association) => { 69 | const options = association.getSequelizeOptions(model, this); 70 | const associatedClass = this.model(association.getAssociatedClass()); 71 | 72 | if (!associatedClass.isInitialized) { 73 | throw new ModelNotInitializedError( 74 | associatedClass, 75 | `Association between ${associatedClass.name} and ${model.name} cannot be resolved.` 76 | ); 77 | } 78 | model[association.getAssociation() as any](associatedClass, options as any); 79 | }); 80 | }); 81 | } 82 | 83 | private defineModels(models: ModelCtor[]): ModelCtor[] { 84 | return models.map((model) => { 85 | const modelName = getModelName(model.prototype); 86 | const attributes = getAttributes(model.prototype); 87 | const indexes = getIndexes(model.prototype); 88 | const modelOptions = getOptions(model.prototype); 89 | 90 | if (!modelOptions) 91 | throw new Error(`@Table annotation is missing on class "${model['name']}"`); 92 | 93 | const indexArray = Object.keys(indexes.named) 94 | .map((key) => indexes.named[key]) 95 | .concat(indexes.unnamed); 96 | const initOptions: InitOptions & { modelName } = { 97 | ...(indexArray.length > 0 && { indexes: indexArray }), 98 | ...modelOptions, 99 | modelName, 100 | sequelize: this, 101 | }; 102 | const definedModel = this.repositoryMode ? this.createRepositoryModel(model) : model; 103 | 104 | definedModel.initialize(attributes, initOptions); 105 | 106 | return definedModel; 107 | }); 108 | } 109 | 110 | private createRepositoryModel(modelClass: ModelCtor): ModelCtor { 111 | return class extends modelClass {}; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/sequelize/validation-only/db-dialect-dummy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Dummy database file, which gets required by sequelize, to 3 | * make validation of models possible without using the 4 | * actual ORM or any database connection. 5 | */ 6 | 7 | export function verbose(): any { 8 | return {}; 9 | } 10 | -------------------------------------------------------------------------------- /src/shared/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes duplicates from specified array 3 | */ 4 | export function unique(arr: T[]): T[] { 5 | return arr.filter(uniqueFilter); 6 | } 7 | 8 | /** 9 | * Returns true for items, that only exists once on an array 10 | */ 11 | export const uniqueFilter = (item: T, index: number, arr: T[]): boolean => 12 | arr.indexOf(item) === index; 13 | -------------------------------------------------------------------------------- /src/shared/object.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deep copies properties of all sources into target object. 3 | * The last source overrides all properties of the previous 4 | * ones, if they have the same names 5 | */ 6 | export function deepAssign( 7 | target: T, 8 | source1: S1, 9 | source2: S2, 10 | source3: S3 11 | ): T & S1 & S2 & S3; 12 | export function deepAssign(target: T, source1: S1, source2: S2): T & S1 & S2; 13 | export function deepAssign(target: T, source: S): T & S; 14 | export function deepAssign(target: {}, source: S): S; 15 | export function deepAssign(target: any, ...sources: any[]): any { 16 | sources.forEach((source) => { 17 | Object.getOwnPropertyNames(source).forEach( 18 | (key) => 19 | !['__proto__', 'constructor', 'prototype'].includes(key) && assign(key, target, source) 20 | ); 21 | /* istanbul ignore next */ 22 | if (Object.getOwnPropertySymbols) { 23 | Object.getOwnPropertySymbols(source).forEach((key) => assign(key, target, source)); 24 | } 25 | }); 26 | return target; 27 | 28 | function assign(key: string | number | symbol, _target: any, _source: any): void { 29 | const sourceValue = _source[key]; 30 | 31 | if (sourceValue !== void 0) { 32 | let targetValue = _target[key]; 33 | 34 | if (Array.isArray(sourceValue)) { 35 | if (!Array.isArray(targetValue)) { 36 | targetValue = []; 37 | } 38 | const length = targetValue.length; 39 | 40 | sourceValue.forEach((_, index) => assign(length + index, targetValue, sourceValue)); 41 | } else if (typeof sourceValue === 'object') { 42 | if (sourceValue instanceof RegExp) { 43 | targetValue = cloneRegExp(sourceValue); 44 | } else if (sourceValue instanceof Date) { 45 | targetValue = new Date(sourceValue); 46 | } else if (sourceValue === null) { 47 | targetValue = null; 48 | } else { 49 | if (!targetValue) { 50 | targetValue = Object.create(sourceValue.constructor.prototype); 51 | } 52 | deepAssign(targetValue, sourceValue); 53 | } 54 | } else { 55 | targetValue = sourceValue; 56 | } 57 | _target[key] = targetValue; 58 | } 59 | } 60 | } 61 | 62 | /** 63 | * I clone the given RegExp object, and ensure that the given flags exist on 64 | * the clone. The injectFlags parameter is purely additive - it cannot remove 65 | * flags that already exist on the 66 | * 67 | * @param input RegExp - I am the regular expression object being cloned. 68 | * @param injectFlags String( Optional ) - I am the flags to enforce on the clone. 69 | * @source https://www.bennadel.com/blog/2664-cloning-regexp-regular-expression-objects-in-javascript.htm 70 | */ 71 | export function cloneRegExp(input: RegExp, injectFlags?: string): RegExp { 72 | const pattern = input.source; 73 | let flags = ''; 74 | // Make sure the parameter is a defined string - it will make the conditional 75 | // logic easier to read. 76 | injectFlags = injectFlags || ''; 77 | // Test for global. 78 | if (input.global || /g/i.test(injectFlags)) { 79 | flags += 'g'; 80 | } 81 | // Test for ignoreCase. 82 | if (input.ignoreCase || /i/i.test(injectFlags)) { 83 | flags += 'i'; 84 | } 85 | // Test for multiline. 86 | if (input.multiline || /m/i.test(injectFlags)) { 87 | flags += 'm'; 88 | } 89 | // Return a clone with the additive flags. 90 | return new RegExp(pattern, flags); 91 | } 92 | 93 | export function getAllPropertyNames(obj: any): string[] { 94 | const names: string[] = []; 95 | const exists: { [name: string]: boolean | undefined } = {}; 96 | do { 97 | // eslint-disable-next-line prefer-spread 98 | names.push.apply(names, Object.getOwnPropertyNames(obj)); 99 | obj = Object.getPrototypeOf(obj); 100 | } while (obj !== Object.prototype); 101 | 102 | return names.filter((name) => { 103 | const isValid = !exists[name] && name !== 'constructor'; 104 | exists[name] = true; 105 | return isValid; 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /src/shared/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Capitalize specified string value 3 | */ 4 | export function capitalize(value: string): string { 5 | return value.charAt(0).toUpperCase() + value.substr(1, value.length); 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/types.ts: -------------------------------------------------------------------------------- 1 | export type Diff = ({ 2 | [P in T]: P; 3 | } & { [P in U]: never } & { [x: string]: never })[T]; 4 | 5 | export type Omit = { [P in Diff]: T[P] }; 6 | 7 | export type RecursivePartial = { [P in keyof T]?: RecursivePartial }; 8 | 9 | export type NonAbstract = { [P in keyof T]: T[P] }; 10 | 11 | export type Constructor = new (...args: any[]) => T; 12 | -------------------------------------------------------------------------------- /src/validation/contains.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Force specific substrings 5 | */ 6 | export function Contains(value: string | { msg: string; args: string }): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | contains: value, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/equals.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow a specific value 5 | */ 6 | export function Equals(value: string | { msg: string; args: string }): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | equals: value, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/is-after.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow date strings after a specific date 5 | */ 6 | export function IsAfter(date: string): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | isAfter: date, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/is-alpha.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Will only allow letters 5 | */ 6 | export function IsAlpha(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isAlpha: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-alphanumeric.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Will only allow alphanumeric characters, so "_abc" will fail 5 | */ 6 | export function IsAlphanumeric(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isAlphanumeric: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-array.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Will only allow arrays 5 | */ 6 | export function IsArray(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isArray: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-before.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow date strings before a specific date 5 | */ 6 | export function IsBefore(date: string): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | isBefore: date, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/is-credit-card.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Check for valid credit card numbers 5 | */ 6 | export function IsCreditCard(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isCreditCard: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-date.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow date strings 5 | */ 6 | export function IsDate(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isDate: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-decimal.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for any numbers 5 | */ 6 | export function IsDecimal(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isDecimal: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-email.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for email format (foo@bar.com) 5 | */ 6 | export function IsEmail(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isEmail: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-float.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for valid floating point numbers 5 | */ 6 | export function IsFloat(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isFloat: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-in.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Check the value is one of these 5 | */ 6 | export function IsIn(arg: any[][] | { msg: string; args: any[][] }): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | isIn: arg, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/is-int.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for valid integers 5 | */ 6 | export function IsInt(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isInt: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-ip-v4.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for IPv4 (129.89.23.1) 5 | */ 6 | export function IsIPv4(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isIPv4: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-ip-v6.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for IPv6 format 5 | */ 6 | export function IsIPv6(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isIPv6: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-ip.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for IPv4 (129.89.23.1) or IPv6 format 5 | */ 6 | export function IsIP(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isIP: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-lowercase.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for lowercase 5 | */ 6 | export function IsLowercase(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isLowercase: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-null.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allows null 5 | */ 6 | export function IsNull(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isNull: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-numeric.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Will only allow numbers 5 | */ 6 | export function IsNumeric(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isNumeric: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-uppercase.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for uppercase 5 | */ 6 | export function IsUppercase(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isUppercase: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-url.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Checks for url format (http://foo.com) 5 | */ 6 | export function IsUrl(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | isUrl: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/is-uuid.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /* 4 | * Only allow uuids. 5 | * Version's regular expressions: 6 | * https://github.com/chriso/validator.js/blob/b59133b1727b6af355b403a9a97a19226cceb34b/lib/isUUID.js#L14-L19. 7 | */ 8 | export function IsUUID(version: 3 | 4 | 5 | '3' | '4' | '5' | 'all'): Function { 9 | return (target: any, propertyName: string) => 10 | addAttributeOptions(target, propertyName, { 11 | validate: { 12 | isUUID: version as any, 13 | }, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/validation/is.ts: -------------------------------------------------------------------------------- 1 | import { ModelValidateOptions } from 'sequelize'; 2 | 3 | import { addAttributeOptions } from '../model/column/attribute-service'; 4 | 5 | /** 6 | * Adds custom validator 7 | * @param name Name of validator 8 | * @param validator Validator function 9 | */ 10 | export function Is(name: string, validator: (value: any) => any): Function; 11 | /** 12 | * Adds custom validator 13 | * @param validator Validator function 14 | */ 15 | export function Is(validator: (value: any) => any): Function; 16 | /** 17 | * Will only allow values, that match the string regex or real regex 18 | */ 19 | export function Is( 20 | arg: 21 | | string 22 | | (string | RegExp)[] 23 | | RegExp 24 | | { msg: string; args: string | (string | RegExp)[] | RegExp } 25 | ): Function; 26 | export function Is(...args: any[]): Function { 27 | const options: ModelValidateOptions = {}; 28 | const argIsFunction = typeof args[0] === 'function'; 29 | 30 | if (argIsFunction || (typeof args[0] === 'string' && typeof args[1] === 'function')) { 31 | let validator: (value: any) => any; 32 | let name: string; 33 | 34 | if (argIsFunction) { 35 | validator = args[0] as (value: any) => any; 36 | name = validator.name; 37 | 38 | if (!name) throw new Error(`Passed validator function must have a name`); 39 | } else { 40 | name = args[0]; 41 | validator = args[1]; 42 | } 43 | 44 | options[`is${name.charAt(0).toUpperCase() + name.substr(1, name.length)}`] = validator; 45 | } else { 46 | options.is = args[0]; 47 | } 48 | 49 | return (target: any, propertyName: string) => 50 | addAttributeOptions(target, propertyName, { 51 | validate: options, 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /src/validation/length.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow values with length between min and max 5 | */ 6 | export function Length({ msg, min, max }: { msg?: string; min?: number; max?: number }): Function { 7 | const length = [min || 0, max] as [number, number]; 8 | const options: [number, number] | { msg: string; args: [number, number] } = msg 9 | ? { args: length, msg: msg as string } 10 | : length; 11 | 12 | return (target: any, propertyName: string) => 13 | addAttributeOptions(target, propertyName, { 14 | validate: { 15 | len: options, 16 | }, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/validation/max.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow values <= limit 5 | */ 6 | export function Max(limit: number): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | max: limit, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/min.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Only allow values >= limit 5 | */ 6 | export function Min(limit: number): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | min: limit, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/not-contains.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Don't allow specific substrings 5 | */ 6 | export function NotContains(value: string | { msg: string; args: string }): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | notContains: value, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/not-empty.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Don't allow empty strings 5 | */ 6 | export function NotEmpty(target: any, propertyName: string): void; 7 | export function NotEmpty(options: { msg: string }): Function; 8 | export function NotEmpty(...args: any[]): void | Function { 9 | if (args.length === 1) { 10 | const options = args[0]; 11 | 12 | return (target: any, propertyName: string) => 13 | addAttributeOptions(target, propertyName, { 14 | validate: { 15 | notEmpty: options, 16 | }, 17 | }); 18 | } else { 19 | const target = args[0]; 20 | const propertyName = args[1]; 21 | 22 | addAttributeOptions(target, propertyName, { 23 | validate: { 24 | notEmpty: true, 25 | }, 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/validation/not-in.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Check the value is not one of these 5 | */ 6 | export function NotIn(arg: any[][] | { msg: string; args: any[][] }): Function { 7 | return (target: any, propertyName: string) => 8 | addAttributeOptions(target, propertyName, { 9 | validate: { 10 | notIn: arg, 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/validation/not-null.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Won't allow null 5 | */ 6 | export function NotNull(target: any, propertyName: string): void { 7 | addAttributeOptions(target, propertyName, { 8 | validate: { 9 | notNull: true, 10 | }, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/validation/not.ts: -------------------------------------------------------------------------------- 1 | import { addAttributeOptions } from '../model/column/attribute-service'; 2 | 3 | /** 4 | * Will not allow values, that match the string regex or real regex 5 | */ 6 | export function Not( 7 | arg: 8 | | string 9 | | (string | RegExp)[] 10 | | RegExp 11 | | { msg: string; args: string | (string | RegExp)[] | RegExp } 12 | ): Function { 13 | return (target: any, propertyName: string) => 14 | addAttributeOptions(target, propertyName, { 15 | validate: { 16 | not: arg, 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/validation/validate.ts: -------------------------------------------------------------------------------- 1 | import { ModelValidateOptions } from 'sequelize'; 2 | 3 | import { addAttributeOptions } from '../model/column/attribute-service'; 4 | 5 | /** 6 | * Sets validation options for annotated field 7 | */ 8 | export function Validate(options: ModelValidateOptions): Function { 9 | options = { ...options }; 10 | 11 | return (target: any, propertyName: string) => 12 | addAttributeOptions(target, propertyName, { 13 | validate: options, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/validation/validator.ts: -------------------------------------------------------------------------------- 1 | import { addOptions } from '../model/shared/model-service'; 2 | 3 | export const Validator: MethodDecorator = ( 4 | target: Object, 5 | propertyName: string, 6 | descriptor: TypedPropertyDescriptor 7 | ) => { 8 | addOptions(target, { 9 | validate: { 10 | [propertyName]: descriptor.value, 11 | }, 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /test/models/Book.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column, HasMany } from '../../src'; 2 | import { Page } from './Page'; 3 | 4 | @Table 5 | export class Book extends Model { 6 | @Column 7 | title: string; 8 | 9 | @HasMany(() => Page) 10 | pages: Page[]; 11 | } 12 | -------------------------------------------------------------------------------- /test/models/Box.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../src'; 2 | 3 | @Table 4 | export class Box extends Model { 5 | @Column 6 | length: number; 7 | 8 | @Column 9 | width: number; 10 | 11 | @Column 12 | height: number; 13 | } 14 | -------------------------------------------------------------------------------- /test/models/Manufacturer.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column, HasMany, Scopes } from '../../src'; 2 | import { ShoeWithScopes } from './ShoeWithScopes'; 3 | 4 | @Scopes({ 5 | brandOnly: { 6 | attributes: { 7 | exclude: ['notInScopeBrandOnly'], 8 | }, 9 | }, 10 | }) 11 | @Table 12 | export class Manufacturer extends Model { 13 | @Column 14 | brand: string; 15 | 16 | @Column 17 | notInScopeBrandOnly: string; 18 | 19 | @HasMany(() => ShoeWithScopes) 20 | shoes: ShoeWithScopes[]; 21 | } 22 | -------------------------------------------------------------------------------- /test/models/Page.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, ForeignKey, Column, BelongsTo, DataType } from '../../src'; 2 | import { Book } from './Book'; 3 | 4 | @Table 5 | export class Page extends Model { 6 | @Column(DataType.TEXT) 7 | content: string; 8 | 9 | @ForeignKey(() => Book) 10 | @Column 11 | bookId: number; 12 | 13 | @BelongsTo(() => Book) 14 | book: Book; 15 | } 16 | -------------------------------------------------------------------------------- /test/models/Person.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column, PrimaryKey, DataType, Default } from '../../src'; 2 | 3 | @Table 4 | export class Person extends Model { 5 | @PrimaryKey 6 | @Default(DataType.UUIDV1) 7 | @Column(DataType.UUID) 8 | id: string; 9 | 10 | @Column 11 | name: string; 12 | } 13 | -------------------------------------------------------------------------------- /test/models/Player.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Model, 4 | PrimaryKey, 5 | ForeignKey, 6 | Column, 7 | AutoIncrement, 8 | HasOne, 9 | BelongsTo, 10 | } from '../../src'; 11 | import { Team } from './Team'; 12 | import { Shoe } from './Shoe'; 13 | 14 | @Table 15 | export class Player extends Model { 16 | @PrimaryKey 17 | @AutoIncrement 18 | @Column 19 | id: number; 20 | 21 | @Column 22 | name: string; 23 | 24 | @HasOne(() => Shoe) 25 | shoe: Shoe; 26 | 27 | @ForeignKey(() => Team) 28 | @Column 29 | teamId: number; 30 | 31 | @BelongsTo(() => Team) 32 | team: Team; 33 | } 34 | -------------------------------------------------------------------------------- /test/models/Shoe.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, ForeignKey, Column, BelongsTo } from '../../src'; 2 | import { Player } from './Player'; 3 | 4 | export const SHOE_TABLE_NAME = 'Glove'; 5 | 6 | @Table({ 7 | tableName: SHOE_TABLE_NAME, 8 | }) 9 | export class Shoe extends Model { 10 | @Column 11 | brand: string; 12 | 13 | @ForeignKey(() => Player) 14 | @Column 15 | playerId: number; 16 | 17 | @BelongsTo(() => Player) 18 | player: Player; 19 | } 20 | -------------------------------------------------------------------------------- /test/models/ShoeWithDeprecatedScopes.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | import { Table, Model, Column, ForeignKey, BelongsTo, DefaultScope, Scopes } from '../../src'; 3 | import { Manufacturer } from './Manufacturer'; 4 | import { Person } from './Person'; 5 | 6 | export const SHOE_DEFAULT_SCOPE = { 7 | attributes: ['id', 'primaryColor', 'secondaryColor', 'producedAt'], 8 | }; 9 | export const SHOE_SCOPES = { 10 | full: { 11 | include: [() => Manufacturer], 12 | }, 13 | yellow: { 14 | where: { primaryColor: 'yellow' }, 15 | }, 16 | red: { 17 | where: { primaryColor: 'red' }, 18 | }, 19 | noImg: { 20 | where: { img: null }, 21 | }, 22 | /*manufacturerWithScope: { 23 | include: [() => Manufacturer.scope('brandOnly')] 24 | },*/ 25 | primaryColor: (primaryColor) => ({ 26 | where: { primaryColor }, 27 | }), 28 | primaryColorWithManufacturer: (primaryColor) => ({ 29 | include: [Manufacturer], 30 | where: { primaryColor }, 31 | }), 32 | }; 33 | 34 | @DefaultScope(SHOE_DEFAULT_SCOPE) 35 | @Scopes(SHOE_SCOPES) 36 | @Table 37 | export class ShoeWithDeprecatedScopes extends Model { 38 | @Column 39 | readonly secretKey: string; 40 | 41 | @Column 42 | primaryColor: string; 43 | 44 | @Column 45 | secondaryColor: string; 46 | 47 | @Column 48 | img: Buffer; 49 | 50 | @Column 51 | producedAt: Date; 52 | 53 | @ForeignKey(() => Manufacturer) 54 | @Column 55 | manufacturerId: number; 56 | 57 | @BelongsTo(() => Manufacturer) 58 | manufacturer: Manufacturer; 59 | 60 | @ForeignKey(() => Person) 61 | @Column 62 | ownerId: number; 63 | 64 | @BelongsTo(() => Person) 65 | owner: Person; 66 | } 67 | -------------------------------------------------------------------------------- /test/models/ShoeWithScopes.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | import { Table, Model, Column, ForeignKey, BelongsTo, DefaultScope, Scopes } from '../../src'; 3 | import { Manufacturer } from './Manufacturer'; 4 | import { Person } from './Person'; 5 | 6 | export const SHOE_DEFAULT_SCOPE_GETTER = () => ({ 7 | attributes: ['id', 'primaryColor', 'secondaryColor', 'producedAt'], 8 | }); 9 | export const SHOE_SCOPES__GETTER = () => ({ 10 | full: { 11 | include: [Manufacturer], 12 | }, 13 | yellow: { 14 | where: { primaryColor: 'yellow' }, 15 | }, 16 | red: { 17 | where: { primaryColor: 'red' }, 18 | }, 19 | noImg: { 20 | where: { img: null }, 21 | }, 22 | manufacturerWithScope: { 23 | include: [Manufacturer.scope('brandOnly')], 24 | }, 25 | primaryColor: (primaryColor) => ({ 26 | where: { primaryColor }, 27 | }), 28 | primaryColorWithManufacturer: (primaryColor) => ({ 29 | include: [Manufacturer], 30 | where: { primaryColor }, 31 | }), 32 | }); 33 | 34 | @DefaultScope(SHOE_DEFAULT_SCOPE_GETTER) 35 | @Scopes(SHOE_SCOPES__GETTER) 36 | @Table 37 | export class ShoeWithScopes extends Model { 38 | @Column 39 | readonly secretKey: string; 40 | 41 | @Column 42 | primaryColor: string; 43 | 44 | @Column 45 | secondaryColor: string; 46 | 47 | @Column 48 | img: Buffer; 49 | 50 | @Column 51 | producedAt: Date; 52 | 53 | @ForeignKey(() => Manufacturer) 54 | @Column 55 | manufacturerId: number; 56 | 57 | @BelongsTo(() => Manufacturer) 58 | manufacturer: Manufacturer; 59 | 60 | @ForeignKey(() => Person) 61 | @Column 62 | ownerId: number; 63 | 64 | @BelongsTo(() => Person) 65 | owner: Person; 66 | } 67 | -------------------------------------------------------------------------------- /test/models/ShoeWithValidation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Model, 4 | Column, 5 | PrimaryKey, 6 | DataType, 7 | Equals, 8 | Contains, 9 | Is, 10 | IsDate, 11 | Length, 12 | IsUrl, 13 | IsAfter, 14 | IsBefore, 15 | IsUUID, 16 | IsAlpha, 17 | IsAlphanumeric, 18 | IsEmail, 19 | IsInt, 20 | IsDecimal, 21 | IsFloat, 22 | IsIn, 23 | IsIP, 24 | IsIPv4, 25 | IsIPv6, 26 | IsLowercase, 27 | IsUppercase, 28 | Max, 29 | Min, 30 | Not, 31 | NotContains, 32 | NotIn, 33 | Validate, 34 | NotEmpty, 35 | IsNumeric, 36 | IsNull, 37 | IsArray, 38 | } from '../../src'; 39 | import { IsCreditCard } from '../../src/validation/is-credit-card'; 40 | 41 | export const HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; 42 | export function hexColor(value: string): void { 43 | if (!HEX_REGEX.test(value)) { 44 | throw new Error(`"${value}" is not a hex color value.`); 45 | } 46 | } 47 | export const UUID_VERSION = 4; 48 | export const IS_IN = [['a', 'b']]; 49 | export const NOT_CONTAINS = 'b'; 50 | export const NOT = /b/; 51 | export const MAX = 100; 52 | export const MIN = -100; 53 | export const KEY_VALUE = 'READONLY'; 54 | export const PARTIAL_SPECIAL_VALUE = 'Special'; 55 | export const PRODUCED_AT_IS_AFTER = '1987-04-04'; 56 | export const PRODUCED_AT_IS_BEFORE = '2017-02-27'; 57 | export const BRAND_LENGTH = { min: 3, max: 15 }; 58 | 59 | @Table 60 | export class ShoeWithValidation extends Model { 61 | @IsUUID(UUID_VERSION) 62 | @PrimaryKey 63 | @Column 64 | id: string; 65 | 66 | @Equals(KEY_VALUE) 67 | @Column 68 | readonly key: string; 69 | 70 | @Contains(PARTIAL_SPECIAL_VALUE) 71 | @Column 72 | special: string; 73 | 74 | @Length(BRAND_LENGTH) 75 | @Column 76 | brand: string; 77 | 78 | @IsUrl 79 | @Column 80 | brandUrl: string; 81 | 82 | @Is('HexColor', hexColor) 83 | @Length({ min: 4, msg: 'too short' }) 84 | @Column 85 | primaryColor: string; 86 | 87 | @Is(hexColor) 88 | @Column 89 | secondaryColor: string; 90 | 91 | @Is(HEX_REGEX) 92 | @Column 93 | tertiaryColor: string; 94 | 95 | @IsDate 96 | @IsAfter(PRODUCED_AT_IS_AFTER) 97 | @IsBefore(PRODUCED_AT_IS_BEFORE) 98 | @Column 99 | producedAt: Date; 100 | 101 | @IsCreditCard 102 | @IsAlpha 103 | @IsAlphanumeric 104 | @IsEmail 105 | @IsDecimal 106 | @IsFloat 107 | @IsInt 108 | @IsIP 109 | @IsIPv4 110 | @IsIPv6 111 | @IsLowercase 112 | @IsUppercase 113 | @NotEmpty 114 | @IsArray 115 | @IsNull 116 | @IsNumeric 117 | @Max(MAX) 118 | @Min(MIN) 119 | @Not(NOT) 120 | @IsIn(IS_IN) 121 | @NotIn(IS_IN) 122 | @NotContains(NOT_CONTAINS) 123 | @Validate({ isArray: true }) 124 | @Column(DataType.STRING) 125 | dummy: any; 126 | } 127 | -------------------------------------------------------------------------------- /test/models/Team.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, PrimaryKey, AutoIncrement, Column, HasMany } from '../../src'; 2 | import { Player } from './Player'; 3 | 4 | @Table 5 | export class Team extends Model { 6 | @PrimaryKey 7 | @AutoIncrement 8 | @Column 9 | id: number; 10 | 11 | @Column 12 | name: string; 13 | 14 | @HasMany(() => Player) 15 | players: Player[]; 16 | } 17 | -------------------------------------------------------------------------------- /test/models/TimeStampsUser.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, PrimaryKey, AutoIncrement, Column } from '../../src'; 2 | 3 | @Table({ 4 | timestamps: true, 5 | }) 6 | export class TimeStampsUser extends Model { 7 | @PrimaryKey 8 | @AutoIncrement 9 | @Column 10 | id: number; 11 | 12 | @Column 13 | aNumber: number; 14 | 15 | @Column 16 | username: string; 17 | 18 | @Column 19 | updatedAt: Date; 20 | } 21 | -------------------------------------------------------------------------------- /test/models/User.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table, 3 | Model, 4 | PrimaryKey, 5 | Column, 6 | AutoIncrement, 7 | DataType, 8 | Default, 9 | AllowNull, 10 | Unique, 11 | Length, 12 | IsInt, 13 | } from '../../src'; 14 | 15 | @Table 16 | export class User extends Model { 17 | @PrimaryKey 18 | @AutoIncrement 19 | @Column 20 | id: number; 21 | 22 | @Column({ 23 | type: DataType.UUID, 24 | defaultValue: DataType.UUIDV1, 25 | }) 26 | uuidv1: string; 27 | 28 | @Unique 29 | @Default(DataType.UUIDV4) 30 | @Column(DataType.UUID) 31 | uuidv4: string; 32 | 33 | @Length({ max: 20 }) 34 | @Column 35 | username: string; 36 | 37 | @Column(DataType.STRING(5)) 38 | username2: string; 39 | 40 | @IsInt 41 | @Column 42 | aNumber: number; 43 | 44 | @Column 45 | bNumber: number; 46 | 47 | @Column 48 | isAdmin: boolean; 49 | 50 | @Default(false) 51 | @AllowNull(false) 52 | @Column(DataType.BOOLEAN) 53 | isSuperUser: boolean | number; 54 | 55 | @Column({ 56 | defaultValue: DataType.NOW, 57 | }) 58 | touchedAt: Date; 59 | 60 | @Column 61 | birthDate: Date; 62 | 63 | @Column({ 64 | allowNull: true, 65 | }) 66 | dateAllowNullTrue: Date; 67 | 68 | @Column 69 | name: string; 70 | 71 | @Column(DataType.TEXT) 72 | bio: string; 73 | 74 | @Column 75 | email: string; 76 | 77 | @Column('VARCHAR(255)') 78 | city: string; 79 | 80 | extraField: string; 81 | extraField2: boolean; 82 | extraField3: number; 83 | } 84 | -------------------------------------------------------------------------------- /test/models/UserWithCreatedAtButWithoutUpdatedAt.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../src'; 2 | 3 | @Table({ 4 | timestamps: true, 5 | updatedAt: false, 6 | }) 7 | export class UserWithCreatedAtButWithoutUpdatedAt extends Model { 8 | @Column 9 | name: string; 10 | } 11 | -------------------------------------------------------------------------------- /test/models/UserWithCustomUpdatedAt.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../src'; 2 | 3 | @Table({ timestamps: false }) 4 | export class UserWithCustomUpdatedAt extends Model { 5 | @Column 6 | name: string; 7 | 8 | @Column 9 | updatedAt: Date; 10 | } 11 | -------------------------------------------------------------------------------- /test/models/UserWithNoAutoIncrementation.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column, DataType } from '../../src'; 2 | 3 | @Table 4 | export class UserWithNoAutoIncrementation extends Model { 5 | @Column({ 6 | type: DataType.INTEGER.UNSIGNED, 7 | autoIncrement: false, 8 | primaryKey: true, 9 | }) 10 | id: number; 11 | 12 | @Column 13 | username: string; 14 | } 15 | -------------------------------------------------------------------------------- /test/models/UserWithSwag.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column, DataType } from '../../src'; 2 | 3 | @Table 4 | export class UserWithSwag extends Model { 5 | @Column 6 | name: string; 7 | 8 | @Column({ 9 | type: DataType.VIRTUAL, 10 | get(): string { 11 | return 'swag'; 12 | }, 13 | }) 14 | bio: string; 15 | 16 | @Column({ 17 | validate: { 18 | isEmail: true, 19 | }, 20 | }) 21 | email: string; 22 | } 23 | -------------------------------------------------------------------------------- /test/models/UserWithValidation.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column, DataType } from '../../src'; 2 | 3 | @Table 4 | export class UserWithValidation extends Model { 5 | @Column 6 | name: string; 7 | 8 | @Column({ 9 | type: DataType.TEXT, 10 | }) 11 | bio: string; 12 | 13 | @Column({ 14 | validate: { 15 | isEmail: true, 16 | }, 17 | }) 18 | email: string; 19 | } 20 | -------------------------------------------------------------------------------- /test/models/UserWithVersion.ts: -------------------------------------------------------------------------------- 1 | import { Column, Model, Table } from '../../src'; 2 | 3 | @Table({ version: true }) 4 | export class UserWithVersion extends Model { 5 | @Column 6 | name: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/exports/Game.ts: -------------------------------------------------------------------------------- 1 | import { Table, Column, Model } from '../../../src'; 2 | 3 | @Table 4 | export class Game extends Model { 5 | @Column 6 | title: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/exports/custom-match/match.model.ts: -------------------------------------------------------------------------------- 1 | import { Table, Column, Model } from '../../../../src'; 2 | 3 | export const MatchN = 'Not a model'; 4 | export const NMatch = 'Not a model'; 5 | 6 | @Table 7 | export class Match extends Model { 8 | @Column 9 | title: string; 10 | } 11 | -------------------------------------------------------------------------------- /test/models/exports/gamer.model.ts: -------------------------------------------------------------------------------- 1 | import { Table, Column, Model } from '../../../src'; 2 | 3 | @Table 4 | export default class Gamer extends Model { 5 | @Column 6 | nickname: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/exports/throws/_cheater.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model } from '../../../../src'; 2 | 3 | @Table 4 | export class Cheater extends Model {} 5 | -------------------------------------------------------------------------------- /test/models/globs/match-dir-only/PlayerDir.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../../../src'; 2 | 3 | @Table 4 | export default class PlayerDir extends Model { 5 | @Column 6 | name: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/globs/match-dir-only/ShoeDir.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../../../src'; 2 | 3 | @Table 4 | export default class ShoeDir extends Model { 5 | @Column 6 | brand: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/globs/match-dir-only/TeamDir.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../../../src'; 2 | 3 | @Table 4 | export default class TeamDir extends Model { 5 | @Column 6 | name: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/globs/match-files/AddressDir.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '../../../../src'; 2 | import { Table } from '../../../../src/model/table/table'; 3 | 4 | @Table 5 | export class AddressDir extends Model {} 6 | -------------------------------------------------------------------------------- /test/models/globs/match-files/UserDir.ts: -------------------------------------------------------------------------------- 1 | import { Model, Table } from '../../../../src'; 2 | 3 | @Table 4 | export class UserDir extends Model {} 5 | -------------------------------------------------------------------------------- /test/models/globs/match-sub-dir-files/players/player.model.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../../../../src'; 2 | 3 | @Table 4 | export default class PlayerGlob extends Model { 5 | @Column 6 | name: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/globs/match-sub-dir-files/shoes/shoe.model.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../../../../src'; 2 | 3 | @Table 4 | export default class ShoeGlob extends Model { 5 | @Column 6 | brand: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/models/globs/match-sub-dir-files/teams/team.model.ts: -------------------------------------------------------------------------------- 1 | import { Table, Model, Column } from '../../../../../src'; 2 | 3 | @Table 4 | export default class TeamGlob extends Model { 5 | @Column 6 | name: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/specs/annotations/belongs-to-many.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import * as chaiAsPromised from 'chai-as-promised'; 3 | import { Table } from '../../../src/model/table/table'; 4 | import { Model } from '../../../src/model/model/model'; 5 | import { createSequelize } from '../../utils/sequelize'; 6 | import { BelongsToMany } from '../../../src/associations/belongs-to-many/belongs-to-many'; 7 | 8 | use(chaiAsPromised); 9 | 10 | describe('BelongsToMany', () => { 11 | const as = 'manyTeams'; 12 | const sequelize = createSequelize(false); 13 | 14 | @Table 15 | class Team extends Model {} 16 | 17 | @Table 18 | class Player extends Model { 19 | @BelongsToMany(() => Team, { 20 | as, 21 | through: 'TeamPlayer', 22 | foreignKey: 'playerId', 23 | otherKey: 'teamId', 24 | }) 25 | teams: Team[]; 26 | } 27 | 28 | sequelize.addModels([Team, Player]); 29 | 30 | it('should pass as options to sequelize association', () => { 31 | expect(Player['associations']).to.have.property(as); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/specs/annotations/belongs-to.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import * as chaiAsPromised from 'chai-as-promised'; 3 | import { Table } from '../../../src/model/table/table'; 4 | import { BelongsTo } from '../../../src/associations/belongs-to/belongs-to'; 5 | import { Model } from '../../../src/model/model/model'; 6 | import { createSequelize } from '../../utils/sequelize'; 7 | 8 | use(chaiAsPromised); 9 | 10 | describe('BelongsTo', () => { 11 | const as = 'parent'; 12 | const sequelize = createSequelize(false); 13 | 14 | @Table 15 | class Team extends Model {} 16 | 17 | @Table 18 | class Player extends Model { 19 | @BelongsTo(() => Team, { as, foreignKey: 'teamId' }) 20 | team: Team; 21 | } 22 | 23 | sequelize.addModels([Team, Player]); 24 | 25 | it('should pass as options to sequelize association', () => { 26 | expect(Player['associations']).to.have.property(as); 27 | }); 28 | 29 | it('should throw due to missing foreignKey', () => { 30 | const _sequelize = createSequelize(false); 31 | 32 | @Table 33 | class Team extends Model {} 34 | 35 | @Table 36 | class Player extends Model { 37 | @BelongsTo(() => Team) 38 | team: Team; 39 | } 40 | 41 | expect(() => _sequelize.addModels([Team, Player])).to.throw( 42 | /Foreign key for "\w+" is missing on "\w+"./ 43 | ); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/specs/annotations/comment.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { createSequelize } from '../../utils/sequelize'; 3 | import { Table } from '../../../src/model/table/table'; 4 | import { Model } from '../../../src/model/model/model'; 5 | import { Column } from '../../../src/model/column/column'; 6 | import { Comment } from '../../../src/model/column/column-options/comment'; 7 | 8 | describe('comment', () => { 9 | const sequelize = createSequelize(false); 10 | const BookTitleCommentString = 'title for book'; 11 | 12 | @Table 13 | class Book extends Model { 14 | @Comment(BookTitleCommentString) 15 | @Column 16 | title: string; 17 | } 18 | 19 | sequelize.addModels([Book]); 20 | 21 | it('should set comment property on model options', () => { 22 | expect(Book['tableAttributes'].title.comment).to.be.eq(BookTitleCommentString); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/specs/annotations/has-many.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import * as chaiAsPromised from 'chai-as-promised'; 3 | import { Table } from '../../../src/model/table/table'; 4 | import { Model } from '../../../src/model/model/model'; 5 | import { createSequelize } from '../../utils/sequelize'; 6 | import { HasMany } from '../../../src/associations/has/has-many'; 7 | 8 | use(chaiAsPromised); 9 | 10 | describe('HasMany', () => { 11 | const as = 'babies'; 12 | const sequelize = createSequelize(false); 13 | 14 | @Table 15 | class Player extends Model {} 16 | 17 | @Table 18 | class Team extends Model { 19 | @HasMany(() => Player, { 20 | as, 21 | foreignKey: 'teamId', 22 | }) 23 | players: Player[]; 24 | } 25 | 26 | sequelize.addModels([Team, Player]); 27 | 28 | it('should pass as options to sequelize association', () => { 29 | expect(Team['associations']).to.have.property(as); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/specs/annotations/has-one.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import * as chaiAsPromised from 'chai-as-promised'; 3 | import { Table } from '../../../src/model/table/table'; 4 | import { Model } from '../../../src/model/model/model'; 5 | import { createSequelize } from '../../utils/sequelize'; 6 | import { HasOne } from '../../../src/associations/has/has-one'; 7 | 8 | use(chaiAsPromised); 9 | 10 | describe('HasOne', () => { 11 | const as = 'baby'; 12 | const sequelize = createSequelize(false); 13 | 14 | @Table 15 | class Player extends Model {} 16 | 17 | @Table 18 | class Team extends Model { 19 | @HasOne(() => Player, { 20 | as, 21 | foreignKey: 'teamId', 22 | }) 23 | player: Player; 24 | } 25 | 26 | sequelize.addModels([Team, Player]); 27 | 28 | it('should pass as options to sequelize association', () => { 29 | expect(Team['associations']).to.have.property(as); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/specs/browser.spec.ts: -------------------------------------------------------------------------------- 1 | import * as browser from '../../src/browser'; 2 | import { should } from 'chai'; 3 | 4 | describe('browser definitions', () => { 5 | it('should be able to use noop implementations as decorators', () => { 6 | class TestModel extends browser.Model { 7 | // we don't care about the types matching here since at runtime you can pass 8 | // as many args to a function as you like 9 | @((browser.AllowNull as any)({ someOption: (browser.DataType as any).STRING } as any)) 10 | @browser.AutoIncrement() 11 | @browser.Column() 12 | @browser.Comment() 13 | @browser.CreatedAt() 14 | @browser.Default() 15 | @browser.DefaultScope() 16 | @browser.DeletedAt() 17 | @browser.ForeignKey() 18 | @browser.PrimaryKey() 19 | @browser.Scopes() 20 | @browser.Table() 21 | @browser.Unique() 22 | @browser.UpdatedAt() 23 | @browser.BelongsTo() 24 | @browser.BelongsToMany() 25 | @browser.HasMany() 26 | @browser.HasOne() 27 | @browser.AfterBulkCreate() 28 | @browser.AfterBulkDelete() 29 | @browser.AfterBulkDestroy() 30 | @browser.AfterBulkRestore() 31 | @browser.AfterBulkSync() 32 | @browser.AfterBulkUpdate() 33 | @browser.AfterConnect() 34 | @browser.AfterCreate() 35 | @browser.AfterDefine() 36 | @browser.AfterDelete() 37 | @browser.AfterDestroy() 38 | @browser.AfterFind() 39 | @browser.AfterInit() 40 | @browser.AfterRestore() 41 | @browser.AfterSave() 42 | @browser.AfterSync() 43 | @browser.AfterUpdate() 44 | @browser.AfterUpsert() 45 | @browser.AfterValidate() 46 | @browser.BeforeBulkCreate() 47 | @browser.BeforeBulkDelete() 48 | @browser.BeforeBulkDestroy() 49 | @browser.BeforeBulkRestore() 50 | @browser.BeforeBulkSync() 51 | @browser.BeforeBulkUpdate() 52 | @browser.BeforeConnect() 53 | @browser.BeforeCount() 54 | @browser.BeforeCreate() 55 | @browser.BeforeDefine() 56 | @browser.BeforeDelete() 57 | @browser.BeforeDestroy() 58 | @browser.BeforeFind() 59 | @browser.BeforeFindAfterExpandIncludeAll() 60 | @browser.BeforeFindAfterOptions() 61 | @browser.BeforeInit() 62 | @browser.BeforeRestore() 63 | @browser.BeforeSave() 64 | @browser.BeforeSync() 65 | @browser.BeforeUpdate() 66 | @browser.BeforeUpsert() 67 | @browser.BeforeValidate() 68 | @browser.ValidationFailed() 69 | @browser.Contains() 70 | @browser.Equals() 71 | @browser.Is() 72 | @browser.IsAfter() 73 | @browser.IsAlpha() 74 | @browser.IsAlphanumeric() 75 | @browser.IsArray() 76 | @browser.IsBefore() 77 | @browser.IsCreditCard() 78 | @browser.IsDate() 79 | @browser.IsDecimal() 80 | @browser.IsEmail() 81 | @browser.IsFloat() 82 | @browser.IsIn() 83 | @browser.IsInt() 84 | @browser.IsIP() 85 | @browser.IsIPv4() 86 | @browser.IsIPv6() 87 | @browser.IsLowercase() 88 | @browser.IsNull() 89 | @browser.IsNumeric() 90 | @browser.IsUppercase() 91 | @browser.IsUrl() 92 | @browser.IsUUID() 93 | @browser.Length() 94 | @browser.Max() 95 | @browser.Min() 96 | @browser.Not() 97 | @browser.NotContains() 98 | @browser.NotEmpty() 99 | @browser.NotIn() 100 | @browser.NotNull() 101 | @browser.Validate() 102 | @browser.Validator() 103 | field: string; 104 | } 105 | 106 | should().exist(new TestModel()); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/specs/instance-methods.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Model, Table, Column } from '../../src'; 3 | import { createSequelize } from '../utils/sequelize'; 4 | 5 | describe('instance-methods', () => { 6 | let sequelize; 7 | 8 | @Table 9 | class User extends Model { 10 | @Column 11 | firstName: string; 12 | 13 | @Column 14 | lastName: string; 15 | 16 | getFullName(): string { 17 | return this.firstName + ' ' + this.lastName; 18 | } 19 | 20 | setFullName(name: string): void { 21 | const split = name.split(' '); 22 | 23 | this.lastName = split.pop(); 24 | this.firstName = split.join(' '); 25 | } 26 | } 27 | 28 | before(() => { 29 | sequelize = createSequelize(); 30 | sequelize.addModels([User]); 31 | }); 32 | 33 | beforeEach(() => sequelize.sync({ force: true })); 34 | 35 | const suites: Array<[string, () => Promise]> = [ 36 | ['build', () => Promise.resolve(User.build({ firstName: 'Peter', lastName: 'Parker' }))], 37 | ['new', () => Promise.resolve(new User({ firstName: 'Peter', lastName: 'Parker' }))], 38 | ['create', () => User.create({ firstName: 'Peter', lastName: 'Parker' })], 39 | ]; 40 | 41 | suites.forEach(([name, create]) => { 42 | describe(name, () => { 43 | let user; 44 | 45 | beforeEach(() => create().then((_user) => (user = _user))); 46 | 47 | it('should have access to functions of prototype', () => { 48 | Object.keys(User.prototype).forEach((key) => { 49 | expect(user).to.have.property(key, User.prototype[key]); 50 | }); 51 | }); 52 | 53 | describe('"get" function', () => { 54 | it('should return appropriate value', () => { 55 | expect(user.getFullName()).to.equal(user.firstName + ' ' + user.lastName); 56 | }); 57 | }); 58 | 59 | describe('"set" function', () => { 60 | const firstName = 'Tony'; 61 | const lastName = 'Stark'; 62 | const fullName = firstName + ' ' + lastName; 63 | 64 | it('should set specified value to instance', () => { 65 | user.setFullName(fullName); 66 | 67 | expect(user.firstName).to.equal(firstName); 68 | expect(user.lastName).to.equal(lastName); 69 | }); 70 | 71 | it('should store set value', () => { 72 | user.setFullName(fullName); 73 | 74 | return user 75 | .save() 76 | .then(() => User.findByPk(user.id)) 77 | .then((_user) => { 78 | expect(_user.firstName).to.equal(firstName); 79 | expect(_user.lastName).to.equal(lastName); 80 | }); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/specs/model-methods.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Model, Table, Column } from '../../src'; 3 | import { createSequelize } from '../utils/sequelize'; 4 | 5 | describe('model-methods', () => { 6 | let sequelize; 7 | 8 | @Table 9 | class User extends Model { 10 | @Column 11 | firstName: string; 12 | 13 | @Column 14 | lastName: string; 15 | 16 | static createDemoUser(): User { 17 | return new User({ firstName: 'Peter', lastName: 'Parker' }); 18 | } 19 | 20 | static findDemoUser(): Promise { 21 | return this.findOne({ where: { firstName: 'Peter', lastName: 'Parker' } }); 22 | } 23 | } 24 | 25 | before(() => { 26 | sequelize = createSequelize(); 27 | sequelize.addModels([User]); 28 | }); 29 | 30 | beforeEach(() => sequelize.sync({ force: true })); 31 | 32 | it('should work as expected', () => { 33 | const user = User.createDemoUser(); 34 | 35 | expect(user).to.be.an.instanceOf(User); 36 | 37 | return user 38 | .save() 39 | .then(() => User.findDemoUser()) 40 | .then((_user) => expect(_user.equals(user)).to.be.true); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/specs/models/model.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Model } from '../../../src/model/model/model'; 3 | 4 | describe('model', () => { 5 | describe('constructor', () => { 6 | it('should equal Model class', () => { 7 | expect(Model.prototype.constructor).to.equal(Model); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/specs/services/association.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { addForeignKey } from '../../../src/associations/foreign-key/foreign-key-service'; 3 | import { Model } from '../../../src/model/model/model'; 4 | import { getForeignKeys } from '../../../src/associations/foreign-key/foreign-key-service'; 5 | 6 | describe('service.association', () => { 7 | describe('addForeignKey', () => { 8 | it('should add foreign key to target metadata', () => { 9 | const target = {}; 10 | const FOREIGN_KEY = 'testId'; 11 | const RELATED_CLASS_GETTER = () => class T extends Model {}; 12 | addForeignKey(target, RELATED_CLASS_GETTER, FOREIGN_KEY); 13 | const foreignKeys = getForeignKeys(target); 14 | 15 | expect(foreignKeys).to.have.property('length', 1); 16 | expect(foreignKeys[0]).to.eql({ 17 | foreignKey: FOREIGN_KEY, 18 | relatedClassGetter: RELATED_CLASS_GETTER, 19 | }); 20 | }); 21 | 22 | it('should add foreign key to target metadata, but not parent', () => { 23 | const parent = {}; 24 | const target = Object.create(parent); 25 | const FOREIGN_KEY = 'testId'; 26 | const PARENT_FOREIGN_KEY = 'parentTestId'; 27 | const RELATED_CLASS_GETTER = () => class T extends Model {}; 28 | addForeignKey(parent, RELATED_CLASS_GETTER, PARENT_FOREIGN_KEY); 29 | addForeignKey(target, RELATED_CLASS_GETTER, FOREIGN_KEY); 30 | 31 | const foreignKeys = getForeignKeys(target); 32 | expect(foreignKeys).to.have.property('length', 2); 33 | expect(foreignKeys[0]).to.eql({ 34 | foreignKey: PARENT_FOREIGN_KEY, 35 | relatedClassGetter: RELATED_CLASS_GETTER, 36 | }); 37 | expect(foreignKeys[1]).to.eql({ 38 | foreignKey: FOREIGN_KEY, 39 | relatedClassGetter: RELATED_CLASS_GETTER, 40 | }); 41 | 42 | const parentForeignKeys = getForeignKeys(parent); 43 | expect(parentForeignKeys).to.have.property('length', 1); 44 | expect(parentForeignKeys[0]).to.eql({ 45 | foreignKey: PARENT_FOREIGN_KEY, 46 | relatedClassGetter: RELATED_CLASS_GETTER, 47 | }); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/specs/services/models.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { resolveModelGetter } from '../../../src/model/shared/model-service'; 3 | import { Book } from '../../models/Book'; 4 | import { DataType } from '../../../src/sequelize/data-type/data-type'; 5 | import { 6 | addAttribute, 7 | addAttributeOptions, 8 | getAttributes, 9 | setAttributes, 10 | } from '../../../src/model/column/attribute-service'; 11 | 12 | describe('services.models', () => { 13 | describe('resolveModelGetter', () => { 14 | const options = { 15 | a: () => Book, 16 | b: () => null, 17 | c: { 18 | c1: () => Book, 19 | c2: () => null, 20 | }, 21 | include: [() => Book, () => null], 22 | }; 23 | 24 | const resolvedOptions = resolveModelGetter(options); 25 | 26 | it('should resolve getter', () => { 27 | expect(resolvedOptions.a).to.be.equal(Book); 28 | expect(resolvedOptions.c.c1).to.be.equal(Book); 29 | expect(resolvedOptions.include[0]).to.be.equal(Book); 30 | }); 31 | 32 | it('should not resolve other functions', () => { 33 | expect(resolvedOptions.b).to.be.a('function'); 34 | expect(resolvedOptions.c.c2).to.be.a('function'); 35 | expect(resolvedOptions.include[1]).to.be.a('function'); 36 | }); 37 | }); 38 | 39 | describe('addAttribute', () => { 40 | it('should not throw', () => { 41 | expect(() => addAttribute({}, 'test', {})).to.not.throw(); 42 | }); 43 | }); 44 | 45 | describe('getAttributes', () => { 46 | const target = {}; 47 | const ATTRIBUTES = { name: { primaryKey: true }, age: { type: DataType.NUMBER } }; 48 | setAttributes(target, ATTRIBUTES); 49 | 50 | it('should not return reference but copy of attributes', () => { 51 | const attributes = getAttributes(target); 52 | expect(attributes).to.not.equal(ATTRIBUTES); 53 | }); 54 | }); 55 | 56 | describe('addAttributeOptions', () => { 57 | const target = {}; 58 | const PROPERTY_NAME = 'test'; 59 | const OPTIONS = { allowNull: true }; 60 | addAttribute(target, PROPERTY_NAME, {}); 61 | addAttributeOptions(target, PROPERTY_NAME, OPTIONS); 62 | 63 | it('should be able to retrieve added attribute options', () => { 64 | const attributes = getAttributes(target); 65 | expect(Object.keys(attributes)).to.have.property('length', 1); 66 | expect(Object.keys(attributes[PROPERTY_NAME])).to.have.property( 67 | 'length', 68 | Object.keys(OPTIONS).length 69 | ); 70 | expect(attributes).to.have.property(PROPERTY_NAME).that.eqls(OPTIONS); 71 | }); 72 | 73 | it('should be able to retrieve added attribute options of prototype linked object', () => { 74 | const child = Object.create(target); 75 | const attributes = getAttributes(child); 76 | expect(Object.keys(attributes)).to.have.property('length', 1); 77 | expect(Object.keys(attributes[PROPERTY_NAME])).to.have.property( 78 | 'length', 79 | Object.keys(OPTIONS).length 80 | ); 81 | expect(attributes).to.have.property(PROPERTY_NAME).that.eqls(OPTIONS); 82 | }); 83 | 84 | it('should add new options to child prototype but not parent one', () => { 85 | const child = Object.create(target); 86 | const NEW_OPTIONS = { primaryKey: true }; 87 | addAttributeOptions(child, PROPERTY_NAME, NEW_OPTIONS); 88 | 89 | // for child 90 | const attributes = getAttributes(child); 91 | expect(Object.keys(attributes)).to.have.property('length', 1); 92 | expect(Object.keys(attributes[PROPERTY_NAME])).to.have.property( 93 | 'length', 94 | Object.keys(OPTIONS).length + Object.keys(NEW_OPTIONS).length 95 | ); 96 | expect(attributes) 97 | .to.have.property(PROPERTY_NAME) 98 | .that.eqls({ ...OPTIONS, ...NEW_OPTIONS }); 99 | 100 | // for parent 101 | const parentAttributes = getAttributes(target); 102 | expect(Object.keys(parentAttributes)).to.have.property('length', 1); 103 | expect(Object.keys(parentAttributes[PROPERTY_NAME])).to.have.property( 104 | 'length', 105 | Object.keys(OPTIONS).length 106 | ); 107 | expect(parentAttributes).to.have.property(PROPERTY_NAME).that.eqls(OPTIONS); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/specs/unique.spec.ts: -------------------------------------------------------------------------------- 1 | import { getAttributes, Model, Sequelize } from '../../src'; 2 | import { expect } from 'chai'; 3 | import { Table } from '../../src/model/table/table'; 4 | import { Column } from '../../src/model/column/column'; 5 | import { Unique } from '../../src/model/column/column-options/unique'; 6 | 7 | describe('unique decorator', () => { 8 | let User; 9 | 10 | before(() => { 11 | @Table 12 | class UserModel extends Model { 13 | @Unique('test') @Column name: string; 14 | @Unique @Column key: string; 15 | } 16 | User = UserModel; 17 | 18 | new Sequelize({ 19 | dialect: 'sqlite', 20 | storage: ':memory:', 21 | logging: !('DISABLE_LOGGING' in process.env), 22 | models: [User], 23 | }); 24 | }); 25 | 26 | it('should set advanced unique options', () => { 27 | const attributes = getAttributes(User.prototype); 28 | expect(attributes).to.have.property('name').which.has.property('unique', 'test'); 29 | }); 30 | 31 | it('should enable unique options', () => { 32 | const attributes = getAttributes(User.prototype); 33 | expect(attributes).to.have.property('key').which.has.property('unique', true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/specs/utils/array.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { unique } from '../../../src/shared/array'; 3 | 4 | describe('utils', () => { 5 | describe('array', () => { 6 | describe('unique', () => { 7 | const duplicates = [1, 'a', 'b', 1, 'a', 'c', 2, 'd', 'b', 2, 3, 'd', 'b']; 8 | 9 | it('should not throw', () => { 10 | expect(() => unique(duplicates)).not.to.throw(); 11 | }); 12 | 13 | it('should remove duplicates from array', () => { 14 | const unified = unique(duplicates); 15 | 16 | expect(unified).to.have.property('length', 7); 17 | }); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/specs/utils/data-type.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { DataType } from '../../../src/sequelize/data-type/data-type'; 3 | import { inferDataType, isDataType } from '../../../src/sequelize/data-type/data-type-service'; 4 | 5 | describe('utils', () => { 6 | describe('data-type', () => { 7 | describe('isDataType', () => { 8 | it('should return true', () => { 9 | Object.keys(DataType).forEach((key) => { 10 | if (key.toUpperCase() === key) { 11 | expect(isDataType(DataType[key])).to.be.true; 12 | } 13 | }); 14 | 15 | expect(isDataType(DataType.STRING(55))).to.be.true; 16 | expect(isDataType(DataType.ENUM('a', 'b'))).to.be.true; 17 | expect(isDataType(DataType.ARRAY(DataType.STRING))).to.be.true; 18 | expect(isDataType('VARCHAR(255)')).to.be.true; 19 | }); 20 | 21 | it('should return false', () => { 22 | expect( 23 | isDataType(function (): void { 24 | //function 25 | }) 26 | ).to.be.false; 27 | expect(isDataType(() => null)).to.be.false; 28 | expect(isDataType({})).to.be.false; 29 | }); 30 | }); 31 | 32 | describe('inferDataType', () => { 33 | it('should return appropriate sequelize data type', () => { 34 | expect(inferDataType(Number)).to.equal(DataType.INTEGER); 35 | expect(inferDataType(Boolean)).to.equal(DataType.BOOLEAN); 36 | expect(inferDataType(Date)).to.equal(DataType.DATE); 37 | expect(inferDataType(String)).to.equal(DataType.STRING); 38 | }); 39 | 40 | it('should return undefined', () => { 41 | expect(inferDataType('abc')).to.be.undefined; 42 | expect( 43 | inferDataType(function (): void { 44 | // function 45 | }) 46 | ).to.be.undefined; 47 | expect(inferDataType(() => null)).to.be.undefined; 48 | expect(inferDataType({})).to.be.undefined; 49 | expect(inferDataType(class hey {})).to.be.undefined; 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/specs/utils/object.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { deepAssign } from '../../../src/shared/object'; 3 | import { addScopeOptions } from '../../../src/scopes/scope-service'; 4 | 5 | describe('utils', () => { 6 | describe('object', () => { 7 | describe('deepAssign', () => { 8 | const childSourceF = {}; 9 | const childSourceA = { f: childSourceF }; 10 | const childSourceB = {}; 11 | const source1 = { 12 | a: childSourceA, 13 | b: childSourceB, 14 | c: 1, 15 | d: 'd', 16 | over: 'ride', 17 | regex: /reg/gim, 18 | notNull: null, 19 | }; 20 | const source2 = { 21 | e: 'für elisa', 22 | g: () => null, 23 | arr: [{ h: 1 }, {}, 'e'], 24 | over: 'ridden', 25 | nullable: null, 26 | notNull: 'notNull', 27 | }; 28 | const sourceKeys = [].concat(Object.keys(source1), Object.keys(source2)); 29 | 30 | it('should not be undefined', () => { 31 | const copy = deepAssign({}, source1, source2); 32 | 33 | expect(copy).not.to.be.undefined; 34 | }); 35 | 36 | it('should have all keys of sources', () => { 37 | const copy = deepAssign({}, source1, source2); 38 | 39 | sourceKeys.forEach((key) => expect(copy).to.have.property(key)); 40 | }); 41 | 42 | it('should override previous properties', () => { 43 | const copy = deepAssign({}, source1, source2); 44 | 45 | expect(copy).to.have.property('over', 'ridden'); 46 | }); 47 | 48 | it('should have all primitive & function values of sources', () => { 49 | const copy = deepAssign({}, source1, source2); 50 | 51 | sourceKeys.forEach((key) => { 52 | if (typeof copy[key] !== 'object') { 53 | expect(copy[key]).to.equal(source2[key] || source1[key]); 54 | } 55 | }); 56 | }); 57 | 58 | it('should have copies of all non-primitive values of sources', () => { 59 | const copy = deepAssign({}, source1, source2); 60 | 61 | sourceKeys.forEach((key) => { 62 | if (typeof copy[key] === 'object' && copy[key] !== null) { 63 | expect(copy[key]).not.to.equal(source1[key] || source2[key]); 64 | expect(copy[key]).to.eql(source1[key] || source2[key]); 65 | } 66 | }); 67 | }); 68 | 69 | it('should have copies of child source', () => { 70 | const copy = deepAssign({}, source1, source2); 71 | 72 | expect(copy.a).to.have.property('f'); 73 | expect(copy.a.f).to.not.equal(source1.a.f); 74 | expect(copy.a.f).to.eql(source1.a.f); 75 | }); 76 | 77 | it('should have copy of array items', () => { 78 | const copy = deepAssign({}, source1, source2); 79 | 80 | expect(copy.arr).to.be.an('array'); 81 | 82 | copy.arr.forEach((value, index) => { 83 | const isObject = typeof value === 'object'; 84 | 85 | if (isObject) { 86 | expect(value).not.to.equal(source2.arr[index]); 87 | expect(value).to.eql(source2.arr[index]); 88 | } else { 89 | expect(value).to.equal(source2.arr[index]); 90 | } 91 | }); 92 | }); 93 | 94 | it('should have copy of nullable', () => { 95 | const copy = deepAssign({}, source1, source2); 96 | 97 | expect(copy.nullable).to.equals(null); 98 | expect(copy.notNull).to.not.equals(null); 99 | }); 100 | 101 | it('should keep prototype chain', () => { 102 | class Test { 103 | protoFn(): any { 104 | // protoFn 105 | } 106 | } 107 | 108 | const copy = deepAssign({}, { test: new Test() }); 109 | 110 | expect(copy.test).to.have.property('protoFn').that.is.a('function'); 111 | }); 112 | 113 | it('ignore prototype property', () => { 114 | const BAD_JSON = JSON.parse('{"__proto__":{"polluted":true}}'); 115 | const empty_scope = {}; 116 | 117 | addScopeOptions(empty_scope, BAD_JSON); 118 | expect(empty_scope).not.to.have.property('polluted'); 119 | }); 120 | 121 | if (Object.getOwnPropertySymbols) { 122 | it('should copy symbol based objects', () => { 123 | const symbol = Symbol('test'); 124 | const value = 'test'; 125 | const copy = deepAssign({}, { [symbol]: value }); 126 | 127 | expect(copy[symbol]).to.equal(value); 128 | }); 129 | } 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/specs/utils/string.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { capitalize } from '../../../src/shared/string'; 3 | 4 | describe('utils', () => { 5 | describe('string', () => { 6 | describe('capitalize', () => { 7 | it('should not throw', () => { 8 | const value = 'abc'; 9 | 10 | expect(() => capitalize(value)).not.to.throw(); 11 | }); 12 | 13 | it('should capitalize specified value', () => { 14 | const value = 'abc'; 15 | 16 | expect(capitalize(value)).to.equal('Abc'); 17 | }); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "strictNullChecks": false, 8 | "noUnusedLocals": false, 9 | "pretty": true, 10 | "lib": ["es2017"] 11 | }, 12 | "include": [ 13 | "models", 14 | "specs", 15 | "utils" 16 | ], 17 | "exclude": [ 18 | "../node_modules", 19 | "../src", 20 | "../src/index.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test/tsconfig.mocha.js: -------------------------------------------------------------------------------- 1 | require("ts-node").register({ 2 | project: "test/tsconfig.json", 3 | }); 4 | -------------------------------------------------------------------------------- /test/types/attributes.spec.ts: -------------------------------------------------------------------------------- 1 | // Types only test. This should compile successfully. 2 | 3 | import { Optional } from 'sequelize'; 4 | import { 5 | AutoIncrement, 6 | BelongsTo, 7 | BelongsToMany, 8 | ForeignKey, 9 | PrimaryKey, 10 | Sequelize, 11 | } from '../../src/index'; 12 | import { Column } from '../../src/model/column/column'; 13 | import { Model } from '../../src/model/model/model'; 14 | import { Table } from '../../src/model/table/table'; 15 | import { DataType } from '../../src/sequelize/data-type/data-type'; 16 | 17 | interface PetPersonAttributes { 18 | petId: number; 19 | personId: number; 20 | } 21 | 22 | @Table 23 | class PetPerson extends Model { 24 | @ForeignKey(() => Pet) 25 | @Column 26 | petId: number; 27 | 28 | @ForeignKey(() => Person) 29 | @Column 30 | personId: number; 31 | } 32 | 33 | interface PersonAttributes { 34 | id: number; 35 | name: string; 36 | } 37 | type PersonCreationAttributes = Optional; 38 | 39 | @Table 40 | export class Person extends Model { 41 | @PrimaryKey 42 | @AutoIncrement 43 | @Column(DataType.INTEGER) 44 | id: number; 45 | 46 | @Column(DataType.STRING) 47 | name: string; 48 | } 49 | 50 | @Table 51 | export class Pet extends Model { 52 | @PrimaryKey 53 | @AutoIncrement 54 | @Column(DataType.INTEGER) 55 | petId: number; 56 | 57 | @Column(DataType.STRING) 58 | name: string; 59 | 60 | // model with attributes 61 | @BelongsToMany(() => Person, () => PetPerson) 62 | owners: Person[]; 63 | } 64 | 65 | @Table 66 | export class Toy extends Model { 67 | @ForeignKey(() => Pet) 68 | @Column(DataType.INTEGER) 69 | petId: number; 70 | 71 | @Column(DataType.STRING) 72 | name: string; 73 | 74 | // model without attributes 75 | @BelongsTo(() => Pet) 76 | pet: Pet; 77 | } 78 | 79 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 80 | function testTypes() { 81 | // all models should be accepted 82 | new Sequelize().addModels([Person, Pet, PetPerson]); 83 | } 84 | -------------------------------------------------------------------------------- /test/types/model.spec.ts: -------------------------------------------------------------------------------- 1 | // Types only test. This should compile successfully. 2 | 3 | import { Column } from '../../src/model/column/column'; 4 | import { Model } from '../../src/model/model/model'; 5 | import { Table } from '../../src/model/table/table'; 6 | import { DataType } from '../../src/sequelize/data-type/data-type'; 7 | 8 | @Table 9 | export class User extends Model { 10 | @Column(DataType.ARRAY(DataType.STRING)) 11 | myCol: string[]; 12 | } 13 | 14 | @Table({ 15 | hooks: { 16 | beforeUpdate: (instance) => { 17 | // without generic random will result in error 18 | instance.random = 4; 19 | }, 20 | }, 21 | }) 22 | export class Post extends Model { 23 | @Column(DataType.INTEGER) 24 | random: number; 25 | } 26 | -------------------------------------------------------------------------------- /test/types/scopes.spec.ts: -------------------------------------------------------------------------------- 1 | // Types only test. This should compile successfully. 2 | 3 | import { Column } from '../../src/model/column/column'; 4 | import { Model } from '../../src/model/model/model'; 5 | import { Table } from '../../src/model/table/table'; 6 | import { DataType } from '../../src/sequelize/data-type/data-type'; 7 | import { Scopes } from '../../src/scopes/scopes'; 8 | import { DefaultScope } from '../../src/scopes/default-scope'; 9 | 10 | @DefaultScope(() => ({ 11 | order: [['myCol', 'ASC']], 12 | })) 13 | @Scopes(() => ({ 14 | desc: { 15 | order: [['myCol', 'DESC']], 16 | }, 17 | })) 18 | @Table 19 | export class User extends Model { 20 | @Column(DataType.ARRAY(DataType.STRING)) 21 | myCol: string[]; 22 | } 23 | -------------------------------------------------------------------------------- /test/utils/association.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { capitalize } from '../../src/shared/string'; 3 | 4 | /** 5 | * Checks auto generated functions on targets prototype 6 | */ 7 | export function expectAutoGeneratedFunctions( 8 | target: any, 9 | keys: { singular: string; plural?: string } 10 | ): void { 11 | const prefixes: { [type: string]: { singular: string[]; plural: string[] } } = { 12 | toMany: { 13 | singular: ['create', 'remove', 'has'], 14 | plural: ['add', 'count', 'get', 'set', 'remove', 'has'], 15 | }, 16 | toOne: { 17 | singular: ['create', 'get', 'set'], 18 | plural: [], 19 | }, 20 | }; 21 | 22 | const finalPrefixes = prefixes[keys.plural ? 'toMany' : 'toOne']; 23 | 24 | Object.keys(keys).forEach((key) => { 25 | finalPrefixes[key].forEach((prefix) => { 26 | expect(target.prototype) 27 | .to.have.property(prefix + capitalize(keys[key])) 28 | .that.is.a('function'); 29 | }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /test/utils/common.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | /** 4 | * Compares instance with expected values 5 | */ 6 | export function assertInstance(instance: any | any[], expectedValues: any | any[]): void { 7 | if (Array.isArray(expectedValues)) { 8 | expect(instance).to.have.property('length', expectedValues.length); 9 | 10 | return instance.forEach((_instance, i) => assertInstance(_instance, expectedValues[i])); 11 | } 12 | 13 | expect(instance).to.have.property('id').that.is.not.null; 14 | 15 | Object.keys(expectedValues).forEach((key) => { 16 | const value = instance[key]; 17 | const expectedValue = expectedValues[key]; 18 | 19 | expect(instance).to.have.property(key).that.is.not.null.and.not.undefined; 20 | 21 | if (typeof expectedValue === 'object') { 22 | assertInstance(value, expectedValue); 23 | } else { 24 | expect(instance).to.have.property(key, expectedValue); 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/utils/sequelize.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from '../../src/sequelize/sequelize/sequelize'; 2 | import { ModelOptions } from 'sequelize'; 3 | import { SequelizeOptions } from '../../src/sequelize/sequelize/sequelize-options'; 4 | 5 | export function createSequelize(partialOptions: Partial): Sequelize; 6 | export function createSequelize(useModelsInPath?: boolean, define?: ModelOptions): Sequelize; 7 | export function createSequelize( 8 | useModelsInPathOrPartialOptions?: boolean | Partial, 9 | define: ModelOptions = {} 10 | ): Sequelize { 11 | let useModelsInPath = true; 12 | let partialOptions = {}; 13 | if (typeof useModelsInPathOrPartialOptions === 'object') { 14 | partialOptions = useModelsInPathOrPartialOptions; 15 | } else if (typeof useModelsInPathOrPartialOptions === 'boolean') { 16 | useModelsInPath = useModelsInPathOrPartialOptions; 17 | } 18 | 19 | return new Sequelize({ 20 | database: '__', 21 | dialect: 'sqlite' as const, 22 | username: 'root', 23 | password: '', 24 | define, 25 | storage: ':memory:', 26 | logging: !('DISABLE_LOGGING' in process.env), 27 | modelPaths: useModelsInPath ? [__dirname + '/../models'] : [], 28 | ...partialOptions, 29 | }); 30 | } 31 | 32 | export function createSequelizeValidationOnly(useModelsInPath = true): Sequelize { 33 | return new Sequelize({ 34 | validateOnly: true, 35 | logging: !('DISABLE_LOGGING' in process.env), 36 | models: useModelsInPath ? [__dirname + '/../models'] : [], 37 | }); 38 | } 39 | 40 | export function createSequelizeFromUri(useModelsInPath = true): Sequelize { 41 | const sequelize = new Sequelize('sqlite://'); 42 | sequelize.addModels(useModelsInPath ? [__dirname + '/../models'] : []); 43 | 44 | return sequelize; 45 | } 46 | 47 | export function createSequelizeFromUriObject(useModelsInPath = true): Sequelize { 48 | return new Sequelize('sqlite://', { 49 | modelPaths: useModelsInPath ? [__dirname + '/../models'] : [], 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "strictNullChecks": true, 10 | "noUnusedLocals": true, 11 | "skipLibCheck": true, 12 | "pretty": true, 13 | "outDir": "dist", 14 | "lib": ["es2017"] 15 | }, 16 | "include": [ 17 | "src" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "examples", 22 | "test" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------