├── .codeclimate.yml ├── .commitlintrc.json ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── major-release.yml │ ├── minor-release.yml │ ├── patch-release.yml │ └── test.yml ├── .gitignore ├── .huskyrc.json ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docker-compose.yaml ├── jest.config.base.js ├── jest.setup.js ├── lerna.json ├── lint-staged.config.js ├── package-lock.json ├── package.json ├── packages ├── bull │ ├── .eslintrc.js │ ├── .npmignore │ ├── .prettierrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── bull-core.module.spec.ts │ │ ├── bull-core.module.ts │ │ ├── bull.constants.ts │ │ ├── bull.decorator.ts │ │ ├── bull.interfaces.ts │ │ ├── bull.module.spec.ts │ │ ├── bull.module.ts │ │ ├── bull.utils.spec.ts │ │ ├── bull.utils.ts │ │ ├── examples │ │ │ ├── 1.basic.example.spec.ts │ │ │ ├── 10.skip-processor.example.spec.ts │ │ │ ├── 2.event.example.spec.ts │ │ │ ├── 3.extra-job-options.example.spec.ts │ │ │ ├── 4.multiple-processor.example.spec.ts │ │ │ ├── 5.parent-child-queue.example.spec.ts │ │ │ ├── 6.symbol.example.spec.ts │ │ │ ├── 7.using-bull-queue-service.example.spec.ts │ │ │ ├── 8.async-basic.example.spec.ts │ │ │ └── 9.mock.example.spec.ts │ │ ├── index.ts │ │ ├── services │ │ │ ├── bull-queue-provider.service.spec.ts │ │ │ ├── bull-queue-provider.service.ts │ │ │ ├── bull.service.ts │ │ │ └── explorers │ │ │ │ ├── base-explorer.service.ts │ │ │ │ ├── event-explorer.service.ts │ │ │ │ └── processor-explorer.service.ts │ │ └── testing │ │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── bullmq-terminus │ ├── .eslintrc.js │ ├── .npmignore │ ├── .prettierrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── bull.health.ts │ │ ├── constants.ts │ │ ├── health.module.spec.ts │ │ ├── health.module.ts │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── bullmq │ ├── .eslintrc.js │ ├── .npmignore │ ├── .prettierrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── bull-core.module.ts │ │ ├── bull.constants.ts │ │ ├── bull.decorator.ts │ │ ├── bull.explorer.service.ts │ │ ├── bull.mock.spec.ts │ │ ├── bull.mock.ts │ │ ├── bull.module.ts │ │ ├── bull.providers.spec.ts │ │ ├── bull.providers.ts │ │ ├── bull.service.ts │ │ ├── bull.utils.ts │ │ ├── examples │ │ │ ├── 1.basic.example.spec.ts │ │ │ ├── 2.shared-ioredis-connection.example.spec.ts │ │ │ ├── 3.queue-events-decorator.example.spec.ts │ │ │ ├── 4.queue-decorator.example.spec.ts │ │ │ ├── 5.worker-decorator.example.spec.ts │ │ │ ├── 6.async.example.spec.ts │ │ │ ├── 7.delayed-job.example.spec.ts │ │ │ ├── 8.flow.example.spec.ts │ │ │ └── 9.mock.example.spec.ts │ │ ├── index.ts │ │ └── interfaces │ │ │ ├── bull-base.interface.ts │ │ │ ├── bull-module.interface.ts │ │ │ ├── bull-queue-events.interface.ts │ │ │ ├── bull-queue.interface.ts │ │ │ ├── bull-worker.interface.ts │ │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── terminus │ ├── .eslintrc.js │ ├── .npmignore │ ├── .prettierrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── bull.health.ts │ ├── constants.ts │ ├── health.module.spec.ts │ ├── health.module.ts │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── renovate.json ├── tsconfig.base.json ├── tsconfig.eslint.json └── turbo.json /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | plugins: 3 | eslint: 4 | enabled: true 5 | channel: "eslint-5" 6 | checks: 7 | similar-code: 8 | enabled: false 9 | exclude_patterns: 10 | - "**/coverage/" 11 | - "**/node_modules/" 12 | - "**/dist/" 13 | - "**/*.spec.ts" 14 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const path = require("path"); 3 | module.exports = { 4 | env: { 5 | node: true, 6 | jest: true, 7 | }, 8 | extends: ["plugin:@typescript-eslint/recommended", "prettier"], 9 | parser: "@typescript-eslint/parser", 10 | parserOptions: { 11 | sourceType: "module", 12 | project: path.resolve(__dirname, "tsconfig.eslint.json"), 13 | }, 14 | plugins: ["@typescript-eslint"], 15 | rules: { 16 | "@typescript-eslint/ban-types": "off", 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "no-useless-constructor": "off", 20 | "@typescript-eslint/no-useless-constructor": "error", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-non-null-assertion": "off", 23 | "lines-between-class-members": ["error", "always"], 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: "0 5 * * 4" 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ["javascript"] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v3 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v3 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v3 67 | -------------------------------------------------------------------------------- /.github/workflows/major-release.yml: -------------------------------------------------------------------------------- 1 | name: Major Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Checkout master 12 | run: git checkout master 13 | - uses: volta-cli/action@v4.0.1 14 | - name: Install npm packages 15 | run: npm ci 16 | - name: Publish monorepo packages 17 | run: | 18 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc 19 | git config --global user.name 'github-actions[bot]' 20 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 21 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY 22 | npx lerna publish --yes major 23 | -------------------------------------------------------------------------------- /.github/workflows/minor-release.yml: -------------------------------------------------------------------------------- 1 | name: Minor Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Checkout master 12 | run: git checkout master 13 | - uses: volta-cli/action@v4.0.1 14 | - name: Install npm packages 15 | run: npm ci 16 | - name: Publish monorepo packages 17 | run: | 18 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc 19 | git config --global user.name 'github-actions[bot]' 20 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 21 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY 22 | npx lerna publish --yes minor 23 | -------------------------------------------------------------------------------- /.github/workflows/patch-release.yml: -------------------------------------------------------------------------------- 1 | name: Patch Release 2 | 3 | on: 4 | schedule: 5 | - cron: "0 4 * * 0" 6 | branches: 7 | - master 8 | workflow_dispatch: 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Checkout master 16 | run: git checkout master 17 | - uses: volta-cli/action@v4.0.1 18 | - name: Install npm packages 19 | run: npm ci 20 | - name: Publish monorepo packages 21 | run: | 22 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc 23 | git config --global user.name 'github-actions[bot]' 24 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 25 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY 26 | npx lerna publish --yes patch 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | env: 6 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} 7 | TURBO_TEAM: anchan828 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up docker-compose 16 | run: docker-compose up -d 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 20 20 | cache: npm 21 | - name: Install npm packages 22 | run: npm ci 23 | - name: Lint 24 | run: npm run lint 25 | - name: Test monorepo packages 26 | uses: nick-fields/retry@v2 27 | with: 28 | timeout_minutes: 10 29 | max_attempts: 3 30 | command: npm test 31 | - name: Upload coverage to Codecov 32 | uses: codecov/codecov-action@v3 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | .idea 63 | dist 64 | .idea 65 | 66 | .DS_Store 67 | .turbo 68 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged", 4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | dist/**/*.tsbuildinfo 4 | dist/**/*.js.map 5 | dist/**/*.ts.map 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | trailingComma: "all", 4 | bracketSpacing: true, 5 | }; 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": ["source.organizeImports", "source.fixAll"], 3 | "editor.codeActionsOnSaveTimeout": 2000, 4 | "editor.formatOnSave": true, 5 | "eslint.options": { 6 | "extensions": [".js", ".ts"] 7 | }, 8 | "eslint.validate": ["javascript", "typescript"], 9 | "eslint.workingDirectories": [ 10 | { 11 | "mode": "auto" 12 | } 13 | ], 14 | "eslint.codeAction.showDocumentation": { 15 | "enable": false 16 | }, 17 | "eslint.codeAction.disableRuleComment": { 18 | "enable": false 19 | }, 20 | "files.exclude": { 21 | "**/.DS_Store": true, 22 | "**/.git": true, 23 | "**/.hg": true, 24 | "**/.svn": true, 25 | "**/CVS": true 26 | }, 27 | "typescript.preferences.importModuleSpecifier": "relative", 28 | "typescript.tsdk": "node_modules/typescript/lib" 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 anchan828 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # These packages have completed their mission. Please use @nestjs/bull or @nestjs/bullmq from now on. 2 | 3 | 4 | # @anchan828/nest-bull-packages 5 | 6 | [![codecov](https://codecov.io/gh/anchan828/nest-bull/branch/master/graph/badge.svg?token=T9UwUKE5Z5)](https://codecov.io/gh/anchan828/nest-bull) 7 | 8 | The [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest). 9 | 10 | ## Bull packages 11 | 12 | [@anchan828/nest-bull](https://github.com/anchan828/nest-bull/tree/master/packages/bull) 13 | 14 | [@anchan828/nest-bull-terminus](https://github.com/anchan828/nest-bull/tree/master/packages/terminus) 15 | 16 | ## BullMQ packages 17 | 18 | [@anchan828/nest-bullmq](https://github.com/anchan828/nest-bull/tree/master/packages/bullmq) 19 | 20 | [@anchan828/nest-bullmq-terminus](https://github.com/anchan828/nest-bull/tree/master/packages/bullmq-terminus) 21 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | redis: 5 | image: redis 6 | ports: 7 | - "6379:6379" 8 | -------------------------------------------------------------------------------- /jest.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | rootDir: ".", 4 | coverageDirectory: "coverage", 5 | coverageReporters: ["text-summary", "json-summary", "lcov", "text", "clover"], 6 | testEnvironment: "node", 7 | setupFilesAfterEnv: ["../../jest.setup.js"], 8 | verbose: true, 9 | }; 10 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(1000 * 60); 2 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "command": { 6 | "publish": { 7 | "ignoreChanges": [ 8 | "dist/**/*" 9 | ], 10 | "message": "chore(release): publish %s", 11 | "conventionalCommits": true, 12 | "graphType": "dependencies" 13 | } 14 | }, 15 | "version": "3.2.23" 16 | } 17 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*.{ts}": ["eslint --fix", "prettier --write", "git add"], 3 | "*.{js,json,yml,yaml,md}": ["prettier --write", "git add"], 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anchan828/nest-bull-packages", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "The [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest).", 6 | "homepage": "https://github.com/anchan828/nest-bull#readme", 7 | "bugs": { 8 | "url": "https://github.com/anchan828/nest-bull/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/anchan828/nest-bull.git" 13 | }, 14 | "license": "MIT", 15 | "author": "anchan828 ", 16 | "workspaces": [ 17 | "packages/*" 18 | ], 19 | "scripts": { 20 | "build": "turbo run build", 21 | "format": "prettier --write '**/*.{js,json,yml,yaml,md}'", 22 | "postinstall": "husky install", 23 | "lint": "turbo run lint", 24 | "lint:fix": "turbo run lint:fix", 25 | "publish": "lerna publish --yes patch", 26 | "publish:major": "lerna publish --yes major", 27 | "publish:minor": "lerna publish --yes minor", 28 | "test": "turbo run test" 29 | }, 30 | "devDependencies": { 31 | "@commitlint/cli": "18.4.3", 32 | "@commitlint/config-conventional": "18.4.3", 33 | "@lerna-lite/cli": "3.1.0", 34 | "@lerna-lite/publish": "3.1.0", 35 | "@nestjs/common": "10.2.10", 36 | "@nestjs/core": "10.2.10", 37 | "@nestjs/platform-express": "10.2.10", 38 | "@nestjs/testing": "10.2.10", 39 | "@types/jest": "29.5.11", 40 | "@types/node": "20.10.4", 41 | "@typescript-eslint/eslint-plugin": "6.14.0", 42 | "@typescript-eslint/parser": "6.14.0", 43 | "eslint": "8.55.0", 44 | "eslint-config-prettier": "9.1.0", 45 | "eslint-plugin-prettier": "5.0.1", 46 | "fast-glob": "3.3.2", 47 | "husky": "8.0.3", 48 | "jest": "29.7.0", 49 | "lint-staged": "15.2.0", 50 | "prettier": "3.1.1", 51 | "reflect-metadata": "0.1.14", 52 | "rxjs": "7.8.1", 53 | "ts-jest": "29.1.1", 54 | "ts-node": "10.9.2", 55 | "turbo": "1.11.1", 56 | "typescript": "5.3.3", 57 | "ulid": "2.3.0" 58 | }, 59 | "volta": { 60 | "node": "20.11.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/bull/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const baseESLintConfig = require("../../.eslintrc"); 2 | 3 | module.exports = { ...baseESLintConfig }; 4 | -------------------------------------------------------------------------------- /packages/bull/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | dist/**/*.tsbuildinfo 4 | dist/**/*.js.map 5 | dist/**/*.ts.map 6 | -------------------------------------------------------------------------------- /packages/bull/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const basePrettierConfig = require("../../.prettierrc"); 2 | 3 | module.exports = { 4 | ...basePrettierConfig, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/bull/README.md: -------------------------------------------------------------------------------- 1 | # @anchan828/nest-bull 2 | 3 | ![npm](https://img.shields.io/npm/v/@anchan828/nest-bull.svg) 4 | ![NPM](https://img.shields.io/npm/l/@anchan828/nest-bull.svg) 5 | 6 | ## Description 7 | 8 | The [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest). 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm i --save @anchan828/nest-bull bull 14 | $ npm i --save-dev @types/bull 15 | ``` 16 | 17 | ## Quick Start 18 | 19 | ### Importing BullModule and Queue component 20 | 21 | ```ts 22 | import { BullModule } from "@anchan828/nest-bull"; 23 | import { Module } from "@nestjs/common"; 24 | import { AppController } from "./app.controller"; 25 | import { AppQueue } from "./app.queue"; 26 | import { AppService } from "./app.service"; 27 | 28 | @Module({ 29 | imports: [ 30 | BullModule.forRoot({ 31 | queues: [__dirname + "/**/*.queue{.ts,.js}"], 32 | options: { 33 | redis: { 34 | host: "127.0.0.1", 35 | }, 36 | }, 37 | }), 38 | ], 39 | controllers: [AppController], 40 | providers: [AppService, AppQueue], 41 | }) 42 | export class AppModule {} 43 | ``` 44 | 45 | ### Creating queue class 46 | 47 | ```ts 48 | import { BullQueue, BullQueueProcess } from "@anchan828/nest-bull"; 49 | import { Job } from "bull"; 50 | import { APP_QUEUE } from "./app.constants"; 51 | import { AppService } from "./app.service"; 52 | 53 | @BullQueue({ name: APP_QUEUE }) 54 | export class AppQueue { 55 | constructor(private readonly service: AppService) {} 56 | 57 | @BullQueueProcess() 58 | public async process(job: Job) { 59 | console.log("called process", job.data, this.service.root()); 60 | } 61 | } 62 | ``` 63 | 64 | ### Adding job 65 | 66 | ```ts 67 | import { Controller, Get, Inject } from "@nestjs/common"; 68 | import { JobId, Queue } from "bull"; 69 | import { APP_QUEUE } from "./app.constants"; 70 | import { BullQueueInject } from "@anchan828/nest-bull"; 71 | 72 | @Controller() 73 | export class AppController { 74 | constructor( 75 | @BullQueueInject(APP_QUEUE) 76 | private readonly queue: Queue, 77 | ) {} 78 | 79 | @Get() 80 | async root(): Promise { 81 | const job = await this.queue.add({ text: "text" }); 82 | return job.id; 83 | } 84 | } 85 | ``` 86 | 87 | ### Override queue settings per queue 88 | 89 | ```ts 90 | @BullQueue({ 91 | name: APP_QUEUE, 92 | options: { 93 | redis: { 94 | db: 3, 95 | }, 96 | }, 97 | }) 98 | export class AppQueue { 99 | // queue.add('processorName1', data); 100 | @BullQueueProcess({ 101 | name: "processorName1", 102 | concurrency: 3, 103 | }) 104 | async process1(job: Job) { 105 | throw new Error(`throw error ${JSON.stringify(job.data)}`); 106 | } 107 | 108 | // queue.add('processorName2', data); 109 | @BullQueueProcess({ 110 | name: "processorName2", 111 | }) 112 | async process2(job: Job) { 113 | throw new Error(`throw error ${JSON.stringify(job.data)}`); 114 | } 115 | } 116 | ``` 117 | 118 | Handling events 119 | 120 | ```ts 121 | @BullQueue({ name: APP_QUEUE }) 122 | export class AppQueue { 123 | constructor(private readonly service: AppService) {} 124 | 125 | @BullQueueProcess() 126 | public async process(job: Job) { 127 | console.log("called process", job.data, this.service.root()); 128 | } 129 | 130 | @BullQueueEventProgress() 131 | public async progress(job: Job, progress: number) { 132 | console.log("progress", job.id, progress); 133 | } 134 | 135 | @BullQueueEventCompleted() 136 | public async completed(job: Job, result: any) { 137 | console.log("completed", job.id, result); 138 | } 139 | 140 | @BullQueueEventFailed() 141 | public async failed(job: Job, error: Error) { 142 | console.error("failed", job.id, error); 143 | } 144 | } 145 | ``` 146 | 147 | ### Getting Queue using BullService 148 | 149 | ```ts 150 | import { Controller, Get, Inject } from "@nestjs/common"; 151 | import { JobId, Queue } from "bull"; 152 | import { APP_QUEUE } from "./app.constants"; 153 | import { BullService, BULL_MODULE_SERVICE } from "@anchan828/nest-bull"; 154 | 155 | @Controller() 156 | export class AppController { 157 | constructor( 158 | @Inject(BULL_MODULE_SERVICE) 159 | private readonly service: BullService, 160 | ) {} 161 | 162 | @Get() 163 | async root(): Promise { 164 | const job = await this.service.getQueue(APP_QUEUE).add({ text: "text" }); 165 | return job.id; 166 | } 167 | } 168 | ``` 169 | 170 | ### forRootAsync 171 | 172 | This package supports forRootAsync. However, you can only BullService if you want to forRootAsync. 173 | 174 | ### More examples... 175 | 176 | See example app: https://github.com/anchan828/nest-bull-example 177 | 178 | And more: https://github.com/anchan828/nest-bull/tree/master/src/examples 179 | 180 | ### Extra 181 | 182 | There are extra options. 183 | 184 | ```ts 185 | export interface BullQueueExtraOptions { 186 | defaultProcessorOptions?: { 187 | /** 188 | * Bull will then call your handler in parallel respecting this maximum value. 189 | */ 190 | concurrency?: number; 191 | 192 | /** 193 | * Skip call this processor if true. 194 | */ 195 | skip?: boolean; 196 | }; 197 | 198 | defaultJobOptions?: { 199 | /** 200 | * Set TTL when job in the completed. (Default: -1) 201 | */ 202 | setTTLOnComplete?: number; 203 | /** 204 | * Set TTL when job in the failed. (Default: -1) 205 | */ 206 | setTTLOnFail?: number; 207 | }; 208 | } 209 | ``` 210 | 211 | You can set options to module and per queue. 212 | 213 | ```ts 214 | @Module({ 215 | imports: [ 216 | BullModule.forRoot({ 217 | queues: [__dirname + "/**/*.queue{.ts,.js}"], 218 | options: { 219 | redis: { 220 | host: "127.0.0.1", 221 | }, 222 | }, 223 | extra: { 224 | defaultProcessorOptions: { 225 | concurrency: 3, 226 | }, 227 | defaultJobOptions: { 228 | setTTLOnComplete: 30, 229 | }, 230 | }, 231 | }), 232 | ], 233 | controllers: [AppController], 234 | providers: [AppService, AppQueue], 235 | }) 236 | export class AppModule {} 237 | ``` 238 | 239 | ```ts 240 | @BullQueue({ 241 | name: APP_QUEUE, 242 | extra: { 243 | defaultJobOptions: { 244 | setTTLOnComplete: 300, 245 | }, 246 | }, 247 | }) 248 | export class AppQueue { 249 | @BullQueueProcess() 250 | public async process(job: Job) { 251 | return Promise.resolve(); 252 | } 253 | } 254 | ``` 255 | 256 | ## Testing 257 | 258 | Example for TestingModule 259 | 260 | Set `mock: true` if you don't want to create Queue instance. 261 | BullModule create mock instance instead of Queue. 262 | 263 | ```ts 264 | @Module({ 265 | imports: [ 266 | BullModule.forRoot({ 267 | queues: [__filename], 268 | mock: true, 269 | }), 270 | ], 271 | }) 272 | export class ApplicationModule {} 273 | ``` 274 | 275 | Or you can use createTestBullProvider 276 | 277 | ```ts 278 | import { BullQueueInject } from "@anchan828/nest-bull"; 279 | 280 | @Injectable() 281 | export class Service { 282 | constructor( 283 | @BullQueueInject("Queue name") 284 | private readonly queue: Queue, 285 | ) {} 286 | 287 | public async someMethod() { 288 | await this.queue.add({ key: "value" }); 289 | } 290 | } 291 | ``` 292 | 293 | ```ts 294 | import { createTestBullProvider } from "@anchan828/nest-bull/dist/testing"; 295 | const app: TestingModule = await Test.createTestingModule({ 296 | providers: [Service, createTestBullProvider("Queue name")], 297 | }).compile(); 298 | ``` 299 | 300 | ## License 301 | 302 | [MIT](LICENSE). 303 | -------------------------------------------------------------------------------- /packages/bull/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require("../../jest.config.base"); 2 | module.exports = { 3 | ...base, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/bull/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anchan828/nest-bull", 3 | "version": "3.2.23", 4 | "description": "The [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest).", 5 | "homepage": "https://github.com/anchan828/nest-bull/tree/master/packages/bull#readme", 6 | "bugs": { 7 | "url": "https://github.com/anchan828/nest-bull/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/anchan828/nest-bull.git" 12 | }, 13 | "license": "MIT", 14 | "author": "anchan828 ", 15 | "main": "dist/index.js", 16 | "types": "dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsc -p tsconfig.build.json", 19 | "copy:license": "cp ../../LICENSE ./", 20 | "lint": "TIMING=1 eslint --ignore-path ../../.eslintignore '**/*.ts'", 21 | "lint:fix": "npm run lint -- --fix", 22 | "prepublishOnly": "npm run build && rm -f dist/*.tsbuildinfo && npm run copy:license", 23 | "test": "jest --coverage --runInBand --detectOpenHandles --forceExit --logHeapUsage", 24 | "test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest --runInBand ---detectOpenHandles --logHeapUsage", 25 | "test:watch": "npm run test -- --watch", 26 | "watch": "tsc --watch" 27 | }, 28 | "dependencies": { 29 | "deepmerge": "^4.3.1", 30 | "fast-glob": "^3.3.1" 31 | }, 32 | "devDependencies": { 33 | "@nestjs/common": "10.2.10", 34 | "@types/bull": "3.15.9", 35 | "bull": "4.11.5", 36 | "rxjs": "7.8.1" 37 | }, 38 | "peerDependencies": { 39 | "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/bull/src/bull-core.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { BullCoreModule } from "./bull-core.module"; 4 | import { BULL_MODULE_OPTIONS } from "./bull.constants"; 5 | import { BullModuleOptions, BullModuleOptionsFactory } from "./bull.interfaces"; 6 | describe("Bull", () => { 7 | describe("forRoot", () => { 8 | it("should compile", async () => { 9 | await expect( 10 | Test.createTestingModule({ 11 | imports: [ 12 | BullCoreModule.forRoot({ 13 | queues: ["test"], 14 | }), 15 | ], 16 | }).compile(), 17 | ).resolves.toBeDefined(); 18 | }); 19 | }); 20 | 21 | describe("forRootAsync", () => { 22 | it("should compile using useClass", async () => { 23 | @Injectable() 24 | class TestBullModuleOptionsFactory implements BullModuleOptionsFactory { 25 | createBullModuleOptions(): BullModuleOptions { 26 | return { 27 | queues: ["test"], 28 | }; 29 | } 30 | } 31 | const app = await Test.createTestingModule({ 32 | imports: [ 33 | BullCoreModule.forRootAsync({ 34 | imports: [], 35 | useClass: TestBullModuleOptionsFactory, 36 | }), 37 | ], 38 | }).compile(); 39 | expect(app).toBeDefined(); 40 | expect(app.get(BULL_MODULE_OPTIONS)).toStrictEqual({ 41 | queues: ["test"], 42 | }); 43 | await app.close(); 44 | }); 45 | 46 | it("should compile using useFactory", async () => { 47 | const app = await Test.createTestingModule({ 48 | imports: [ 49 | BullCoreModule.forRootAsync({ 50 | imports: [], 51 | useFactory: () => ({ queues: ["test"] }), 52 | }), 53 | ], 54 | }).compile(); 55 | expect(app).toBeDefined(); 56 | expect(app.get(BULL_MODULE_OPTIONS)).toStrictEqual({ 57 | queues: ["test"], 58 | }); 59 | await app.close(); 60 | }); 61 | 62 | it("should compile using imports and inject", async () => { 63 | @Injectable() 64 | class TestEnv { 65 | test = "test"; 66 | } 67 | @Module({ 68 | providers: [TestEnv], 69 | exports: [TestEnv], 70 | }) 71 | class TestModule {} 72 | 73 | const app = await Test.createTestingModule({ 74 | imports: [ 75 | BullCoreModule.forRootAsync({ 76 | imports: [TestModule], 77 | inject: [TestEnv], 78 | useFactory: (env: any) => ({ queues: [env.test] } as BullModuleOptions), 79 | }), 80 | ], 81 | }).compile(); 82 | expect(app).toBeDefined(); 83 | expect(app.get(BULL_MODULE_OPTIONS)).toStrictEqual({ 84 | queues: ["test"], 85 | }); 86 | await app.close(); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/bull/src/bull-core.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Inject, Module, OnModuleDestroy, OnModuleInit, Provider } from "@nestjs/common"; 2 | import { ClassProvider, FactoryProvider } from "@nestjs/common/interfaces"; 3 | // import { ModulesContainer } from '@nestjs/core'; 4 | // import { 5 | // CustomValue, 6 | // Module as CoreModule, 7 | // } from '@nestjs/core/injector/module'; 8 | import { MetadataScanner } from "@nestjs/core/metadata-scanner"; 9 | import { BULL_MODULE_OPTIONS, BULL_MODULE_SERVICE } from "./bull.constants"; 10 | import { BullModuleAsyncOptions, BullModuleOptions, BullModuleOptionsFactory } from "./bull.interfaces"; 11 | import { BullQueueProviderService } from "./services/bull-queue-provider.service"; 12 | import { BullService } from "./services/bull.service"; 13 | import { BullQueueEventExplorerService } from "./services/explorers/event-explorer.service"; 14 | import { BullQueueProcessorExplorerService } from "./services/explorers/processor-explorer.service"; 15 | 16 | @Global() 17 | @Module({ 18 | providers: [MetadataScanner, BullQueueProcessorExplorerService, BullQueueEventExplorerService], 19 | }) 20 | export class BullCoreModule implements OnModuleInit, OnModuleDestroy { 21 | async onModuleInit(): Promise { 22 | if (this.bullService.isAsync) { 23 | const bullProviderService = new BullQueueProviderService(this.options, this.bullService); 24 | // TODO: get value providers, but get injector error before adding BullCoreModule to them. 25 | bullProviderService.createBullQueueProviders(); 26 | // const bullCoreModule = [...this.container.values()].find( 27 | // module => module.metatype === BullCoreModule, 28 | // ) as CoreModule; 29 | // for (const provider of (bullQueueProviders as unknown) as CustomValue[]) { 30 | // provider.name = provider.provide; 31 | // bullCoreModule.addProvider(provider); 32 | // bullCoreModule.exports.add(provider.provide); 33 | // } 34 | } 35 | 36 | this.processorExplorer.explore(); 37 | this.eventExplorer.explore(); 38 | await this.bullService.isReady(); 39 | } 40 | 41 | async onModuleDestroy(): Promise { 42 | await this.bullService.closeAll(); 43 | } 44 | 45 | constructor( 46 | private readonly processorExplorer: BullQueueProcessorExplorerService, 47 | private readonly eventExplorer: BullQueueEventExplorerService, 48 | @Inject(BULL_MODULE_OPTIONS) 49 | private readonly options: BullModuleOptions, 50 | @Inject(BULL_MODULE_SERVICE) 51 | private readonly bullService: BullService, 52 | ) {} 53 | 54 | public static forRoot(options: BullModuleOptions): DynamicModule { 55 | const bullService = new BullService(); 56 | const bullProviderService = new BullQueueProviderService(options, bullService); 57 | const bullQueueProviders = bullProviderService.createBullQueueProviders(); 58 | const bullQueueServiceProvider = { 59 | provide: BULL_MODULE_SERVICE, 60 | useValue: bullService, 61 | }; 62 | return { 63 | module: BullCoreModule, 64 | providers: [ 65 | { 66 | provide: BULL_MODULE_OPTIONS, 67 | useValue: options, 68 | }, 69 | bullQueueServiceProvider, 70 | ...bullQueueProviders, 71 | ], 72 | exports: [bullQueueServiceProvider, ...bullQueueProviders], 73 | }; 74 | } 75 | 76 | // TODO: I don't know how to create bull queue providers by async... 77 | public static forRootAsync(options: BullModuleAsyncOptions): DynamicModule { 78 | const bullService = new BullService(true); 79 | const bullQueueServiceProvider = { 80 | provide: BULL_MODULE_SERVICE, 81 | useValue: bullService, 82 | }; 83 | 84 | const asyncProviders = this.createAsyncProviders(options); 85 | return { 86 | module: BullCoreModule, 87 | imports: [...(options.imports || [])], 88 | providers: [bullQueueServiceProvider, ...asyncProviders], 89 | exports: [bullQueueServiceProvider], 90 | }; 91 | } 92 | 93 | private static createAsyncProviders(options: BullModuleAsyncOptions): Provider[] { 94 | if (options.useFactory) { 95 | return [this.createAsyncOptionsProvider(options)]; 96 | } 97 | return [ 98 | this.createAsyncOptionsProvider(options), 99 | { 100 | provide: options.useClass, 101 | useClass: options.useClass, 102 | inject: options.inject, 103 | } as ClassProvider, 104 | ]; 105 | } 106 | 107 | private static createAsyncOptionsProvider(options: BullModuleAsyncOptions): FactoryProvider { 108 | if (options.useFactory) { 109 | return { 110 | provide: BULL_MODULE_OPTIONS, 111 | useFactory: options.useFactory, 112 | inject: options.inject || [], 113 | }; 114 | } 115 | return { 116 | provide: BULL_MODULE_OPTIONS, 117 | useFactory: async (optionsFactory: BullModuleOptionsFactory): Promise => 118 | await optionsFactory.createBullModuleOptions(), 119 | inject: options.useClass ? [options.useClass] : [], 120 | }; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/bull/src/bull.constants.ts: -------------------------------------------------------------------------------- 1 | export const BULL_MODULE = "BullModule"; 2 | export const BULL_MODULE_SERVICE = "BullModuleService"; 3 | export const BULL_MODULE_OPTIONS = "BullModuleOptions"; 4 | export const BULL_QUEUE_DECORATOR = "BullQueue"; 5 | export const BULL_QUEUE_PROCESSOR_DECORATOR = "BullQueueProcess"; 6 | export const BULL_QUEUE_EVENT_DECORATOR = "BullQueueEvent"; 7 | export const BULL_QUEUE_HANDLER_NAMES = "BullQueueHandlerNames"; 8 | export const BULL_QUEUE_DEFAULT_JOB_NAME = "__default__"; 9 | export const BULL_QUEUE_DEFAULT_PROCESSOR_NAME = BULL_QUEUE_DEFAULT_JOB_NAME; 10 | export const BULL_QUEUE_PROCESSOR_DEFAULT_CONCURRENCY = 1; 11 | -------------------------------------------------------------------------------- /packages/bull/src/bull.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Logger } from "@nestjs/common"; 2 | import * as deepmerge from "deepmerge"; 3 | import "reflect-metadata"; 4 | import { 5 | BULL_MODULE, 6 | BULL_QUEUE_DECORATOR, 7 | BULL_QUEUE_EVENT_DECORATOR, 8 | BULL_QUEUE_PROCESSOR_DECORATOR, 9 | } from "./bull.constants"; 10 | import { 11 | BullName, 12 | BullQueueEvent, 13 | BullQueueEventOptions, 14 | BullQueueOptions, 15 | BullQueueProcessorOptions, 16 | } from "./bull.interfaces"; 17 | import { getBullQueueToken } from "./bull.utils"; 18 | 19 | export const BullQueue = (options?: BullQueueOptions): ClassDecorator => { 20 | return (target: any): void => { 21 | Reflect.defineMetadata(BULL_QUEUE_DECORATOR, deepmerge({ name: target.name }, options || {}), target); 22 | }; 23 | }; 24 | 25 | export const BullQueueProcess = (options?: BullQueueProcessorOptions): MethodDecorator => { 26 | return (target: any, propertyName: string | symbol): void => { 27 | Reflect.defineMetadata(BULL_QUEUE_PROCESSOR_DECORATOR, options, target, propertyName); 28 | }; 29 | }; 30 | 31 | export const EventHandler = (type: BullQueueEvent, isGlobal: boolean): MethodDecorator => { 32 | return (target: any, propertyName: string | symbol): void => { 33 | const eventName = `${isGlobal ? "global:" : ""}${type}`; 34 | const options: BullQueueEventOptions = deepmerge( 35 | { eventNames: [] }, 36 | Reflect.getMetadata(BULL_QUEUE_EVENT_DECORATOR, target, propertyName) || {}, 37 | ); 38 | if (options.eventNames.indexOf(eventName) !== -1) { 39 | Logger.warn( 40 | `Not allowed multiple event on same function. ${eventName} on ${propertyName.toString()}`, 41 | BULL_MODULE, 42 | false, 43 | ); 44 | return; 45 | } 46 | options.eventNames.push(eventName); 47 | Reflect.defineMetadata(BULL_QUEUE_EVENT_DECORATOR, options, target, propertyName); 48 | }; 49 | }; 50 | 51 | // locally events 52 | export const BullQueueEventError = (): MethodDecorator => EventHandler("error", false); 53 | export const BullQueueEventWaiting = (): MethodDecorator => EventHandler("waiting", false); 54 | export const BullQueueEventActive = (): MethodDecorator => EventHandler("active", false); 55 | export const BullQueueEventStalled = (): MethodDecorator => EventHandler("stalled", false); 56 | export const BullQueueEventProgress = (): MethodDecorator => EventHandler("progress", false); 57 | export const BullQueueEventCompleted = (): MethodDecorator => EventHandler("completed", false); 58 | export const BullQueueEventFailed = (): MethodDecorator => EventHandler("failed", false); 59 | export const BullQueueEventPaused = (): MethodDecorator => EventHandler("paused", false); 60 | export const BullQueueEventResumed = (): MethodDecorator => EventHandler("resumed", false); 61 | export const BullQueueEventCleaned = (): MethodDecorator => EventHandler("cleaned", false); 62 | export const BullQueueEventDrained = (): MethodDecorator => EventHandler("drained", false); 63 | export const BullQueueEventRemoved = (): MethodDecorator => EventHandler("removed", false); 64 | 65 | // global events 66 | export const BullQueueEventGlobalError = (): MethodDecorator => EventHandler("error", true); 67 | export const BullQueueEventGlobalWaiting = (): MethodDecorator => EventHandler("waiting", true); 68 | export const BullQueueEventGlobalActive = (): MethodDecorator => EventHandler("active", true); 69 | export const BullQueueEventGlobalStalled = (): MethodDecorator => EventHandler("stalled", true); 70 | export const BullQueueEventGlobalProgress = (): MethodDecorator => EventHandler("progress", true); 71 | export const BullQueueEventGlobalCompleted = (): MethodDecorator => EventHandler("completed", true); 72 | export const BullQueueEventGlobalFailed = (): MethodDecorator => EventHandler("failed", true); 73 | export const BullQueueEventGlobalPaused = (): MethodDecorator => EventHandler("paused", true); 74 | export const BullQueueEventGlobalResumed = (): MethodDecorator => EventHandler("resumed", true); 75 | export const BullQueueEventGlobalCleaned = (): MethodDecorator => EventHandler("cleaned", true); 76 | export const BullQueueEventGlobalDrained = (): MethodDecorator => EventHandler("drained", true); 77 | export const BullQueueEventGlobalRemoved = (): MethodDecorator => EventHandler("removed", true); 78 | 79 | // Avoid same name with Queue class 80 | export const BullQueueInject = (name: BullName): ParameterDecorator => Inject(getBullQueueToken(name)); 81 | -------------------------------------------------------------------------------- /packages/bull/src/bull.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Type } from "@nestjs/common"; 2 | import { ModuleMetadata } from "@nestjs/common/interfaces"; 3 | import * as Bull from "bull"; 4 | import Redis from "ioredis"; 5 | 6 | export type BullName = string | symbol; 7 | 8 | export interface BullQueueDefaultProcessorOptions { 9 | /** 10 | * Bull will then call your handler in parallel respecting this maximum value. 11 | */ 12 | concurrency?: number; 13 | 14 | /** 15 | * Skip call this processor if true. 16 | */ 17 | skip?: boolean; 18 | } 19 | 20 | export interface BullQueueDefaultJobOptions { 21 | /** 22 | * Set TTL when job in the completed. (Default: -1) 23 | */ 24 | setTTLOnComplete?: number; 25 | /** 26 | * Set TTL when job in the failed. (Default: -1) 27 | */ 28 | setTTLOnFail?: number; 29 | } 30 | 31 | export interface BullQueueExtraOptions { 32 | defaultProcessorOptions?: BullQueueDefaultProcessorOptions; 33 | 34 | defaultJobOptions?: BullQueueDefaultJobOptions; 35 | } 36 | export type BullQueueType = string | Type; 37 | 38 | export interface BullQueueMock extends Pick { 39 | on: (listener: string, callback: () => void) => void; 40 | } 41 | export interface BullModuleOptions { 42 | queues: BullQueueType[]; 43 | options?: Bull.QueueOptions; 44 | extra?: BullQueueExtraOptions; 45 | 46 | /** 47 | * Set true if you don't want to create ${@link Queue} object. 48 | * If you set to true, module create {@link BullQueueMock} object 49 | * @type {boolean} 50 | * @memberof BullModuleOptions 51 | */ 52 | mock?: boolean; 53 | } 54 | 55 | export type BullModuleAsyncOptions = { 56 | useClass?: Type; 57 | /** 58 | * The factory which should be used to provide the Bull options 59 | */ 60 | useFactory?: (...args: any[]) => Promise | BullModuleOptions; 61 | /** 62 | * The providers which should get injected 63 | */ 64 | inject?: Array | string | any>; 65 | } & Pick; 66 | 67 | export interface BullModuleOptionsFactory { 68 | createBullModuleOptions(): Promise | BullModuleOptions; 69 | } 70 | 71 | export interface BaseBullQueueOptions { 72 | name?: BullName; 73 | } 74 | 75 | export interface BullQueueOptions extends BaseBullQueueOptions { 76 | options?: Bull.QueueOptions; 77 | extra?: BullQueueExtraOptions; 78 | } 79 | 80 | export interface BullQueueProcessorOptions extends BaseBullQueueOptions { 81 | name?: string; 82 | concurrency?: number; 83 | 84 | /** 85 | * Skip call this processor if true. 86 | */ 87 | skip?: boolean; 88 | isCustomProcessorName?: boolean; 89 | } 90 | 91 | export interface BullQueueEventOptions extends BaseBullQueueOptions { 92 | eventNames: string[]; 93 | } 94 | 95 | export type BullQueue = Bull.Queue & { clients: Redis[] }; 96 | export type BullJob = Bull.Job & { toKey: () => string; queue: BullQueue }; 97 | export type BullQueueEvent = 98 | | "error" 99 | | "waiting" 100 | | "active" 101 | | "stalled" 102 | | "progress" 103 | | "completed" 104 | | "failed" 105 | | "paused" 106 | | "resumed" 107 | | "cleaned" 108 | | "drained" 109 | | "removed"; 110 | -------------------------------------------------------------------------------- /packages/bull/src/bull.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from "@nestjs/testing"; 2 | import { BullModuleOptions } from "./bull.interfaces"; 3 | import { BullModule } from "./bull.module"; 4 | import { cleanTestFiles, createTestFile } from "./bull.utils.spec"; 5 | describe("BullModule", () => { 6 | it("should be defined", () => { 7 | expect(BullModule).toBeDefined(); 8 | }); 9 | 10 | describe("forRoot", () => { 11 | cleanTestFiles(); 12 | 13 | it("should compile", async () => { 14 | const app = await Test.createTestingModule({ 15 | imports: [BullModule.forRoot({} as any)], 16 | }).compile(); 17 | expect(app).toBeDefined(); 18 | }); 19 | 20 | const compileModule = async ( 21 | filePath: string, 22 | bullModuleOptions: BullModuleOptions = { 23 | queues: [filePath], 24 | }, 25 | ): Promise => { 26 | const app = await Test.createTestingModule({ 27 | // eslint-disable-next-line @typescript-eslint/no-var-requires 28 | imports: [BullModule.forRoot(bullModuleOptions), require(filePath).TestModule], 29 | }).compile(); 30 | expect(app).toBeDefined(); 31 | await app.close(); 32 | }; 33 | 34 | it(`BullQueue don't set to providers`, async () => { 35 | await compileModule( 36 | createTestFile(` 37 | @BullQueue() 38 | export class TestClass { 39 | } 40 | @Module({ 41 | providers: [TestClass] 42 | }) 43 | export class TestModule {}`), 44 | ); 45 | }); 46 | 47 | it("BullQueue decorator only", async () => { 48 | await compileModule( 49 | createTestFile(` 50 | @BullQueue() 51 | export class TestClass { 52 | } 53 | @Module({ 54 | providers: [TestClass], 55 | }) 56 | export class TestModule {}`), 57 | ); 58 | }); 59 | 60 | it("BullQueueProcess", async () => { 61 | await compileModule( 62 | createTestFile(` 63 | @BullQueue() 64 | export class TestClass { 65 | @BullQueueProcess() 66 | public async process(job: any) { 67 | console.log("hello") 68 | } 69 | } 70 | @Module({ 71 | providers: [TestClass], 72 | }) 73 | export class TestModule {}`), 74 | ); 75 | }); 76 | 77 | it("BullQueueProcess has options", async () => { 78 | await compileModule( 79 | createTestFile(` 80 | @BullQueue() 81 | export class TestClass { 82 | @BullQueueProcess({ 83 | concurrency: 2 84 | }) 85 | public async process(job: any) { 86 | console.log("hello") 87 | } 88 | } 89 | @Module({ 90 | providers: [TestClass], 91 | }) 92 | export class TestModule {}`), 93 | ); 94 | }); 95 | 96 | it("BullQueue has extra options", async () => { 97 | await compileModule( 98 | createTestFile(` 99 | @BullQueue({ 100 | extra: { 101 | defaultProcessorOptions: { 102 | concurrency: 2, 103 | skip: true 104 | }, 105 | }, 106 | }) 107 | export class TestClass { 108 | @BullQueueProcess() 109 | public async process(job: any) { 110 | console.log("hello") 111 | } 112 | } 113 | @Module({ 114 | providers: [TestClass], 115 | }) 116 | export class TestModule {}`), 117 | ); 118 | }); 119 | 120 | it("BullModule has extra options", async () => { 121 | const filePath = createTestFile(` 122 | @BullQueue() 123 | export class TestClass { 124 | @BullQueueProcess() 125 | public async process(job: any) { 126 | console.log("hello") 127 | } 128 | } 129 | @Module({ 130 | providers: [TestClass], 131 | }) 132 | export class TestModule {}`); 133 | await compileModule(filePath, { 134 | queues: [filePath], 135 | extra: { 136 | defaultProcessorOptions: { 137 | concurrency: 2, 138 | skip: true, 139 | }, 140 | }, 141 | }); 142 | }); 143 | 144 | it("BullQueue has completed event", async () => { 145 | await compileModule( 146 | createTestFile(` 147 | @BullQueue() 148 | export class TestClass { 149 | @BullQueueEventCompleted() 150 | public async completed(job: any, result: any) { 151 | console.log('completed', job.id, result); 152 | } 153 | } 154 | @Module({ 155 | providers: [TestClass], 156 | }) 157 | export class TestModule {}`), 158 | ); 159 | }); 160 | 161 | it("BullQueue has 2 completed event", async () => { 162 | await compileModule( 163 | createTestFile(` 164 | @BullQueue() 165 | export class TestClass { 166 | @BullQueueEventCompleted() 167 | @BullQueueEventCompleted() 168 | public async completed(job: any, result: any) { 169 | console.log('completed', job.id, result); 170 | } 171 | 172 | @BullQueueEventCompleted() 173 | public async completed2(job: any, result: any) { 174 | console.log('completed', job.id, result); 175 | } 176 | } 177 | @Module({ 178 | providers: [TestClass], 179 | }) 180 | export class TestModule {}`), 181 | ); 182 | }); 183 | it("BullQueue has all events", async () => { 184 | await compileModule( 185 | createTestFile(` 186 | @BullQueue() 187 | export class TestClass { 188 | @BullQueueEventError() 189 | public async error(error: Error) { 190 | console.error('error', error); 191 | } 192 | @BullQueueEventWaiting() 193 | public async waiting(jobId: any) { 194 | console.log('wating', jobId); 195 | } 196 | @BullQueueEventActive() 197 | public async active(job: any, jobPromise: Promise) { 198 | console.log('active', job.id); 199 | } 200 | @BullQueueEventStalled() 201 | public async stalled(job: any) { 202 | console.log('stalled', job.id); 203 | } 204 | @BullQueueEventProgress() 205 | public async progress(job: any, progress: number) { 206 | console.log('progress', job.id, progress); 207 | } 208 | @BullQueueEventCompleted() 209 | public async completed(job: any, result: any) { 210 | console.log('completed', job.id, result); 211 | } 212 | @BullQueueEventFailed() 213 | public async failed(job: any, error: Error) { 214 | console.error('failed', job.id, error); 215 | } 216 | @BullQueueEventPaused() 217 | public async paused() { 218 | console.log('paused'); 219 | } 220 | @BullQueueEventResumed() 221 | public async resumed(job: any) { 222 | console.log('resumed', job.id); 223 | } 224 | @BullQueueEventCleaned() 225 | public async cleaned(job: any, type: string) { 226 | console.log('cleaned', job.id, type); 227 | } 228 | @BullQueueEventDrained() 229 | public async drained() { 230 | console.log('drained'); 231 | } 232 | @BullQueueEventRemoved() 233 | public async removed(job: any) { 234 | console.log('removed', job.id); 235 | } 236 | 237 | // global events 238 | 239 | @BullQueueEventGlobalError() 240 | public async globalError(error: Error) { 241 | console.error('global error', error); 242 | } 243 | @BullQueueEventGlobalWaiting() 244 | public async globalWaiting(jobId: any) { 245 | console.log('global wating', jobId); 246 | } 247 | @BullQueueEventGlobalActive() 248 | public async globalActive(jobId: any) { 249 | console.log('global active', jobId); 250 | } 251 | @BullQueueEventGlobalStalled() 252 | public async globalStalled(jobId: any) { 253 | console.log('global stalled', jobId); 254 | } 255 | @BullQueueEventGlobalProgress() 256 | public async globalProgress(jobId: any, progress: number) { 257 | console.log('global progress', jobId, progress); 258 | } 259 | @BullQueueEventGlobalCompleted() 260 | public async globalCompleted(jobId: any, result: any) { 261 | console.log('global completed', jobId, result); 262 | } 263 | @BullQueueEventGlobalFailed() 264 | public async globalFailed(jobId: any, error: Error) { 265 | console.error('global failed', jobId, error); 266 | } 267 | @BullQueueEventGlobalPaused() 268 | public async globalPaused() { 269 | console.log('global paused'); 270 | } 271 | @BullQueueEventGlobalResumed() 272 | public async globalResumed(jobId: any) { 273 | console.log('global resumed', jobId); 274 | } 275 | @BullQueueEventGlobalCleaned() 276 | public async globalCleaned(jobId: any, type: string) { 277 | console.log('global cleaned', jobId, type); 278 | } 279 | @BullQueueEventGlobalDrained() 280 | public async globalDrained() { 281 | console.log('global drained'); 282 | } 283 | @BullQueueEventGlobalRemoved() 284 | public async globalRemoved(jobId: any) { 285 | console.log('global removed', jobId); 286 | } 287 | } 288 | @Module({ 289 | providers: [TestClass], 290 | }) 291 | export class TestModule {}`), 292 | ); 293 | }); 294 | }); 295 | }); 296 | -------------------------------------------------------------------------------- /packages/bull/src/bull.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Module } from "@nestjs/common"; 2 | import { BullCoreModule } from "./bull-core.module"; 3 | import { BullModuleAsyncOptions, BullModuleOptions } from "./bull.interfaces"; 4 | 5 | @Global() 6 | @Module({}) 7 | export class BullModule { 8 | public static forRoot(options: BullModuleOptions): DynamicModule { 9 | return { 10 | module: BullModule, 11 | imports: [BullCoreModule.forRoot(options)], 12 | }; 13 | } 14 | 15 | public static forRootAsync(options: BullModuleAsyncOptions): DynamicModule { 16 | return { 17 | module: BullModule, 18 | imports: [BullCoreModule.forRootAsync(options)], 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/bull/src/bull.utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as glob from "fast-glob"; 2 | import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs"; 3 | import { tmpdir } from "os"; 4 | import { join, resolve } from "path"; 5 | import { ulid } from "ulid"; 6 | import { getBullQueueToken } from "./bull.utils"; 7 | 8 | export const tmpWorkspaceDir = resolve(tmpdir(), "nest-bull"); 9 | export const cleanTestFiles = (): void => { 10 | const unlinkFiles = (): void => 11 | glob.sync(`${tmpWorkspaceDir}/*.ts`).forEach((entry) => { 12 | unlinkSync(entry.toString()); 13 | }); 14 | 15 | beforeEach(() => { 16 | if (!existsSync(tmpWorkspaceDir)) { 17 | mkdirSync(tmpWorkspaceDir); 18 | } 19 | unlinkFiles(); 20 | }); 21 | 22 | afterEach(() => { 23 | unlinkFiles(); 24 | }); 25 | }; 26 | export const createTestFile = (...classText: string[]): string => { 27 | const filePath = join(tmpWorkspaceDir, `${ulid()}.ts`); 28 | const bullDecoratorPath = __dirname + "/bull.decorator"; 29 | const nodeModulesPath = __dirname + "/../../../node_modules"; 30 | writeFileSync( 31 | filePath, 32 | [ 33 | `import { Module } from '${nodeModulesPath}/@nestjs/common';`, 34 | // tslint:disable-next-line: max-line-length 35 | `import { BullQueue, BullQueueProcess, BullQueueEventError, BullQueueEventWaiting, BullQueueEventActive, BullQueueEventStalled, BullQueueEventProgress, BullQueueEventCompleted, BullQueueEventFailed, BullQueueEventPaused, BullQueueEventResumed, BullQueueEventCleaned, BullQueueEventDrained, BullQueueEventRemoved, BullQueueEventGlobalError, BullQueueEventGlobalWaiting, BullQueueEventGlobalActive, BullQueueEventGlobalStalled, BullQueueEventGlobalProgress, BullQueueEventGlobalCompleted, BullQueueEventGlobalFailed, BullQueueEventGlobalPaused, BullQueueEventGlobalResumed, BullQueueEventGlobalCleaned, BullQueueEventGlobalDrained, BullQueueEventGlobalRemoved } from '${bullDecoratorPath}';`, 36 | classText.join("\n"), 37 | ].join("\n"), 38 | ); 39 | 40 | require(filePath); 41 | 42 | return filePath; 43 | }; 44 | export const wait = async (timer: number): Promise => 45 | await new Promise( 46 | (resolve): NodeJS.Timeout => 47 | setTimeout((): void => { 48 | resolve(); 49 | }, timer), 50 | ); 51 | 52 | describe("BullUtil", () => { 53 | describe("getBullQueueToken", () => { 54 | it("should return queue token", () => { 55 | expect(getBullQueueToken("test")).toBe("_BullQueue_test"); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/bull/src/bull.utils.ts: -------------------------------------------------------------------------------- 1 | import { isSymbol } from "util"; 2 | import { BullName } from "./bull.interfaces"; 3 | 4 | export function getBullQueueToken(name: BullName): string | symbol { 5 | if (isSymbol(name)) { 6 | return name; 7 | } 8 | return `_BullQueue_${name}`; 9 | } 10 | -------------------------------------------------------------------------------- /packages/bull/src/examples/1.basic.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bull"; 4 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | 7 | @BullQueue() 8 | export class BasicExampleBullQueue { 9 | @BullQueueProcess() 10 | public async process(job: Job): Promise<{ status: string }> { 11 | expect(job.data).toStrictEqual({ test: "test" }); 12 | return { status: "ok" }; 13 | } 14 | } 15 | 16 | @Injectable() 17 | export class BasicExampleService { 18 | constructor(@BullQueueInject("BasicExampleBullQueue") public readonly queue: Queue) {} 19 | 20 | public async addJob(): Promise> { 21 | return this.queue.add({ test: "test" }); 22 | } 23 | } 24 | 25 | @Module({ 26 | providers: [BasicExampleBullQueue, BasicExampleService], 27 | }) 28 | export class BasicExampleModule {} 29 | 30 | @Module({ 31 | imports: [ 32 | BullModule.forRoot({ 33 | queues: [__filename], 34 | }), 35 | BasicExampleModule, 36 | ], 37 | }) 38 | export class ApplicationModule {} 39 | 40 | describe("1. Basic Example", () => { 41 | it("test", async () => { 42 | const app = await Test.createTestingModule({ 43 | imports: [ApplicationModule], 44 | }).compile(); 45 | await app.init(); 46 | const service = app.get(BasicExampleService); 47 | expect(service).toBeDefined(); 48 | expect(service.queue).toBeDefined(); 49 | const job = await service.addJob(); 50 | await expect(job.finished()).resolves.toStrictEqual({ status: "ok" }); 51 | await app.close(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/bull/src/examples/10.skip-processor.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Queue } from "bull"; 4 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { wait } from "../bull.utils.spec"; 7 | 8 | @BullQueue() 9 | export class SkipProcessorExampleBullQueue { 10 | @BullQueueProcess({ 11 | skip: true, 12 | }) 13 | public async process(): Promise<{ status: string }> { 14 | return { status: "not call" }; 15 | } 16 | } 17 | 18 | @Injectable() 19 | export class SkipProcessorExampleService { 20 | constructor( 21 | @BullQueueInject("SkipProcessorExampleBullQueue") 22 | public readonly queue: Queue, 23 | ) {} 24 | } 25 | 26 | @Module({ 27 | providers: [SkipProcessorExampleBullQueue, SkipProcessorExampleService], 28 | }) 29 | export class SkipProcessorExampleModule {} 30 | @Module({ 31 | imports: [ 32 | BullModule.forRoot({ 33 | queues: [__filename], 34 | extra: { 35 | defaultProcessorOptions: { 36 | skip: true, 37 | }, 38 | }, 39 | }), 40 | SkipProcessorExampleModule, 41 | ], 42 | }) 43 | export class ApplicationModule {} 44 | 45 | describe("10. Skip Processor Example", () => { 46 | it("test", async () => { 47 | const app = await Test.createTestingModule({ 48 | imports: [ApplicationModule], 49 | }).compile(); 50 | await app.init(); 51 | const service = app.get(SkipProcessorExampleService); 52 | expect(service).toBeDefined(); 53 | expect(service.queue).toBeDefined(); 54 | const job = await service.queue.add({ data: "test" }); 55 | 56 | await wait(2000); 57 | 58 | await expect(job.getState()).resolves.toBe("waiting"); 59 | 60 | await app.close(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/bull/src/examples/2.event.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bull"; 4 | import { BullQueue, BullQueueEventCompleted, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { wait } from "../bull.utils.spec"; 7 | 8 | @BullQueue() 9 | export class EventExampleBullQueue { 10 | public called = false; 11 | 12 | @BullQueueProcess() 13 | public async process(job: Job): Promise<{ status: string }> { 14 | expect(job.data).toStrictEqual({ test: "test" }); 15 | return { status: "ok" }; 16 | } 17 | 18 | @BullQueueEventCompleted() 19 | public completed(): void { 20 | this.called = true; 21 | } 22 | } 23 | 24 | @Injectable() 25 | export class EventExampleService { 26 | constructor( 27 | @BullQueueInject("EventExampleBullQueue") 28 | public readonly queue: Queue, 29 | ) {} 30 | 31 | public async addJob(): Promise> { 32 | return this.queue.add({ test: "test" }); 33 | } 34 | } 35 | 36 | @Module({ 37 | providers: [EventExampleBullQueue, EventExampleService], 38 | }) 39 | export class EventExampleModule {} 40 | 41 | @Module({ 42 | imports: [ 43 | BullModule.forRoot({ 44 | queues: [__filename], 45 | }), 46 | EventExampleModule, 47 | ], 48 | }) 49 | export class ApplicationModule {} 50 | 51 | describe("2. Event Example", () => { 52 | it("test", async () => { 53 | const app = await Test.createTestingModule({ 54 | imports: [ApplicationModule], 55 | }).compile(); 56 | 57 | await app.init(); 58 | 59 | const queueClass = app.get(EventExampleBullQueue); 60 | expect(queueClass.called).toBeFalsy(); 61 | const service = app.get(EventExampleService); 62 | expect(service).toBeDefined(); 63 | expect(service.queue).toBeDefined(); 64 | const job = await service.addJob(); 65 | await expect(job.finished()).resolves.toStrictEqual({ status: "ok" }); 66 | await wait(100); 67 | expect(queueClass.called).toBeTruthy(); 68 | await app.close(); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/bull/src/examples/3.extra-job-options.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { ModuleMetadata } from "@nestjs/common/interfaces"; 3 | import { Test, TestingModule } from "@nestjs/testing"; 4 | import { Job, Queue } from "bull"; 5 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 6 | import { BullJob } from "../bull.interfaces"; 7 | import { BullModule } from "../bull.module"; 8 | 9 | @BullQueue({ 10 | extra: { 11 | defaultJobOptions: { 12 | setTTLOnComplete: 10, 13 | }, 14 | }, 15 | }) 16 | export class ExtraJobOptionsBullQueue { 17 | public called = false; 18 | 19 | @BullQueueProcess() 20 | public async process(job: Job): Promise<{ status: string }> { 21 | const { throwError } = job.data; 22 | if (throwError) { 23 | throw new Error("error"); 24 | } 25 | return { status: "ok" }; 26 | } 27 | } 28 | 29 | @Injectable() 30 | export class ExtraJobOptionsService { 31 | constructor( 32 | @BullQueueInject("ExtraJobOptionsBullQueue") 33 | public readonly queue: Queue, 34 | ) {} 35 | 36 | public async addJob(throwError: boolean): Promise> { 37 | return this.queue.add({ throwError }); 38 | } 39 | } 40 | 41 | @Module({ 42 | providers: [ExtraJobOptionsBullQueue, ExtraJobOptionsService], 43 | }) 44 | export class ExtraJobOptionsModule {} 45 | 46 | @Module({ 47 | imports: [ 48 | BullModule.forRoot({ 49 | queues: [__filename], 50 | extra: { 51 | defaultJobOptions: { 52 | setTTLOnFail: 10, 53 | }, 54 | defaultProcessorOptions: { 55 | concurrency: 2, 56 | }, 57 | }, 58 | }), 59 | ExtraJobOptionsModule, 60 | ], 61 | }) 62 | export class ApplicationModule {} 63 | 64 | describe("3. Extra Job Options", () => { 65 | const compileModule = async (metadata: ModuleMetadata): Promise => { 66 | return Test.createTestingModule(metadata).compile(); 67 | }; 68 | const getTTL = async (job: BullJob): Promise => { 69 | return job.queue.clients[0].ttl(`${job.toKey()}${job.id}`); 70 | }; 71 | 72 | it("should set ttl to 10", async () => { 73 | const app = await compileModule({ 74 | imports: [ApplicationModule], 75 | }); 76 | await app.init(); 77 | const service = app.get(ExtraJobOptionsService); 78 | const completedJob = (await service.addJob(false)) as BullJob; 79 | const failedJob = (await service.addJob(true)) as BullJob; 80 | await expect(completedJob.finished()).resolves.toStrictEqual({ 81 | status: "ok", 82 | }); 83 | await expect(failedJob.finished()).rejects.toThrowError("error"); 84 | await new Promise((resolve): void => { 85 | const interval = setInterval(async (): Promise => { 86 | if ((await getTTL(completedJob)) !== -1 && (await getTTL(failedJob)) !== -1) { 87 | clearInterval(interval); 88 | resolve(); 89 | } 90 | }, 10); 91 | }); 92 | 93 | const completedTTL = await getTTL(completedJob); 94 | expect(completedTTL).toBeGreaterThan(0); 95 | expect(completedTTL).toBeLessThanOrEqual(10); 96 | 97 | const failedTTL = await getTTL(failedJob); 98 | expect(failedTTL).toBeGreaterThan(0); 99 | expect(failedTTL).toBeLessThanOrEqual(10); 100 | await app.close(); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /packages/bull/src/examples/4.multiple-processor.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bull"; 4 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | 7 | @BullQueue() 8 | export class MultipleProcessorExampleBullQueue { 9 | @BullQueueProcess() 10 | public async process1(job: Job): Promise<{ status: string }> { 11 | expect(job.data).toStrictEqual({ test: "process1 test" }); 12 | return { status: "process1 ok" }; 13 | } 14 | 15 | @BullQueueProcess() 16 | public async process2(job: Job): Promise<{ status: string }> { 17 | expect(job.data).toStrictEqual({ test: "process2 test" }); 18 | return { status: "process2 ok" }; 19 | } 20 | } 21 | 22 | @Injectable() 23 | export class MultipleProcessorExampleService { 24 | constructor( 25 | @BullQueueInject("MultipleProcessorExampleBullQueue") 26 | public readonly queue: Queue, 27 | ) {} 28 | 29 | public async addJobs(): Promise { 30 | return Promise.all([ 31 | this.queue.add("process1", { test: "process1 test" }), 32 | this.queue.add("process2", { test: "process2 test" }), 33 | ]); 34 | } 35 | } 36 | 37 | @Module({ 38 | providers: [MultipleProcessorExampleBullQueue, MultipleProcessorExampleService], 39 | }) 40 | export class MultipleProcessorExampleModule {} 41 | 42 | @Module({ 43 | imports: [ 44 | BullModule.forRoot({ 45 | queues: [__filename], 46 | }), 47 | MultipleProcessorExampleModule, 48 | ], 49 | }) 50 | export class ApplicationModule {} 51 | 52 | describe("4. Multiple Processor Example", () => { 53 | it("test", async () => { 54 | const app = await Test.createTestingModule({ 55 | imports: [ApplicationModule], 56 | }).compile(); 57 | await app.init(); 58 | const service = app.get(MultipleProcessorExampleService); 59 | expect(service).toBeDefined(); 60 | expect(service.queue).toBeDefined(); 61 | const jobs = await service.addJobs(); 62 | expect(jobs).toHaveLength(2); 63 | await expect(jobs[0].finished()).resolves.toStrictEqual({ 64 | status: "process1 ok", 65 | }); 66 | 67 | await expect(jobs[1].finished()).resolves.toStrictEqual({ 68 | status: "process2 ok", 69 | }); 70 | await app.close(); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/bull/src/examples/5.parent-child-queue.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bull"; 4 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | 7 | @BullQueue() 8 | export class ParentQueue { 9 | constructor( 10 | @BullQueueInject("Child1Queue") 11 | private readonly child1Queue: Queue, 12 | @BullQueueInject("Child2Queue") 13 | private readonly child2Queue: Queue, 14 | ) {} 15 | 16 | @BullQueueProcess() 17 | public async process(job: Job): Promise<{ child1: string; child2: string }> { 18 | const child1Job = await this.child1Queue.add(job.data); 19 | const child2Job = await this.child2Queue.add(job.data); 20 | return { ...(await child1Job.finished()), ...(await child2Job.finished()) }; 21 | } 22 | } 23 | 24 | @BullQueue() 25 | export class Child1Queue { 26 | @BullQueueProcess() 27 | public async process(job: Job): Promise<{ child1: string }> { 28 | expect(job.data).toStrictEqual({ test: "test" }); 29 | return { child1: "ok" }; 30 | } 31 | } 32 | 33 | @BullQueue() 34 | export class Child2Queue { 35 | @BullQueueProcess() 36 | public async process(job: Job): Promise<{ child2: string }> { 37 | expect(job.data).toStrictEqual({ test: "test" }); 38 | return { child2: "ok" }; 39 | } 40 | } 41 | 42 | @Injectable() 43 | export class ParentChildQueueExampleService { 44 | constructor( 45 | @BullQueueInject("ParentQueue") 46 | public readonly queue: Queue, 47 | ) {} 48 | 49 | public async addJob(): Promise> { 50 | return this.queue.add({ test: "test" }); 51 | } 52 | } 53 | 54 | @Module({ 55 | providers: [ParentQueue, Child1Queue, Child2Queue, ParentChildQueueExampleService], 56 | }) 57 | export class ParentChildQueueExampleModule {} 58 | 59 | @Module({ 60 | imports: [ 61 | BullModule.forRoot({ 62 | queues: [__filename], 63 | }), 64 | ParentChildQueueExampleModule, 65 | ], 66 | }) 67 | export class ApplicationModule {} 68 | 69 | describe("5. Parent Child Example", () => { 70 | it("test", async () => { 71 | const app = await Test.createTestingModule({ 72 | imports: [ApplicationModule], 73 | }).compile(); 74 | await app.init(); 75 | const service = app.get(ParentChildQueueExampleService); 76 | expect(service).toBeDefined(); 77 | expect(service.queue).toBeDefined(); 78 | const job = await service.addJob(); 79 | await expect(job.finished()).resolves.toStrictEqual({ 80 | child1: "ok", 81 | child2: "ok", 82 | }); 83 | await app.close(); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /packages/bull/src/examples/6.symbol.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bull"; 4 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | 7 | const QUEUE_NAME = Symbol("QUEUE_NAME"); 8 | 9 | @BullQueue({ 10 | name: QUEUE_NAME, 11 | }) 12 | export class SymbolExampleBullQueue { 13 | @BullQueueProcess() 14 | public async process(job: Job): Promise<{ status: string }> { 15 | expect(job.data).toStrictEqual({ test: "test" }); 16 | return { status: "ok" }; 17 | } 18 | } 19 | 20 | @Injectable() 21 | export class SymbolExampleService { 22 | constructor(@BullQueueInject(QUEUE_NAME) public readonly queue: Queue) {} 23 | 24 | public async addJob(): Promise> { 25 | return this.queue.add({ test: "test" }); 26 | } 27 | } 28 | 29 | @Module({ 30 | providers: [SymbolExampleBullQueue, SymbolExampleService], 31 | }) 32 | export class SymbolExampleModule {} 33 | 34 | @Module({ 35 | imports: [ 36 | BullModule.forRoot({ 37 | queues: [__filename], 38 | }), 39 | SymbolExampleModule, 40 | ], 41 | }) 42 | export class ApplicationModule {} 43 | 44 | describe("6. Symbol Example", () => { 45 | it("test", async () => { 46 | const app = await Test.createTestingModule({ 47 | imports: [ApplicationModule], 48 | }).compile(); 49 | await app.init(); 50 | const service = app.get(SymbolExampleService); 51 | expect(service).toBeDefined(); 52 | expect(service.queue).toBeDefined(); 53 | const job = await service.addJob(); 54 | await expect(job.finished()).resolves.toStrictEqual({ status: "ok" }); 55 | await app.close(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/bull/src/examples/7.using-bull-queue-service.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job } from "bull"; 4 | import { BULL_MODULE_SERVICE } from "../bull.constants"; 5 | import { BullQueue, BullQueueProcess } from "../bull.decorator"; 6 | import { BullModule } from "../bull.module"; 7 | import { BullService } from "../services/bull.service"; 8 | 9 | @BullQueue() 10 | export class UsingBullQueueServiceExampleBullQueue { 11 | @BullQueueProcess() 12 | public async process(job: Job): Promise<{ status: string }> { 13 | expect(job.data).toStrictEqual({ test: "test" }); 14 | return { status: "ok" }; 15 | } 16 | } 17 | 18 | @Injectable() 19 | export class UsingBullQueueServiceExampleService { 20 | constructor(@Inject(BULL_MODULE_SERVICE) public bullService: BullService) {} 21 | 22 | public async addJob(): Promise> { 23 | const queue = this.bullService.getQueue("UsingBullQueueServiceExampleBullQueue"); 24 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 25 | return queue!.add({ test: "test" }); 26 | } 27 | } 28 | 29 | @Module({ 30 | providers: [UsingBullQueueServiceExampleBullQueue, UsingBullQueueServiceExampleService], 31 | }) 32 | export class UsingBullQueueServiceExampleModule {} 33 | 34 | @Module({ 35 | imports: [ 36 | BullModule.forRoot({ 37 | queues: [__filename], 38 | }), 39 | UsingBullQueueServiceExampleModule, 40 | ], 41 | }) 42 | export class ApplicationModule {} 43 | 44 | describe("7. Using BullQueueService Example", () => { 45 | it("test", async () => { 46 | const app = await Test.createTestingModule({ 47 | imports: [ApplicationModule], 48 | }).compile(); 49 | await app.init(); 50 | const service = app.get(UsingBullQueueServiceExampleService); 51 | expect(service).toBeDefined(); 52 | expect(service.bullService).toBeDefined(); 53 | const job = await service.addJob(); 54 | await expect(job.finished()).resolves.toStrictEqual({ status: "ok" }); 55 | await app.close(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/bull/src/examples/8.async-basic.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job } from "bull"; 4 | import { BULL_MODULE_SERVICE } from "../bull.constants"; 5 | import { BullQueue, BullQueueProcess } from "../bull.decorator"; 6 | import { BullModule } from "../bull.module"; 7 | import { BullService } from "../services/bull.service"; 8 | 9 | @BullQueue() 10 | export class AsyncBasicExampleBullQueue { 11 | @BullQueueProcess() 12 | public async process(job: Job): Promise<{ status: string }> { 13 | expect(job.data).toStrictEqual({ test: "test" }); 14 | return { status: "ok" }; 15 | } 16 | } 17 | 18 | @Injectable() 19 | export class AsyncBasicExampleService { 20 | constructor(@Inject(BULL_MODULE_SERVICE) public readonly service: BullService) {} 21 | 22 | public async addJob(): Promise> { 23 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 24 | return this.service.getQueue("AsyncBasicExampleBullQueue")!.add({ test: "test" }); 25 | } 26 | } 27 | 28 | @Module({ 29 | providers: [AsyncBasicExampleBullQueue, AsyncBasicExampleService], 30 | }) 31 | export class AsyncBasicExampleModule {} 32 | 33 | @Module({ 34 | imports: [ 35 | BullModule.forRootAsync({ 36 | useFactory: () => ({ 37 | queues: [__filename], 38 | }), 39 | }), 40 | AsyncBasicExampleModule, 41 | ], 42 | }) 43 | export class ApplicationModule {} 44 | 45 | describe("8. Async Basic Example", () => { 46 | it("test", async () => { 47 | const app = await Test.createTestingModule({ 48 | imports: [ApplicationModule], 49 | }).compile(); 50 | await app.init(); 51 | const service = app.get(AsyncBasicExampleService); 52 | expect(service).toBeDefined(); 53 | expect(service.service).toBeDefined(); 54 | const job = await service.addJob(); 55 | await expect(job.finished()).resolves.toStrictEqual({ status: "ok" }); 56 | await app.close(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/bull/src/examples/9.mock.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Queue } from "bull"; 4 | import { BullQueue, BullQueueInject, BullQueueProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { wait } from "../bull.utils.spec"; 7 | 8 | @BullQueue() 9 | export class MockExampleBullQueue { 10 | @BullQueueProcess() 11 | public async process(): Promise<{ status: string }> { 12 | return { status: "not call" }; 13 | } 14 | } 15 | 16 | @Injectable() 17 | export class MockExampleService { 18 | constructor(@BullQueueInject("MockExampleBullQueue") public readonly queue: Queue) {} 19 | } 20 | 21 | @Module({ 22 | providers: [MockExampleBullQueue, MockExampleService], 23 | }) 24 | export class MockExampleModule {} 25 | @Module({ 26 | imports: [ 27 | BullModule.forRoot({ 28 | queues: [__filename], 29 | extra: { 30 | defaultJobOptions: { 31 | setTTLOnComplete: 10, 32 | }, 33 | }, 34 | mock: true, 35 | }), 36 | MockExampleModule, 37 | ], 38 | }) 39 | export class ApplicationModule {} 40 | 41 | describe("9. Mock Example", () => { 42 | it("test", async () => { 43 | const app = await Test.createTestingModule({ 44 | imports: [ApplicationModule], 45 | }).compile(); 46 | await app.init(); 47 | const service = app.get(MockExampleService); 48 | expect(service).toBeDefined(); 49 | expect(service.queue).toBeDefined(); 50 | const job = await service.queue.add({ data: "test" }); 51 | expect(job).toStrictEqual({ data: "test" }); 52 | await wait(100); 53 | await app.close(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/bull/src/index.ts: -------------------------------------------------------------------------------- 1 | export { BULL_MODULE_SERVICE } from "./bull.constants"; 2 | export { 3 | BullQueue, 4 | BullQueueEventActive, 5 | BullQueueEventCleaned, 6 | BullQueueEventCompleted, 7 | BullQueueEventDrained, 8 | BullQueueEventError, 9 | BullQueueEventFailed, 10 | BullQueueEventGlobalActive, 11 | BullQueueEventGlobalCleaned, 12 | BullQueueEventGlobalCompleted, 13 | BullQueueEventGlobalDrained, 14 | BullQueueEventGlobalError, 15 | BullQueueEventGlobalFailed, 16 | BullQueueEventGlobalPaused, 17 | BullQueueEventGlobalProgress, 18 | BullQueueEventGlobalRemoved, 19 | BullQueueEventGlobalResumed, 20 | BullQueueEventGlobalStalled, 21 | BullQueueEventGlobalWaiting, 22 | BullQueueEventPaused, 23 | BullQueueEventProgress, 24 | BullQueueEventRemoved, 25 | BullQueueEventResumed, 26 | BullQueueEventStalled, 27 | BullQueueEventWaiting, 28 | BullQueueInject, 29 | BullQueueProcess, 30 | } from "./bull.decorator"; 31 | export { 32 | BullModuleAsyncOptions, 33 | BullModuleOptions, 34 | BullModuleOptionsFactory, 35 | BullQueueDefaultJobOptions, 36 | BullQueueDefaultProcessorOptions, 37 | BullQueueExtraOptions, 38 | BullQueueOptions, 39 | BullQueueProcessorOptions, 40 | } from "./bull.interfaces"; 41 | export { BullModule } from "./bull.module"; 42 | export { getBullQueueToken } from "./bull.utils"; 43 | export { BullService } from "./services/bull.service"; 44 | -------------------------------------------------------------------------------- /packages/bull/src/services/bull-queue-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from "@nestjs/testing"; 2 | import { BULL_MODULE_OPTIONS } from "../bull.constants"; 3 | import { BullQueue } from "../bull.decorator"; 4 | import { BullModuleOptions } from "../bull.interfaces"; 5 | import { getBullQueueToken } from "../bull.utils"; 6 | import { cleanTestFiles, createTestFile, tmpWorkspaceDir } from "../bull.utils.spec"; 7 | import { BullQueueProviderService } from "./bull-queue-provider.service"; 8 | import { BullService } from "./bull.service"; 9 | describe("BullQueueProviderService", () => { 10 | const compileService = async (options: BullModuleOptions): Promise => { 11 | const app = await Test.createTestingModule({ 12 | providers: [ 13 | { 14 | provide: BULL_MODULE_OPTIONS, 15 | useValue: options, 16 | }, 17 | ], 18 | }).compile(); 19 | 20 | expect(app.get(BULL_MODULE_OPTIONS)).toStrictEqual(options); 21 | await app.close(); 22 | return new BullQueueProviderService(options, new BullService()); 23 | }; 24 | 25 | it("should be defined", async () => { 26 | expect(BullQueueProviderService).toBeDefined(); 27 | }); 28 | 29 | describe("createBullQueueProviders", () => { 30 | cleanTestFiles(); 31 | 32 | it("should get Test class", async () => { 33 | @BullQueue() 34 | class Test {} 35 | 36 | const service = await compileService({ 37 | queues: [Test], 38 | }); 39 | const providers = service.createBullQueueProviders(); 40 | expect(providers).toHaveLength(1); 41 | expect(providers).toMatchObject([ 42 | { 43 | provide: getBullQueueToken("Test"), 44 | useValue: { 45 | name: "Test", 46 | }, 47 | }, 48 | ]); 49 | }); 50 | it("should get HasBullQueueDecorator class only", async () => { 51 | @BullQueue() 52 | class HasBullQueueDecorator {} 53 | 54 | class Test {} 55 | 56 | const service = await compileService({ 57 | queues: [HasBullQueueDecorator, Test], 58 | }); 59 | const providers = service.createBullQueueProviders(); 60 | expect(providers).toHaveLength(1); 61 | expect(providers).toMatchObject([ 62 | { 63 | provide: getBullQueueToken("HasBullQueueDecorator"), 64 | useValue: { 65 | name: "HasBullQueueDecorator", 66 | }, 67 | }, 68 | ]); 69 | }); 70 | it("should get Test class by file path", async () => { 71 | const filePath = createTestFile(["@BullQueue()", `export class Test {}`].join("\n")); 72 | 73 | const service = await compileService({ queues: [filePath] }); 74 | const providers = service.createBullQueueProviders(); 75 | expect(providers).toHaveLength(1); 76 | expect(providers).toMatchObject([ 77 | { 78 | provide: getBullQueueToken("Test"), 79 | useValue: { 80 | name: "Test", 81 | }, 82 | }, 83 | ]); 84 | }); 85 | it("should get Test1 class only by file path", async () => { 86 | const filePath = createTestFile( 87 | ["@BullQueue()", `export class Test1 {}`].join("\n"), 88 | [`export class Test2 {}`].join("\n"), 89 | ); 90 | 91 | const service = await compileService({ 92 | queues: [filePath], 93 | }); 94 | const providers = service.createBullQueueProviders(); 95 | expect(providers).toHaveLength(1); 96 | expect(providers).toMatchObject([ 97 | { 98 | provide: getBullQueueToken("Test1"), 99 | useValue: { 100 | name: "Test1", 101 | }, 102 | }, 103 | ]); 104 | }); 105 | it("should get Test1 and Test2 class by file path", async () => { 106 | const filePath = createTestFile( 107 | ["@BullQueue()", `export class Test1 {}`].join("\n"), 108 | ["@BullQueue()", `export class Test2 {}`].join("\n"), 109 | ); 110 | 111 | const service = await compileService({ 112 | queues: [filePath], 113 | }); 114 | const providers = service.createBullQueueProviders(); 115 | expect(providers).toHaveLength(2); 116 | expect(providers).toMatchObject([ 117 | { 118 | provide: getBullQueueToken("Test1"), 119 | useValue: { 120 | name: "Test1", 121 | }, 122 | }, 123 | { 124 | provide: getBullQueueToken("Test2"), 125 | useValue: { 126 | name: "Test2", 127 | }, 128 | }, 129 | ]); 130 | }); 131 | it("should get Test1 and Test2 class by file paths", async () => { 132 | const filePath1 = createTestFile(["@BullQueue()", `export class Test1 {}`].join("\n")); 133 | const filePath2 = createTestFile(["@BullQueue()", `export class Test2 {}`].join("\n")); 134 | 135 | const service = await compileService({ 136 | queues: [filePath1, filePath2], 137 | }); 138 | const providers = service.createBullQueueProviders(); 139 | expect(providers).toHaveLength(2); 140 | expect(providers).toMatchObject([ 141 | { 142 | provide: getBullQueueToken("Test1"), 143 | useValue: { 144 | name: "Test1", 145 | }, 146 | }, 147 | { 148 | provide: getBullQueueToken("Test2"), 149 | useValue: { 150 | name: "Test2", 151 | }, 152 | }, 153 | ]); 154 | }); 155 | it("should get Test1 and Test2 class by file glob path", async () => { 156 | createTestFile(["@BullQueue()", `export class Test1 {}`].join("\n")); 157 | createTestFile(["@BullQueue()", `export class Test2 {}`].join("\n")); 158 | 159 | const service = await compileService({ 160 | queues: [`${tmpWorkspaceDir}/*.ts`], 161 | }); 162 | const providers = service 163 | .createBullQueueProviders() 164 | .sort((x, y) => (x.provide as string).localeCompare(y.provide as string)); 165 | 166 | expect(providers).toHaveLength(2); 167 | expect(providers).toMatchObject([ 168 | { 169 | provide: getBullQueueToken("Test1"), 170 | useValue: { 171 | name: "Test1", 172 | }, 173 | }, 174 | { 175 | provide: getBullQueueToken("Test2"), 176 | useValue: { 177 | name: "Test2", 178 | }, 179 | }, 180 | ]); 181 | }); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /packages/bull/src/services/bull-queue-provider.service.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { flatten, Injectable, Logger } from "@nestjs/common"; 3 | import { ValueProvider } from "@nestjs/common/interfaces"; 4 | import * as Bull from "bull"; 5 | import * as deepmerge from "deepmerge"; 6 | import * as glob from "fast-glob"; 7 | import { BULL_MODULE, BULL_QUEUE_DECORATOR } from "../bull.constants"; 8 | import { BullJob, BullModuleOptions, BullQueue, BullQueueOptions, BullQueueType } from "../bull.interfaces"; 9 | import { getBullQueueToken } from "../bull.utils"; 10 | import { BullService } from "./bull.service"; 11 | 12 | @Injectable() 13 | export class BullQueueProviderService { 14 | private readonly logger = new Logger(BULL_MODULE, { timestamp: true }); 15 | 16 | constructor(private readonly bullModuleOptions: BullModuleOptions, private readonly bullService: BullService) {} 17 | 18 | public createBullQueueProviders(): ValueProvider[] { 19 | const providers: ValueProvider[] = []; 20 | if (!Array.isArray(this.bullModuleOptions.queues)) { 21 | return []; 22 | } 23 | for (const queueTarget of this.bullModuleOptions.queues) { 24 | const targets = this.getBullQueueTargets(queueTarget); 25 | for (const target of targets) { 26 | const options = this.getBullQueueOptions(target) as BullQueueOptions; 27 | const queue = this.createQueue(target, options); 28 | 29 | this.createExtraJobEvents(queue, options); 30 | this.logger.log(`${queue.name} queue initialized`); 31 | const token = getBullQueueToken(options.name!); 32 | providers.push({ 33 | provide: token, 34 | useValue: queue, 35 | }); 36 | this.bullService.addQueue(token, queue); 37 | } 38 | } 39 | return providers; 40 | } 41 | 42 | private async setJobExpire(job: BullJob, expire: number): Promise { 43 | try { 44 | const jobKey = `${job.toKey()}${job.id}`; 45 | const client = job.queue.clients[0]; 46 | await client.expire(jobKey, expire); 47 | } catch (e: any) { 48 | this.logger.error(e.message, e.stack); 49 | } 50 | } 51 | 52 | private createExtraJobEvents(queue: BullQueue, options: BullQueueOptions): void { 53 | if (!(options.extra && options.extra.defaultJobOptions)) { 54 | return; 55 | } 56 | 57 | const { setTTLOnComplete, setTTLOnFail } = options.extra.defaultJobOptions; 58 | if (setTTLOnComplete !== undefined && setTTLOnComplete !== null && setTTLOnComplete > -1) { 59 | queue.on("completed", (job: BullJob) => { 60 | this.setJobExpire(job, setTTLOnComplete); 61 | }); 62 | } 63 | 64 | if (setTTLOnFail !== undefined && setTTLOnFail !== null && setTTLOnFail > -1) { 65 | queue.on("failed", (job: BullJob) => this.setJobExpire(job, setTTLOnFail)); 66 | } 67 | } 68 | 69 | private createQueue(target: any, options: BullQueueOptions): BullQueue { 70 | if (this.bullModuleOptions.mock) { 71 | return { 72 | name: String(options.name), 73 | add: (args: any) => Promise.resolve(args), 74 | isReady: () => Promise.resolve(true), 75 | close: () => Promise.resolve(), 76 | process: () => Promise.resolve(), 77 | // eslint-disable-next-line @typescript-eslint/no-empty-function 78 | on: () => {}, 79 | } as any as BullQueue; 80 | } 81 | 82 | return new Bull(String(options.name), options.options) as BullQueue; 83 | } 84 | 85 | private getBullQueueOptions(target: any): BullQueueOptions { 86 | const targetOptions = Reflect.getMetadata(BULL_QUEUE_DECORATOR, target) as BullQueueOptions; 87 | 88 | return deepmerge.all( 89 | [ 90 | { 91 | options: this.bullModuleOptions.options, 92 | extra: this.bullModuleOptions.extra, 93 | }, 94 | targetOptions, 95 | ], 96 | {}, 97 | ) as BullQueueOptions; 98 | } 99 | 100 | private getBullQueueTargets(queue: BullQueueType): object[] { 101 | const targets: object[] = []; 102 | if (typeof queue === "function" && this.hasBullQueueDecorator(queue)) { 103 | targets.push(queue); 104 | } else if (typeof queue === "string") { 105 | const queues = this.loadQueues(queue); 106 | 107 | targets.push(...queues.filter((q) => this.hasBullQueueDecorator(q))); 108 | } 109 | return targets; 110 | } 111 | 112 | private hasBullQueueDecorator(target: any): boolean { 113 | try { 114 | return Reflect.hasMetadata(BULL_QUEUE_DECORATOR, target); 115 | } catch { 116 | return false; 117 | } 118 | } 119 | 120 | private loadQueues(filePath: string): any[] { 121 | return flatten( 122 | glob 123 | .sync(filePath) 124 | // eslint-disable-next-line @typescript-eslint/no-var-requires 125 | .map((entry) => require(entry.toString()) as { [name: string]: any }) 126 | .map((x) => Object.values(x)), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /packages/bull/src/services/bull.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { BullName, BullQueue } from "../bull.interfaces"; 3 | import { getBullQueueToken } from "../bull.utils"; 4 | 5 | @Injectable() 6 | export class BullService { 7 | constructor(public isAsync: boolean = false) {} 8 | 9 | private queues: Map = new Map(); 10 | 11 | public getQueue(token: BullName): BullQueue | undefined { 12 | return this.queues.get(getBullQueueToken(token)); 13 | } 14 | 15 | public addQueue(token: BullName, queue: BullQueue): void { 16 | this.queues.set(token, queue); 17 | } 18 | 19 | public async closeAll(): Promise { 20 | for (const queue of this.queues.values()) { 21 | await queue.close(); 22 | } 23 | this.queues.clear(); 24 | } 25 | 26 | public async isReady(): Promise { 27 | for (const queue of this.queues.values()) { 28 | await queue.isReady(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/bull/src/services/explorers/base-explorer.service.ts: -------------------------------------------------------------------------------- 1 | import { flatten, Logger } from "@nestjs/common"; 2 | import { Injectable, Type } from "@nestjs/common/interfaces"; 3 | import { ModulesContainer } from "@nestjs/core"; 4 | import { InstanceWrapper } from "@nestjs/core/injector/instance-wrapper"; 5 | import { Module } from "@nestjs/core/injector/module"; 6 | import { MetadataScanner } from "@nestjs/core/metadata-scanner"; 7 | import { BullCoreModule } from "../../bull-core.module"; 8 | import { BULL_MODULE, BULL_QUEUE_DECORATOR } from "../../bull.constants"; 9 | import { BullModuleOptions, BullQueue, BullQueueOptions } from "../../bull.interfaces"; 10 | import { BullService } from "../bull.service"; 11 | 12 | export abstract class BaseExplorerService { 13 | protected readonly logger = new Logger(BULL_MODULE, { timestamp: true }); 14 | 15 | constructor( 16 | protected readonly options: BullModuleOptions, 17 | protected readonly bullService: BullService, 18 | protected readonly modulesContainer: ModulesContainer, 19 | protected readonly metadataScanner: MetadataScanner, 20 | ) {} 21 | 22 | protected getAllModules(): Module[] { 23 | return [...this.modulesContainer.values()]; 24 | } 25 | 26 | public explore(): void { 27 | const modules = this.getAllModules(); 28 | 29 | this.getComponents(modules).forEach((component) => { 30 | for (const wrapper of component.values()) { 31 | if (wrapper.isNotMetatype || !this.hasBullQueueDecorator(wrapper.metatype)) { 32 | continue; 33 | } 34 | 35 | const metadata = Reflect.getMetadata(BULL_QUEUE_DECORATOR, wrapper.metatype) as Required; 36 | 37 | const bullQueue = this.bullService.getQueue(metadata.name) as Required; 38 | if (bullQueue) { 39 | this.onBullQueueProcess(bullQueue, wrapper); 40 | } 41 | } 42 | }); 43 | } 44 | 45 | private getComponents(modules: Module[]): Array>> { 46 | return flatten( 47 | modules.filter((module) => module.metatype !== BullCoreModule).map((module) => module.providers), 48 | ) as unknown as Array>>; 49 | } 50 | 51 | private hasBullQueueDecorator(metatype: Function | Type): boolean { 52 | return Reflect.hasMetadata(BULL_QUEUE_DECORATOR, metatype); 53 | } 54 | 55 | protected onBullQueueProcess(bullQueue: BullQueue, wrapper: InstanceWrapper): void { 56 | const { instance } = wrapper; 57 | const prototype = Object.getPrototypeOf(instance); 58 | const propertyNames = this.metadataScanner 59 | .scanFromPrototype(instance, prototype, (propertyName) => { 60 | if (this.verifyPropertyName(prototype, propertyName)) { 61 | return propertyName; 62 | } 63 | }) 64 | .filter((x) => x) as string[]; 65 | 66 | propertyNames.forEach((propertyName) => 67 | this.onBullQueuePropertyProcess(bullQueue, instance, prototype, propertyName, propertyNames), 68 | ); 69 | } 70 | 71 | protected abstract onBullQueuePropertyProcess( 72 | bullQueue: BullQueue, 73 | instance: Injectable, 74 | prototype: any, 75 | propertyName: string, 76 | allPropertyNames?: string[], 77 | ): void; 78 | 79 | protected abstract verifyPropertyName(target: any, propertyName: string): boolean; 80 | 81 | protected abstract getOptions(prototype: any, propertyName: string): Options; 82 | } 83 | -------------------------------------------------------------------------------- /packages/bull/src/services/explorers/event-explorer.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable as InjectableDecorator } from "@nestjs/common"; 2 | import { Injectable } from "@nestjs/common/interfaces"; 3 | import { ModulesContainer } from "@nestjs/core"; 4 | import { MetadataScanner } from "@nestjs/core/metadata-scanner"; 5 | import * as deepmerge from "deepmerge"; 6 | import { BULL_MODULE_OPTIONS, BULL_MODULE_SERVICE, BULL_QUEUE_EVENT_DECORATOR } from "../../bull.constants"; 7 | import { BullModuleOptions, BullQueue, BullQueueEventOptions } from "../../bull.interfaces"; 8 | import { BullService } from "../bull.service"; 9 | import { BaseExplorerService } from "./base-explorer.service"; 10 | 11 | @InjectableDecorator() 12 | export class BullQueueEventExplorerService extends BaseExplorerService { 13 | protected onBullQueuePropertyProcess( 14 | bullQueue: BullQueue, 15 | instance: Injectable, 16 | prototype: any, 17 | propertyName: string, 18 | ): void { 19 | const options = this.getOptions(prototype, propertyName); 20 | options.eventNames.forEach((eventName) => { 21 | bullQueue.on(eventName, prototype[propertyName].bind(instance)); 22 | this.logger.log(`${eventName} listener on ${bullQueue.name} initialized`); 23 | }); 24 | } 25 | 26 | protected verifyPropertyName(target: any, propertyName: string): boolean { 27 | return Reflect.hasMetadata(BULL_QUEUE_EVENT_DECORATOR, target, propertyName); 28 | } 29 | 30 | protected getOptions(prototype: any, propertyName: string): BullQueueEventOptions { 31 | return deepmerge({ eventNames: [] }, Reflect.getMetadata(BULL_QUEUE_EVENT_DECORATOR, prototype, propertyName)); 32 | } 33 | 34 | constructor( 35 | @Inject(BULL_MODULE_OPTIONS) 36 | readonly options: BullModuleOptions, 37 | @Inject(BULL_MODULE_SERVICE) 38 | readonly bullService: BullService, 39 | readonly modulesContainer: ModulesContainer, 40 | readonly metadataScanner: MetadataScanner, 41 | ) { 42 | super(options, bullService, modulesContainer, metadataScanner); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/bull/src/services/explorers/processor-explorer.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable as InjectableDecorator } from "@nestjs/common"; 2 | import { Injectable } from "@nestjs/common/interfaces"; 3 | import { ModulesContainer } from "@nestjs/core"; 4 | import { MetadataScanner } from "@nestjs/core/metadata-scanner"; 5 | import * as deepmerge from "deepmerge"; 6 | import { 7 | BULL_MODULE_OPTIONS, 8 | BULL_MODULE_SERVICE, 9 | BULL_QUEUE_DEFAULT_PROCESSOR_NAME, 10 | BULL_QUEUE_PROCESSOR_DECORATOR, 11 | BULL_QUEUE_PROCESSOR_DEFAULT_CONCURRENCY, 12 | } from "../../bull.constants"; 13 | import { 14 | BullModuleOptions, 15 | BullQueue, 16 | BullQueueDefaultProcessorOptions, 17 | BullQueueProcessorOptions, 18 | } from "../../bull.interfaces"; 19 | import { BullService } from "../bull.service"; 20 | import { BaseExplorerService } from "./base-explorer.service"; 21 | 22 | @InjectableDecorator() 23 | export class BullQueueProcessorExplorerService extends BaseExplorerService { 24 | private getExtraProcessorOptions(): BullQueueDefaultProcessorOptions | undefined { 25 | if (this.options.extra && this.options.extra.defaultProcessorOptions) { 26 | return this.options.extra.defaultProcessorOptions; 27 | } 28 | } 29 | 30 | protected getOptions(prototype: any, propertyName: string): Required { 31 | const options = Reflect.getMetadata( 32 | BULL_QUEUE_PROCESSOR_DECORATOR, 33 | prototype, 34 | propertyName, 35 | ) as BullQueueProcessorOptions; 36 | 37 | return deepmerge.all>([ 38 | { 39 | name: propertyName, 40 | concurrency: BULL_QUEUE_PROCESSOR_DEFAULT_CONCURRENCY, 41 | isCustomProcessorName: options && options.name, 42 | skip: options && options.skip, 43 | }, 44 | this.getExtraProcessorOptions() || {}, 45 | Reflect.getMetadata(BULL_QUEUE_PROCESSOR_DECORATOR, prototype, propertyName) || {}, 46 | ]); 47 | } 48 | 49 | protected onBullQueuePropertyProcess( 50 | bullQueue: BullQueue, 51 | instance: Injectable, 52 | prototype: any, 53 | propertyName: string, 54 | allPropertyNames: string[], 55 | ): void { 56 | const processorOptions = this.getOptions(prototype, propertyName); 57 | 58 | if (processorOptions.skip) { 59 | return; 60 | } 61 | 62 | const processorName = 63 | allPropertyNames.length === 1 && !processorOptions.isCustomProcessorName 64 | ? BULL_QUEUE_DEFAULT_PROCESSOR_NAME 65 | : processorOptions.name; 66 | 67 | bullQueue.process(processorName, processorOptions.concurrency, prototype[propertyName].bind(instance)); 68 | 69 | this.logger.log(`${processorOptions.name} processor on ${bullQueue.name} initialized`); 70 | } 71 | 72 | protected verifyPropertyName(target: any, propertyName: string): boolean { 73 | return Reflect.hasMetadata(BULL_QUEUE_PROCESSOR_DECORATOR, target, propertyName); 74 | } 75 | 76 | constructor( 77 | @Inject(BULL_MODULE_OPTIONS) 78 | readonly options: BullModuleOptions, 79 | @Inject(BULL_MODULE_SERVICE) 80 | readonly bullService: BullService, 81 | readonly modulesContainer: ModulesContainer, 82 | readonly metadataScanner: MetadataScanner, 83 | ) { 84 | super(options, bullService, modulesContainer, metadataScanner); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/bull/src/testing/index.ts: -------------------------------------------------------------------------------- 1 | import { ValueProvider } from "@nestjs/common/interfaces"; 2 | import { BullName } from "../bull.interfaces"; 3 | import { getBullQueueToken } from "../bull.utils"; 4 | 5 | export const createTestBullProvider = (name: BullName): ValueProvider => { 6 | let counter = 1; 7 | return { 8 | provide: getBullQueueToken(name), 9 | useValue: { 10 | add: async (...args: unknown[]): Promise => ({ id: counter++, ...args }), 11 | }, 12 | } as ValueProvider; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/bull/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts", "dist"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/bull/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "declarationMap": true 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const baseESLintConfig = require("../../.eslintrc"); 2 | 3 | module.exports = { ...baseESLintConfig }; 4 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | dist/**/*.tsbuildinfo 4 | dist/**/*.js.map 5 | dist/**/*.ts.map 6 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const basePrettierConfig = require("../../.prettierrc"); 2 | 3 | module.exports = { 4 | ...basePrettierConfig, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/README.md: -------------------------------------------------------------------------------- 1 | # @anchan828/nest-bullmq-terminus 2 | 3 | ![npm](https://img.shields.io/npm/v/@anchan828/nest-bullmq-terminus.svg) 4 | ![NPM](https://img.shields.io/npm/l/@anchan828/nest-bullmq-terminus.svg) 5 | 6 | ## Description 7 | 8 | The terminus of The [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest). 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm i --save @anchan828/nest-bullmq-terminus @nestjs/terminus @anchan828/nest-bullmq bullmq 14 | ``` 15 | 16 | ## Quick Start 17 | 18 | ```ts 19 | import { BullHealthIndicator, BullHealthModule } from "@anchan828/nest-bullmq-terminus"; 20 | 21 | @Controller("/health") 22 | class BullHealthController { 23 | constructor(private health: HealthCheckService, private bull: BullHealthIndicator) {} 24 | 25 | @Get() 26 | @HealthCheck() 27 | check() { 28 | return this.health.check([() => this.bull.isHealthy()]); 29 | } 30 | } 31 | 32 | @Module({ 33 | controllers: [BullHealthController], 34 | imports: [BullHealthModule, TerminusModule], 35 | }) 36 | export class HealthModule {} 37 | ``` 38 | 39 | 40 | ## License 41 | 42 | [MIT](LICENSE) 43 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require("../../jest.config.base"); 2 | module.exports = { 3 | ...base, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anchan828/nest-bullmq-terminus", 3 | "version": "3.2.23", 4 | "description": "The terminus of [BullMQ](https://github.com/taskforcesh/bullmq) module for [Nest](https://github.com/nestjs/nest).", 5 | "homepage": "https://github.com/anchan828/nest-bull/tree/master/packages/bullmq-terminus#readme", 6 | "bugs": { 7 | "url": "https://github.com/anchan828/nest-bull/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/anchan828/nest-bull.git" 12 | }, 13 | "license": "MIT", 14 | "author": "anchan828 ", 15 | "main": "dist/index.js", 16 | "types": "dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsc -p tsconfig.build.json", 19 | "copy:license": "cp ../../LICENSE ./", 20 | "lint": "TIMING=1 eslint --ignore-path ../../.eslintignore '**/*.ts'", 21 | "lint:fix": "npm run lint -- --fix", 22 | "prepublishOnly": "npm run build && rm -f dist/*.tsbuildinfo && npm run copy:license", 23 | "test": "jest --coverage --runInBand --detectOpenHandles --forceExit", 24 | "test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest --runInBand --logHeapUsage", 25 | "test:watch": "npm run test -- --watch", 26 | "watch": "tsc --watch" 27 | }, 28 | "dependencies": { 29 | "@anchan828/nest-bullmq": "^3.2.23" 30 | }, 31 | "devDependencies": { 32 | "@nestjs/axios": "3.0.1", 33 | "@nestjs/common": "10.2.10", 34 | "@nestjs/terminus": "10.2.0", 35 | "@types/supertest": "2.0.16", 36 | "bullmq": "4.15.2", 37 | "ioredis": "5.3.2", 38 | "rxjs": "7.8.1", 39 | "supertest": "6.3.3" 40 | }, 41 | "peerDependencies": { 42 | "@nestjs/axios": "^0.0.8 || ^0.1.0 || ^1.0.0 || ^2.0.0 || ^3.0.0", 43 | "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/src/bull.health.ts: -------------------------------------------------------------------------------- 1 | import { BullQueue, BullQueueEvents, BullService, BullWorker, BullWorkerProcess } from "@anchan828/nest-bullmq"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from "@nestjs/terminus"; 4 | import { QUEUE_NAME } from "./constants"; 5 | 6 | @BullQueue({ queueName: QUEUE_NAME, options: { defaultJobOptions: { removeOnComplete: true } } }) 7 | export class BullHealthCheckQueue {} 8 | 9 | @BullWorker({ queueName: QUEUE_NAME }) 10 | export class BullHealthCheckWorker { 11 | @BullWorkerProcess() 12 | async process(): Promise { 13 | return Promise.resolve(true); 14 | } 15 | } 16 | 17 | @BullQueueEvents({ queueName: QUEUE_NAME }) 18 | export class BullHealthCheckQueueEvents {} 19 | 20 | @Injectable() 21 | export class BullHealthIndicator extends HealthIndicator { 22 | constructor(private readonly service: BullService) { 23 | super(); 24 | } 25 | 26 | async isHealthy(key = "bull"): Promise { 27 | try { 28 | const job = await this.service.queues[QUEUE_NAME].add("Health check", {}); 29 | await job.waitUntilFinished(this.service.queueEvents[QUEUE_NAME]); 30 | } catch (e: any) { 31 | throw new HealthCheckError("BullHealthCheck failed", this.getStatus(key, false, { message: e.message })); 32 | } 33 | return this.getStatus(key, true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const BULL_HEALTH_INDICATOR = "BullHealthIndicator" as const; 2 | export const QUEUE_NAME = "BullHealthCheckQueue" as const; 3 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/src/health.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { BullModule, BullService } from "@anchan828/nest-bullmq"; 2 | import { Controller, Get, Module } from "@nestjs/common"; 3 | import { HealthCheck, HealthCheckService, TerminusModule } from "@nestjs/terminus"; 4 | import { Test } from "@nestjs/testing"; 5 | import * as request from "supertest"; 6 | import { BullHealthIndicator } from "./bull.health"; 7 | import { QUEUE_NAME } from "./constants"; 8 | import { BullHealthModule } from "./health.module"; 9 | 10 | describe("BullHealthModule", () => { 11 | @Controller("/health") 12 | class BullHealthController { 13 | constructor(private health: HealthCheckService, private bull: BullHealthIndicator) {} 14 | 15 | @Get() 16 | @HealthCheck() 17 | check() { 18 | return this.health.check([async () => this.bull.isHealthy()]); 19 | } 20 | } 21 | it("should compile module", async () => { 22 | const app = await Test.createTestingModule({ 23 | imports: [BullModule.forRoot({}), BullHealthModule], 24 | }).compile(); 25 | 26 | await app.init(); 27 | await app.close(); 28 | }); 29 | 30 | it("should compile health module", async () => { 31 | @Module({ 32 | controllers: [BullHealthController], 33 | imports: [BullHealthModule, TerminusModule], 34 | }) 35 | class HealthModule {} 36 | 37 | const app = await Test.createTestingModule({ 38 | imports: [BullModule.forRoot({}), HealthModule], 39 | }).compile(); 40 | 41 | await app.init(); 42 | await app.close(); 43 | }); 44 | 45 | describe("e2e tests", () => { 46 | it("should create nest application", async () => { 47 | @Module({ 48 | controllers: [BullHealthController], 49 | imports: [BullHealthModule, TerminusModule], 50 | }) 51 | class HealthModule {} 52 | 53 | const module = await Test.createTestingModule({ 54 | imports: [BullModule.forRoot({}), HealthModule], 55 | }).compile(); 56 | const app = module.createNestApplication(); 57 | await expect(app.init()).resolves.toBeDefined(); 58 | await app.close(); 59 | }); 60 | 61 | it("should return status is up", async () => { 62 | @Module({ 63 | controllers: [BullHealthController], 64 | imports: [BullHealthModule, TerminusModule], 65 | }) 66 | class HealthModule {} 67 | 68 | const module = await Test.createTestingModule({ 69 | imports: [BullModule.forRoot({}), HealthModule], 70 | }).compile(); 71 | 72 | const app = module.createNestApplication(); 73 | await app.init(); 74 | await request(app.getHttpServer()) 75 | .get("/health") 76 | .expect(200) 77 | .expect({ 78 | status: "ok", 79 | error: {}, 80 | info: { bull: { status: "up" } }, 81 | details: { bull: { status: "up" } }, 82 | }); 83 | 84 | await app.close(); 85 | }); 86 | 87 | it("should return status is down", async () => { 88 | @Module({ 89 | controllers: [BullHealthController], 90 | imports: [BullHealthModule, TerminusModule], 91 | }) 92 | class HealthModule {} 93 | 94 | const module = await Test.createTestingModule({ 95 | imports: [BullModule.forRoot({}), HealthModule], 96 | }).compile(); 97 | 98 | const app = module.createNestApplication(); 99 | 100 | await app.init(); 101 | const service = app.get(BullService); 102 | const queue = service.queues[QUEUE_NAME]; 103 | jest.spyOn(queue, "add").mockResolvedValueOnce({ 104 | waitUntilFinished: (): Promise => { 105 | throw new Error("faild"); 106 | }, 107 | } as any); 108 | 109 | await request(app.getHttpServer()) 110 | .get("/health") 111 | .expect(503) 112 | .expect({ 113 | status: "error", 114 | info: {}, 115 | error: { bull: { status: "down", message: "faild" } }, 116 | details: { bull: { status: "down", message: "faild" } }, 117 | }); 118 | await app.close(); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/src/health.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { 3 | BullHealthCheckQueue, 4 | BullHealthCheckQueueEvents, 5 | BullHealthCheckWorker, 6 | BullHealthIndicator, 7 | } from "./bull.health"; 8 | @Module({ 9 | providers: [BullHealthCheckQueue, BullHealthCheckWorker, BullHealthCheckQueueEvents, BullHealthIndicator], 10 | exports: [BullHealthCheckQueue, BullHealthCheckWorker, BullHealthCheckQueueEvents, BullHealthIndicator], 11 | }) 12 | export class BullHealthModule {} 13 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/src/index.ts: -------------------------------------------------------------------------------- 1 | export { BullHealthCheckQueue, BullHealthIndicator } from "./bull.health"; 2 | export { BullHealthModule } from "./health.module"; 3 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts", "dist"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/bullmq-terminus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "declarationMap": true 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/bullmq/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const baseESLintConfig = require("../../.eslintrc"); 2 | 3 | module.exports = { ...baseESLintConfig }; 4 | -------------------------------------------------------------------------------- /packages/bullmq/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | dist/**/*.tsbuildinfo 4 | dist/**/*.js.map 5 | dist/**/*.ts.map 6 | -------------------------------------------------------------------------------- /packages/bullmq/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const basePrettierConfig = require("../../.prettierrc"); 2 | 3 | module.exports = { 4 | ...basePrettierConfig, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/bullmq/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 3.2.23 (2023-12-17) 7 | 8 | **Note:** Version bump only for package @anchan828/nest-bullmq 9 | 10 | ## 3.2.22 (2023-12-10) 11 | 12 | **Note:** Version bump only for package @anchan828/nest-bullmq 13 | 14 | ## 3.2.21 (2023-12-03) 15 | 16 | **Note:** Version bump only for package @anchan828/nest-bullmq 17 | 18 | ## 3.2.20 (2023-11-12) 19 | 20 | **Note:** Version bump only for package @anchan828/nest-bullmq 21 | 22 | ## 3.2.19 (2023-11-05) 23 | 24 | **Note:** Version bump only for package @anchan828/nest-bullmq 25 | 26 | ## 3.2.18 (2023-10-29) 27 | 28 | **Note:** Version bump only for package @anchan828/nest-bullmq 29 | 30 | ## 3.2.17 (2023-10-22) 31 | 32 | **Note:** Version bump only for package @anchan828/nest-bullmq 33 | 34 | ## 3.2.16 (2023-10-15) 35 | 36 | **Note:** Version bump only for package @anchan828/nest-bullmq 37 | 38 | ## 3.2.15 (2023-10-08) 39 | 40 | **Note:** Version bump only for package @anchan828/nest-bullmq 41 | 42 | ## 3.2.14 (2023-10-01) 43 | 44 | **Note:** Version bump only for package @anchan828/nest-bullmq 45 | 46 | ## 3.2.13 (2023-09-24) 47 | 48 | **Note:** Version bump only for package @anchan828/nest-bullmq 49 | 50 | ## 3.2.12 (2023-09-17) 51 | 52 | **Note:** Version bump only for package @anchan828/nest-bullmq 53 | 54 | ## 3.2.11 (2023-09-10) 55 | 56 | **Note:** Version bump only for package @anchan828/nest-bullmq 57 | 58 | ## 3.2.10 (2023-09-03) 59 | 60 | **Note:** Version bump only for package @anchan828/nest-bullmq 61 | 62 | ## 3.2.9 (2023-08-27) 63 | 64 | **Note:** Version bump only for package @anchan828/nest-bullmq 65 | 66 | ## 3.2.8 (2023-08-20) 67 | 68 | **Note:** Version bump only for package @anchan828/nest-bullmq 69 | 70 | ## 3.2.7 (2023-08-13) 71 | 72 | **Note:** Version bump only for package @anchan828/nest-bullmq 73 | 74 | ## 3.2.6 (2023-08-06) 75 | 76 | **Note:** Version bump only for package @anchan828/nest-bullmq 77 | 78 | ## 3.2.5 (2023-07-30) 79 | 80 | **Note:** Version bump only for package @anchan828/nest-bullmq 81 | 82 | ## 3.2.4 (2023-07-23) 83 | 84 | **Note:** Version bump only for package @anchan828/nest-bullmq 85 | 86 | ## 3.2.3 (2023-07-16) 87 | 88 | **Note:** Version bump only for package @anchan828/nest-bullmq 89 | 90 | ## 3.2.2 (2023-07-09) 91 | 92 | **Note:** Version bump only for package @anchan828/nest-bullmq 93 | 94 | ## 3.2.1 (2023-07-02) 95 | 96 | **Note:** Version bump only for package @anchan828/nest-bullmq 97 | 98 | # 3.2.0 (2023-06-25) 99 | 100 | **Note:** Version bump only for package @anchan828/nest-bullmq 101 | 102 | ## 3.1.30 (2023-06-11) 103 | 104 | **Note:** Version bump only for package @anchan828/nest-bullmq 105 | 106 | ## 3.1.29 (2023-06-04) 107 | 108 | **Note:** Version bump only for package @anchan828/nest-bullmq 109 | 110 | ## 3.1.28 (2023-05-28) 111 | 112 | **Note:** Version bump only for package @anchan828/nest-bullmq 113 | 114 | ## 3.1.27 (2023-05-21) 115 | 116 | **Note:** Version bump only for package @anchan828/nest-bullmq 117 | 118 | ## 3.1.26 (2023-05-18) 119 | 120 | **Note:** Version bump only for package @anchan828/nest-bullmq 121 | 122 | ## 3.1.25 (2023-04-16) 123 | 124 | **Note:** Version bump only for package @anchan828/nest-bullmq 125 | 126 | ## 3.1.24 (2023-04-09) 127 | 128 | **Note:** Version bump only for package @anchan828/nest-bullmq 129 | 130 | ## 3.1.23 (2023-04-02) 131 | 132 | **Note:** Version bump only for package @anchan828/nest-bullmq 133 | 134 | ## 3.1.22 (2023-03-26) 135 | 136 | **Note:** Version bump only for package @anchan828/nest-bullmq 137 | 138 | ## 3.1.21 (2023-03-19) 139 | 140 | ### Bug Fixes 141 | 142 | * **deps:** update dependency deepmerge to ^4.3.1 ([9dcb692](https://github.com/anchan828/nest-bull/commit/9dcb69239354380ca0e2ff6d46c7c8899612f585)) 143 | 144 | ## 3.1.20 (2023-03-12) 145 | 146 | **Note:** Version bump only for package @anchan828/nest-bullmq 147 | 148 | ## 3.1.19 (2023-03-05) 149 | 150 | **Note:** Version bump only for package @anchan828/nest-bullmq 151 | 152 | ## 3.1.18 (2023-02-26) 153 | 154 | **Note:** Version bump only for package @anchan828/nest-bullmq 155 | 156 | ## 3.1.17 (2023-02-19) 157 | 158 | **Note:** Version bump only for package @anchan828/nest-bullmq 159 | 160 | ## 3.1.16 (2023-02-12) 161 | 162 | **Note:** Version bump only for package @anchan828/nest-bullmq 163 | 164 | ## 3.1.15 (2023-02-05) 165 | 166 | **Note:** Version bump only for package @anchan828/nest-bullmq 167 | 168 | ## 3.1.14 (2023-01-29) 169 | 170 | **Note:** Version bump only for package @anchan828/nest-bullmq 171 | 172 | ## 3.1.13 (2023-01-22) 173 | 174 | **Note:** Version bump only for package @anchan828/nest-bullmq 175 | 176 | ## 3.1.12 (2023-01-15) 177 | 178 | **Note:** Version bump only for package @anchan828/nest-bullmq 179 | 180 | ## 3.1.11 (2023-01-08) 181 | 182 | **Note:** Version bump only for package @anchan828/nest-bullmq 183 | 184 | ## 3.1.10 (2023-01-01) 185 | 186 | **Note:** Version bump only for package @anchan828/nest-bullmq 187 | 188 | ## 3.1.9 (2022-12-25) 189 | 190 | **Note:** Version bump only for package @anchan828/nest-bullmq 191 | 192 | ## 3.1.8 (2022-12-18) 193 | 194 | **Note:** Version bump only for package @anchan828/nest-bullmq 195 | 196 | ## 3.1.7 (2022-12-11) 197 | 198 | **Note:** Version bump only for package @anchan828/nest-bullmq 199 | 200 | ## 3.1.6 (2022-12-04) 201 | 202 | **Note:** Version bump only for package @anchan828/nest-bullmq 203 | 204 | ## 3.1.5 (2022-11-27) 205 | 206 | **Note:** Version bump only for package @anchan828/nest-bullmq 207 | 208 | ## 3.1.4 (2022-11-20) 209 | 210 | **Note:** Version bump only for package @anchan828/nest-bullmq 211 | 212 | ## 3.1.3 (2022-11-13) 213 | 214 | **Note:** Version bump only for package @anchan828/nest-bullmq 215 | 216 | ## 3.1.2 (2022-11-06) 217 | 218 | **Note:** Version bump only for package @anchan828/nest-bullmq 219 | 220 | ## 3.1.1 (2022-10-30) 221 | 222 | **Note:** Version bump only for package @anchan828/nest-bullmq 223 | 224 | # 3.1.0 (2022-10-26) 225 | 226 | **Note:** Version bump only for package @anchan828/nest-bullmq 227 | 228 | ## 3.0.6 (2022-10-26) 229 | 230 | **Note:** Version bump only for package @anchan828/nest-bullmq 231 | 232 | ## 3.0.5 (2022-10-23) 233 | 234 | **Note:** Version bump only for package @anchan828/nest-bullmq 235 | 236 | ## 3.0.4 (2022-10-16) 237 | 238 | **Note:** Version bump only for package @anchan828/nest-bullmq 239 | 240 | ## 3.0.3 (2022-10-09) 241 | 242 | **Note:** Version bump only for package @anchan828/nest-bullmq 243 | 244 | ## 3.0.2 (2022-10-02) 245 | 246 | **Note:** Version bump only for package @anchan828/nest-bullmq 247 | 248 | ## 3.0.1 (2022-09-25) 249 | 250 | **Note:** Version bump only for package @anchan828/nest-bullmq 251 | 252 | # 3.0.0 (2022-09-21) 253 | 254 | **Note:** Version bump only for package @anchan828/nest-bullmq 255 | 256 | ## 2.0.11 (2022-09-21) 257 | 258 | **Note:** Version bump only for package @anchan828/nest-bullmq 259 | 260 | ## 2.0.10 (2022-09-18) 261 | 262 | **Note:** Version bump only for package @anchan828/nest-bullmq 263 | 264 | ## 2.0.9 (2022-09-11) 265 | 266 | **Note:** Version bump only for package @anchan828/nest-bullmq 267 | 268 | ## 2.0.8 (2022-09-04) 269 | 270 | **Note:** Version bump only for package @anchan828/nest-bullmq 271 | 272 | ## 2.0.7 (2022-08-28) 273 | 274 | **Note:** Version bump only for package @anchan828/nest-bullmq 275 | 276 | ## 2.0.6 (2022-08-21) 277 | 278 | **Note:** Version bump only for package @anchan828/nest-bullmq 279 | 280 | ## 2.0.5 (2022-08-14) 281 | 282 | **Note:** Version bump only for package @anchan828/nest-bullmq 283 | 284 | ## 2.0.4 (2022-08-07) 285 | 286 | **Note:** Version bump only for package @anchan828/nest-bullmq 287 | 288 | ## 2.0.3 (2022-07-31) 289 | 290 | **Note:** Version bump only for package @anchan828/nest-bullmq 291 | 292 | ## 2.0.2 (2022-07-24) 293 | 294 | **Note:** Version bump only for package @anchan828/nest-bullmq 295 | 296 | ## 2.0.1 (2022-07-17) 297 | 298 | **Note:** Version bump only for package @anchan828/nest-bullmq 299 | 300 | # 2.0.0 (2022-07-11) 301 | 302 | **Note:** Version bump only for package @anchan828/nest-bullmq 303 | 304 | ## 1.2.15 (2022-07-10) 305 | 306 | **Note:** Version bump only for package @anchan828/nest-bullmq 307 | 308 | ## 1.2.14 (2022-07-03) 309 | 310 | **Note:** Version bump only for package @anchan828/nest-bullmq 311 | 312 | ## 1.2.13 (2022-06-26) 313 | 314 | **Note:** Version bump only for package @anchan828/nest-bullmq 315 | 316 | ## 1.2.12 (2022-06-19) 317 | 318 | **Note:** Version bump only for package @anchan828/nest-bullmq 319 | 320 | ## 1.2.11 (2022-06-12) 321 | 322 | **Note:** Version bump only for package @anchan828/nest-bullmq 323 | 324 | ## 1.2.10 (2022-06-05) 325 | 326 | **Note:** Version bump only for package @anchan828/nest-bullmq 327 | 328 | ## 1.2.9 (2022-05-29) 329 | 330 | **Note:** Version bump only for package @anchan828/nest-bullmq 331 | 332 | ## 1.2.8 (2022-05-22) 333 | 334 | **Note:** Version bump only for package @anchan828/nest-bullmq 335 | 336 | ## 1.2.7 (2022-05-15) 337 | 338 | **Note:** Version bump only for package @anchan828/nest-bullmq 339 | 340 | ## 1.2.6 (2022-05-08) 341 | 342 | **Note:** Version bump only for package @anchan828/nest-bullmq 343 | 344 | ## 1.2.5 (2022-05-01) 345 | 346 | **Note:** Version bump only for package @anchan828/nest-bullmq 347 | 348 | ## 1.2.4 (2022-04-24) 349 | 350 | **Note:** Version bump only for package @anchan828/nest-bullmq 351 | 352 | ## 1.2.3 (2022-04-17) 353 | 354 | **Note:** Version bump only for package @anchan828/nest-bullmq 355 | 356 | ## 1.2.2 (2022-04-10) 357 | 358 | **Note:** Version bump only for package @anchan828/nest-bullmq 359 | 360 | ## 1.2.1 (2022-04-03) 361 | 362 | **Note:** Version bump only for package @anchan828/nest-bullmq 363 | 364 | # 1.2.0 (2022-03-29) 365 | 366 | **Note:** Version bump only for package @anchan828/nest-bullmq 367 | 368 | ## 1.1.8 (2022-03-29) 369 | 370 | **Note:** Version bump only for package @anchan828/nest-bullmq 371 | 372 | ## 1.1.7 (2022-03-27) 373 | 374 | **Note:** Version bump only for package @anchan828/nest-bullmq 375 | 376 | ## 1.1.6 (2022-03-20) 377 | 378 | **Note:** Version bump only for package @anchan828/nest-bullmq 379 | 380 | ## 1.1.5 (2022-03-13) 381 | 382 | **Note:** Version bump only for package @anchan828/nest-bullmq 383 | 384 | ## 1.1.4 (2022-03-06) 385 | 386 | **Note:** Version bump only for package @anchan828/nest-bullmq 387 | 388 | ## 1.1.3 (2022-02-27) 389 | 390 | **Note:** Version bump only for package @anchan828/nest-bullmq 391 | 392 | ## 1.1.2 (2022-02-20) 393 | 394 | **Note:** Version bump only for package @anchan828/nest-bullmq 395 | 396 | ## 1.1.1 (2022-02-13) 397 | 398 | **Note:** Version bump only for package @anchan828/nest-bullmq 399 | 400 | # 1.1.0 (2022-02-12) 401 | 402 | **Note:** Version bump only for package @anchan828/nest-bullmq 403 | 404 | ## 1.0.31 (2022-02-06) 405 | 406 | **Note:** Version bump only for package @anchan828/nest-bullmq 407 | 408 | ## 1.0.30 (2022-01-30) 409 | 410 | **Note:** Version bump only for package @anchan828/nest-bullmq 411 | 412 | ## 1.0.29 (2022-01-23) 413 | 414 | **Note:** Version bump only for package @anchan828/nest-bullmq 415 | 416 | ## 1.0.28 (2022-01-16) 417 | 418 | **Note:** Version bump only for package @anchan828/nest-bullmq 419 | 420 | ## 1.0.27 (2022-01-09) 421 | 422 | **Note:** Version bump only for package @anchan828/nest-bullmq 423 | 424 | ## 1.0.26 (2022-01-02) 425 | 426 | **Note:** Version bump only for package @anchan828/nest-bullmq 427 | 428 | ## 1.0.25 (2021-12-26) 429 | 430 | **Note:** Version bump only for package @anchan828/nest-bullmq 431 | 432 | ## 1.0.24 (2021-12-19) 433 | 434 | **Note:** Version bump only for package @anchan828/nest-bullmq 435 | 436 | ## 1.0.23 (2021-12-12) 437 | 438 | **Note:** Version bump only for package @anchan828/nest-bullmq 439 | 440 | ## 1.0.22 (2021-12-05) 441 | 442 | **Note:** Version bump only for package @anchan828/nest-bullmq 443 | 444 | ## 1.0.21 (2021-11-28) 445 | 446 | **Note:** Version bump only for package @anchan828/nest-bullmq 447 | 448 | ## 1.0.20 (2021-11-21) 449 | 450 | **Note:** Version bump only for package @anchan828/nest-bullmq 451 | 452 | ## 1.0.19 (2021-11-14) 453 | 454 | **Note:** Version bump only for package @anchan828/nest-bullmq 455 | 456 | ## 1.0.18 (2021-11-07) 457 | 458 | **Note:** Version bump only for package @anchan828/nest-bullmq 459 | 460 | ## 1.0.17 (2021-10-31) 461 | 462 | **Note:** Version bump only for package @anchan828/nest-bullmq 463 | 464 | ## 1.0.16 (2021-10-24) 465 | 466 | **Note:** Version bump only for package @anchan828/nest-bullmq 467 | 468 | ## 1.0.15 (2021-10-17) 469 | 470 | **Note:** Version bump only for package @anchan828/nest-bullmq 471 | 472 | ## 1.0.14 (2021-10-10) 473 | 474 | **Note:** Version bump only for package @anchan828/nest-bullmq 475 | 476 | ## 1.0.13 (2021-10-03) 477 | 478 | **Note:** Version bump only for package @anchan828/nest-bullmq 479 | 480 | ## 1.0.12 (2021-09-26) 481 | 482 | **Note:** Version bump only for package @anchan828/nest-bullmq 483 | 484 | ## 1.0.11 (2021-09-19) 485 | 486 | **Note:** Version bump only for package @anchan828/nest-bullmq 487 | 488 | ## 1.0.10 (2021-09-12) 489 | 490 | **Note:** Version bump only for package @anchan828/nest-bullmq 491 | 492 | ## 1.0.9 (2021-09-05) 493 | 494 | **Note:** Version bump only for package @anchan828/nest-bullmq 495 | 496 | ## 1.0.8 (2021-08-29) 497 | 498 | **Note:** Version bump only for package @anchan828/nest-bullmq 499 | 500 | ## 1.0.7 (2021-08-22) 501 | 502 | **Note:** Version bump only for package @anchan828/nest-bullmq 503 | 504 | ## 1.0.6 (2021-08-15) 505 | 506 | **Note:** Version bump only for package @anchan828/nest-bullmq 507 | 508 | ## 1.0.5 (2021-08-08) 509 | 510 | **Note:** Version bump only for package @anchan828/nest-bullmq 511 | 512 | ## 1.0.4 (2021-08-01) 513 | 514 | **Note:** Version bump only for package @anchan828/nest-bullmq 515 | 516 | ## 1.0.3 (2021-07-18) 517 | 518 | **Note:** Version bump only for package @anchan828/nest-bullmq 519 | 520 | ## 1.0.2 (2021-07-18) 521 | 522 | **Note:** Version bump only for package @anchan828/nest-bullmq 523 | 524 | ## 1.0.1 (2021-07-11) 525 | 526 | **Note:** Version bump only for package @anchan828/nest-bullmq 527 | 528 | # [1.0.0](https://github.com/anchan828/nest-bull/compare/v0.5.33...v1.0.0) (2021-07-08) 529 | 530 | **Note:** Version bump only for package @anchan828/nest-bullmq 531 | 532 | ## 0.5.33 (2021-07-04) 533 | 534 | **Note:** Version bump only for package @anchan828/nest-bullmq 535 | 536 | ## 0.5.32 (2021-06-27) 537 | 538 | **Note:** Version bump only for package @anchan828/nest-bullmq 539 | 540 | ## 0.5.31 (2021-06-20) 541 | 542 | **Note:** Version bump only for package @anchan828/nest-bullmq 543 | 544 | ## 0.5.30 (2021-06-13) 545 | 546 | **Note:** Version bump only for package @anchan828/nest-bullmq 547 | 548 | ## 0.5.29 (2021-06-06) 549 | 550 | **Note:** Version bump only for package @anchan828/nest-bullmq 551 | 552 | ## 0.5.28 (2021-05-30) 553 | 554 | **Note:** Version bump only for package @anchan828/nest-bullmq 555 | 556 | ## 0.5.27 (2021-05-23) 557 | 558 | **Note:** Version bump only for package @anchan828/nest-bullmq 559 | 560 | ## 0.5.26 (2021-05-16) 561 | 562 | **Note:** Version bump only for package @anchan828/nest-bullmq 563 | 564 | ## 0.5.25 (2021-05-09) 565 | 566 | **Note:** Version bump only for package @anchan828/nest-bullmq 567 | 568 | ## 0.5.24 (2021-05-02) 569 | 570 | **Note:** Version bump only for package @anchan828/nest-bullmq 571 | 572 | ## 0.5.23 (2021-04-25) 573 | 574 | **Note:** Version bump only for package @anchan828/nest-bullmq 575 | 576 | ## 0.5.22 (2021-04-18) 577 | 578 | **Note:** Version bump only for package @anchan828/nest-bullmq 579 | 580 | ## 0.5.21 (2021-04-11) 581 | 582 | **Note:** Version bump only for package @anchan828/nest-bullmq 583 | 584 | ## 0.5.20 (2021-04-04) 585 | 586 | **Note:** Version bump only for package @anchan828/nest-bullmq 587 | 588 | ## 0.5.19 (2021-03-28) 589 | 590 | **Note:** Version bump only for package @anchan828/nest-bullmq 591 | 592 | ## 0.5.18 (2021-03-21) 593 | 594 | **Note:** Version bump only for package @anchan828/nest-bullmq 595 | 596 | ## 0.5.17 (2021-03-14) 597 | 598 | **Note:** Version bump only for package @anchan828/nest-bullmq 599 | 600 | ## 0.5.16 (2021-03-07) 601 | 602 | **Note:** Version bump only for package @anchan828/nest-bullmq 603 | 604 | ## 0.5.15 (2021-02-28) 605 | 606 | **Note:** Version bump only for package @anchan828/nest-bullmq 607 | 608 | ## 0.5.14 (2021-02-21) 609 | 610 | **Note:** Version bump only for package @anchan828/nest-bullmq 611 | 612 | ## 0.5.13 (2021-02-14) 613 | 614 | **Note:** Version bump only for package @anchan828/nest-bullmq 615 | 616 | ## 0.5.12 (2021-02-07) 617 | 618 | **Note:** Version bump only for package @anchan828/nest-bullmq 619 | 620 | ## 0.5.11 (2021-01-31) 621 | 622 | **Note:** Version bump only for package @anchan828/nest-bullmq 623 | 624 | ## 0.5.10 (2021-01-24) 625 | 626 | **Note:** Version bump only for package @anchan828/nest-bullmq 627 | 628 | ## 0.5.9 (2021-01-17) 629 | 630 | **Note:** Version bump only for package @anchan828/nest-bullmq 631 | 632 | ## 0.5.8 (2021-01-10) 633 | 634 | **Note:** Version bump only for package @anchan828/nest-bullmq 635 | 636 | ## 0.5.7 (2021-01-03) 637 | 638 | **Note:** Version bump only for package @anchan828/nest-bullmq 639 | 640 | ## 0.5.6 (2020-12-27) 641 | 642 | **Note:** Version bump only for package @anchan828/nest-bullmq 643 | 644 | ## 0.5.5 (2020-12-20) 645 | 646 | **Note:** Version bump only for package @anchan828/nest-bullmq 647 | 648 | ## 0.5.4 (2020-12-13) 649 | 650 | **Note:** Version bump only for package @anchan828/nest-bullmq 651 | 652 | ## [0.5.3](https://github.com/anchan828/nest-bull/compare/v0.5.2...v0.5.3) (2020-12-10) 653 | 654 | ### Bug Fixes 655 | 656 | * should remove undefined provider ([b3bd3f6](https://github.com/anchan828/nest-bull/commit/b3bd3f6c5eab937306a43cf0632d6f06251a3eeb)) 657 | 658 | ## [0.5.2](https://github.com/anchan828/nest-bull/compare/v0.5.1...v0.5.2) (2020-12-10) 659 | 660 | ### Bug Fixes 661 | 662 | * should be checked undefeind ([c504b42](https://github.com/anchan828/nest-bull/commit/c504b4259bd2aa4adc80774cd3c0e164748f05d2)) 663 | 664 | # [0.5.0](https://github.com/anchan828/nest-bull/compare/v0.4.18...v0.5.0) (2020-12-10) 665 | 666 | **Note:** Version bump only for package @anchan828/nest-bullmq 667 | 668 | ## [0.4.18](https://github.com/anchan828/nest-bull/compare/v0.4.17...v0.4.18) (2020-12-09) 669 | 670 | ### Bug Fixes 671 | 672 | * **bullmq:** add only those with a decorator ([756e440](https://github.com/anchan828/nest-bull/commit/756e44029aafedb61d5e454110722b5a2fcfd482)) 673 | 674 | ## 0.4.17 (2020-12-06) 675 | 676 | **Note:** Version bump only for package @anchan828/nest-bullmq 677 | 678 | ## 0.4.16 (2020-11-29) 679 | 680 | **Note:** Version bump only for package @anchan828/nest-bullmq 681 | 682 | ## 0.4.15 (2020-11-22) 683 | 684 | **Note:** Version bump only for package @anchan828/nest-bullmq 685 | 686 | ## 0.4.14 (2020-11-15) 687 | 688 | **Note:** Version bump only for package @anchan828/nest-bullmq 689 | 690 | ## 0.4.13 (2020-11-08) 691 | 692 | **Note:** Version bump only for package @anchan828/nest-bullmq 693 | 694 | ## 0.4.12 (2020-11-01) 695 | 696 | **Note:** Version bump only for package @anchan828/nest-bullmq 697 | 698 | ## 0.4.11 (2020-10-25) 699 | 700 | **Note:** Version bump only for package @anchan828/nest-bullmq 701 | 702 | ## 0.4.10 (2020-10-18) 703 | 704 | **Note:** Version bump only for package @anchan828/nest-bullmq 705 | 706 | ## 0.4.9 (2020-10-11) 707 | 708 | **Note:** Version bump only for package @anchan828/nest-bullmq 709 | 710 | ## 0.4.8 (2020-10-04) 711 | 712 | **Note:** Version bump only for package @anchan828/nest-bullmq 713 | 714 | ## 0.4.7 (2020-09-27) 715 | 716 | **Note:** Version bump only for package @anchan828/nest-bullmq 717 | 718 | ## 0.4.6 (2020-09-20) 719 | 720 | **Note:** Version bump only for package @anchan828/nest-bullmq 721 | 722 | ## 0.4.5 (2020-09-13) 723 | 724 | **Note:** Version bump only for package @anchan828/nest-bullmq 725 | 726 | ## 0.4.4 (2020-09-06) 727 | 728 | **Note:** Version bump only for package @anchan828/nest-bullmq 729 | 730 | ## 0.4.3 (2020-08-30) 731 | 732 | **Note:** Version bump only for package @anchan828/nest-bullmq 733 | 734 | ## 0.4.2 (2020-08-23) 735 | 736 | **Note:** Version bump only for package @anchan828/nest-bullmq 737 | 738 | ## 0.4.1 (2020-08-16) 739 | 740 | **Note:** Version bump only for package @anchan828/nest-bullmq 741 | 742 | # [0.4.0](https://github.com/anchan828/nest-bull/compare/v0.3.40...v0.4.0) (2020-04-15) 743 | 744 | ### Bug Fixes 745 | 746 | - lint ([5169d77](https://github.com/anchan828/nest-bull/commit/5169d7789950537ed971a40c26076ce2f6f3aeec)) 747 | - return type ([b0924f7](https://github.com/anchan828/nest-bull/commit/b0924f74fab61d3b4ef327958f625472eba4356a)) 748 | 749 | ## [0.3.40](https://github.com/anchan828/nest-bull/compare/v0.3.38...v0.3.40) (2020-01-14) 750 | 751 | ### Features 752 | 753 | - change to variable argument in forQueue ([7f48c38](https://github.com/anchan828/nest-bull/commit/7f48c386e384f17f0d18b5b8065b9c00598f05f6)) 754 | - support scheduler ([2f6fac6](https://github.com/anchan828/nest-bull/commit/2f6fac6a1909b29df90890b4a31e90928bffac50)) 755 | 756 | ## [0.3.39](https://github.com/anchan828/nest-bull/compare/v0.3.38...v0.3.39) (2020-01-14) 757 | 758 | ### Features 759 | 760 | - change to variable argument in forQueue ([7f48c38](https://github.com/anchan828/nest-bull/commit/7f48c386e384f17f0d18b5b8065b9c00598f05f6)) 761 | - support scheduler ([2f6fac6](https://github.com/anchan828/nest-bull/commit/2f6fac6a1909b29df90890b4a31e90928bffac50)) 762 | 763 | ## [0.3.38](https://github.com/anchan828/nest-bull/compare/v0.3.37...v0.3.38) (2019-12-10) 764 | 765 | ### Bug Fixes 766 | 767 | - lint ([9cf1cfc](https://github.com/anchan828/nest-bull/commit/9cf1cfc43ba13b44a5d43024dc9ddc3bdb02f088)) 768 | - merge default options ([d316abb](https://github.com/anchan828/nest-bull/commit/d316abbb36466c77581a167f857fd3a39653be91)) 769 | - option property ([d14a6ba](https://github.com/anchan828/nest-bull/commit/d14a6ba36d340f075f29ec763340e11fd7de5a1d)) 770 | - use default config ([8eaa26a](https://github.com/anchan828/nest-bull/commit/8eaa26ad5aa8bf698416de73e2170facc7d1f216)) 771 | 772 | ### Features 773 | 774 | - add mock option ([1931e26](https://github.com/anchan828/nest-bull/commit/1931e26867d4ef67b522dc11daa67b8e7dcad502)) 775 | - queue decorator ([ed1ff1b](https://github.com/anchan828/nest-bull/commit/ed1ff1bdf8723fc622a3341bfb607221b189d0b4)) 776 | - queue events decorator ([17faba1](https://github.com/anchan828/nest-bull/commit/17faba150ca363cc477eec0fc7fc4a6ea21c029e)) 777 | - remove processor name ([02cda51](https://github.com/anchan828/nest-bull/commit/02cda515285728535bab864f06409322edc7d0fb)) 778 | - update forQueue ([e219c33](https://github.com/anchan828/nest-bull/commit/e219c33a4f6e0ba4721cb96b7c8ce24b220aea78)) 779 | -------------------------------------------------------------------------------- /packages/bullmq/README.md: -------------------------------------------------------------------------------- 1 | # @anchan828/nest-bullmq 2 | 3 | ![npm](https://img.shields.io/npm/v/@anchan828/nest-bullmq.svg) 4 | ![NPM](https://img.shields.io/npm/l/@anchan828/nest-bullmq.svg) 5 | 6 | ## Description 7 | 8 | The [BullMQ](https://github.com/taskforcesh/bullmq) module for [Nest](https://github.com/nestjs/nest). 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm i --save @anchan828/nest-bullmq bullmq 14 | ``` 15 | 16 | ## Quick Start 17 | 18 | ### Import BullModule 19 | 20 | ```ts 21 | import { BullModule } from "@anchan828/nest-bullmq"; 22 | import { Module } from "@nestjs/common"; 23 | 24 | @Module({ 25 | imports: [ 26 | BullModule.forRoot({ 27 | options: { 28 | connection: { 29 | host: "127.0.0.1", 30 | }, 31 | }, 32 | }), 33 | ], 34 | }) 35 | export class AppModule {} 36 | ``` 37 | 38 | ### Create queue provider 39 | 40 | ```ts 41 | import { Module } from "@nestjs/common"; 42 | import { ExampleService } from "./example.service"; 43 | import { APP_QUEUE } from "./app.constants"; 44 | 45 | @Module({ 46 | imports: [BullModule.registerQueue(APP_QUEUE)], 47 | providers: [ExampleService], 48 | }) 49 | export class ExampleModule {} 50 | ``` 51 | 52 | #### With queue options 53 | 54 | ```ts 55 | import { Module } from "@nestjs/common"; 56 | import { ExampleService } from "./example.service"; 57 | import { APP_QUEUE } from "./app.constants"; 58 | 59 | @Module({ 60 | imports: [ 61 | BullModule.registerQueue({ 62 | queueName, 63 | options: { 64 | defaultJobOptions: { priority: 1 }, 65 | }, 66 | }), 67 | ], 68 | providers: [ExampleService], 69 | }) 70 | export class ExampleModule {} 71 | ``` 72 | 73 | ### Inject Queue provider 74 | 75 | ```ts 76 | import { Inject, Injectable } from "@nestjs/common"; 77 | import { Queue } from "bullmq"; 78 | import { APP_QUEUE } from "./app.constants"; 79 | import { BullQueueInject } from "@anchan828/nest-bullmq"; 80 | 81 | @Injectable() 82 | export class ExampleService { 83 | constructor( 84 | @BullQueueInject(APP_QUEUE) 85 | private readonly queue: Queue, 86 | ) {} 87 | 88 | async addJob(): Promise { 89 | return this.queue.add("example", { text: "text" }); 90 | } 91 | } 92 | ``` 93 | 94 | ### Create worker provider 95 | 96 | ```ts 97 | import { BullWorker, BullWorkerProcess } from "@anchan828/nest-bullmq"; 98 | import { APP_QUEUE } from "./app.constants"; 99 | 100 | @BullWorker({ queueName: APP_QUEUE }) 101 | export class ExampleBullWorker { 102 | @BullWorkerProcess() 103 | public async process(job: Job): Promise<{ status: string }> { 104 | return { status: "ok" }; 105 | } 106 | } 107 | ``` 108 | 109 | ### Add Worker/Queue/QueueEvents listeners 110 | 111 | Listeners can be added via the decorator. 112 | 113 | #### Worker listeners 114 | 115 | All event names can be found [here](https://github.com/taskforcesh/bullmq/blob/6ded7bae22b0f369ebb68960d48780f547d43346/src/classes/worker.ts#L31). 116 | 117 | ```ts 118 | import { BullWorker, BullWorkerProcess, BullWorkerListener, BullWorkerListenerArgs } from "@anchan828/nest-bullmq"; 119 | import { APP_QUEUE } from "./app.constants"; 120 | 121 | @BullWorker({ queueName: APP_QUEUE }) 122 | export class ExampleBullWorker { 123 | @BullWorkerProcess() 124 | public async process(job: Job): Promise<{ status: string }> { 125 | return { status: "ok" }; 126 | } 127 | 128 | @BullWorkerListener("completed") 129 | public async completed(job: BullWorkerListenerArgs["completed"]): Promise { 130 | calledEvents("completed"); 131 | console.debug(`[${job.id}] completed`); 132 | } 133 | } 134 | ``` 135 | 136 | #### Queue listeners 137 | 138 | All event names can be found [here](https://github.com/taskforcesh/bullmq/blob/6ded7bae22b0f369ebb68960d48780f547d43346/src/classes/queue.ts#L11). 139 | 140 | ```ts 141 | import { BullQueue, BullQueueListener, BullQueueListenerArgs } from "@anchan828/nest-bullmq"; 142 | import { APP_QUEUE } from "./app.constants"; 143 | 144 | @BullQueue({ queueName: APP_QUEUE }) 145 | export class ExampleBullQueue { 146 | @BullQueueListener("waiting") 147 | public async waiting(job: BullQueueListenerArgs["waiting"]): Promise { 148 | calledEvents("waiting"); 149 | console.debug(`[${job.id}] waiting`); 150 | } 151 | } 152 | ``` 153 | 154 | #### QueueEvents listeners 155 | 156 | All event names can be found [here](https://github.com/taskforcesh/bullmq/blob/6ded7bae22b0f369ebb68960d48780f547d43346/src/classes/queue-events.ts#L12). 157 | 158 | ```ts 159 | import { BullQueueEvents, BullQueueEventsListener, BullQueueEventsListenerArgs } from "@anchan828/nest-bullmq"; 160 | import { APP_QUEUE } from "./app.constants"; 161 | 162 | @BullQueueEvents({ queueName: APP_QUEUE }) 163 | export class ExampleBullQueueEvents { 164 | @BullQueueEventsListener("added") 165 | public async added(args: BullQueueEventsListenerArgs["added"]): Promise { 166 | console.debug(`[${args.jobId}] added`); 167 | } 168 | 169 | @BullQueueEventsListener("active") 170 | public async active(args: BullQueueEventsListenerArgs["active"]): Promise { 171 | console.debug(`[${args.jobId}] active`); 172 | } 173 | 174 | @BullQueueEventsListener("completed") 175 | public async completed(args: BullQueueEventsListenerArgs["completed"]): Promise { 176 | console.debug(`[${args.jobId}] completed`); 177 | } 178 | 179 | @BullQueueEventsListener("waiting") 180 | public async waiting(args: BullQueueEventsListenerArgs["waiting"]): Promise { 181 | console.debug(`[${args.jobId}] waiting`); 182 | } 183 | } 184 | ``` 185 | 186 | ## Examples 187 | 188 | There are [examples](./src/examples). 189 | 190 | ## License 191 | 192 | [MIT](LICENSE) 193 | -------------------------------------------------------------------------------- /packages/bullmq/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require("../../jest.config.base"); 2 | module.exports = { 3 | ...base, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/bullmq/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anchan828/nest-bullmq", 3 | "version": "3.2.23", 4 | "description": "The [BullMQ](https://github.com/taskforcesh/bullmq) module for [Nest](https://github.com/nestjs/nest).", 5 | "homepage": "https://github.com/anchan828/nest-bull/tree/master/packages/bullmq#readme", 6 | "bugs": { 7 | "url": "https://github.com/anchan828/nest-bull/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/anchan828/nest-bull.git" 12 | }, 13 | "license": "MIT", 14 | "author": "anchan828 ", 15 | "main": "dist/index.js", 16 | "types": "dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsc -p tsconfig.build.json", 19 | "copy:license": "cp ../../LICENSE ./", 20 | "lint": "TIMING=1 eslint --ignore-path ../../.eslintignore '**/*.ts'", 21 | "lint:fix": "npm run lint -- --fix", 22 | "prepublishOnly": "npm run build && rm -f dist/*.tsbuildinfo && npm run copy:license", 23 | "test": "jest --coverage --runInBand --detectOpenHandles --logHeapUsage --forceExit", 24 | "test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest --runInBand ---detectOpenHandles --logHeapUsage", 25 | "test:watch": "npm run test -- --watch", 26 | "watch": "tsc --watch" 27 | }, 28 | "devDependencies": { 29 | "@nestjs/common": "10.2.10", 30 | "bullmq": "4.15.2", 31 | "ioredis": "5.3.2", 32 | "rxjs": "7.8.1" 33 | }, 34 | "peerDependencies": { 35 | "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" 36 | } 37 | } -------------------------------------------------------------------------------- /packages/bullmq/src/bull-core.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Inject, Module } from "@nestjs/common"; 2 | import { OnApplicationShutdown, OnModuleInit } from "@nestjs/common/interfaces"; 3 | import { DiscoveryModule } from "@nestjs/core"; 4 | import { MetadataScanner } from "@nestjs/core/metadata-scanner"; 5 | import { BULL_MODULE_OPTIONS } from "./bull.constants"; 6 | import { BullExplorerService } from "./bull.explorer.service"; 7 | import { createAsyncProviders, createQueue, createQueueEvents, createWorker } from "./bull.providers"; 8 | import { BullService } from "./bull.service"; 9 | import { mergeQueueBaseOptions } from "./bull.utils"; 10 | import { BullModuleAsyncOptions, BullModuleOptions } from "./interfaces"; 11 | 12 | @Global() 13 | @Module({ 14 | imports: [DiscoveryModule], 15 | providers: [MetadataScanner, BullExplorerService], 16 | }) 17 | export class BullCoreModule implements OnModuleInit, OnApplicationShutdown { 18 | constructor( 19 | @Inject(BULL_MODULE_OPTIONS) 20 | private readonly options: BullModuleOptions, 21 | private readonly explorer: BullExplorerService, 22 | private readonly service: BullService, 23 | ) {} 24 | 25 | async onApplicationShutdown(): Promise { 26 | await this.service.close(); 27 | } 28 | 29 | async onModuleInit(): Promise { 30 | const { workers, queueEvents, queues } = this.explorer.explore(); 31 | for (const queue of queues) { 32 | const queueOptions = mergeQueueBaseOptions(this.options?.options, queue.options.options); 33 | 34 | const queueInstance = await createQueue(queue.options.queueName, queueOptions, this.options.mock); 35 | 36 | for (const event of queue.events) { 37 | queueInstance.on(event.type, event.processor); 38 | } 39 | 40 | this.service.queues[queue.options.queueName] = queueInstance; 41 | } 42 | 43 | for (const worker of workers) { 44 | for (const workerProcessor of worker.processors) { 45 | const workerInstance = await createWorker( 46 | worker.options.queueName, 47 | workerProcessor.processor, 48 | mergeQueueBaseOptions(this.options?.options, worker?.options?.options, workerProcessor.options), 49 | this.options.mock, 50 | ); 51 | 52 | for (const event of worker.events) { 53 | workerInstance.on(event.type, event.processor); 54 | } 55 | 56 | this.service.workers[worker.options.queueName] = workerInstance; 57 | } 58 | } 59 | for (const queueEvent of queueEvents) { 60 | const queueEventInstance = await createQueueEvents( 61 | queueEvent.options.queueName, 62 | mergeQueueBaseOptions(this.options?.options, queueEvent.options.options), 63 | this.options.mock, 64 | ); 65 | 66 | for (const event of queueEvent.events) { 67 | queueEventInstance.on(event.type, event.processor); 68 | } 69 | 70 | this.service.queueEvents[queueEvent.options.queueName] = queueEventInstance; 71 | } 72 | } 73 | 74 | public static forRoot(options: BullModuleOptions): DynamicModule { 75 | const providers = [{ provide: BULL_MODULE_OPTIONS, useValue: options }, BullService]; 76 | return { 77 | module: BullCoreModule, 78 | providers, 79 | exports: providers, 80 | }; 81 | } 82 | 83 | public static forRootAsync(options: BullModuleAsyncOptions): DynamicModule { 84 | const providers = [...createAsyncProviders(options), BullService]; 85 | return { 86 | module: BullCoreModule, 87 | imports: [...(options.imports || [])], 88 | providers, 89 | exports: providers, 90 | }; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.constants.ts: -------------------------------------------------------------------------------- 1 | export const BULL_MODULE_SERVICE = "BULL_MODULE_SERVICE"; 2 | export const BULL_MODULE_OPTIONS = "BULL_MODULE_OPTIONS"; 3 | 4 | export const BULL_QUEUE_DECORATOR = "BULL_QUEUE_DECORATOR"; 5 | 6 | export const BULL_WORKER_DECORATOR = "BULL_WORKER_DECORATOR"; 7 | export const BULL_WORKER_PROCESSOR_DECORATOR = "BULL_WORKER_PROCESSOR_DECORATOR"; 8 | export const BULL_QUEUE_EVENTS_DECORATOR = "BULL_QUEUE_EVENTS_DECORATOR"; 9 | export const BULL_LISTENER_DECORATOR = "BULL_LISTENER_DECORATOR"; 10 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Inject, SetMetadata } from "@nestjs/common"; 2 | import { QueueEventsListener, QueueListener, WorkerListener, WorkerOptions } from "bullmq"; 3 | import { 4 | BULL_LISTENER_DECORATOR, 5 | BULL_QUEUE_DECORATOR, 6 | BULL_QUEUE_EVENTS_DECORATOR, 7 | BULL_WORKER_DECORATOR, 8 | BULL_WORKER_PROCESSOR_DECORATOR, 9 | } from "./bull.constants"; 10 | import { getBullQueueToken } from "./bull.utils"; 11 | import { BullQueueEventsOptions, BullQueueOptions, BullWorkerOptions } from "./interfaces"; 12 | 13 | export function BullQueue(options: BullQueueOptions): ClassDecorator { 14 | return SetMetadata(BULL_QUEUE_DECORATOR, options); 15 | } 16 | 17 | export function BullQueueListener(type: keyof QueueListener): MethodDecorator { 18 | return SetMetadata(BULL_LISTENER_DECORATOR, type); 19 | } 20 | 21 | export function BullWorker(options: BullWorkerOptions): ClassDecorator { 22 | return SetMetadata(BULL_WORKER_DECORATOR, options); 23 | } 24 | 25 | export function BullWorkerProcess(options?: WorkerOptions): MethodDecorator { 26 | return SetMetadata(BULL_WORKER_PROCESSOR_DECORATOR, options || {}); 27 | } 28 | 29 | export function BullWorkerListener(type: keyof WorkerListener): MethodDecorator { 30 | return SetMetadata(BULL_LISTENER_DECORATOR, type); 31 | } 32 | 33 | export function BullQueueEvents(options?: BullQueueEventsOptions): ClassDecorator { 34 | return SetMetadata(BULL_QUEUE_EVENTS_DECORATOR, options); 35 | } 36 | 37 | export function BullQueueEventsListener(type: keyof QueueEventsListener): MethodDecorator { 38 | return SetMetadata(BULL_LISTENER_DECORATOR, type); 39 | } 40 | 41 | /** 42 | * 43 | * @deprecated Use BullQueueListener, BullWorkerListener and BullQueueEventsListener instead. 44 | * @export 45 | * @param {keyof QueueEventsListener} type 46 | * @return {*} {MethodDecorator} 47 | */ 48 | export function BullQueueEventProcess(type: keyof QueueEventsListener): MethodDecorator { 49 | return SetMetadata(BULL_LISTENER_DECORATOR, type); 50 | } 51 | 52 | export function BullQueueInject(queueName: string): ParameterDecorator { 53 | return Inject(getBullQueueToken(queueName)); 54 | } 55 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.explorer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { DiscoveryService } from "@nestjs/core"; 3 | import { InstanceWrapper } from "@nestjs/core/injector/instance-wrapper"; 4 | import { MetadataScanner } from "@nestjs/core/metadata-scanner"; 5 | import { 6 | BULL_LISTENER_DECORATOR, 7 | BULL_QUEUE_DECORATOR, 8 | BULL_QUEUE_EVENTS_DECORATOR, 9 | BULL_WORKER_DECORATOR, 10 | BULL_WORKER_PROCESSOR_DECORATOR, 11 | } from "./bull.constants"; 12 | import { 13 | BullExploreResults, 14 | BullQueueEventsMetadata, 15 | BullQueueMetadata, 16 | BullWorkerMetadata, 17 | BullWorkerProcessMetadata, 18 | } from "./interfaces"; 19 | import { BullBaseMetadata, BullProcessMetadata } from "./interfaces/bull-base.interface"; 20 | @Injectable() 21 | export class BullExplorerService { 22 | constructor(private readonly discoveryService: DiscoveryService, private readonly metadataScanner: MetadataScanner) {} 23 | 24 | public explore(): BullExploreResults { 25 | const queues = this.getMetadata(BULL_QUEUE_DECORATOR); 26 | const workers = this.getMetadata(BULL_WORKER_DECORATOR); 27 | const queueEvents = this.getMetadata(BULL_QUEUE_EVENTS_DECORATOR); 28 | 29 | for (const queue of queues) { 30 | queue.events = this.getListeners(queue); 31 | } 32 | 33 | for (const worker of workers) { 34 | worker.processors = this.getWorkerProcessors(worker); 35 | worker.events = this.getListeners(worker); 36 | } 37 | 38 | for (const queueEvent of queueEvents) { 39 | queueEvent.events = this.getListeners(queueEvent); 40 | } 41 | 42 | return { workers, queueEvents, queues }; 43 | } 44 | 45 | private getMetadata>(metadataKey: string): T[] { 46 | const metadata: T[] = []; 47 | for (const classInstance of this.getClassInstances()) { 48 | const options = Reflect.getMetadata(metadataKey, classInstance.constructor); 49 | 50 | if (options) { 51 | metadata.push({ instance: classInstance, options } as T); 52 | } 53 | } 54 | return metadata; 55 | } 56 | 57 | private getClassInstances(): InstanceWrapper[] { 58 | return this.discoveryService 59 | .getProviders() 60 | .filter((instanceWrapper) => instanceWrapper.instance?.constructor) 61 | .map((x) => x.instance); 62 | } 63 | 64 | private getWorkerProcessors(worker: BullWorkerMetadata): BullWorkerProcessMetadata[] { 65 | const instance = worker.instance; 66 | const prototype = Object.getPrototypeOf(instance); 67 | const workerProcessors: BullWorkerProcessMetadata[] = []; 68 | 69 | for (const methodName of this.metadataScanner.getAllFilteredMethodNames(prototype)) { 70 | const options = Reflect.getMetadata(BULL_WORKER_PROCESSOR_DECORATOR, prototype[methodName]); 71 | if (options) { 72 | workerProcessors.push({ processor: prototype[methodName].bind(instance), options }); 73 | } 74 | } 75 | 76 | return workerProcessors; 77 | } 78 | 79 | private getListeners, U extends BullProcessMetadata>(metadata: T): U[] { 80 | const instance = metadata.instance; 81 | const prototype = Object.getPrototypeOf(instance); 82 | const eventListeners: U[] = []; 83 | 84 | for (const methodName of this.metadataScanner.getAllFilteredMethodNames(prototype)) { 85 | const type = Reflect.getMetadata(BULL_LISTENER_DECORATOR, prototype[methodName]); 86 | 87 | if (type) { 88 | eventListeners.push({ 89 | processor: prototype[methodName].bind(instance), 90 | type, 91 | } as U); 92 | } 93 | } 94 | 95 | return eventListeners; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.mock.spec.ts: -------------------------------------------------------------------------------- 1 | import { createQueueEventsMock, createQueueMock, createWorkerMock } from "./bull.mock"; 2 | 3 | describe("createQueueMock", () => { 4 | it("should return mock object", () => { 5 | expect(createQueueMock("test", {})).toStrictEqual({ 6 | add: expect.any(Function), 7 | addBulk: expect.any(Function), 8 | name: "test", 9 | on: expect.any(Function), 10 | waitUntilReady: expect.any(Function), 11 | close: expect.any(Function), 12 | opts: {}, 13 | }); 14 | }); 15 | 16 | it("should call mock functions", async () => { 17 | const queue = createQueueMock("test", {}); 18 | 19 | await queue.add("test", {}); 20 | await queue.addBulk([ 21 | { name: "test1", data: {}, opts: {} }, 22 | { name: "test2", data: {}, opts: {} }, 23 | ]); 24 | queue.on("waiting", () => { 25 | console.log(test); 26 | }); 27 | }); 28 | }); 29 | 30 | describe("createWorkerMock", () => { 31 | it("should return mock object", () => { 32 | expect(createWorkerMock("test")).toStrictEqual({ 33 | name: "test", 34 | on: expect.any(Function), 35 | close: expect.any(Function), 36 | waitUntilReady: expect.any(Function), 37 | }); 38 | }); 39 | 40 | it("should call mock functions", () => { 41 | const worker = createWorkerMock("test"); 42 | worker.on("active", () => { 43 | console.log(test); 44 | }); 45 | }); 46 | }); 47 | 48 | describe("createQueueEventsMock", () => { 49 | it("should return mock object", () => { 50 | expect(createQueueEventsMock("test")).toStrictEqual({ 51 | name: "test", 52 | on: expect.any(Function), 53 | waitUntilReady: expect.any(Function), 54 | close: expect.any(Function), 55 | }); 56 | }); 57 | 58 | it("should call mock functions", () => { 59 | const queueEvent = createQueueEventsMock("test"); 60 | queueEvent.on("active", () => { 61 | console.log(test); 62 | }); 63 | }); 64 | }); 65 | 66 | describe("createJobMock", () => { 67 | it("should return mock object", async () => { 68 | await expect(createQueueMock("test", {}).add("test", {})).resolves.toBeDefined(); 69 | }); 70 | 71 | it("should call mock functions", async () => { 72 | const job = await createQueueMock("test", {}).add("test", {}); 73 | await job.waitUntilFinished(createQueueEventsMock("test")); 74 | await expect(job.isCompleted()).resolves.toBeTruthy(); 75 | await expect(job.isFailed()).resolves.toBeTruthy(); 76 | await expect(job.isActive()).resolves.toBeTruthy(); 77 | await expect(job.isWaiting()).resolves.toBeFalsy(); 78 | await expect(job.getState()).resolves.toBe("completed"); 79 | await expect(job.remove()).resolves.toBeUndefined(); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.mock.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import { Queue, QueueBaseOptions, QueueEvents, QueueEventsListener, Worker } from "bullmq"; 3 | 4 | function createJobMock(...args: any[]): any { 5 | return { 6 | ...args, 7 | waitUntilFinished: (): Promise => Promise.resolve(true), 8 | isCompleted: (): Promise => Promise.resolve(true), 9 | isFailed: (): Promise => Promise.resolve(true), 10 | isActive: (): Promise => Promise.resolve(true), 11 | isWaiting: (): Promise => Promise.resolve(false), 12 | getState: (): Promise => Promise.resolve("completed"), 13 | remove: (): Promise => Promise.resolve(), 14 | close: (): Promise => Promise.resolve(), 15 | }; 16 | } 17 | export function createQueueMock(queueName: string, options: QueueBaseOptions): Queue { 18 | return { 19 | name: queueName, 20 | opts: options, 21 | add: (...args: any[]) => Promise.resolve(createJobMock(...args)), 22 | addBulk: (args: any[]) => Promise.all(args.map((x) => createJobMock(x))), 23 | on: () => {}, 24 | waitUntilReady: (): Promise => Promise.resolve(), 25 | close: (): Promise => Promise.resolve(), 26 | } as any; 27 | } 28 | 29 | export function createWorkerMock(queueName: string): Worker { 30 | return { 31 | name: queueName, 32 | on: () => {}, 33 | waitUntilReady: (): Promise => Promise.resolve(), 34 | close: (): Promise => Promise.resolve(), 35 | } as any; 36 | } 37 | 38 | export function createQueueEventsMock(queueName: string): QueueEvents { 39 | return { 40 | name: queueName, 41 | on: () => {}, 42 | waitUntilReady: (): Promise => Promise.resolve(), 43 | close: (): Promise => Promise.resolve(), 44 | } as any; 45 | } 46 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Global, Module, Provider } from "@nestjs/common"; 2 | import { BullCoreModule } from "./bull-core.module"; 3 | import { createQueueProviders } from "./bull.providers"; 4 | import { BullModuleAsyncOptions, BullModuleOptions, BullQueueOptions } from "./interfaces"; 5 | 6 | @Global() 7 | @Module({}) 8 | export class BullModule { 9 | public static forRoot(options: BullModuleOptions): DynamicModule { 10 | return { 11 | module: BullModule, 12 | imports: [BullCoreModule.forRoot(options)], 13 | }; 14 | } 15 | 16 | public static forRootAsync(options: BullModuleAsyncOptions): DynamicModule { 17 | return { 18 | module: BullModule, 19 | imports: [BullCoreModule.forRootAsync(options)], 20 | }; 21 | } 22 | 23 | public static registerQueue(...queues: (string | BullQueueOptions)[]): DynamicModule { 24 | const queueProviders: Provider[] = createQueueProviders(queues); 25 | return { 26 | module: BullModule, 27 | providers: queueProviders, 28 | exports: queueProviders, 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.providers.spec.ts: -------------------------------------------------------------------------------- 1 | import { BULL_MODULE_OPTIONS } from "./bull.constants"; 2 | import { createAsyncOptionsProvider, createAsyncProviders, createQueueEvents } from "./bull.providers"; 3 | import { BullModuleOptions, BullModuleOptionsFactory } from "./interfaces"; 4 | 5 | describe("createQueueEvents", () => { 6 | it("should create mock object", async () => { 7 | await expect(createQueueEvents("test", {}, true)).resolves.toStrictEqual({ 8 | name: "test", 9 | on: expect.any(Function), 10 | close: expect.any(Function), 11 | waitUntilReady: expect.any(Function), 12 | }); 13 | }); 14 | }); 15 | 16 | describe("createAsyncOptionsProvider", () => { 17 | it("should create factory provider: useFactory", () => { 18 | expect(createAsyncOptionsProvider({ useFactory: () => ({}) })).toStrictEqual({ 19 | inject: [], 20 | provide: BULL_MODULE_OPTIONS, 21 | useFactory: expect.any(Function), 22 | }); 23 | }); 24 | 25 | it("should create factory provider: useClass", async () => { 26 | class TestClass implements BullModuleOptionsFactory { 27 | createBullModuleOptions(): BullModuleOptions { 28 | return {}; 29 | } 30 | } 31 | const provider = createAsyncOptionsProvider({ useClass: TestClass }); 32 | 33 | expect(provider).toStrictEqual({ 34 | inject: [expect.any(Function)], 35 | provide: BULL_MODULE_OPTIONS, 36 | useFactory: expect.any(Function), 37 | }); 38 | 39 | await provider.useFactory(new TestClass()); 40 | }); 41 | 42 | it("should create factory provider: useExisting", async () => { 43 | class TestClass implements BullModuleOptionsFactory { 44 | createBullModuleOptions(): BullModuleOptions { 45 | return {}; 46 | } 47 | } 48 | const provider = createAsyncOptionsProvider({ useExisting: TestClass }); 49 | 50 | expect(provider).toStrictEqual({ 51 | inject: [expect.any(Function)], 52 | provide: BULL_MODULE_OPTIONS, 53 | useFactory: expect.any(Function), 54 | }); 55 | 56 | await provider.useFactory(new TestClass()); 57 | }); 58 | }); 59 | 60 | describe("createAsyncProviders", () => { 61 | it("should get providers", () => { 62 | class TestClass implements BullModuleOptionsFactory { 63 | createBullModuleOptions(): BullModuleOptions { 64 | return {}; 65 | } 66 | } 67 | expect(createAsyncProviders({ useClass: TestClass })).toStrictEqual([ 68 | { inject: [TestClass], provide: BULL_MODULE_OPTIONS, useFactory: expect.any(Function) }, 69 | { provide: TestClass, useClass: TestClass }, 70 | ]); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.providers.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Type } from "@nestjs/common"; 2 | import { ClassProvider, FactoryProvider } from "@nestjs/common/interfaces"; 3 | import { Processor, Queue, QueueBase, QueueBaseOptions, QueueEvents, Worker } from "bullmq"; 4 | import { BullService, getBullQueueToken } from "."; 5 | import { BULL_MODULE_OPTIONS } from "./bull.constants"; 6 | import { createQueueEventsMock, createQueueMock, createWorkerMock } from "./bull.mock"; 7 | import { mergeQueueBaseOptions } from "./bull.utils"; 8 | import { BullModuleAsyncOptions, BullModuleOptions, BullModuleOptionsFactory, BullQueueOptions } from "./interfaces"; 9 | 10 | async function createQueueBase( 11 | createMockFunction: () => T, 12 | createFunction: () => T, 13 | mock: boolean, 14 | ): Promise { 15 | if (mock) { 16 | return createMockFunction(); 17 | } 18 | const queueBase = createFunction(); 19 | await queueBase.waitUntilReady(); 20 | return queueBase; 21 | } 22 | 23 | export async function createQueue(queueName: string, options: QueueBaseOptions, mock = false): Promise { 24 | return createQueueBase( 25 | () => createQueueMock(queueName, options), 26 | () => new Queue(queueName, options), 27 | mock, 28 | ); 29 | } 30 | 31 | export async function createWorker( 32 | queueName: string, 33 | processor: Processor, 34 | options: QueueBaseOptions, 35 | mock = false, 36 | ): Promise { 37 | return createQueueBase( 38 | () => createWorkerMock(queueName), 39 | () => new Worker(queueName, processor, options), 40 | mock, 41 | ); 42 | } 43 | export async function createQueueEvents( 44 | queueName: string, 45 | options: QueueBaseOptions, 46 | mock = false, 47 | ): Promise { 48 | return createQueueBase( 49 | () => createQueueEventsMock(queueName), 50 | () => new QueueEvents(queueName, options), 51 | mock, 52 | ); 53 | } 54 | 55 | export function createAsyncOptionsProvider(options: BullModuleAsyncOptions): FactoryProvider { 56 | if (options.useFactory) { 57 | return { 58 | inject: options.inject || [], 59 | provide: BULL_MODULE_OPTIONS, 60 | useFactory: options.useFactory, 61 | }; 62 | } 63 | return { 64 | inject: [options.useClass || options.useExisting].filter( 65 | (x): x is Type => x !== undefined, 66 | ), 67 | provide: BULL_MODULE_OPTIONS, 68 | useFactory: async (optionsFactory?: BullModuleOptionsFactory): Promise => { 69 | if (!optionsFactory) { 70 | return {}; 71 | } 72 | return optionsFactory.createBullModuleOptions(); 73 | }, 74 | }; 75 | } 76 | 77 | export function createAsyncProviders(options: BullModuleAsyncOptions): Provider[] { 78 | const asyncOptionsProvider = createAsyncOptionsProvider(options); 79 | if (options.useExisting || options.useFactory) { 80 | return [asyncOptionsProvider]; 81 | } 82 | 83 | const providers: Provider[] = [asyncOptionsProvider]; 84 | 85 | if (options.useClass) { 86 | providers.push({ 87 | provide: options.useClass, 88 | useClass: options.useClass, 89 | } as ClassProvider); 90 | } 91 | 92 | return providers; 93 | } 94 | 95 | export function createQueueProviders(queues: (string | BullQueueOptions)[]): Provider[] { 96 | return queues.map((queue) => { 97 | const queueName = typeof queue === "string" ? queue : queue.queueName; 98 | const queueOptions = typeof queue === "string" ? {} : queue.options || {}; 99 | return { 100 | provide: getBullQueueToken(queueName), 101 | useFactory: async (options: BullModuleOptions, service: BullService): Promise => { 102 | const mergedOptions = mergeQueueBaseOptions(options?.options, queueOptions); 103 | const queueInstance = await createQueue(queueName, mergedOptions, options.mock); 104 | service.queues[queueName] = queueInstance; 105 | return queueInstance; 106 | }, 107 | inject: [BULL_MODULE_OPTIONS, BullService], 108 | } as Provider; 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { Queue, QueueEvents, Worker } from "bullmq"; 3 | @Injectable() 4 | export class BullService { 5 | public queues: Record = {}; 6 | 7 | public workers: Record = {}; 8 | 9 | public queueEvents: Record = {}; 10 | 11 | public async waitUntilReady(): Promise { 12 | for (const instance of [ 13 | ...Object.values(this.queues), 14 | ...Object.values(this.workers), 15 | ...Object.values(this.queueEvents), 16 | ]) { 17 | await instance.waitUntilReady(); 18 | } 19 | } 20 | 21 | public async close(): Promise { 22 | for (const instance of [ 23 | ...Object.values(this.queues), 24 | ...Object.values(this.workers), 25 | ...Object.values(this.queueEvents), 26 | ]) { 27 | await instance.close(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/bullmq/src/bull.utils.ts: -------------------------------------------------------------------------------- 1 | import { QueueBaseOptions, QueueEvents, QueueEventsOptions } from "bullmq"; 2 | import Redis from "ioredis"; 3 | 4 | export function getBullQueueToken(name: string): string { 5 | return `_BullQueue_${name}`; 6 | } 7 | 8 | export function mergeQueueBaseOptions(...options: (QueueBaseOptions | undefined)[]): QueueBaseOptions { 9 | const opts = options.filter((x): x is QueueBaseOptions => x !== undefined) as QueueEventsOptions[]; 10 | const obj = Object.assign({}, ...opts); 11 | 12 | if (obj?.connection?.options) { 13 | // IORedis object, but 'instanceof Redis' returns false. 14 | // for now, it uses last connection object. 15 | obj.connection = opts 16 | .reverse() 17 | .map((x) => x?.connection) 18 | .find((connection) => connection instanceof Redis); 19 | } 20 | 21 | return obj; 22 | } 23 | 24 | export async function createQueueEvents(queueName: string): Promise { 25 | const qe = new QueueEvents(queueName); 26 | await qe.waitUntilReady(); 27 | return qe; 28 | } 29 | 30 | export const wait = async (timer: number): Promise => 31 | await new Promise((resolve): any => 32 | setTimeout((): void => { 33 | resolve(); 34 | }, timer), 35 | ); 36 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/1.basic.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bullmq"; 4 | import { BullQueueInject, BullWorker, BullWorkerProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { createQueueEvents } from "../bull.utils"; 7 | 8 | const queueName = "basicExample"; 9 | 10 | @BullWorker({ queueName }) 11 | export class TestBullWorker { 12 | @BullWorkerProcess() 13 | public async process(job: Job): Promise<{ status: string }> { 14 | expect(job.data).toStrictEqual({ test: "test" }); 15 | return { status: "ok" }; 16 | } 17 | } 18 | 19 | @Injectable() 20 | export class TestService { 21 | constructor(@BullQueueInject(queueName) public readonly queue: Queue) {} 22 | 23 | public async addJob(): Promise { 24 | return this.queue.add("job", { test: "test" }); 25 | } 26 | } 27 | 28 | @Module({ 29 | imports: [BullModule.registerQueue(queueName)], 30 | providers: [TestBullWorker, TestService], 31 | }) 32 | export class TestModule {} 33 | 34 | @Module({ 35 | imports: [BullModule.forRootAsync({}), TestModule], 36 | }) 37 | export class ApplicationModule {} 38 | 39 | describe("Basic Example", () => { 40 | it("test", async () => { 41 | const app = await Test.createTestingModule({ 42 | imports: [ApplicationModule], 43 | }).compile(); 44 | await app.init(); 45 | const service = app.get(TestService); 46 | expect(service).toBeDefined(); 47 | expect(service.queue).toBeDefined(); 48 | const job = await service.addJob(); 49 | const events = await createQueueEvents(queueName); 50 | await expect(job.waitUntilFinished(events)).resolves.toStrictEqual({ status: "ok" }); 51 | await events.close(); 52 | await app.close(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/2.shared-ioredis-connection.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bullmq"; 4 | import IORedis from "ioredis"; 5 | import { BullQueueInject, BullWorker, BullWorkerProcess } from "../bull.decorator"; 6 | import { BullModule } from "../bull.module"; 7 | import { createQueueEvents, wait } from "../bull.utils"; 8 | const queueName = "sharedConnectionExample"; 9 | 10 | @BullWorker({ queueName }) 11 | export class TestBullWorker { 12 | @BullWorkerProcess() 13 | public async process(job: Job): Promise<{ status: string }> { 14 | expect(job.data).toStrictEqual({ test: "test" }); 15 | return { status: "ok" }; 16 | } 17 | } 18 | 19 | @Injectable() 20 | export class TestService { 21 | constructor(@BullQueueInject(queueName) public readonly queue: Queue) {} 22 | 23 | public async addJob(): Promise { 24 | return this.queue.add("job", { test: "test" }); 25 | } 26 | } 27 | 28 | @Module({ 29 | imports: [BullModule.registerQueue(queueName)], 30 | providers: [TestBullWorker, TestService], 31 | }) 32 | export class TestModule {} 33 | 34 | const connection = new IORedis(); 35 | @Module({ 36 | imports: [ 37 | BullModule.forRoot({ 38 | options: { connection }, 39 | }), 40 | TestModule, 41 | ], 42 | }) 43 | export class ApplicationModule {} 44 | 45 | // https://docs.bullmq.io/guide/connections 46 | describe("Shared IORedis connection", () => { 47 | it("test", async () => { 48 | const app = await Test.createTestingModule({ 49 | imports: [ApplicationModule], 50 | }).compile(); 51 | await app.init(); 52 | const service = app.get(TestService); 53 | expect(service).toBeDefined(); 54 | expect(service.queue).toBeDefined(); 55 | await wait(1000); 56 | 57 | const job = await service.addJob(); 58 | const events = await createQueueEvents(queueName); 59 | await expect(job.waitUntilFinished(events)).resolves.toStrictEqual({ status: "ok" }); 60 | await events.close(); 61 | await app.close(); 62 | await connection.quit(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/3.queue-events-decorator.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bullmq"; 4 | import { 5 | BullQueueEvents, 6 | BullQueueEventsListener, 7 | BullQueueInject, 8 | BullWorker, 9 | BullWorkerProcess, 10 | } from "../bull.decorator"; 11 | import { BullModule } from "../bull.module"; 12 | import { BullService } from "../bull.service"; 13 | import { wait } from "../bull.utils"; 14 | import { BullQueueEventsListenerArgs } from "../interfaces"; 15 | const queueName = "queueEventDecoratorExample"; 16 | const calledEvents = jest.fn(); 17 | 18 | @BullWorker({ queueName }) 19 | export class TestBullWorker { 20 | @BullWorkerProcess() 21 | public async process(job: Job): Promise<{ status: string }> { 22 | expect(job.data).toStrictEqual({ test: "test" }); 23 | await wait(500); 24 | await job.updateProgress(50); 25 | await wait(500); 26 | return { status: "ok" }; 27 | } 28 | } 29 | 30 | @BullQueueEvents({ queueName }) 31 | export class TestBullQueueEvents { 32 | @BullQueueEventsListener("added") 33 | public async added(args: BullQueueEventsListenerArgs["added"]): Promise { 34 | calledEvents("added"); 35 | console.debug(`[${args.jobId}] added`); 36 | } 37 | 38 | @BullQueueEventsListener("active") 39 | public async active(args: BullQueueEventsListenerArgs["active"]): Promise { 40 | calledEvents("active"); 41 | console.debug(`[${args.jobId}] active`); 42 | } 43 | 44 | @BullQueueEventsListener("cleaned") 45 | public async cleaned(args: BullQueueEventsListenerArgs["cleaned"]): Promise { 46 | calledEvents("cleaned"); 47 | console.debug(`[${args.count}] cleaned`); 48 | } 49 | 50 | @BullQueueEventsListener("completed") 51 | public async completed(args: BullQueueEventsListenerArgs["completed"]): Promise { 52 | calledEvents("completed"); 53 | console.debug(`[${args.jobId}] completed`); 54 | } 55 | 56 | @BullQueueEventsListener("delayed") 57 | public async delayed(args: BullQueueEventsListenerArgs["delayed"]): Promise { 58 | calledEvents("delayed"); 59 | console.debug(`[${args.jobId}] delayed`); 60 | } 61 | 62 | @BullQueueEventsListener("drained") 63 | public async drained(id: BullQueueEventsListenerArgs["drained"]): Promise { 64 | calledEvents("drained"); 65 | console.debug(`[${id}] drained`); 66 | } 67 | 68 | @BullQueueEventsListener("error") 69 | public async error(error: BullQueueEventsListenerArgs["error"]): Promise { 70 | calledEvents("error"); 71 | console.debug(`[${error}] error`); 72 | } 73 | 74 | @BullQueueEventsListener("failed") 75 | public async failed(args: BullQueueEventsListenerArgs["failed"]): Promise { 76 | calledEvents("failed"); 77 | console.debug(`[${args.jobId}] failed`); 78 | } 79 | 80 | @BullQueueEventsListener("paused") 81 | public async paused(args: BullQueueEventsListenerArgs["paused"]): Promise { 82 | calledEvents("paused"); 83 | console.debug(`[${args}] paused`); 84 | } 85 | 86 | @BullQueueEventsListener("progress") 87 | public async progress(args: BullQueueEventsListenerArgs["progress"]): Promise { 88 | calledEvents("progress"); 89 | console.debug(`[${args.jobId}] waiting`); 90 | } 91 | 92 | @BullQueueEventsListener("removed") 93 | public async removed(args: BullQueueEventsListenerArgs["removed"]): Promise { 94 | calledEvents("removed"); 95 | console.debug(`[${args.jobId}] removed`); 96 | } 97 | 98 | @BullQueueEventsListener("waiting") 99 | public async waiting(args: BullQueueEventsListenerArgs["waiting"]): Promise { 100 | calledEvents("waiting"); 101 | console.debug(`[${args.jobId}] waiting`); 102 | } 103 | } 104 | 105 | @Injectable() 106 | export class TestService { 107 | constructor(@BullQueueInject(queueName) public readonly queue: Queue) {} 108 | 109 | public async addJob(): Promise { 110 | return this.queue.add("job", { test: "test" }); 111 | } 112 | } 113 | 114 | @Module({ 115 | imports: [BullModule.registerQueue(queueName)], 116 | providers: [TestBullWorker, TestService, TestBullQueueEvents], 117 | }) 118 | export class TestModule {} 119 | 120 | @Module({ 121 | imports: [BullModule.forRoot({}), TestModule], 122 | }) 123 | export class ApplicationModule {} 124 | 125 | // https://docs.bullmq.io/guide/connections 126 | describe("QueueEvents decorator", () => { 127 | it("test", async () => { 128 | const app = await Test.createTestingModule({ 129 | imports: [ApplicationModule], 130 | }).compile(); 131 | await app.init(); 132 | const service = app.get(TestService); 133 | expect(service).toBeDefined(); 134 | expect(service.queue).toBeDefined(); 135 | const job = await service.addJob(); 136 | 137 | const bullService = app.get(BullService); 138 | expect(bullService).toBeDefined(); 139 | const qe = bullService.queueEvents[queueName]; 140 | await expect(job.waitUntilFinished(qe)).resolves.toStrictEqual({ status: "ok" }); 141 | await job.remove(); 142 | await wait(1000); 143 | 144 | expect(calledEvents.mock.calls).toEqual([["active"], ["progress"], ["completed"], ["drained"], ["removed"]]); 145 | 146 | await app.close(); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/4.queue-decorator.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job } from "bullmq"; 4 | import { BullQueueListenerArgs } from ".."; 5 | import { BullQueue, BullQueueListener, BullWorker, BullWorkerProcess } from "../bull.decorator"; 6 | import { BullModule } from "../bull.module"; 7 | import { BullService } from "../bull.service"; 8 | import { createQueueEvents, wait } from "../bull.utils"; 9 | const queueName = "queueDecoratorExample"; 10 | const calledEvents = jest.fn(); 11 | 12 | @BullQueue({ queueName }) 13 | export class TestBullQueue { 14 | @BullQueueListener("waiting") 15 | public async waiting(job: BullQueueListenerArgs["waiting"]): Promise { 16 | calledEvents("waiting"); 17 | console.debug(`[${job.id}] waiting`); 18 | } 19 | } 20 | 21 | @BullWorker({ queueName }) 22 | export class TestBullWorker { 23 | @BullWorkerProcess() 24 | public async process(job: Job): Promise<{ status: string }> { 25 | expect(job.data).toStrictEqual({ test: "test" }); 26 | 27 | await job.updateProgress(50); 28 | await wait(500); 29 | return { status: "ok" }; 30 | } 31 | } 32 | 33 | @Injectable() 34 | export class TestService { 35 | constructor(public readonly service: BullService) {} 36 | 37 | public async addJob(): Promise { 38 | return this.service.queues[queueName]?.add("job", { test: "test" }); 39 | } 40 | } 41 | 42 | @Module({ 43 | providers: [TestBullWorker, TestService, TestBullQueue], 44 | }) 45 | export class TestModule {} 46 | 47 | @Module({ 48 | imports: [BullModule.forRoot({}), TestModule], 49 | }) 50 | export class ApplicationModule {} 51 | 52 | // https://docs.bullmq.io/guide/connections 53 | describe("Queue decorator", () => { 54 | it("test", async () => { 55 | const app = await Test.createTestingModule({ 56 | imports: [ApplicationModule], 57 | }).compile(); 58 | await app.init(); 59 | const service = app.get(TestService); 60 | expect(service).toBeDefined(); 61 | expect(service.service).toBeDefined(); 62 | const job = await service.addJob(); 63 | const events = await createQueueEvents(queueName); 64 | await expect(job.waitUntilFinished(events)).resolves.toStrictEqual({ status: "ok" }); 65 | await events.close(); 66 | expect(calledEvents.mock.calls).toEqual([["waiting"]]); 67 | 68 | await app.close(); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/5.worker-decorator.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job } from "bullmq"; 4 | import { BullWorkerListener, BullWorkerListenerArgs } from ".."; 5 | import { BullWorker, BullWorkerProcess } from "../bull.decorator"; 6 | import { BullModule } from "../bull.module"; 7 | import { BullService } from "../bull.service"; 8 | import { createQueueEvents, wait } from "../bull.utils"; 9 | const queueName = "workerDecoratorExample"; 10 | const calledEvents = jest.fn(); 11 | @BullWorker({ queueName }) 12 | export class TestBullWorker { 13 | @BullWorkerProcess() 14 | public async process(job: Job): Promise<{ status: string }> { 15 | expect(job.data).toStrictEqual({ test: "test" }); 16 | return { status: "ok" }; 17 | } 18 | 19 | @BullWorkerListener("completed") 20 | public async completed(job: BullWorkerListenerArgs["completed"]): Promise { 21 | calledEvents("completed"); 22 | console.debug(`[${job.id}] completed`); 23 | } 24 | } 25 | 26 | @Injectable() 27 | export class TestService { 28 | constructor(public readonly service: BullService) {} 29 | 30 | public async addJob(): Promise { 31 | return this.service.queues[queueName]?.add("job", { test: "test" }); 32 | } 33 | } 34 | 35 | @Module({ 36 | imports: [BullModule.registerQueue(queueName)], 37 | providers: [TestBullWorker, TestService], 38 | }) 39 | export class TestModule {} 40 | 41 | @Module({ 42 | imports: [BullModule.forRoot({}), TestModule], 43 | }) 44 | export class ApplicationModule {} 45 | 46 | // https://docs.bullmq.io/guide/connections 47 | describe("Worker decorator", () => { 48 | it("test", async () => { 49 | const app = await Test.createTestingModule({ 50 | imports: [ApplicationModule], 51 | }).compile(); 52 | await app.init(); 53 | const service = app.get(TestService); 54 | expect(service).toBeDefined(); 55 | expect(service.service).toBeDefined(); 56 | const job = await service.addJob(); 57 | const events = await createQueueEvents(queueName); 58 | await expect(job.waitUntilFinished(events)).resolves.toStrictEqual({ status: "ok" }); 59 | await wait(500); 60 | expect(calledEvents.mock.calls).toEqual([["completed"]]); 61 | await events.close(); 62 | await app.close(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/6.async.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bullmq"; 4 | import { BullQueueInject, BullWorker, BullWorkerProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { createQueueEvents } from "../bull.utils"; 7 | 8 | const queueName = "asyncExampleExample"; 9 | 10 | @BullWorker({ queueName }) 11 | export class TestBullWorker { 12 | @BullWorkerProcess() 13 | public async process(job: Job): Promise<{ status: string }> { 14 | expect(job.data).toStrictEqual({ test: "test" }); 15 | return { status: "ok" }; 16 | } 17 | } 18 | 19 | @Injectable() 20 | export class TestService { 21 | constructor(@BullQueueInject(queueName) public readonly queue: Queue) {} 22 | 23 | public async addJob(): Promise { 24 | return this.queue.add("job", { test: "test" }); 25 | } 26 | } 27 | 28 | @Module({ 29 | imports: [BullModule.registerQueue(queueName)], 30 | providers: [TestBullWorker, TestService], 31 | }) 32 | export class TestModule {} 33 | 34 | @Module({ 35 | imports: [ 36 | BullModule.forRootAsync({ 37 | useFactory: () => { 38 | return {}; 39 | }, 40 | }), 41 | TestModule, 42 | ], 43 | }) 44 | export class ApplicationModule {} 45 | 46 | describe("Async Example", () => { 47 | it("test", async () => { 48 | const app = await Test.createTestingModule({ 49 | imports: [ApplicationModule], 50 | }).compile(); 51 | await app.init(); 52 | const service = app.get(TestService); 53 | expect(service).toBeDefined(); 54 | expect(service.queue).toBeDefined(); 55 | const job = await service.addJob(); 56 | const events = await createQueueEvents(queueName); 57 | await expect(job.waitUntilFinished(events)).resolves.toStrictEqual({ status: "ok" }); 58 | await events.close(); 59 | await app.close(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/7.delayed-job.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bullmq"; 4 | import { BullQueueInject, BullWorker, BullWorkerProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { createQueueEvents } from "../bull.utils"; 7 | 8 | const queueName = "delayedJobExample"; 9 | 10 | @BullWorker({ queueName }) 11 | export class TestBullWorker { 12 | @BullWorkerProcess() 13 | public async process(job: Job): Promise<{ status: string }> { 14 | expect(job.data).toStrictEqual({ test: "test" }); 15 | return { status: "ok" }; 16 | } 17 | } 18 | 19 | @Injectable() 20 | export class TestService { 21 | constructor(@BullQueueInject(queueName) public readonly queue: Queue) {} 22 | 23 | public async addJob(): Promise { 24 | return this.queue.add("test", { test: "test" }, { delay: 5000 }); 25 | } 26 | } 27 | 28 | @Module({ 29 | imports: [BullModule.registerQueue(queueName)], 30 | providers: [TestBullWorker, TestService], 31 | }) 32 | export class TestModule {} 33 | 34 | @Module({ 35 | imports: [BullModule.forRoot({}), TestModule], 36 | }) 37 | export class ApplicationModule {} 38 | 39 | describe("Basic Example", () => { 40 | it("test", async () => { 41 | const app = await Test.createTestingModule({ 42 | imports: [ApplicationModule], 43 | }).compile(); 44 | await app.init(); 45 | const service = app.get(TestService); 46 | expect(service).toBeDefined(); 47 | expect(service.queue).toBeDefined(); 48 | const job = await service.addJob(); 49 | const start = Date.now(); 50 | const events = await createQueueEvents(queueName); 51 | await expect(job.waitUntilFinished(events)).resolves.toStrictEqual({ status: "ok" }); 52 | expect(Date.now() - start).toBeGreaterThanOrEqual(5000); 53 | await events.close(); 54 | await app.close(); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/8.flow.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { FlowProducer, Job } from "bullmq"; 4 | import { BullWorker, BullWorkerProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { createQueueEvents } from "../bull.utils"; 7 | 8 | const queueName = "flowExample"; 9 | 10 | const results: string[] = []; 11 | 12 | @BullWorker({ queueName }) 13 | export class TestBullWorker { 14 | @BullWorkerProcess() 15 | public async process(job: Job): Promise<{ status: string }> { 16 | results.push(job.data); 17 | return { status: "ok" }; 18 | } 19 | } 20 | 21 | @Module({ 22 | imports: [BullModule.registerQueue(queueName)], 23 | providers: [TestBullWorker], 24 | }) 25 | export class TestModule {} 26 | 27 | @Module({ 28 | imports: [BullModule.forRootAsync({}), TestModule], 29 | }) 30 | export class ApplicationModule {} 31 | 32 | describe("Flow Example", () => { 33 | it("test", async () => { 34 | const app = await Test.createTestingModule({ 35 | imports: [ApplicationModule], 36 | }).compile(); 37 | await app.init(); 38 | const flow = new FlowProducer({}); 39 | const jobNode = await flow.add({ 40 | name: "flow-test", 41 | data: "parent-data", 42 | queueName, 43 | children: [ 44 | { 45 | name: "child-job", 46 | data: "child-data", 47 | queueName, 48 | children: [{ name: "child-job", data: "child-child-data", queueName }], 49 | }, 50 | ], 51 | }); 52 | const events = await createQueueEvents(queueName); 53 | await expect(jobNode.job.waitUntilFinished(events)).resolves.toStrictEqual({ 54 | status: "ok", 55 | }); 56 | expect(results).toEqual(["child-child-data", "child-data", "parent-data"]); 57 | await events.close(); 58 | await flow.close(); 59 | await app.close(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/bullmq/src/examples/9.mock.example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { Job, Queue } from "bullmq"; 4 | import { BullQueueInject, BullWorker, BullWorkerProcess } from "../bull.decorator"; 5 | import { BullModule } from "../bull.module"; 6 | import { createQueueEvents } from "../bull.utils"; 7 | const queueName = "mockExample"; 8 | 9 | @BullWorker({ queueName }) 10 | export class TestBullWorker { 11 | @BullWorkerProcess() 12 | public async process(job: Job): Promise<{ status: string }> { 13 | expect(job.data).toStrictEqual({ test: "test" }); 14 | return { status: "ok" }; 15 | } 16 | } 17 | 18 | @Injectable() 19 | export class TestService { 20 | constructor(@BullQueueInject(queueName) public readonly queue: Queue) {} 21 | 22 | public async addJob(): Promise { 23 | return this.queue.add("job", { test: "test" }); 24 | } 25 | } 26 | 27 | @Module({ 28 | imports: [BullModule.registerQueue(queueName)], 29 | providers: [TestBullWorker, TestService], 30 | }) 31 | export class TestModule {} 32 | 33 | @Module({ 34 | imports: [ 35 | BullModule.forRoot({ 36 | mock: true, 37 | }), 38 | TestModule, 39 | ], 40 | }) 41 | export class ApplicationModule {} 42 | 43 | describe("Mock Example", () => { 44 | it("test", async () => { 45 | const app = await Test.createTestingModule({ 46 | imports: [ApplicationModule], 47 | }).compile(); 48 | await app.init(); 49 | const service = app.get(TestService); 50 | expect(service).toBeDefined(); 51 | expect(service.queue).toBeDefined(); 52 | const job = await service.addJob(); 53 | const events = await createQueueEvents(queueName); 54 | await job.waitUntilFinished(events); 55 | await events.close(); 56 | await app.close(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/bullmq/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | BullQueue, 3 | BullQueueEventProcess, 4 | BullQueueEvents, 5 | BullQueueEventsListener, 6 | BullQueueInject, 7 | BullQueueListener, 8 | BullWorker, 9 | BullWorkerListener, 10 | BullWorkerProcess, 11 | } from "./bull.decorator"; 12 | export { BullModule } from "./bull.module"; 13 | export { BullService } from "./bull.service"; 14 | export { getBullQueueToken } from "./bull.utils"; 15 | export { 16 | BullModuleAsyncOptions, 17 | BullModuleOptions, 18 | BullModuleOptionsFactory, 19 | BullQueueEventsListenerArgs, 20 | BullQueueEventsOptions, 21 | BullQueueListenerArgs, 22 | BullQueueOptions, 23 | BullWorkerListenerArgs, 24 | BullWorkerOptions, 25 | } from "./interfaces"; 26 | -------------------------------------------------------------------------------- /packages/bullmq/src/interfaces/bull-base.interface.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common/interfaces"; 2 | import { Processor } from "bullmq"; 3 | 4 | export interface BullBaseMetadata { 5 | instance: Injectable; 6 | options: Options; 7 | events: EventType[]; 8 | } 9 | 10 | export interface BullProcessMetadata { 11 | type: Type; 12 | processor: Processor | any; 13 | } 14 | -------------------------------------------------------------------------------- /packages/bullmq/src/interfaces/bull-module.interface.ts: -------------------------------------------------------------------------------- 1 | import { Type } from "@nestjs/common"; 2 | import { ModuleMetadata } from "@nestjs/common/interfaces"; 3 | import { QueueBaseOptions } from "bullmq"; 4 | import { BullQueueEventsMetadata } from "./bull-queue-events.interface"; 5 | import { BullQueueMetadata } from "./bull-queue.interface"; 6 | import { BullWorkerMetadata } from "./bull-worker.interface"; 7 | 8 | /** 9 | * Module interfaces 10 | */ 11 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 12 | export interface BullModuleOptions { 13 | options?: QueueBaseOptions; 14 | 15 | /** 16 | * Set true if you don't want to create {@link Queue}/{@link Worker}/{@link QueueEvents} object. 17 | * If you set to true, module create mock object for them 18 | * @type {boolean} 19 | * @memberof BullModuleOptions 20 | */ 21 | mock?: boolean; 22 | } 23 | 24 | export interface BullModuleAsyncOptions extends Pick { 25 | useClass?: Type; 26 | useExisting?: Type; 27 | useFactory?: (...args: any[]) => Promise | BullModuleOptions; 28 | inject?: Array | string | any>; 29 | } 30 | 31 | export interface BullModuleOptionsFactory { 32 | createBullModuleOptions(): Promise | BullModuleOptions; 33 | } 34 | 35 | export interface BullExploreResults { 36 | queues: BullQueueMetadata[]; 37 | workers: BullWorkerMetadata[]; 38 | queueEvents: BullQueueEventsMetadata[]; 39 | } 40 | -------------------------------------------------------------------------------- /packages/bullmq/src/interfaces/bull-queue-events.interface.ts: -------------------------------------------------------------------------------- 1 | import { QueueEventsListener, QueueEventsOptions } from "bullmq"; 2 | import { BullBaseMetadata, BullProcessMetadata } from "./bull-base.interface"; 3 | 4 | /** 5 | * Queue events options 6 | */ 7 | export interface BullQueueEventsOptions { 8 | queueName: string; 9 | options?: QueueEventsOptions; 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 13 | export interface BullQueueEventsMetadata 14 | extends BullBaseMetadata> {} 15 | 16 | export type BullQueueEventsListenerArgs = { 17 | [key in keyof QueueEventsListener]: Parameters[0]; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/bullmq/src/interfaces/bull-queue.interface.ts: -------------------------------------------------------------------------------- 1 | import { QueueListener, QueueOptions } from "bullmq"; 2 | import { BullBaseMetadata, BullProcessMetadata } from "./bull-base.interface"; 3 | 4 | /** 5 | * Queue interfaces 6 | */ 7 | export interface BullQueueOptions { 8 | queueName: string; 9 | options?: QueueOptions; 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 13 | export interface BullQueueMetadata 14 | extends BullBaseMetadata>> {} 15 | 16 | export type BullQueueListenerArgs = { 17 | [key in keyof QueueListener]: Parameters< 18 | QueueListener[key] 19 | >[0]; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/bullmq/src/interfaces/bull-worker.interface.ts: -------------------------------------------------------------------------------- 1 | import { Processor, WorkerListener, WorkerOptions } from "bullmq"; 2 | import { BullBaseMetadata, BullProcessMetadata } from "./bull-base.interface"; 3 | 4 | /** 5 | * Worker interfaces 6 | */ 7 | export interface BullWorkerOptions { 8 | queueName: string; 9 | options?: WorkerOptions; 10 | } 11 | 12 | /** 13 | * Worker process interfaces 14 | */ 15 | 16 | export interface BullWorkerProcessMetadata { 17 | processor: Processor; 18 | options: WorkerOptions; 19 | } 20 | export interface BullWorkerMetadata 21 | extends BullBaseMetadata> { 22 | processors: BullWorkerProcessMetadata[]; 23 | } 24 | 25 | export type BullWorkerListenerArgs = { 26 | [key in keyof WorkerListener]: Parameters[0]; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/bullmq/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bull-module.interface"; 2 | export * from "./bull-queue-events.interface"; 3 | export * from "./bull-queue.interface"; 4 | export * from "./bull-worker.interface"; 5 | -------------------------------------------------------------------------------- /packages/bullmq/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts", "dist"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/bullmq/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "esModuleInterop": true, 7 | "declarationMap": true 8 | }, 9 | "exclude": ["node_modules", "dist"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/terminus/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const baseESLintConfig = require("../../.eslintrc"); 2 | 3 | module.exports = { ...baseESLintConfig }; 4 | -------------------------------------------------------------------------------- /packages/terminus/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/**/* 3 | dist/**/*.tsbuildinfo 4 | dist/**/*.js.map 5 | dist/**/*.ts.map 6 | -------------------------------------------------------------------------------- /packages/terminus/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const basePrettierConfig = require("../../.prettierrc"); 2 | 3 | module.exports = { 4 | ...basePrettierConfig, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/terminus/README.md: -------------------------------------------------------------------------------- 1 | # @anchan828/nest-bull-terminus 2 | 3 | ![npm](https://img.shields.io/npm/v/@anchan828/nest-bull-terminus.svg) 4 | ![NPM](https://img.shields.io/npm/l/@anchan828/nest-bull-terminus.svg) 5 | 6 | ## Description 7 | 8 | The terminus of The [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest). 9 | 10 | ## Installation 11 | 12 | ```bash 13 | $ npm i --save @anchan828/nest-bull-terminus @nestjs/terminus @anchan828/nest-bull bull 14 | $ npm i --save-dev @types/bull 15 | ``` 16 | 17 | ## Quick Start 18 | 19 | ```ts 20 | import { BullHealthCheckQueue, BullHealthIndicator, BullHealthModule } from "@anchan828/nest-bull-terminus"; 21 | 22 | @Controller("/health") 23 | class BullHealthController { 24 | constructor(private health: HealthCheckService, private bull: BullHealthIndicator) {} 25 | 26 | @Get() 27 | @HealthCheck() 28 | check() { 29 | return this.health.check([() => this.bull.isHealthy()]); 30 | } 31 | } 32 | 33 | @Module({ 34 | controllers: [BullHealthController], 35 | imports: [BullHealthModule, TerminusModule], 36 | }) 37 | export class HealthModule {} 38 | ``` 39 | 40 | ## License 41 | 42 | [MIT](LICENSE) 43 | -------------------------------------------------------------------------------- /packages/terminus/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require("../../jest.config.base"); 2 | module.exports = { 3 | ...base, 4 | }; 5 | -------------------------------------------------------------------------------- /packages/terminus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anchan828/nest-bull-terminus", 3 | "version": "3.2.23", 4 | "description": "The terminus of [Bull](https://github.com/OptimalBits/bull) module for [Nest](https://github.com/nestjs/nest).", 5 | "homepage": "https://github.com/anchan828/nest-bull/tree/master/packages/terminus#readme", 6 | "bugs": { 7 | "url": "https://github.com/anchan828/nest-bull/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/anchan828/nest-bull.git" 12 | }, 13 | "license": "MIT", 14 | "author": "anchan828 ", 15 | "main": "dist/index.js", 16 | "types": "dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsc -p tsconfig.build.json", 19 | "copy:license": "cp ../../LICENSE ./", 20 | "lint": "TIMING=1 eslint --ignore-path ../../.eslintignore '**/*.ts'", 21 | "lint:fix": "npm run lint -- --fix", 22 | "prepublishOnly": "npm run build && rm -f dist/*.tsbuildinfo && npm run copy:license", 23 | "test": "jest --coverage --runInBand --detectOpenHandles --forceExit", 24 | "test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest --runInBand --logHeapUsage", 25 | "test:watch": "npm run test -- --watch", 26 | "watch": "tsc --watch" 27 | }, 28 | "dependencies": { 29 | "@anchan828/nest-bull": "^3.2.23" 30 | }, 31 | "devDependencies": { 32 | "@nestjs/axios": "3.0.1", 33 | "@nestjs/common": "10.2.10", 34 | "@nestjs/terminus": "10.2.0", 35 | "@types/bull": "3.15.9", 36 | "@types/supertest": "2.0.16", 37 | "bull": "4.11.5", 38 | "rxjs": "7.8.1", 39 | "supertest": "6.3.3" 40 | }, 41 | "peerDependencies": { 42 | "@nestjs/axios": "^0.0.8 || ^0.1.0 || ^1.0.0 || ^2.0.0 || ^3.0.0", 43 | "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/terminus/src/bull.health.ts: -------------------------------------------------------------------------------- 1 | import { BullQueue, BullQueueInject, BullQueueProcess } from "@anchan828/nest-bull"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from "@nestjs/terminus"; 4 | import { Queue } from "bull"; 5 | import { QUEUE_NAME } from "./constants"; 6 | 7 | @BullQueue({ 8 | name: QUEUE_NAME, 9 | options: { 10 | defaultJobOptions: { 11 | priority: 1, 12 | }, 13 | }, 14 | extra: { 15 | defaultJobOptions: { 16 | setTTLOnComplete: 10, 17 | setTTLOnFail: 10, 18 | }, 19 | }, 20 | }) 21 | export class BullHealthCheckQueue { 22 | @BullQueueProcess() 23 | async process(): Promise { 24 | return Promise.resolve(); 25 | } 26 | } 27 | 28 | @Injectable() 29 | export class BullHealthIndicator extends HealthIndicator { 30 | constructor(@BullQueueInject(QUEUE_NAME) private readonly queue: Queue) { 31 | super(); 32 | } 33 | 34 | async isHealthy(key = "bull"): Promise { 35 | try { 36 | const job = await this.queue.add({}); 37 | await job.finished(); 38 | } catch (e: any) { 39 | throw new HealthCheckError("BullHealthCheck failed", this.getStatus(key, false, { message: e.message })); 40 | } 41 | return this.getStatus(key, true); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/terminus/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const QUEUE_NAME = "BullHealthCheckQueue"; 2 | -------------------------------------------------------------------------------- /packages/terminus/src/health.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { BullModule, getBullQueueToken } from "@anchan828/nest-bull"; 2 | import { Controller, Get, Module } from "@nestjs/common"; 3 | import { HealthCheck, HealthCheckService, TerminusModule } from "@nestjs/terminus"; 4 | import { Test } from "@nestjs/testing"; 5 | import { Job, Queue } from "bull"; 6 | import * as request from "supertest"; 7 | import { BullHealthCheckQueue, BullHealthIndicator } from "./bull.health"; 8 | import { QUEUE_NAME } from "./constants"; 9 | import { BullHealthModule } from "./health.module"; 10 | 11 | describe("BullHealthModule", () => { 12 | @Controller("/health") 13 | class BullHealthController { 14 | constructor(private health: HealthCheckService, private bull: BullHealthIndicator) {} 15 | 16 | @Get() 17 | @HealthCheck() 18 | check() { 19 | return this.health.check([() => this.bull.isHealthy()]); 20 | } 21 | } 22 | 23 | it("should return status is up", async () => { 24 | @Module({ 25 | controllers: [BullHealthController], 26 | imports: [BullHealthModule, TerminusModule], 27 | }) 28 | class HealthModule {} 29 | 30 | const module = await Test.createTestingModule({ 31 | imports: [ 32 | BullModule.forRoot({ 33 | queues: [BullHealthCheckQueue], 34 | }), 35 | HealthModule, 36 | ], 37 | }).compile(); 38 | 39 | const app = module.createNestApplication(); 40 | await app.init(); 41 | await request(app.getHttpServer()) 42 | .get("/health") 43 | .expect(200) 44 | .expect({ 45 | status: "ok", 46 | error: {}, 47 | info: { bull: { status: "up" } }, 48 | details: { bull: { status: "up" } }, 49 | }); 50 | await app.close(); 51 | }); 52 | 53 | it("should return status is down", async () => { 54 | @Module({ 55 | controllers: [BullHealthController], 56 | imports: [BullHealthModule, TerminusModule], 57 | }) 58 | class HealthModule {} 59 | 60 | const module = await Test.createTestingModule({ 61 | imports: [ 62 | BullModule.forRoot({ 63 | queues: [BullHealthCheckQueue], 64 | }), 65 | HealthModule, 66 | ], 67 | }).compile(); 68 | 69 | const app = module.createNestApplication(); 70 | 71 | await app.init(); 72 | const queue = app.get(getBullQueueToken(QUEUE_NAME)); 73 | jest.spyOn(queue, "add").mockResolvedValueOnce({ 74 | finished: (): Promise => { 75 | throw new Error("faild"); 76 | }, 77 | } as Job); 78 | 79 | await request(app.getHttpServer()) 80 | .get("/health") 81 | .expect(503) 82 | .expect({ 83 | status: "error", 84 | info: {}, 85 | error: { bull: { status: "down", message: "faild" } }, 86 | details: { bull: { status: "down", message: "faild" } }, 87 | }); 88 | 89 | await app.close(); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /packages/terminus/src/health.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { BullHealthCheckQueue, BullHealthIndicator } from "./bull.health"; 3 | 4 | const providers = [BullHealthCheckQueue, BullHealthIndicator]; 5 | 6 | @Module({ providers, exports: providers }) 7 | export class BullHealthModule {} 8 | -------------------------------------------------------------------------------- /packages/terminus/src/index.ts: -------------------------------------------------------------------------------- 1 | export { BullHealthCheckQueue, BullHealthIndicator } from "./bull.health"; 2 | export { BullHealthModule } from "./health.module"; 3 | -------------------------------------------------------------------------------- /packages/terminus/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts", "dist"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/terminus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "declarationMap": true 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>anchan828/renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": false, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2021", 9 | "sourceMap": true, 10 | "strict": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "skipLibCheck": true, 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist", "scripts"] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { "allowJs": true }, 4 | "include": ["packages/*/src/**/*.ts", "scripts/**/*.ts", ".eslintrc.js"], 5 | "exclude": ["node_modules", "dist"] 6 | } 7 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**"] 7 | }, 8 | "lint": { 9 | "outputs": [] 10 | }, 11 | "lint:fix": { 12 | "outputs": [] 13 | }, 14 | "watch": { 15 | "outputs": [] 16 | }, 17 | "test": { 18 | "dependsOn": ["^build"], 19 | "outputs": ["coverage/**"] 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------