[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    es6: true,\n    node: true,\n  },\n  plugins: ['prettier'],\n  extends: ['standard', 'prettier'],\n  globals: {\n    Atomics: 'readonly',\n    SharedArrayBuffer: 'readonly',\n  },\n  parserOptions: {\n    ecmaVersion: 2018,\n  },\n  rules: {\n    'prettier/prettier': 'error',\n  },\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# Learn how to add code owners here:\n# https://help.github.com/en/articles/about-code-owners\n\n*             @jpedroschmitz\n*.js          @HigoRibeiro\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nThis 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.\n\nOur community strives to:\n\n- **Be welcoming, friendly and patient**.\n- **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.\n- **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.\n- **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.\n\nThis 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.\n\n### Diversity Statement\n\nWe 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.\n\nAlthough 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.\n\n### Reporting Issues\n\nIf 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:\n\n- Your contact information.\n- 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.\n- Any additional information that may be helpful.\n\nAfter 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.\n\nAnyone 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.\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contribution guidelines\n\n## Table of Contents\n\n- [Getting started](#getting-started)\n  - [Language](#language)\n    - [For native English speakers](#for-native-english-speakers)\n  - [Code of Conduct](#code-of-conduct)\n- [How can I help?](#how-can-i-help)\n  - [Documentation](#documentation)\n  - [Issues](#issues)\n    - [Submitting an issue](#submitting-an-issue)\n  - [Feedback](#feedback)\n  - [Code](#code)\n- [Commiting](#commiting)\n  - [Why all these rules?](#why-all-these-rules)\n- [Submitting a pull request](#submitting-a-pull-request)\n\n## Getting started\n\nFirst off, we would like to thank you for taking the time to contribute and make this a better project!\n\nHere 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.\n\n### Language\n\nPlease, while contributing or interacting in any way in this project, **refrain from using any language other than English**.\n\n#### For native English speakers\n\nTry 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.\n\nTry to encourage newcomers to express their opinions, and make them comfortable enough to do so.\n\n### Code of Conduct\n\nWe 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.\n\nBy participating in this project, you agree to abide by its terms.\n\n## How can I help?\n\nHere are some ways you can help along with some guidelines.\n\n### Documentation\n\nAs a user of `adonis-bull`, you're the perfect candidate to help us improve our documentation!\n\nTypos, 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 :)\n\nWhile documenting, try to keep things simple and clear.\n\n### Issues\n\nSome issues are created with missing information, without a template, not reproducible, or plain\ninvalid. You can make them easier to understand and resolve.\n\n#### Submitting an issue\n\n- Please search for similar issues before opening a new one;\n- Use one of the corresponding issue templates;\n- Use a clear and descriptive title;\n- Include as much information as possible by filling out the provided issue template;\n- Most of the time, the best way to report an issue is a failing test proving it.\n\n### Feedback\n\nThe 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.\n\nThis includes submitting an enhancement suggestion, including completely new features and minor improvements to existing functionality.\n\nThe [`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.\n\n### Code\n\nYou can use issue labels to discover issues you could help out with:\n\n- [`bug` issues](https://github.com/Rocketseat/adonis-bull/labels/kind%3A%20bug) are known bugs we'd like to fix;\n- [`enhancement` issues](https://github.com/Rocketseat/adonis-bull/labels/type%3A%20feature%20request) are features we're open to include.\n\nThe [`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.\n\nWhen 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.\n\n## Commiting\n\nA 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:\n\n```\n<type>: <subject>\n\n[optional body]\n\n[optional footer]\n```\n\nTo 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:\n\n- The header (first line) is the only mandatory part of the commit message;\n- The body and footer are both optional but its use is highly encouraged;\n- The header should contains:\n  - A type:\n    - Must be lowercase;\n    - Must be one of:\n      - **chore**: A change that neither fix a bug nor adds a feature;\n      - **ci**: A CI change;\n      - **docs**: A documentation change or fix;\n      - **feat**: A new feature;\n      - **fix**: A bug fix;\n      - **test**: A test-related change.\n      - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n  - A subject:\n    - Must be capitalized;\n    - Must be limited to 50 characters or less;\n    - Must omit any trailing punctuation.\n- The body:\n  - Must have a leading blank line;\n  - Each line must be limited to 72 characters or less.\n- The footer:\n  - Must have a leading blank line;\n  - Each line must be limited to 72 characters or less;\n  - If your commit is about documentation or meta files, please add the tag **[skip ci]** to skip the building process.\n  - If needed, reference to issues and pull requests must be made here in the last line.\n\nYou also should follow these general guidelines when committing:\n\n- Use the present tense (\"Add feature\" not \"Added feature\");\n- Use the imperative mood (\"Move cursor to...\" not \"Moves cursor to...\");\n- Try to answer the following questions:\n  - Why is this change necessary?\n  - How does it address the issue?\n  - What side effects (if any) does this change may have?\n\nExample of a commit message:\n\n```\ntype: Commit message style guide for Git\n\nThe first line of a commit message serves as a summary.  When displayed\non the web, it's often styled as a heading, and in emails, it's\ntypically used as the subject. As such, you should specify a \"type\" and\na \"subject\". The type must be lowercase and one of: chore, ci, docs,\nfeat, fix, test. For the subject you'll need capitalize it and\nomit any trailing punctuation. Aim for about 50 characters, give or\ntake, otherwise it may be painfully truncated in some contexts. Write\nit, along with the rest of your message, in the present tense and\nimperative mood: \"Fix bug\" and not \"Fixed bug\" or \"Fixes bug\".\nConsistent wording makes it easier to mentally process a list of\ncommits.\n\nOftentimes a subject by itself is sufficient. When it's not, add a\nblank line (this is important) followed by one or more paragraphs hard\nwrapped to 72 characters. Git is strongly opinionated that the author\nis responsible for line breaks; if you omit them, command line tooling\nwill show it as one extremely long unwrapped line. Fortunately, most\ntext editors are capable of automating this.\n\nIssues and pull request can be referenced on the footer: #3 #12\n```\n\n### Why all these rules?\n\nWe try to enforce these rules for the following reasons:\n\n- Automatically generating changelog;\n- Communicating in a better way the nature of changes;\n- Triggering build and publish processes;\n- Automatically determining a semantic version bump (based on the types of commits);\n- Making it easier for people to contribute, by allowing them to explore a more structured commit history.\n\n## Submitting a pull request\n\nBefore submitting a pull request, please make sure the following is done:\n\n- [Fork](https://help.github.com/en/articles/fork-a-repo) the repository and create your branch from `master`.\n  - Example: `feat/my-awesome-feature` or `fix/annoying-bug`;\n- Run `yarn` in the repository root;\n- If you’ve fixed a bug or added code that should be tested, **add tests**;\n- Ensure the test suite passes;\n- Ensure your commit is validated;\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n---\n\n<!-- Verify first that your issue is not already reported -->\n\n<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! -->\n\n<!-- If possible complete *all* sections as described. Don't remove any section. -->\n\n**Description of bug**\n\n<!-- A clear and concise description of what the bug is. -->\n\n**To Reproduce**\n\n<!--\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n-->\n\n**Expected behavior**\n\n<!-- A clear and concise description of what you expected to happen. -->\n\n**Exception or Error**\n\n<pre><code>\n<!-- If the issue is accompanied by an exception or an error, please share it below: -->\n<!-- ✍️-->\n</code></pre>\n\n**Screenshots**\n\n<!-- If applicable, add screenshots to help explain your problem. -->\n\n**Environment:**\n\n<!--\n Add information about your environment\n - OS: [e.g. Ubuntu, Windows, macOS]\n - Node [e.g 12.0.0]\n - Adonis [e.g 4.1.0]\n - Etc\n-->\n\n**Additional context**\n\n<!-- Add any other context about the problem here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation-report.md",
    "content": "---\nname: Documentation report\nabout: Use this template for documentation related issues\n---\n\n<!-- Verify first that your issue is not already reported -->\n\n<!-- Please only use this template for documentation related issues -->\n\n<!-- If possible complete *all* sections as described. Don't remove any section. -->\n\n**Description of issue**\n\n<!-- A clear description what needs changing, why should it be changed? How is it useful? -->\n\n**Usage example**\n\n<!-- Is there a usage example? -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement-request.md",
    "content": "---\nname: Enhancement request\nabout: Suggest an idea for this project\n---\n\n<!-- Verify first that your issue is not already reported -->\n\n<!-- Please only use this template for submitting enhancement requests -->\n\n<!-- If possible complete *all* sections as described. Don't remove any section. -->\n\n**What would you like to be added**:\n\n<!-- A clear and concise description of what would you like to be added. -->\n\n**Why is this needed**:\n\n<!-- A clear and concise description of why is it needed. -->\n\n**Is your enhancement request related to a problem? Please describe.**\n\n<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->\n\n**Additional context**\n\n<!-- Add any other context or screenshots about the enhancement request here. -->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE",
    "content": "<!-- If this is your first time, please read our contribution guidelines: (https://github.com/Rocketseat/adonis-bull/blob/master/.github/CONTRIBUTING.md) -->\n\n<!-- Verify first that your pull request is not already proposed -->\n\n<!-- Refrain from using any language other than English -->\n\n<!-- Ensure you have added or ran the appropriate tests for your PR -->\n\n**Changes proposed**\n<!--- What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) -->\n\n<!--- Does this PR introduce a breaking change? -->\n\n<!--- What is the current behavior? (You can also link to an open issue here) -->\n\n<!--- What is the new behavior (if this is a feature change)?** -->\n\n**Additional context**\n<!-- Add any other context or screenshots about the feature request here. -->\n"
  },
  {
    "path": ".github/workflows/commit.yml",
    "content": "name: Lint Commit Messages\n\non: [pull_request]\n\njobs:\n  commit:\n    name: Lint commit messages\n    runs-on: ubuntu-latest\n    env:\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Check commit message\n        uses: wagoid/commitlint-github-action@v1\n        env:\n          NODE_PATH: ${{ github.workspace }}/node_modules\n"
  },
  {
    "path": ".github/workflows/coveralls.yml",
    "content": "name: Coveralls\n\non: [pull_request]\nenv:\n  CI: true\n  COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}\n  NODE_ENV: test\n\njobs:\n  run:\n    name: Coverage [Node.js ${{ matrix.node-version }} on ${{ matrix.os }}]\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n        node-version: [12.x]\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Setup Redis\n        uses: zhulik/redis-action@1.1.0\n        with:\n          redis version: '5'\n          number of databases: 1\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Run tests\n        run: yarn coverage\n\n      - name: Coveralls\n        uses: coverallsapp/github-action@master\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non: [pull_request]\n\njobs:\n  code:\n    name: Lint code\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Set up Node\n        uses: actions/setup-node@v1\n        with:\n          node-version: 12\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Run ESLint\n        run: yarn lint\n"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "content": "name: Node.js\n\non: [push, pull_request]\nenv:\n  CI: true\n  COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}\n\njobs:\n  run:\n    name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n        node-version: [10.x, 12.x]\n\n    steps:\n      - name: Clone repository\n        uses: actions/checkout@v1\n\n      - name: Setup Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Setup Redis\n        uses: zhulik/redis-action@1.1.0\n        with:\n          redis version: '5'\n          number of databases: 1\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Run tests\n        run: yarn test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.gitignore.io/api/node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Coveralls configuration file\n.coveralls.yml\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# tmp dirs\ntest/tmp\ntest/database\n\n\n# End of https://www.gitignore.io/api/node"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2021 Rocketseat\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<br />\n\n<img width=\"275\" alt=\"Adonis Bull\" src=\"https://user-images.githubusercontent.com/16545335/71373449-f2146880-2595-11ea-8a8c-9f51384a3f22.png\">\n\n<p align=\"center\">\n  <h2>A <a href=\"https://github.com/taskforcesh/bullmq\">Bull</a> provider for <a href=\"https://adonisjs.com/\">AdonisJS</a> </br>\n  Adonis Bull provides an easy way to start using Bull.\n  </h2>\n</p>\n\n<div>\n\n[![build-image]][build-url] [![coveralls-image]][coveralls-url] [![license-image]][license-url] [![npm-image]][npm-url]\n\n</div>\n\n<br />\n\n> **This documentation refers to the stable version of Adonis Bull, for Adonis v4.x** <br /> > **If you are using Adonis v5, [click here](https://github.com/Rocketseat/adonis-bull/tree/alpha).**\n\n## Why\n\nUsing 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.\n\n## Install\n\n```sh\nadonis install @rocketseat/adonis-bull\n```\n\n## Usage\n\nRegister the Bull commands at `start/app.js`\n\n```js\nconst aceProviders = ['@rocketseat/adonis-bull/providers/Command']\n```\n\nRegister the Bull provider at `start/app.js`\n\n```js\nconst providers = [\n  //...\n  '@rocketseat/adonis-bull/providers/Bull',\n]\n```\n\nCreate a file with the `jobs` that will be processed at `start/jobs.js`:\n\n```js\nmodule.exports = ['App/Jobs/UserRegisterEmail']\n```\n\nAdd the config file at `config/bull.js`:\n\n```js\n'use strict'\n\nconst Env = use('Env')\n\nmodule.exports = {\n  // redis connection\n  connection: Env.get('BULL_CONNECTION', 'bull'),\n  bull: {\n    redis: {\n      host: '127.0.0.1',\n      port: 6379,\n      password: null,\n      db: 0,\n      keyPrefix: '',\n    },\n  },\n  remote: 'redis://redis.example.com?password=correcthorsebatterystaple',\n}\n```\n\nIn 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).\n\nCreate a file to initiate `Bull` at `preloads/bull.js`:\n\n```js\nconst Bull = use('Rocketseat/Bull')\n\nBull.process()\n  // Optionally you can start BullBoard:\n  .ui(9999, 'localhost') // http://localhost:9999\n// You don't need to specify either port or hostname, the default port number is 9999 and the default hostname is localhost\n```\n\nAdd .preLoad in server.js to initialize the bull preload\n\n```js\nnew Ignitor(require('@adonisjs/fold'))\n  .appRoot(__dirname)\n  .preLoad('preloads/bull') // Add This Line\n  .fireHttpServer()\n  .catch(console.error)\n```\n\n## Creating your job\n\nCreate a class that mandatorily has the methods `key` and `handle`.\n\nThe `key` method is the unique identification of each job. It has to be a `static get` method.\n\nThe `handle` is the method that contains the functionality of your `job`.\n\n```js\nconst Mail = use('Mail')\n\nclass UserRegisterEmail {\n  static get key() {\n    return 'UserRegisterEmail-key'\n  }\n\n  async handle(job) {\n    const { data } = job // the 'data' variable has user data\n\n    await Mail.send('emails.welcome', data, (message) => {\n      message\n        .to(data.email)\n        .from('<from-email>')\n        .subject('Welcome to yardstick')\n    })\n\n    return data\n  }\n}\n\nmodule.exports = UserRegisterEmail\n```\n\nYou can use the `connection` static get method to specify which connection your `job` will work.\n\n```js\nclass UserRegisterEmail {\n  // ...\n  static get connection() {\n    return 'remote'\n  }\n}\n```\n\n### Events\n\nThe package has support for all events triggered in the bull, just add \"on\" and complete with the name of the event\nEx: `onCompleted()`, `onActive()`, `onWaiting()` and etc.\n\n```js\nclass UserRegisterEmail {\n  ...\n  onCompleted(job, result) {}\n  onActive(job) {}\n  ...\n}\n\nmodule.exports = UserRegisterEmail;\n\n```\n\n## Processing the jobs\n\n### Simple job\n\nYou can share the `job` of any `controller`, `hook` or any other place you might like:\n\n```js\nconst User = use('App/Models/User')\nconst Bull = use('Rocketseat/Bull')\nconst Job = use('App/Jobs/UserRegisterEmail')\n\nclass UserController {\n  store ({ request, response }) {\n    const data = request.only(['email', 'name', 'password'])\n\n    const user = await User.create(data)\n\n\n    Bull.add(Job.key, user)\n  }\n}\n\nmodule.exports = UserController\n```\n\n### Scheduled job\n\nSometimes it is necessary to schedule a job instead of shooting it imediately. You should use `schedule` for that:\n\n```js\nconst User = use('App/Models/User')\nconst Bull = use('Rocketseat/Bull')\nconst Job = use('App/Jobs/HolidayOnSaleEmail')\n\nclass HolidayOnSaleController {\n  store ({ request, response }) {\n    const data = request.only(['date', 'product_list']) // 2019-11-15 12:00:00\n\n    const products = await ProductOnSale.create(data)\n\n\n    Bull.schedule(Job.key, products, data.date)\n  }\n}\n\nmodule.exports = HolidayOnSaleController\n```\n\nThis `job` will be sent only on the specific date, wich for example here is on November 15th at noon.\n\nWhen finishing a date, never use past dates because it will cause an error.\n\nother ways of using `schedule`:\n\n```js\nBull.schedule(key, data, new Date('2019-11-15 12:00:00'))\nBull.schedule(key, data, '2 hours') // 2 hours from now\nBull.schedule(key, data, 60 * 1000) // 1 minute from now.\n```\n\n### Advanced jobs\n\nYou can use the own `Bull` configs to improve your job:\n\n```js\nBull.add(key, data, {\n  repeat: {\n    cron: '0 30 12 * * WED,FRI',\n  },\n})\n```\n\nThis `job` will be run at 12:30 PM, only on wednesdays and fridays.\n\n### Exceptions\n\nTo 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`:\n\n```js\nconst Sentry = use('Sentry')\n\nclass QueueHandler {\n  async report(error, job) {\n    Sentry.configureScope((scope) => {\n      scope.setExtra(job)\n    })\n\n    Sentry.captureException(error)\n  }\n}\n\nmodule.exports = QueueHandler\n```\n\n## Contributing\n\nThank 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.\n\n### Contribution Guidelines\n\nTo understand how to submit an issue, commit and create pull requests, check our [Contribution Guidelines](/.github/CONTRIBUTING.md).\n\n### Code of Conduct\n\nWe 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.\n\n## License\n\nMIT License © [Rocketseat](https://github.com/Rocketseat)\n\n[npm-image]: https://img.shields.io/npm/v/@rocketseat/adonis-bull?color=8257E5&style=for-the-badge\n[npm-url]: https://www.npmjs.com/package/@rocketseat/adonis-bull 'npm'\n[license-url]: LICENSE.md\n[license-image]: https://img.shields.io/github/license/adonisjs/adonis-framework?color=8257E5&style=for-the-badge\n[build-url]: https://github.com/Rocketseat/adonis-bull/actions\n[build-image]: https://img.shields.io/github/workflow/status/Rocketseat/adonis-bull/Node.js/master?color=8257E5&style=for-the-badge\n[coveralls-image]: https://img.shields.io/coveralls/github/Rocketseat/adonis-bull/master?color=8257E5&style=for-the-badge\n[coveralls-url]: https://coveralls.io/github/Rocketseat/adonis-bull?branch=master\n"
  },
  {
    "path": "japaFile.js",
    "content": "const { configure } = require('japa')\nconfigure({\n  files: ['test/**/*.spec.js'],\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@rocketseat/adonis-bull\",\n  \"version\": \"0.3.0\",\n  \"main\": \"src/Queue.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"test\": \"node japaFile.js\",\n    \"coverage\": \"nyc --reporter=lcov --reporter=text-summary npm run test\",\n    \"lint\": \"eslint --ignore-path .gitignore\",\n    \"format\": \"prettier \\\"**/*.js\\\" --write --ignore-path .gitignore\"\n  },\n  \"nyc\": {\n    \"exclude\": [\n      \"**/*.spec.js\",\n      \"bin\"\n    ]\n  },\n  \"directories\": {\n    \"test\": \"test\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\",\n      \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.js\": [\n      \"yarn lint --fix\",\n      \"yarn format\"\n    ]\n  },\n  \"commitlint\": {\n    \"extends\": [\n      \"@rocketseat/commitlint-config\"\n    ]\n  },\n  \"keywords\": [\n    \"bull\",\n    \"queue\",\n    \"adonis\",\n    \"adonisjs\",\n    \"adonis-js\",\n    \"adonis-bull\",\n    \"adonis-queue\"\n  ],\n  \"devDependencies\": {\n    \"@adonisjs/ace\": \"^5.0.8\",\n    \"@adonisjs/fold\": \"^4.0.9\",\n    \"@adonisjs/sink\": \"^1.0.17\",\n    \"@commitlint/cli\": \"^11.0.0\",\n    \"@rocketseat/commitlint-config\": \"^0.0.2\",\n    \"delay\": \"^4.3.0\",\n    \"eslint\": \"^7.18.0\",\n    \"eslint-config-prettier\": \"^7.2.0\",\n    \"eslint-config-standard\": \"^16.0.2\",\n    \"eslint-plugin-import\": \"^2.22.1\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-prettier\": \"^3.3.1\",\n    \"eslint-plugin-promise\": \"^4.2.1\",\n    \"eslint-plugin-standard\": \"^4.0.1\",\n    \"husky\": \"^4.3.8\",\n    \"japa\": \"^3.0.1\",\n    \"lint-staged\": \"^10.5.3\",\n    \"nyc\": \"^14.1.1\",\n    \"prettier\": \"^2.2.1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/rocketseat/adonis-bull.git\"\n  },\n  \"dependencies\": {\n    \"bull\": \"^3.22.1\",\n    \"bull-board\": \"^1.5.1\",\n    \"date-fns\": \"^2.7.0\",\n    \"human-interval\": \"^0.1.6\"\n  },\n  \"resolutions\": {\n    \"**/**/minimist\": \"^1.2.3\",\n    \"**/**/set-value\": \"^3.0.1\"\n  }\n}\n"
  },
  {
    "path": "providers/Bull.js",
    "content": "const { ServiceProvider, resolver } = require('@adonisjs/fold')\nconst path = require('path')\n\nclass BullProvider extends ServiceProvider {\n  register() {\n    this.app.singleton('Rocketseat/Bull', (app) => {\n      const Queue = require('../src/Queue')\n      const Helpers = app.use('Adonis/Src/Helpers')\n      const Logger = app.use('Adonis/Src/Logger')\n      const Config = app.use('Adonis/Src/Config')\n\n      const jobs = require(path.join(Helpers.appRoot(), 'start/jobs.js')) || []\n\n      return new Queue(Logger, Config, jobs, app, resolver)\n    })\n\n    this.app.alias('Rocketseat/Bull', 'Bull')\n  }\n}\n\nmodule.exports = BullProvider\n"
  },
  {
    "path": "providers/Command.js",
    "content": "'use strict'\n\nconst { ServiceProvider } = require('@adonisjs/fold')\n\nclass MigrationsProvider extends ServiceProvider {\n  register() {\n    this.app.bind('Rocketseat/Commands/Bull:Listen', () =>\n      require('../src/Commands/Listen')\n    )\n  }\n\n  boot() {\n    const ace = require('@adonisjs/ace')\n    ace.addCommand('Rocketseat/Commands/Bull:Listen')\n  }\n}\n\nmodule.exports = MigrationsProvider\n"
  },
  {
    "path": "src/Commands/Listen.js",
    "content": "'use strict'\n\nconst { Command } = require('@adonisjs/ace')\n\nclass Listen extends Command {\n  static get inject() {\n    return ['Rocketseat/Bull']\n  }\n\n  constructor(Bull) {\n    super()\n    this.Bull = Bull\n  }\n\n  static get signature() {\n    return `\n      bull:listen\n      { --board?=false : Run bull's dashboard }\n      { --board-hostname?=@value : Dashboard hostname }\n      { --board-port?=@value : Dashboard port }\n    `\n  }\n\n  static get description() {\n    return 'Start the Bull listener'\n  }\n\n  async handle(args, { board = false, boardHostname = 'localhost', boardPort = 9999 }) {\n    this.Bull.process()\n    if (board) {\n      this.Bull.ui(boardPort, boardHostname)\n    }\n  }\n}\n\nmodule.exports = Listen\n"
  },
  {
    "path": "src/Queue.js",
    "content": "'use strict'\n\nconst Bull = require('bull')\nconst BullBoard = require('bull-board')\nconst humanInterval = require('human-interval')\n\nconst differenceInMilliseconds = require('date-fns/differenceInMilliseconds')\nconst parseISO = require('date-fns/parseISO')\nconst fs = require('fs')\n\nclass Queue {\n  constructor(Logger, Config, jobs, app, resolver) {\n    this.Logger = Logger\n    this.jobs = jobs\n\n    this.app = app\n    this.resolver = resolver\n\n    this._queues = null\n\n    const { connection, ...connections } = Config.get('bull')\n\n    this.config = connections[connection]\n    this.connections = connections\n  }\n\n  _getJobListeners(Job) {\n    const jobListeners = Object.getOwnPropertyNames(Job.prototype)\n      .filter((method) => method.startsWith('on'))\n      .map((method) => {\n        const eventName = method\n          .replace(/^on(\\w)/, (match, group) => group.toLowerCase())\n          .replace(/([A-Z]+)/, (match, group) => ` ${group.toLowerCase()}`)\n\n        return { eventName, method }\n      })\n    return jobListeners\n  }\n\n  get queues() {\n    if (!this._queues) {\n      this._queues = this.jobs.reduce((queues, path) => {\n        const Job = this.app.use(path)\n\n        let config = this.config\n        if (Job.connection) {\n          config = this.connections[Job.connection]\n        }\n\n        queues[Job.key] = {\n          bull: new Bull(Job.key, config),\n          Job,\n          name: Job.key,\n          handle: Job.handle,\n          concurrency: Job.concurrency || 1,\n          options: Job.options,\n        }\n\n        return queues\n      }, {})\n    }\n\n    return this._queues\n  }\n\n  get(name) {\n    return this.queues[name]\n  }\n\n  add(name, data, options) {\n    const queue = this.get(name)\n\n    const job = queue.bull.add(data, { ...queue.options, ...options })\n\n    return job\n  }\n\n  removeRepeatable(name, repeat) {\n    const queue = this.get(name)\n\n    return queue.bull.removeRepeatable('__default__', repeat)\n  }\n\n  getRepeatableJobs(name) {\n    const queue = this.get(name)\n    const jobs = queue.bull.getRepeatableJobs()\n\n    return jobs\n  }\n\n  schedule(name, data, date, options) {\n    let delay\n\n    if (typeof date === 'number' || date instanceof Number) {\n      delay = date\n    } else {\n      if (typeof date === 'string' || date instanceof String) {\n        const byHuman = humanInterval(date)\n        if (!isNaN(byHuman)) {\n          delay = byHuman\n        } else {\n          delay = differenceInMilliseconds(parseISO(date), new Date())\n        }\n      } else {\n        delay = differenceInMilliseconds(date, new Date())\n      }\n    }\n\n    if (delay > 0) {\n      return this.add(name, data, { ...options, delay })\n    } else {\n      throw new Error('Invalid schedule time')\n    }\n  }\n\n  ui(port = 9999, hostname = 'localhost') {\n    BullBoard.setQueues(\n      Object.values(this.queues).map(\n        (queue) => new BullBoard.BullAdapter(queue.bull)\n      )\n    )\n\n    const server = BullBoard.router.listen(port, hostname, () => {\n      this.Logger.info(`bull board on http://${hostname}:${port}`)\n    })\n\n    const shutdown = () => {\n      server.close(() => {\n        this.Logger.info('Stopping bull board server')\n        process.exit(0)\n      })\n    }\n\n    process.on('SIGTERM', shutdown)\n    process.on('SIGINT', shutdown)\n  }\n\n  async remove(name, jobId) {\n    const job = await this.queues[name].bull.getJob(jobId)\n\n    job.remove()\n  }\n\n  /* eslint handle-callback-err: \"error\" */\n  handleException(error, job) {\n    try {\n      const exceptionHandlerFile = this.resolver\n        .forDir('exceptions')\n        .getPath('QueueHandler.js')\n      fs.accessSync(exceptionHandlerFile, fs.constants.R_OK)\n\n      const namespace = this.resolver\n        .forDir('exceptions')\n        .translate('QueueHandler')\n      const handler = this.app.make(this.app.use(namespace))\n      handler.report(error, job)\n    } catch (err) {\n      this.Logger.error(`name=${job.queue.name} id=${job.id}`)\n    }\n  }\n\n  process() {\n    this.Logger.info('Queue processing started')\n    Object.values(this.queues).forEach((queue) => {\n      const Job = new queue.Job()\n\n      const jobListeners = this._getJobListeners(queue.Job)\n\n      jobListeners.forEach(function (item) {\n        queue.bull.on(item.eventName, Job[item.method].bind(Job))\n      })\n\n      queue.bull.process(queue.concurrency, (job, done) => {\n        Job.handle(job)\n          .then((result) => {\n            done(null, result)\n          })\n          .catch((error) => {\n            this.handleException(error, job)\n            done(error)\n          })\n      })\n    })\n\n    const shutdown = () => {\n      const promises = Object.values(this.queues).map((queue) => {\n        return queue.bull.close()\n      })\n\n      return Promise.all(promises).then(process.exit(0))\n    }\n\n    process.on('SIGTERM', shutdown)\n    process.on('SIGINT', shutdown)\n\n    return this\n  }\n}\n\nmodule.exports = Queue\n"
  },
  {
    "path": "test/functional/app/SomeJob.js",
    "content": "class SomeJob {\n  static get key() {\n    return 'SomeJob-key'\n  }\n\n  async handle() {\n    return 'good luck'\n  }\n}\n\nmodule.exports = SomeJob\n"
  },
  {
    "path": "test/functional/providers.spec.js",
    "content": "const path = require('path')\nconst test = require('japa')\nconst { ioc, registrar, resolver } = require('@adonisjs/fold')\nconst { Helpers, Config } = require('@adonisjs/sink')\n\ntest.group('Provider', (group) => {\n  group.before(async () => {\n    resolver.appNamespace('App')\n    registrar\n      .providers([path.join(__dirname, '../../providers/Bull')])\n      .register()\n\n    ioc.bind('Adonis/Src/Logger', () => {\n      return console\n    })\n\n    ioc.alias('Adonis/Src/Logger', 'Logger')\n\n    ioc.bind('Adonis/Src/Helpers', () => {\n      return new Helpers(__dirname)\n    })\n    ioc.alias('Adonis/Src/Helpers', 'Helpers')\n\n    ioc.bind('Adonis/Src/Config', () => {\n      const config = new Config()\n      config.set('redis', {\n        connection: 'local',\n        local: {\n          host: '127.0.0.1',\n          port: 6379,\n          db: 0,\n          keyPrefix: '',\n        },\n        bull: {\n          host: '127.0.0.1',\n          port: 6379,\n          db: 0,\n          keyPrefix: 'q',\n        },\n      })\n\n      config.set('bull', {\n        connection: 'bull',\n      })\n\n      return config\n    })\n    ioc.alias('Adonis/Src/Config', 'Config')\n\n    await registrar.boot()\n  })\n\n  group.beforeEach(() => {\n    ioc.restore()\n  })\n\n  test('BullProvider', async (assert) => {\n    assert.isDefined(ioc.use('Rocketseat/Bull'))\n    assert.isTrue(ioc._bindings['Rocketseat/Bull'].singleton)\n  })\n})\n"
  },
  {
    "path": "test/functional/start/jobs.js",
    "content": "module.exports = ['App/SomeJob']\n"
  },
  {
    "path": "test/unit/queue.spec.js",
    "content": "const test = require('japa')\nconst delay = require('delay')\nconst { ioc, registrar, resolver } = require('@adonisjs/fold')\nconst { setupResolver, Helpers, Config } = require('@adonisjs/sink')\nconst path = require('path')\n\nconst Queue = require('../../src/Queue')\n\ntest.group('Bull', (group) => {\n  group.before(async () => {\n    // registrar.before(['@adonisjs/redis/providers/RedisProvider']).register()\n\n    ioc.bind('Adonis/Src/Helpers', () => {\n      return new Helpers(path.join(__dirname, '..'))\n    })\n    ioc.alias('Adonis/Src/Helpers', 'Helpers')\n\n    ioc.bind('Adonis/Src/Config', () => {\n      const config = new Config()\n      config.set('redis', {\n        connection: 'local',\n        local: {\n          host: '127.0.0.1',\n          port: 6379,\n          db: 0,\n          keyPrefix: '',\n        },\n        bull: {\n          host: '127.0.0.1',\n          port: 6379,\n          db: 0,\n          keyPrefix: 'q',\n        },\n      })\n\n      config.set('bull', {\n        connection: 'bull',\n      })\n\n      return config\n    })\n    ioc.alias('Adonis/Src/Config', 'Config')\n    await registrar.boot()\n    setupResolver()\n  })\n\n  group.beforeEach(() => {\n    ioc.restore()\n  })\n\n  test('should add a new job', async (assert) => {\n    ioc.bind('Test/Bull', () => {\n      return class {\n        static get key() {\n          return 'TestBull-name'\n        }\n\n        static get concurrency() {\n          return 2\n        }\n\n        async handle() {}\n      }\n    })\n\n    const bull = new Queue(\n      console,\n      ioc.use('Config'),\n      ['Test/Bull'],\n      ioc,\n      resolver\n    )\n    const Job = ioc.use('Test/Bull')\n    const data = { test: 'data' }\n\n    const queue = bull.get(Job.key)\n\n    const job = await bull.add(Job.key, data)\n    assert.equal(Job.key, job.queue.name)\n    assert.deepEqual(data, job.data)\n\n    assert.equal(queue.concurrency, 2)\n  })\n\n  test('should add a new job with events inside Job class', async (assert) => {\n    assert.plan(1)\n    ioc.bind('Test/Bull', () => {\n      return class {\n        static get key() {\n          return 'TestBull-name'\n        }\n\n        onCompleted() {\n          assert.isOk()\n        }\n\n        async handle() {}\n      }\n    })\n\n    const bull = new Queue(\n      console,\n      ioc.use('Config'),\n      ['Test/Bull'],\n      ioc,\n      resolver\n    )\n    const Job = ioc.use('Test/Bull')\n    const data = { test: 'data' }\n\n    bull.add(Job.key, data)\n    bull.process()\n\n    await delay(1050)\n  })\n\n  test('should schedule a new job', async (assert) => {\n    ioc.bind('Test/Bull', () => {\n      return class {\n        static get key() {\n          return 'TestBull-name'\n        }\n\n        async handle() {}\n      }\n    })\n\n    const bull = new Queue(\n      console,\n      ioc.use('Config'),\n      ['Test/Bull'],\n      ioc,\n      resolver\n    )\n    const Job = ioc.use('Test/Bull')\n    const data = { test: 'data' }\n\n    const job = await bull.schedule(Job.key, data, '1 second')\n\n    assert.equal(Job.key, job.queue.name)\n    assert.equal(job.delay, 1000)\n    assert.deepEqual(data, job.data)\n  })\n\n  test(\"shouldn't schedule when time is invalid\", async (assert) => {\n    assert.plan(1)\n\n    ioc.bind('Test/Bull', () => {\n      return class {\n        static get key() {\n          return 'TestBull-name'\n        }\n\n        async handle() {}\n      }\n    })\n\n    const bull = new Queue(\n      console,\n      ioc.use('Config'),\n      ['Test/Bull'],\n      ioc,\n      resolver\n    )\n    const Job = ioc.use('Test/Bull')\n    const data = { test: 'data' }\n\n    try {\n      await bull.schedule(Job.key, data, 'invalid time')\n    } catch (err) {\n      assert.equal('Invalid schedule time', err.message)\n    }\n  })\n\n  test('should get all repeatable jobs', async (assert) => {\n    ioc.bind('Test/Bull', () => {\n      return class {\n        static get key() {\n          return 'TestBull-name'\n        }\n\n        async handle() {}\n      }\n    })\n\n    const bull = new Queue(\n      console,\n      ioc.use('Config'),\n      ['Test/Bull'],\n      ioc,\n      resolver\n    )\n    const Job = ioc.use('Test/Bull')\n    const data = { test: 'data' }\n    const repeat = { cron: '* * * * *' }\n\n    await bull.add(Job.key, data, { repeat })\n\n    const jobs = await bull.getRepeatableJobs(Job.key)\n\n    assert.equal(jobs.length, 1)\n    assert.deepEqual(repeat.cron, jobs[0].cron)\n  })\n\n  test('should remove a repeatable job', async (assert) => {\n    ioc.bind('Test/Bull', () => {\n      return class {\n        static get key() {\n          return 'TestBull-name'\n        }\n\n        async handle() {}\n      }\n    })\n\n    const bull = new Queue(\n      console,\n      ioc.use('Config'),\n      ['Test/Bull'],\n      ioc,\n      resolver\n    )\n    const Job = ioc.use('Test/Bull')\n    const data = { test: 'data' }\n    const repeat = { cron: '* * * * *' }\n\n    await bull.add(Job.key, data, { repeat })\n\n    let jobs = await bull.getRepeatableJobs(Job.key)\n    assert.equal(jobs.length, 1)\n    assert.deepEqual(repeat.cron, jobs[0].cron)\n\n    await bull.removeRepeatable(Job.key, repeat)\n\n    jobs = await bull.getRepeatableJobs(Job.key)\n    assert.equal(jobs.length, 0)\n  })\n})\n"
  }
]