├── .editorconfig
├── .eslintrc.js
├── .github
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ ├── documentation-report.md
│ └── enhancement-request.md
├── PULL_REQUEST_TEMPLATE
└── workflows
│ ├── commit.yml
│ ├── coveralls.yml
│ ├── lint.yml
│ └── nodejs.yml
├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README.md
├── japaFile.js
├── package.json
├── providers
├── Bull.js
└── Command.js
├── src
├── Commands
│ └── Listen.js
└── Queue.js
├── test
├── functional
│ ├── app
│ │ └── SomeJob.js
│ ├── providers.spec.js
│ └── start
│ │ └── jobs.js
└── unit
│ └── queue.spec.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es6: true,
4 | node: true,
5 | },
6 | plugins: ['prettier'],
7 | extends: ['standard', 'prettier'],
8 | globals: {
9 | Atomics: 'readonly',
10 | SharedArrayBuffer: 'readonly',
11 | },
12 | parserOptions: {
13 | ecmaVersion: 2018,
14 | },
15 | rules: {
16 | 'prettier/prettier': 'error',
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Learn how to add code owners here:
2 | # https://help.github.com/en/articles/about-code-owners
3 |
4 | * @jpedroschmitz
5 | *.js @HigoRibeiro
6 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | This Code of Conduct outlines our expectations for participants within the Rocketseat community as well as steps to reporting unacceptable behavior. Our goal is to make explicit what we expect from participants in this community as well as its leaders. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community.
4 |
5 | Our community strives to:
6 |
7 | - **Be welcoming, friendly and patient**.
8 | - **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that you might not be communicating in someone else’s primary language.
9 | - **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
10 | - **Be understandable**: Disagreements, both social and technical, happen all the time and Rocketseat is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we're different. The strength of Rocketseat comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn't mean that they're wrong. Don't forget that it is human to err and blaming each other doesn't get us anywhere, rather offer to help resolving issues and to help learn from mistakes.
11 |
12 | This code serves to distill our common understanding of a collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in the letter.
13 |
14 | ### Diversity Statement
15 |
16 | We encourage everyone to participate and are committed to building a community for all. Although we may not be able to satisfy everyone, we all agree that everyone is equal. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong.
17 |
18 | Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected characteristics above, including participants with disabilities.
19 |
20 | ### Reporting Issues
21 |
22 | If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via [opensource@rocketseat.com.br](mailto:opensource@rocketseat.com.br). All reports will be handled with discretion. In your report please include:
23 |
24 | - Your contact information.
25 | - Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record, please include a link.
26 | - Any additional information that may be helpful.
27 |
28 | After filing a report, a representative will contact you personally. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. A representative will then review the incident, follow up with any additional questions, and make a decision as to how to respond. We will respect confidentiality requests for the purpose of protecting victims of abuse.
29 |
30 | Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning.
31 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution guidelines
2 |
3 | ## Table of Contents
4 |
5 | - [Getting started](#getting-started)
6 | - [Language](#language)
7 | - [For native English speakers](#for-native-english-speakers)
8 | - [Code of Conduct](#code-of-conduct)
9 | - [How can I help?](#how-can-i-help)
10 | - [Documentation](#documentation)
11 | - [Issues](#issues)
12 | - [Submitting an issue](#submitting-an-issue)
13 | - [Feedback](#feedback)
14 | - [Code](#code)
15 | - [Commiting](#commiting)
16 | - [Why all these rules?](#why-all-these-rules)
17 | - [Submitting a pull request](#submitting-a-pull-request)
18 |
19 | ## Getting started
20 |
21 | First off, we would like to thank you for taking the time to contribute and make this a better project!
22 |
23 | Here we have a set of instructions and guidelines to reduce misunderstandings and make the process of contributing to `adonis-bull` as smooth as possible. We hope this guide makes the contribution process clear and answers any questions you may have.
24 |
25 | ### Language
26 |
27 | Please, while contributing or interacting in any way in this project, **refrain from using any language other than English**.
28 |
29 | #### For native English speakers
30 |
31 | Try to use simple words and sentences. Don't make fun of non-native English speakers if you find something wrong about the way they express themselves.
32 |
33 | Try to encourage newcomers to express their opinions, and make them comfortable enough to do so.
34 |
35 | ### Code of Conduct
36 |
37 | We expect that project participants to adhere to our Code of Conduct. You can check the [full text](CODE_OF_CONDUCT.md) so that you may understand the kind of conduct we are expecting and what actions will and will not be tolerated.
38 |
39 | By participating in this project, you agree to abide by its terms.
40 |
41 | ## How can I help?
42 |
43 | Here are some ways you can help along with some guidelines.
44 |
45 | ### Documentation
46 |
47 | As a user of `adonis-bull`, you're the perfect candidate to help us improve our documentation!
48 |
49 | Typos, errors, lack of examples and/or explanation and so on, are just some examples of things that could be fixed and/or improved. You could even make improvements to this guide :)
50 |
51 | While documenting, try to keep things simple and clear.
52 |
53 | ### Issues
54 |
55 | Some issues are created with missing information, without a template, not reproducible, or plain
56 | invalid. You can make them easier to understand and resolve.
57 |
58 | #### Submitting an issue
59 |
60 | - Please search for similar issues before opening a new one;
61 | - Use one of the corresponding issue templates;
62 | - Use a clear and descriptive title;
63 | - Include as much information as possible by filling out the provided issue template;
64 | - Most of the time, the best way to report an issue is a failing test proving it.
65 |
66 | ### Feedback
67 |
68 | The more feedback the better! We're always looking for more suggestions and opinions on discussions. That's a good opportunity to influence the future direction of this tool.
69 |
70 | This includes submitting an enhancement suggestion, including completely new features and minor improvements to existing functionality.
71 |
72 | The [`question`](https://github.com/Rocketseat/adonis-bull/labels/type%3A%20question%20or%20discussion)and [`rfc`](https://github.com/Rocketseat/adonis-bull/labels/type%3A%20rfc) labels are a good place to find ongoing discussions.
73 |
74 | ### Code
75 |
76 | You can use issue labels to discover issues you could help out with:
77 |
78 | - [`bug` issues](https://github.com/Rocketseat/adonis-bull/labels/kind%3A%20bug) are known bugs we'd like to fix;
79 | - [`enhancement` issues](https://github.com/Rocketseat/adonis-bull/labels/type%3A%20feature%20request) are features we're open to include.
80 |
81 | The [`help wanted`](https://github.com/Rocketseat/adonis-bull/labels/help%20wanted) and [`good first issue`](https://github.com/Rocketseat/adonis-bull/labels/good%20first%20issue) labels are especially useful.
82 |
83 | When you see an issue that is already assigned, please check to see if there isn't someone working on it already (maybe try asking in the issue). This is to prevent unnecessary work for everyone involved.
84 |
85 | ## Commiting
86 |
87 | A commit message can consists of a **header**, **body** and **footer**. The header is the only mandatory part and consists of a type and a subject. The body is used to fully describe the change. The footer is the place to reference any issues or pull requests related to the commit. That said, we end with a template like this:
88 |
89 | ```
90 | :
91 |
92 | [optional body]
93 |
94 | [optional footer]
95 | ```
96 |
97 | To ensure that a commit is valid, easy to read, and changelog-ready, we have a hook that lints the commit message before allowing a commit to pass. This linter verifies the following:
98 |
99 | - The header (first line) is the only mandatory part of the commit message;
100 | - The body and footer are both optional but its use is highly encouraged;
101 | - The header should contains:
102 | - A type:
103 | - Must be lowercase;
104 | - Must be one of:
105 | - **chore**: A change that neither fix a bug nor adds a feature;
106 | - **ci**: A CI change;
107 | - **docs**: A documentation change or fix;
108 | - **feat**: A new feature;
109 | - **fix**: A bug fix;
110 | - **test**: A test-related change.
111 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
112 | - A subject:
113 | - Must be capitalized;
114 | - Must be limited to 50 characters or less;
115 | - Must omit any trailing punctuation.
116 | - The body:
117 | - Must have a leading blank line;
118 | - Each line must be limited to 72 characters or less.
119 | - The footer:
120 | - Must have a leading blank line;
121 | - Each line must be limited to 72 characters or less;
122 | - If your commit is about documentation or meta files, please add the tag **[skip ci]** to skip the building process.
123 | - If needed, reference to issues and pull requests must be made here in the last line.
124 |
125 | You also should follow these general guidelines when committing:
126 |
127 | - Use the present tense ("Add feature" not "Added feature");
128 | - Use the imperative mood ("Move cursor to..." not "Moves cursor to...");
129 | - Try to answer the following questions:
130 | - Why is this change necessary?
131 | - How does it address the issue?
132 | - What side effects (if any) does this change may have?
133 |
134 | Example of a commit message:
135 |
136 | ```
137 | type: Commit message style guide for Git
138 |
139 | The first line of a commit message serves as a summary. When displayed
140 | on the web, it's often styled as a heading, and in emails, it's
141 | typically used as the subject. As such, you should specify a "type" and
142 | a "subject". The type must be lowercase and one of: chore, ci, docs,
143 | feat, fix, test. For the subject you'll need capitalize it and
144 | omit any trailing punctuation. Aim for about 50 characters, give or
145 | take, otherwise it may be painfully truncated in some contexts. Write
146 | it, along with the rest of your message, in the present tense and
147 | imperative mood: "Fix bug" and not "Fixed bug" or "Fixes bug".
148 | Consistent wording makes it easier to mentally process a list of
149 | commits.
150 |
151 | Oftentimes a subject by itself is sufficient. When it's not, add a
152 | blank line (this is important) followed by one or more paragraphs hard
153 | wrapped to 72 characters. Git is strongly opinionated that the author
154 | is responsible for line breaks; if you omit them, command line tooling
155 | will show it as one extremely long unwrapped line. Fortunately, most
156 | text editors are capable of automating this.
157 |
158 | Issues and pull request can be referenced on the footer: #3 #12
159 | ```
160 |
161 | ### Why all these rules?
162 |
163 | We try to enforce these rules for the following reasons:
164 |
165 | - Automatically generating changelog;
166 | - Communicating in a better way the nature of changes;
167 | - Triggering build and publish processes;
168 | - Automatically determining a semantic version bump (based on the types of commits);
169 | - Making it easier for people to contribute, by allowing them to explore a more structured commit history.
170 |
171 | ## Submitting a pull request
172 |
173 | Before submitting a pull request, please make sure the following is done:
174 |
175 | - [Fork](https://help.github.com/en/articles/fork-a-repo) the repository and create your branch from `master`.
176 | - Example: `feat/my-awesome-feature` or `fix/annoying-bug`;
177 | - Run `yarn` in the repository root;
178 | - If you’ve fixed a bug or added code that should be tested, **add tests**;
179 | - Ensure the test suite passes;
180 | - Ensure your commit is validated;
181 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | **Description of bug**
13 |
14 |
15 |
16 | **To Reproduce**
17 |
18 |
25 |
26 | **Expected behavior**
27 |
28 |
29 |
30 | **Exception or Error**
31 |
32 |
33 |
34 |
35 |
36 |
37 | **Screenshots**
38 |
39 |
40 |
41 | **Environment:**
42 |
43 |
50 |
51 | **Additional context**
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Documentation report
3 | about: Use this template for documentation related issues
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | **Description of issue**
13 |
14 |
15 |
16 | **Usage example**
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/enhancement-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Enhancement request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | **What would you like to be added**:
13 |
14 |
15 |
16 | **Why is this needed**:
17 |
18 |
19 |
20 | **Is your enhancement request related to a problem? Please describe.**
21 |
22 |
23 |
24 | **Additional context**
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | **Changes proposed**
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | **Additional context**
19 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/commit.yml:
--------------------------------------------------------------------------------
1 | name: Lint Commit Messages
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | commit:
7 | name: Lint commit messages
8 | runs-on: ubuntu-latest
9 | env:
10 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Install dependencies
19 | run: yarn
20 |
21 | - name: Check commit message
22 | uses: wagoid/commitlint-github-action@v1
23 | env:
24 | NODE_PATH: ${{ github.workspace }}/node_modules
25 |
--------------------------------------------------------------------------------
/.github/workflows/coveralls.yml:
--------------------------------------------------------------------------------
1 | name: Coveralls
2 |
3 | on: [pull_request]
4 | env:
5 | CI: true
6 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
7 | NODE_ENV: test
8 |
9 | jobs:
10 | run:
11 | name: Coverage [Node.js ${{ matrix.node-version }} on ${{ matrix.os }}]
12 | runs-on: ${{ matrix.os }}
13 |
14 | strategy:
15 | matrix:
16 | os: [ubuntu-latest]
17 | node-version: [12.x]
18 |
19 | steps:
20 | - name: Clone repository
21 | uses: actions/checkout@v2
22 | with:
23 | fetch-depth: 0
24 |
25 | - name: Setup Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 |
30 | - name: Setup Redis
31 | uses: zhulik/redis-action@1.1.0
32 | with:
33 | redis version: '5'
34 | number of databases: 1
35 |
36 | - name: Install dependencies
37 | run: yarn
38 |
39 | - name: Run tests
40 | run: yarn coverage
41 |
42 | - name: Coveralls
43 | uses: coverallsapp/github-action@master
44 | with:
45 | github-token: ${{ secrets.GITHUB_TOKEN }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | code:
7 | name: Lint code
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - name: Checkout repository
12 | uses: actions/checkout@v2
13 | with:
14 | fetch-depth: 0
15 |
16 | - name: Set up Node
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: 12
20 |
21 | - name: Install dependencies
22 | run: yarn
23 |
24 | - name: Run ESLint
25 | run: yarn lint
26 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node.js
2 |
3 | on: [push, pull_request]
4 | env:
5 | CI: true
6 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
7 |
8 | jobs:
9 | run:
10 | name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
11 | runs-on: ${{ matrix.os }}
12 |
13 | strategy:
14 | matrix:
15 | os: [ubuntu-latest]
16 | node-version: [10.x, 12.x]
17 |
18 | steps:
19 | - name: Clone repository
20 | uses: actions/checkout@v1
21 |
22 | - name: Setup Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 |
27 | - name: Setup Redis
28 | uses: zhulik/redis-action@1.1.0
29 | with:
30 | redis version: '5'
31 | number of databases: 1
32 |
33 | - name: Install dependencies
34 | run: yarn
35 |
36 | - name: Run tests
37 | run: yarn test
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/node
2 |
3 | ### Node ###
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 | *.pid.lock
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # Coveralls configuration file
24 | .coveralls.yml
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (http://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Typescript v1 declaration files
46 | typings/
47 |
48 | # Optional npm cache directory
49 | .npm
50 |
51 | # Optional eslint cache
52 | .eslintcache
53 |
54 | # Optional REPL history
55 | .node_repl_history
56 |
57 | # Output of 'npm pack'
58 | *.tgz
59 |
60 | # Yarn Integrity file
61 | .yarn-integrity
62 |
63 | # dotenv environment variables file
64 | .env
65 |
66 | # tmp dirs
67 | test/tmp
68 | test/database
69 |
70 |
71 | # End of https://www.gitignore.io/api/node
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Rocketseat
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 |
2 |
3 |
4 |
5 |
6 |
A Bull provider for AdonisJS
7 | Adonis Bull provides an easy way to start using Bull.
8 |
9 |
10 |
11 |
12 |
13 | [![build-image]][build-url] [![coveralls-image]][coveralls-url] [![license-image]][license-url] [![npm-image]][npm-url]
14 |
15 |
16 |
17 |
18 |
19 | > **This documentation refers to the stable version of Adonis Bull, for Adonis v4.x**
> **If you are using Adonis v5, [click here](https://github.com/Rocketseat/adonis-bull/tree/alpha).**
20 |
21 | ## Why
22 |
23 | Using Bull with Adonis shouldn't be hard. It shouldn't require dozens of steps to configure it. That's why adonis-bull exists. It provides an easy way to use queues when developing applications with AdonisJS.
24 |
25 | ## Install
26 |
27 | ```sh
28 | adonis install @rocketseat/adonis-bull
29 | ```
30 |
31 | ## Usage
32 |
33 | Register the Bull commands at `start/app.js`
34 |
35 | ```js
36 | const aceProviders = ['@rocketseat/adonis-bull/providers/Command']
37 | ```
38 |
39 | Register the Bull provider at `start/app.js`
40 |
41 | ```js
42 | const providers = [
43 | //...
44 | '@rocketseat/adonis-bull/providers/Bull',
45 | ]
46 | ```
47 |
48 | Create a file with the `jobs` that will be processed at `start/jobs.js`:
49 |
50 | ```js
51 | module.exports = ['App/Jobs/UserRegisterEmail']
52 | ```
53 |
54 | Add the config file at `config/bull.js`:
55 |
56 | ```js
57 | 'use strict'
58 |
59 | const Env = use('Env')
60 |
61 | module.exports = {
62 | // redis connection
63 | connection: Env.get('BULL_CONNECTION', 'bull'),
64 | bull: {
65 | redis: {
66 | host: '127.0.0.1',
67 | port: 6379,
68 | password: null,
69 | db: 0,
70 | keyPrefix: '',
71 | },
72 | },
73 | remote: 'redis://redis.example.com?password=correcthorsebatterystaple',
74 | }
75 | ```
76 |
77 | In the above file you can define redis connections, there you can pass all `Bull` queue configurations described [here](https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queue).
78 |
79 | Create a file to initiate `Bull` at `preloads/bull.js`:
80 |
81 | ```js
82 | const Bull = use('Rocketseat/Bull')
83 |
84 | Bull.process()
85 | // Optionally you can start BullBoard:
86 | .ui(9999, 'localhost') // http://localhost:9999
87 | // You don't need to specify either port or hostname, the default port number is 9999 and the default hostname is localhost
88 | ```
89 |
90 | Add .preLoad in server.js to initialize the bull preload
91 |
92 | ```js
93 | new Ignitor(require('@adonisjs/fold'))
94 | .appRoot(__dirname)
95 | .preLoad('preloads/bull') // Add This Line
96 | .fireHttpServer()
97 | .catch(console.error)
98 | ```
99 |
100 | ## Creating your job
101 |
102 | Create a class that mandatorily has the methods `key` and `handle`.
103 |
104 | The `key` method is the unique identification of each job. It has to be a `static get` method.
105 |
106 | The `handle` is the method that contains the functionality of your `job`.
107 |
108 | ```js
109 | const Mail = use('Mail')
110 |
111 | class UserRegisterEmail {
112 | static get key() {
113 | return 'UserRegisterEmail-key'
114 | }
115 |
116 | async handle(job) {
117 | const { data } = job // the 'data' variable has user data
118 |
119 | await Mail.send('emails.welcome', data, (message) => {
120 | message
121 | .to(data.email)
122 | .from('')
123 | .subject('Welcome to yardstick')
124 | })
125 |
126 | return data
127 | }
128 | }
129 |
130 | module.exports = UserRegisterEmail
131 | ```
132 |
133 | You can use the `connection` static get method to specify which connection your `job` will work.
134 |
135 | ```js
136 | class UserRegisterEmail {
137 | // ...
138 | static get connection() {
139 | return 'remote'
140 | }
141 | }
142 | ```
143 |
144 | ### Events
145 |
146 | The package has support for all events triggered in the bull, just add "on" and complete with the name of the event
147 | Ex: `onCompleted()`, `onActive()`, `onWaiting()` and etc.
148 |
149 | ```js
150 | class UserRegisterEmail {
151 | ...
152 | onCompleted(job, result) {}
153 | onActive(job) {}
154 | ...
155 | }
156 |
157 | module.exports = UserRegisterEmail;
158 |
159 | ```
160 |
161 | ## Processing the jobs
162 |
163 | ### Simple job
164 |
165 | You can share the `job` of any `controller`, `hook` or any other place you might like:
166 |
167 | ```js
168 | const User = use('App/Models/User')
169 | const Bull = use('Rocketseat/Bull')
170 | const Job = use('App/Jobs/UserRegisterEmail')
171 |
172 | class UserController {
173 | store ({ request, response }) {
174 | const data = request.only(['email', 'name', 'password'])
175 |
176 | const user = await User.create(data)
177 |
178 |
179 | Bull.add(Job.key, user)
180 | }
181 | }
182 |
183 | module.exports = UserController
184 | ```
185 |
186 | ### Scheduled job
187 |
188 | Sometimes it is necessary to schedule a job instead of shooting it imediately. You should use `schedule` for that:
189 |
190 | ```js
191 | const User = use('App/Models/User')
192 | const Bull = use('Rocketseat/Bull')
193 | const Job = use('App/Jobs/HolidayOnSaleEmail')
194 |
195 | class HolidayOnSaleController {
196 | store ({ request, response }) {
197 | const data = request.only(['date', 'product_list']) // 2019-11-15 12:00:00
198 |
199 | const products = await ProductOnSale.create(data)
200 |
201 |
202 | Bull.schedule(Job.key, products, data.date)
203 | }
204 | }
205 |
206 | module.exports = HolidayOnSaleController
207 | ```
208 |
209 | This `job` will be sent only on the specific date, wich for example here is on November 15th at noon.
210 |
211 | When finishing a date, never use past dates because it will cause an error.
212 |
213 | other ways of using `schedule`:
214 |
215 | ```js
216 | Bull.schedule(key, data, new Date('2019-11-15 12:00:00'))
217 | Bull.schedule(key, data, '2 hours') // 2 hours from now
218 | Bull.schedule(key, data, 60 * 1000) // 1 minute from now.
219 | ```
220 |
221 | ### Advanced jobs
222 |
223 | You can use the own `Bull` configs to improve your job:
224 |
225 | ```js
226 | Bull.add(key, data, {
227 | repeat: {
228 | cron: '0 30 12 * * WED,FRI',
229 | },
230 | })
231 | ```
232 |
233 | This `job` will be run at 12:30 PM, only on wednesdays and fridays.
234 |
235 | ### Exceptions
236 |
237 | To have a bigger control over errors that might occur on the line, the events that fail can be manipulated at the file `App/Exceptions/QueueHandler.js`:
238 |
239 | ```js
240 | const Sentry = use('Sentry')
241 |
242 | class QueueHandler {
243 | async report(error, job) {
244 | Sentry.configureScope((scope) => {
245 | scope.setExtra(job)
246 | })
247 |
248 | Sentry.captureException(error)
249 | }
250 | }
251 |
252 | module.exports = QueueHandler
253 | ```
254 |
255 | ## Contributing
256 |
257 | Thank you for being interested in making this package better. We encourage everyone to help improve this project with new features, bug fixes, or performance improvements. Please take a little bit of your time to read our guide to make this process faster and easier.
258 |
259 | ### Contribution Guidelines
260 |
261 | To understand how to submit an issue, commit and create pull requests, check our [Contribution Guidelines](/.github/CONTRIBUTING.md).
262 |
263 | ### Code of Conduct
264 |
265 | We expect you to follow our [Code of Conduct](/.github/CODE_OF_CONDUCT.md). You can read it to understand what kind of behavior will and will not be tolerated.
266 |
267 | ## License
268 |
269 | MIT License © [Rocketseat](https://github.com/Rocketseat)
270 |
271 | [npm-image]: https://img.shields.io/npm/v/@rocketseat/adonis-bull?color=8257E5&style=for-the-badge
272 | [npm-url]: https://www.npmjs.com/package/@rocketseat/adonis-bull 'npm'
273 | [license-url]: LICENSE.md
274 | [license-image]: https://img.shields.io/github/license/adonisjs/adonis-framework?color=8257E5&style=for-the-badge
275 | [build-url]: https://github.com/Rocketseat/adonis-bull/actions
276 | [build-image]: https://img.shields.io/github/workflow/status/Rocketseat/adonis-bull/Node.js/master?color=8257E5&style=for-the-badge
277 | [coveralls-image]: https://img.shields.io/coveralls/github/Rocketseat/adonis-bull/master?color=8257E5&style=for-the-badge
278 | [coveralls-url]: https://coveralls.io/github/Rocketseat/adonis-bull?branch=master
279 |
--------------------------------------------------------------------------------
/japaFile.js:
--------------------------------------------------------------------------------
1 | const { configure } = require('japa')
2 | configure({
3 | files: ['test/**/*.spec.js'],
4 | })
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@rocketseat/adonis-bull",
3 | "version": "0.3.0",
4 | "main": "src/Queue.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "test": "node japaFile.js",
8 | "coverage": "nyc --reporter=lcov --reporter=text-summary npm run test",
9 | "lint": "eslint --ignore-path .gitignore",
10 | "format": "prettier \"**/*.js\" --write --ignore-path .gitignore"
11 | },
12 | "nyc": {
13 | "exclude": [
14 | "**/*.spec.js",
15 | "bin"
16 | ]
17 | },
18 | "directories": {
19 | "test": "test"
20 | },
21 | "husky": {
22 | "hooks": {
23 | "pre-commit": "lint-staged",
24 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
25 | }
26 | },
27 | "lint-staged": {
28 | "*.js": [
29 | "yarn lint --fix",
30 | "yarn format"
31 | ]
32 | },
33 | "commitlint": {
34 | "extends": [
35 | "@rocketseat/commitlint-config"
36 | ]
37 | },
38 | "keywords": [
39 | "bull",
40 | "queue",
41 | "adonis",
42 | "adonisjs",
43 | "adonis-js",
44 | "adonis-bull",
45 | "adonis-queue"
46 | ],
47 | "devDependencies": {
48 | "@adonisjs/ace": "^5.0.8",
49 | "@adonisjs/fold": "^4.0.9",
50 | "@adonisjs/sink": "^1.0.17",
51 | "@commitlint/cli": "^11.0.0",
52 | "@rocketseat/commitlint-config": "^0.0.2",
53 | "delay": "^4.3.0",
54 | "eslint": "^7.18.0",
55 | "eslint-config-prettier": "^7.2.0",
56 | "eslint-config-standard": "^16.0.2",
57 | "eslint-plugin-import": "^2.22.1",
58 | "eslint-plugin-node": "^11.1.0",
59 | "eslint-plugin-prettier": "^3.3.1",
60 | "eslint-plugin-promise": "^4.2.1",
61 | "eslint-plugin-standard": "^4.0.1",
62 | "husky": "^4.3.8",
63 | "japa": "^3.0.1",
64 | "lint-staged": "^10.5.3",
65 | "nyc": "^14.1.1",
66 | "prettier": "^2.2.1"
67 | },
68 | "repository": {
69 | "type": "git",
70 | "url": "git+https://github.com/rocketseat/adonis-bull.git"
71 | },
72 | "dependencies": {
73 | "bull": "^3.22.1",
74 | "bull-board": "^1.5.1",
75 | "date-fns": "^2.7.0",
76 | "human-interval": "^0.1.6"
77 | },
78 | "resolutions": {
79 | "**/**/minimist": "^1.2.3",
80 | "**/**/set-value": "^3.0.1"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/providers/Bull.js:
--------------------------------------------------------------------------------
1 | const { ServiceProvider, resolver } = require('@adonisjs/fold')
2 | const path = require('path')
3 |
4 | class BullProvider extends ServiceProvider {
5 | register() {
6 | this.app.singleton('Rocketseat/Bull', (app) => {
7 | const Queue = require('../src/Queue')
8 | const Helpers = app.use('Adonis/Src/Helpers')
9 | const Logger = app.use('Adonis/Src/Logger')
10 | const Config = app.use('Adonis/Src/Config')
11 |
12 | const jobs = require(path.join(Helpers.appRoot(), 'start/jobs.js')) || []
13 |
14 | return new Queue(Logger, Config, jobs, app, resolver)
15 | })
16 |
17 | this.app.alias('Rocketseat/Bull', 'Bull')
18 | }
19 | }
20 |
21 | module.exports = BullProvider
22 |
--------------------------------------------------------------------------------
/providers/Command.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { ServiceProvider } = require('@adonisjs/fold')
4 |
5 | class MigrationsProvider extends ServiceProvider {
6 | register() {
7 | this.app.bind('Rocketseat/Commands/Bull:Listen', () =>
8 | require('../src/Commands/Listen')
9 | )
10 | }
11 |
12 | boot() {
13 | const ace = require('@adonisjs/ace')
14 | ace.addCommand('Rocketseat/Commands/Bull:Listen')
15 | }
16 | }
17 |
18 | module.exports = MigrationsProvider
19 |
--------------------------------------------------------------------------------
/src/Commands/Listen.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { Command } = require('@adonisjs/ace')
4 |
5 | class Listen extends Command {
6 | static get inject() {
7 | return ['Rocketseat/Bull']
8 | }
9 |
10 | constructor(Bull) {
11 | super()
12 | this.Bull = Bull
13 | }
14 |
15 | static get signature() {
16 | return `
17 | bull:listen
18 | { --board?=false : Run bull's dashboard }
19 | { --board-hostname?=@value : Dashboard hostname }
20 | { --board-port?=@value : Dashboard port }
21 | `
22 | }
23 |
24 | static get description() {
25 | return 'Start the Bull listener'
26 | }
27 |
28 | async handle(args, { board = false, boardHostname = 'localhost', boardPort = 9999 }) {
29 | this.Bull.process()
30 | if (board) {
31 | this.Bull.ui(boardPort, boardHostname)
32 | }
33 | }
34 | }
35 |
36 | module.exports = Listen
37 |
--------------------------------------------------------------------------------
/src/Queue.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Bull = require('bull')
4 | const BullBoard = require('bull-board')
5 | const humanInterval = require('human-interval')
6 |
7 | const differenceInMilliseconds = require('date-fns/differenceInMilliseconds')
8 | const parseISO = require('date-fns/parseISO')
9 | const fs = require('fs')
10 |
11 | class Queue {
12 | constructor(Logger, Config, jobs, app, resolver) {
13 | this.Logger = Logger
14 | this.jobs = jobs
15 |
16 | this.app = app
17 | this.resolver = resolver
18 |
19 | this._queues = null
20 |
21 | const { connection, ...connections } = Config.get('bull')
22 |
23 | this.config = connections[connection]
24 | this.connections = connections
25 | }
26 |
27 | _getJobListeners(Job) {
28 | const jobListeners = Object.getOwnPropertyNames(Job.prototype)
29 | .filter((method) => method.startsWith('on'))
30 | .map((method) => {
31 | const eventName = method
32 | .replace(/^on(\w)/, (match, group) => group.toLowerCase())
33 | .replace(/([A-Z]+)/, (match, group) => ` ${group.toLowerCase()}`)
34 |
35 | return { eventName, method }
36 | })
37 | return jobListeners
38 | }
39 |
40 | get queues() {
41 | if (!this._queues) {
42 | this._queues = this.jobs.reduce((queues, path) => {
43 | const Job = this.app.use(path)
44 |
45 | let config = this.config
46 | if (Job.connection) {
47 | config = this.connections[Job.connection]
48 | }
49 |
50 | queues[Job.key] = {
51 | bull: new Bull(Job.key, config),
52 | Job,
53 | name: Job.key,
54 | handle: Job.handle,
55 | concurrency: Job.concurrency || 1,
56 | options: Job.options,
57 | }
58 |
59 | return queues
60 | }, {})
61 | }
62 |
63 | return this._queues
64 | }
65 |
66 | get(name) {
67 | return this.queues[name]
68 | }
69 |
70 | add(name, data, options) {
71 | const queue = this.get(name)
72 |
73 | const job = queue.bull.add(data, { ...queue.options, ...options })
74 |
75 | return job
76 | }
77 |
78 | removeRepeatable(name, repeat) {
79 | const queue = this.get(name)
80 |
81 | return queue.bull.removeRepeatable('__default__', repeat)
82 | }
83 |
84 | getRepeatableJobs(name) {
85 | const queue = this.get(name)
86 | const jobs = queue.bull.getRepeatableJobs()
87 |
88 | return jobs
89 | }
90 |
91 | schedule(name, data, date, options) {
92 | let delay
93 |
94 | if (typeof date === 'number' || date instanceof Number) {
95 | delay = date
96 | } else {
97 | if (typeof date === 'string' || date instanceof String) {
98 | const byHuman = humanInterval(date)
99 | if (!isNaN(byHuman)) {
100 | delay = byHuman
101 | } else {
102 | delay = differenceInMilliseconds(parseISO(date), new Date())
103 | }
104 | } else {
105 | delay = differenceInMilliseconds(date, new Date())
106 | }
107 | }
108 |
109 | if (delay > 0) {
110 | return this.add(name, data, { ...options, delay })
111 | } else {
112 | throw new Error('Invalid schedule time')
113 | }
114 | }
115 |
116 | ui(port = 9999, hostname = 'localhost') {
117 | BullBoard.setQueues(
118 | Object.values(this.queues).map(
119 | (queue) => new BullBoard.BullAdapter(queue.bull)
120 | )
121 | )
122 |
123 | const server = BullBoard.router.listen(port, hostname, () => {
124 | this.Logger.info(`bull board on http://${hostname}:${port}`)
125 | })
126 |
127 | const shutdown = () => {
128 | server.close(() => {
129 | this.Logger.info('Stopping bull board server')
130 | process.exit(0)
131 | })
132 | }
133 |
134 | process.on('SIGTERM', shutdown)
135 | process.on('SIGINT', shutdown)
136 | }
137 |
138 | async remove(name, jobId) {
139 | const job = await this.queues[name].bull.getJob(jobId)
140 |
141 | job.remove()
142 | }
143 |
144 | /* eslint handle-callback-err: "error" */
145 | handleException(error, job) {
146 | try {
147 | const exceptionHandlerFile = this.resolver
148 | .forDir('exceptions')
149 | .getPath('QueueHandler.js')
150 | fs.accessSync(exceptionHandlerFile, fs.constants.R_OK)
151 |
152 | const namespace = this.resolver
153 | .forDir('exceptions')
154 | .translate('QueueHandler')
155 | const handler = this.app.make(this.app.use(namespace))
156 | handler.report(error, job)
157 | } catch (err) {
158 | this.Logger.error(`name=${job.queue.name} id=${job.id}`)
159 | }
160 | }
161 |
162 | process() {
163 | this.Logger.info('Queue processing started')
164 | Object.values(this.queues).forEach((queue) => {
165 | const Job = new queue.Job()
166 |
167 | const jobListeners = this._getJobListeners(queue.Job)
168 |
169 | jobListeners.forEach(function (item) {
170 | queue.bull.on(item.eventName, Job[item.method].bind(Job))
171 | })
172 |
173 | queue.bull.process(queue.concurrency, (job, done) => {
174 | Job.handle(job)
175 | .then((result) => {
176 | done(null, result)
177 | })
178 | .catch((error) => {
179 | this.handleException(error, job)
180 | done(error)
181 | })
182 | })
183 | })
184 |
185 | const shutdown = () => {
186 | const promises = Object.values(this.queues).map((queue) => {
187 | return queue.bull.close()
188 | })
189 |
190 | return Promise.all(promises).then(process.exit(0))
191 | }
192 |
193 | process.on('SIGTERM', shutdown)
194 | process.on('SIGINT', shutdown)
195 |
196 | return this
197 | }
198 | }
199 |
200 | module.exports = Queue
201 |
--------------------------------------------------------------------------------
/test/functional/app/SomeJob.js:
--------------------------------------------------------------------------------
1 | class SomeJob {
2 | static get key() {
3 | return 'SomeJob-key'
4 | }
5 |
6 | async handle() {
7 | return 'good luck'
8 | }
9 | }
10 |
11 | module.exports = SomeJob
12 |
--------------------------------------------------------------------------------
/test/functional/providers.spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const test = require('japa')
3 | const { ioc, registrar, resolver } = require('@adonisjs/fold')
4 | const { Helpers, Config } = require('@adonisjs/sink')
5 |
6 | test.group('Provider', (group) => {
7 | group.before(async () => {
8 | resolver.appNamespace('App')
9 | registrar
10 | .providers([path.join(__dirname, '../../providers/Bull')])
11 | .register()
12 |
13 | ioc.bind('Adonis/Src/Logger', () => {
14 | return console
15 | })
16 |
17 | ioc.alias('Adonis/Src/Logger', 'Logger')
18 |
19 | ioc.bind('Adonis/Src/Helpers', () => {
20 | return new Helpers(__dirname)
21 | })
22 | ioc.alias('Adonis/Src/Helpers', 'Helpers')
23 |
24 | ioc.bind('Adonis/Src/Config', () => {
25 | const config = new Config()
26 | config.set('redis', {
27 | connection: 'local',
28 | local: {
29 | host: '127.0.0.1',
30 | port: 6379,
31 | db: 0,
32 | keyPrefix: '',
33 | },
34 | bull: {
35 | host: '127.0.0.1',
36 | port: 6379,
37 | db: 0,
38 | keyPrefix: 'q',
39 | },
40 | })
41 |
42 | config.set('bull', {
43 | connection: 'bull',
44 | })
45 |
46 | return config
47 | })
48 | ioc.alias('Adonis/Src/Config', 'Config')
49 |
50 | await registrar.boot()
51 | })
52 |
53 | group.beforeEach(() => {
54 | ioc.restore()
55 | })
56 |
57 | test('BullProvider', async (assert) => {
58 | assert.isDefined(ioc.use('Rocketseat/Bull'))
59 | assert.isTrue(ioc._bindings['Rocketseat/Bull'].singleton)
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/test/functional/start/jobs.js:
--------------------------------------------------------------------------------
1 | module.exports = ['App/SomeJob']
2 |
--------------------------------------------------------------------------------
/test/unit/queue.spec.js:
--------------------------------------------------------------------------------
1 | const test = require('japa')
2 | const delay = require('delay')
3 | const { ioc, registrar, resolver } = require('@adonisjs/fold')
4 | const { setupResolver, Helpers, Config } = require('@adonisjs/sink')
5 | const path = require('path')
6 |
7 | const Queue = require('../../src/Queue')
8 |
9 | test.group('Bull', (group) => {
10 | group.before(async () => {
11 | // registrar.before(['@adonisjs/redis/providers/RedisProvider']).register()
12 |
13 | ioc.bind('Adonis/Src/Helpers', () => {
14 | return new Helpers(path.join(__dirname, '..'))
15 | })
16 | ioc.alias('Adonis/Src/Helpers', 'Helpers')
17 |
18 | ioc.bind('Adonis/Src/Config', () => {
19 | const config = new Config()
20 | config.set('redis', {
21 | connection: 'local',
22 | local: {
23 | host: '127.0.0.1',
24 | port: 6379,
25 | db: 0,
26 | keyPrefix: '',
27 | },
28 | bull: {
29 | host: '127.0.0.1',
30 | port: 6379,
31 | db: 0,
32 | keyPrefix: 'q',
33 | },
34 | })
35 |
36 | config.set('bull', {
37 | connection: 'bull',
38 | })
39 |
40 | return config
41 | })
42 | ioc.alias('Adonis/Src/Config', 'Config')
43 | await registrar.boot()
44 | setupResolver()
45 | })
46 |
47 | group.beforeEach(() => {
48 | ioc.restore()
49 | })
50 |
51 | test('should add a new job', async (assert) => {
52 | ioc.bind('Test/Bull', () => {
53 | return class {
54 | static get key() {
55 | return 'TestBull-name'
56 | }
57 |
58 | static get concurrency() {
59 | return 2
60 | }
61 |
62 | async handle() {}
63 | }
64 | })
65 |
66 | const bull = new Queue(
67 | console,
68 | ioc.use('Config'),
69 | ['Test/Bull'],
70 | ioc,
71 | resolver
72 | )
73 | const Job = ioc.use('Test/Bull')
74 | const data = { test: 'data' }
75 |
76 | const queue = bull.get(Job.key)
77 |
78 | const job = await bull.add(Job.key, data)
79 | assert.equal(Job.key, job.queue.name)
80 | assert.deepEqual(data, job.data)
81 |
82 | assert.equal(queue.concurrency, 2)
83 | })
84 |
85 | test('should add a new job with events inside Job class', async (assert) => {
86 | assert.plan(1)
87 | ioc.bind('Test/Bull', () => {
88 | return class {
89 | static get key() {
90 | return 'TestBull-name'
91 | }
92 |
93 | onCompleted() {
94 | assert.isOk()
95 | }
96 |
97 | async handle() {}
98 | }
99 | })
100 |
101 | const bull = new Queue(
102 | console,
103 | ioc.use('Config'),
104 | ['Test/Bull'],
105 | ioc,
106 | resolver
107 | )
108 | const Job = ioc.use('Test/Bull')
109 | const data = { test: 'data' }
110 |
111 | bull.add(Job.key, data)
112 | bull.process()
113 |
114 | await delay(1050)
115 | })
116 |
117 | test('should schedule a new job', async (assert) => {
118 | ioc.bind('Test/Bull', () => {
119 | return class {
120 | static get key() {
121 | return 'TestBull-name'
122 | }
123 |
124 | async handle() {}
125 | }
126 | })
127 |
128 | const bull = new Queue(
129 | console,
130 | ioc.use('Config'),
131 | ['Test/Bull'],
132 | ioc,
133 | resolver
134 | )
135 | const Job = ioc.use('Test/Bull')
136 | const data = { test: 'data' }
137 |
138 | const job = await bull.schedule(Job.key, data, '1 second')
139 |
140 | assert.equal(Job.key, job.queue.name)
141 | assert.equal(job.delay, 1000)
142 | assert.deepEqual(data, job.data)
143 | })
144 |
145 | test("shouldn't schedule when time is invalid", async (assert) => {
146 | assert.plan(1)
147 |
148 | ioc.bind('Test/Bull', () => {
149 | return class {
150 | static get key() {
151 | return 'TestBull-name'
152 | }
153 |
154 | async handle() {}
155 | }
156 | })
157 |
158 | const bull = new Queue(
159 | console,
160 | ioc.use('Config'),
161 | ['Test/Bull'],
162 | ioc,
163 | resolver
164 | )
165 | const Job = ioc.use('Test/Bull')
166 | const data = { test: 'data' }
167 |
168 | try {
169 | await bull.schedule(Job.key, data, 'invalid time')
170 | } catch (err) {
171 | assert.equal('Invalid schedule time', err.message)
172 | }
173 | })
174 |
175 | test('should get all repeatable jobs', async (assert) => {
176 | ioc.bind('Test/Bull', () => {
177 | return class {
178 | static get key() {
179 | return 'TestBull-name'
180 | }
181 |
182 | async handle() {}
183 | }
184 | })
185 |
186 | const bull = new Queue(
187 | console,
188 | ioc.use('Config'),
189 | ['Test/Bull'],
190 | ioc,
191 | resolver
192 | )
193 | const Job = ioc.use('Test/Bull')
194 | const data = { test: 'data' }
195 | const repeat = { cron: '* * * * *' }
196 |
197 | await bull.add(Job.key, data, { repeat })
198 |
199 | const jobs = await bull.getRepeatableJobs(Job.key)
200 |
201 | assert.equal(jobs.length, 1)
202 | assert.deepEqual(repeat.cron, jobs[0].cron)
203 | })
204 |
205 | test('should remove a repeatable job', async (assert) => {
206 | ioc.bind('Test/Bull', () => {
207 | return class {
208 | static get key() {
209 | return 'TestBull-name'
210 | }
211 |
212 | async handle() {}
213 | }
214 | })
215 |
216 | const bull = new Queue(
217 | console,
218 | ioc.use('Config'),
219 | ['Test/Bull'],
220 | ioc,
221 | resolver
222 | )
223 | const Job = ioc.use('Test/Bull')
224 | const data = { test: 'data' }
225 | const repeat = { cron: '* * * * *' }
226 |
227 | await bull.add(Job.key, data, { repeat })
228 |
229 | let jobs = await bull.getRepeatableJobs(Job.key)
230 | assert.equal(jobs.length, 1)
231 | assert.deepEqual(repeat.cron, jobs[0].cron)
232 |
233 | await bull.removeRepeatable(Job.key, repeat)
234 |
235 | jobs = await bull.getRepeatableJobs(Job.key)
236 | assert.equal(jobs.length, 0)
237 | })
238 | })
239 |
--------------------------------------------------------------------------------