├── .gitignore ├── .npmignore ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── bin └── index.js ├── examples ├── app │ └── Jobs │ │ └── Example.js └── config │ └── ironium.js ├── instructions.js ├── instructions.md ├── japaFile.js ├── package-lock.json ├── package.json ├── providers ├── CommandsProvider.js └── IroniumProvider.js ├── src ├── Commands │ ├── Listen.js │ └── MakeJob.js └── Ironium │ └── index.js ├── templates ├── Job.mustache └── ironium.mustache └── test └── functional ├── app └── Example.js ├── commands.spec.js ├── providers.spec.js └── start └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | .coveralls.yml 40 | 41 | #app/ 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | package-lock.json 40 | .coveralls.yml 41 | bin 42 | examples 43 | test 44 | japaFile.js 45 | README.md 46 | .travis.yml 47 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willvincent/adonis-ironium/75e313446b7f5539f458f533660ce816bda9995f/CONTRIBUTORS.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Will Vincent 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adonis Ironium 2 | 3 | An [Ironium](https://github.com/assaf/ironium) provider for the Adonis 4.x framework. 4 | 5 | Easy integration of job queues backed by beanstalkd, ironMQ, and Amazon SQS 6 | 7 | ## Installation & Configuration 8 | 9 | See [instructions.md](instructions.md) 10 | 11 | ``` 12 | adonis install adonis-ironium 13 | ``` 14 | 15 | ## Usage 16 | 17 | ### Command List 18 | Command | Description 19 | :------------------------|:----------- 20 | `adonis make:job` | Make a new Job Queue 21 | `adonis ironium:listen` | Start the queue listener 22 | 23 | ### Creating your first job queue 24 | 25 | At it's most basic, a job queue provides an async `handle` method that job data is passed into, upon which it 26 | operates. This could be anything from simply the user id for a new user we want to send a welcome email, 27 | or a collection of data needing further processing that doesn't make sense to occur during a regular 28 | user request. 29 | 30 | The `handle` method is the only requirement! 31 | 32 | | Name | Required | Type | Static | Description | 33 | |-------------|----------|-----------|--------|-------------------------------------------------------| 34 | | handle | true | function | false | An async function that is called for this job. | 35 | 36 | [Here's an example.](examples/app/Jobs/Example.js) 37 | 38 | The only real limitation is job payload size, which is dictated by the queue backend: 39 | - AWS and IronMQ: 256k 40 | - beanstalkd: 64k by default, but configurable 41 | 42 | **NOTE:** 43 | 44 | `adonis make:job MyQueue` will create a job class in `app/Jobs`, these are automatically registered and accessibly simply by specifying the class name as the queue name when dispatching a job. 45 | 46 | ie: `ironium.dispatch('MyQueue', { payload_data: 'goes here' })` 47 | 48 | ### Dispatching jobs 49 | 50 | Dispatching jobs is pretty straightforward... 51 | 52 | ```javascript 53 | const ironium = use('Ironium') 54 | const queueName = 'Example' 55 | const job = { 56 | message: 'This is my test job!' 57 | } 58 | 59 | const jobID = ironium.dispatch(queueName, job) 60 | ``` 61 | 62 | It will return a job ID, and process your job behind the scenes. 63 | 64 | You can also pass in an array of jobs, and optionally specify a delay in a format parsable by [ms](https://github.com/zeit/ms): 65 | 66 | ```javascript 67 | const ironium = use('Ironium') 68 | const queueName = 'Example' 69 | const delay = '2hr' 70 | const job = { 71 | message: 'This is my test job!' 72 | } 73 | 74 | const jobID = ironium.dispatch(queueName, job, delay) 75 | ``` 76 | 77 | ```javascript 78 | const ironium = use('Ironium') 79 | const queueName = 'Example' 80 | 81 | const jobs = [ 82 | { message: 'This is my test job!' }, 83 | { message: 'Another test job!' }, 84 | ] 85 | 86 | const jobID = ironium.dispatch(queueName, jobs) 87 | ``` 88 | 89 | ## Reminder 90 | Queued jobs won't process until you fire up one or more queue workers with the `ironium:listen` command. 91 | 92 | 93 | ## Important Note 94 | 95 | If you wish to dispatch jobs from _within_ jobs, `use` does not appear to behave as one might expect, 96 | so instead you should use `ioc.make()` like so: 97 | 98 | ``` 99 | const { ioc } = require('@adonisjs/fold') 100 | 101 | class Example { 102 | async handle (job) { 103 | const Ironium = ioc.make('Ironium') 104 | const Logger = ioc.make('Logger') 105 | 106 | const jobId = await Ironium.dispatch('AnotherJob', anotherPayload) 107 | Logger.info('Dispatching Job(s): ', jobId) 108 | 109 | return 110 | } 111 | } 112 | ``` 113 | 114 | ### Thanks 115 | Heavily inspired by [Adonis Kue](https://github.com/nrempel/adonis-kue), thanks [Nick Rempel](https://github.com/nrempel) for that! 116 | 117 | Also to [Harminder Virk](http://github.com/thetutlage) for creating [Adonis](https://adonisjs.com/). 118 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const semver = require('semver') 4 | const { spawn } = require('child_process') 5 | const spawnArgs = [] 6 | 7 | if (semver.lt(process.version, '8.0.0')) { 8 | spawnArgs.push('--harmony-async-await') 9 | } 10 | 11 | function local () { 12 | spawnArgs.push('./node_modules/.bin/japa') 13 | const tests = spawn('node', spawnArgs) 14 | tests.stdout.on('data', (data) => process.stdout.write(data)) 15 | tests.stderr.on('data', (data) => process.stderr.write(data)) 16 | tests.on('close', (code) => process.exit(code)) 17 | } 18 | 19 | function win () { 20 | spawnArgs.push('./node_modules/japa-cli/index.js') 21 | const tests = spawn('node', spawnArgs) 22 | tests.stdout.on('data', (data) => process.stdout.write(data)) 23 | tests.stderr.on('data', (data) => process.stderr.write(data)) 24 | tests.on('close', (code) => process.exit(code)) 25 | } 26 | 27 | if (process.argv.indexOf('--local') > -1) { 28 | local() 29 | } else if (process.argv.indexOf('--win') > -1) { 30 | win() 31 | } 32 | -------------------------------------------------------------------------------- /examples/app/Jobs/Example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Example { 4 | // This is where the work is done. 5 | handle (data) { 6 | let processed = await someSlowOperation(data) 7 | return Promise.resolve(); 8 | } 9 | } 10 | 11 | module.exports = Example 12 | -------------------------------------------------------------------------------- /examples/config/ironium.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | service: 'beanstalkd', 5 | concurrency: 1, 6 | prefix: '', 7 | beanstalkd: { 8 | host: 'localhost', 9 | port: '11300' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /instructions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * adonis-ironium 5 | * 6 | * (c) Will Vincent 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | const path = require('path') 13 | 14 | module.exports = async function (cli) { 15 | await cli.makeConfig('ironium.js', path.join(__dirname, './templates/ironium.mustache')) 16 | cli.command.completed('create', 'config/ironium.js') 17 | } 18 | -------------------------------------------------------------------------------- /instructions.md: -------------------------------------------------------------------------------- 1 | ## Registering provider 2 | 3 | Make sure to register the provider and make all of the following necessary changes inside the `start/app.js` file! 4 | 5 | ```js 6 | 7 | // Add the ironium provider 8 | const providers = [ 9 | // ... 10 | 'adonis-ironium/providers/IroniumProvider', 11 | ] 12 | 13 | // ... 14 | // Add the command provider 15 | const aceProviders = [ 16 | // ... 17 | 'adonis-ironium/providers/CommandsProvider', 18 | ] 19 | 20 | ``` 21 | 22 | ## Config 23 | 24 | Please update configuration before use. The configuration file is `config/ironium.js`. 25 | -------------------------------------------------------------------------------- /japaFile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const cli = require('japa/cli') 4 | cli.run('test/**/*.spec.js') 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adonis-ironium", 3 | "version": "0.1.10", 4 | "description": "Ironium provider for the Adonis 4.x framework", 5 | "main": "src/Ironium/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "pretest": "npm run lint", 11 | "posttest": "npm run coverage", 12 | "test": "ENV_SILENT=true NO_ANSI=false FORCE_COLOR=true nyc node ./bin/index.js --local", 13 | "lint": "standard", 14 | "lint:fix": "standard --fix", 15 | "precommit": "npm run test && lint-staged", 16 | "prepush": "npm run test && lint-staged", 17 | "coverage": "nyc report --reporter=text-lcov" 18 | }, 19 | "lint-staged": { 20 | "*.js": [ 21 | "npm run lint:fix", 22 | "git add" 23 | ] 24 | }, 25 | "author": "Will Vincent ", 26 | "license": "MIT", 27 | "dependencies": { 28 | "boxen": "^1.3.0", 29 | "ironium": "^6.1.1", 30 | "lodash": "^4.17.10", 31 | "ms": "^2.1.1" 32 | }, 33 | "devDependencies": { 34 | "@adonisjs/ace": "^4.0.8", 35 | "@adonisjs/fold": "^4.0.8", 36 | "@adonisjs/framework": "^4.0.31", 37 | "@adonisjs/sink": "^1.0.16", 38 | "child_process": "^1.0.2", 39 | "coveralls": "^3.0.1", 40 | "dotenv": "^4.0.0", 41 | "husky": "^0.13.4", 42 | "japa": "^1.0.6", 43 | "japa-cli": "^1.0.1", 44 | "lint-staged": "^4.0.0", 45 | "nyc": "^11.8.0", 46 | "semver": "^5.5.0", 47 | "standard": "^10.0.2", 48 | "walk-sync": "^0.3.2" 49 | }, 50 | "nyc": { 51 | "include": [ 52 | "src", 53 | "providers", 54 | "commands" 55 | ] 56 | }, 57 | "standard": { 58 | "globals": [ 59 | "use", 60 | "make" 61 | ] 62 | }, 63 | "repository": { 64 | "type": "git", 65 | "url": "git+https://github.com/willvincent/adonis-ironium.git" 66 | }, 67 | "keywords": [ 68 | "adonis", 69 | "ironium", 70 | "queue" 71 | ], 72 | "bugs": { 73 | "url": "https://github.com/willvincent/adonis-ironium/issues" 74 | }, 75 | "homepage": "https://github.com/willvincent/adonis-ironium#readme" 76 | } 77 | -------------------------------------------------------------------------------- /providers/CommandsProvider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { ServiceProvider } = require('@adonisjs/fold') 4 | 5 | class CommandsProvider extends ServiceProvider { 6 | register () { 7 | this.app.bind('Adonis/Commands/Ironium:Listen', () => require('../src/Commands/Listen')) 8 | this.app.bind('Adonis/Commands/Make:Job', () => require('../src/Commands/MakeJob')) 9 | } 10 | 11 | boot () { 12 | const ace = require('@adonisjs/ace') 13 | ace.addCommand('Adonis/Commands/Ironium:Listen') 14 | ace.addCommand('Adonis/Commands/Make:Job') 15 | } 16 | } 17 | 18 | module.exports = CommandsProvider 19 | -------------------------------------------------------------------------------- /providers/IroniumProvider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const { ioc, ServiceProvider } = require('@adonisjs/fold') 6 | 7 | 8 | class IroniumProvider extends ServiceProvider { 9 | register () { 10 | this.app.singleton('Adonis/Addons/Ironium', (app) => { 11 | const Helpers = app.use('Adonis/Src/Helpers') 12 | const Logger = app.use('Adonis/Src/Logger') 13 | const Config = app.use('Adonis/Src/Config') 14 | const Ironium = require('./../src/Ironium') 15 | 16 | // Auto-register jobs 17 | const jobs = [] 18 | fs.readdirSync(path.join(Helpers.appRoot(), 'app', 'Jobs')).forEach(file => { 19 | if (file === 'index.js' || file.substr(file.lastIndexOf('.') + 1) !== 'js') return 20 | jobs.push(ioc.make(`App/Jobs/${file.replace('.js', '')}`)) 21 | }) 22 | 23 | return new Ironium(Logger, Config.get('ironium'), jobs) 24 | }) 25 | 26 | this.app.alias('Adonis/Addons/Ironium', 'Ironium') 27 | } 28 | } 29 | 30 | module.exports = IroniumProvider 31 | -------------------------------------------------------------------------------- /src/Commands/Listen.js: -------------------------------------------------------------------------------- 1 | 'user strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | 5 | class Listen extends Command { 6 | static get inject () { 7 | return [ 8 | 'Adonis/Addons/Ironium' 9 | ] 10 | } 11 | 12 | constructor (Ironium) { 13 | super() 14 | this.Ironium = Ironium 15 | } 16 | 17 | static get signature () { 18 | return 'ironium:listen' 19 | } 20 | 21 | static get description () { 22 | return 'Start the ironium listener.' 23 | } 24 | 25 | handle () { 26 | return this.Ironium.listen() 27 | } 28 | } 29 | 30 | module.exports = Listen 31 | -------------------------------------------------------------------------------- /src/Commands/MakeJob.js: -------------------------------------------------------------------------------- 1 | 'user strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | const { join, basename } = require('path') 5 | const _ = require('lodash') 6 | const boxen = require('boxen') 7 | 8 | class MakeJob extends Command { 9 | static get signature () { 10 | return `make:job { name: Name of Job (Queue) }` 11 | } 12 | 13 | static get description () { 14 | return 'Make a new Job (Queue)' 15 | } 16 | 17 | async handle ({ name }) { 18 | try { 19 | name = _.upperFirst(_.camelCase(name)) 20 | const templatePath = join(__dirname, '../../templates/Job.mustache') 21 | const templateContent = await this.readFile(templatePath, 'utf-8') 22 | const filePath = join('app/Jobs', name) + '.js' 23 | 24 | await this.generateFile(filePath, templateContent, { name }) 25 | 26 | const namespace = this.getNamespace(filePath) 27 | console.log(`${this.icon('success')} ${this.chalk.green('create')} ${filePath}`) 28 | 29 | this.printInstructions(namespace) 30 | } catch ({ message }) { 31 | this.error(message) 32 | } 33 | } 34 | 35 | /** 36 | * Returns namespace for a given resource 37 | */ 38 | getNamespace (filePath) { 39 | return `App/Jobs/${basename(filePath).replace('.js', '')}` 40 | } 41 | 42 | /** 43 | * Print instructions to the console 44 | */ 45 | printInstructions (namespace) { 46 | const lines = [ 47 | 'Register job/queue as follows', 48 | '', 49 | `1. Open ${this.chalk.cyan('start/app.js')}`, 50 | `2. Add ${this.chalk.cyan(namespace)} to jobs array` 51 | ] 52 | 53 | console.log(boxen(lines.join('\n'), { 54 | dimBorder: true, 55 | align: 'left', 56 | padding: { 57 | left: 8, 58 | right: 8 59 | }, 60 | borderColor: 'yellow' 61 | })) 62 | } 63 | } 64 | 65 | module.exports = MakeJob 66 | -------------------------------------------------------------------------------- /src/Ironium/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ironium = require('ironium') 4 | const ms = require('ms') 5 | 6 | /** 7 | * @module Ironium 8 | * @description Interface to the Ironium job queue library 9 | */ 10 | class Ironium { 11 | constructor (Logger, config = {}, jobs) { 12 | this.Logger = Logger 13 | this.config = config 14 | this.jobs = jobs 15 | this.init() 16 | } 17 | 18 | /** 19 | * @returns {*} 20 | * @public 21 | */ 22 | init () { 23 | if (!this._instance) { 24 | const config = this.config[this.config.service] 25 | if (this.config.prefix) config.prefix = this.config.prefix 26 | if (this.config.concurrency) config.concurrency = this.config.concurrency 27 | if (this.config.canStartQueue) config.canStartQueue = this.config.canStartQueue 28 | ironium.configure(config) 29 | 30 | this._instance = ironium 31 | 32 | if (this.config.log_errors) { 33 | this._instance.onerror(function (error, subject) { 34 | this.Logger.error(`IRONIUM :: Error reported by: ${subject}`) 35 | this.Logger.error(error.stack) 36 | }) 37 | } 38 | 39 | for (const job of this.jobs) { 40 | this._instance.queue(job.constructor.name).eachJob(job.handle) 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Queue up one or more jobs for processing. 47 | * @param {String} queueName 48 | * @param {Mixed} jobs Single job item data, or array of job items' data 49 | * @param {Mixed} delay Optional delay before processing job(s). Must be number or string parsable by ms() 50 | * @returns {Mixed} Job ID or array of Job IDs 51 | * @public 52 | */ 53 | async dispatch (queueName, jobs, delay) { 54 | if (typeof queueName !== 'string') { 55 | throw new Error(`Expected queueName to be of type string but got <${typeof key}>.`) 56 | } 57 | if (delay && !ms(delay)) { 58 | throw new Error(`Expected delay to be either a number or a string parsable by ms().`) 59 | } 60 | if (!this._instance) this.init() 61 | 62 | const queue = this._instance.queue(queueName) 63 | 64 | if (!Array.isArray(jobs)) { 65 | jobs = [jobs] 66 | } 67 | 68 | let ids = [] 69 | 70 | if (delay && ms(delay)) { 71 | ids = await queue.delayJobs(jobs, delay) 72 | } else { 73 | ids = await queue.delayJobs(jobs, 0) 74 | } 75 | 76 | if (ids.length === 1) { 77 | return ids[0] 78 | } 79 | 80 | return ids 81 | } 82 | 83 | /** 84 | * Process queued jobs 85 | * 86 | * @public 87 | */ 88 | listen () { 89 | if (!this._instance) this.init() 90 | this.Logger.info('Ironium queue worker listening for jobs.') 91 | if (this.jobs.length < 1) { 92 | throw new Error('There are no job queues defined!') 93 | } 94 | 95 | process.once('SIGTERM', (sig) => { 96 | this.Logger.info('Ironium queue worker shutting down.') 97 | this._instance.stop() 98 | // Give it 5s to settle and/or finish in flight jobs 99 | setTimeout(() => process.exit(0), 5000) 100 | }) 101 | 102 | return this._instance.start() 103 | } 104 | 105 | /** 106 | * Execute all scheduled jobs 107 | * Should only really be used for testing. 108 | */ 109 | runOnce () { 110 | if (!this._instance) this.init() 111 | if (this.jobs.length < 1) { 112 | throw new Error('There are no job queues defined!') 113 | } 114 | return this._instance.runOnce() 115 | } 116 | } 117 | 118 | module.exports = Ironium 119 | -------------------------------------------------------------------------------- /templates/Job.mustache: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class {{name}} { 4 | // The handle method is required, and must either be async 5 | // or return a resolved promise, ie: 6 | // return Promise.resolve() 7 | async handle (job) { 8 | // PROCESS YOUR JOB HERE 9 | console.log('{{name}} job started') 10 | 11 | return 12 | } 13 | } 14 | 15 | module.exports = {{name}} 16 | 17 | -------------------------------------------------------------------------------- /templates/ironium.mustache: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Env = use('Env') 4 | 5 | module.exports = { 6 | /* 7 | |-------------------------------------------------------------------------- 8 | | Service 9 | |-------------------------------------------------------------------------- 10 | | 11 | | Which queue service to use, must match an object key below, and should 12 | | reference one of the following; aws, beanstalkd, ironmq 13 | | 14 | */ 15 | service: Env.get('QUEUE_SERVICE', 'beanstalkd'), 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Concurrency 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Number of queue items to process concurrently 23 | | 24 | */ 25 | concurrency: Env.get('QUEUE_CONCURRENCY', 15), 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Prefix 30 | |-------------------------------------------------------------------------- 31 | | 32 | | Prefix applied to all queue names. 33 | | This should be a string, usually representative of the node environment 34 | | followed by a dash. Can also be an empty string to disable prefixing. 35 | | 36 | */ 37 | prefix: Env.get('QUEUE_PREFIX', process.NODE_ENV + '-'), 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | canStartQueue 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Function returning true/false indicating whether a queue can be started 45 | | or not. By default a comma separated list of queue names (without prefixes) 46 | | can be specified as an environment variable IRONIUM_QUEUES. 47 | | 48 | | Unless you need more specific logic, that should be sufficient for most 49 | | situations. If this is not provided, and the environment variable is not 50 | | set, all queues will be startable by default. 51 | | 52 | */ 53 | // Uncomment to use: 54 | /* 55 | canStartQueue: function(queueName) { 56 | return ['foo', 'bar', 'baz'].includes(queueName) 57 | }, 58 | */ 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | AWS 63 | |-------------------------------------------------------------------------- 64 | | 65 | | AWS Configuration must include region and access and secret keys. 66 | | 67 | */ 68 | aws: { 69 | accessKeyId: Env.get('AWS_ACCESS_KEY', 'ChangeMe'), 70 | secretAccessKey: Env.get('AWS_SECRET_KEY', 'ChangeMe'), 71 | region: Env.get('AWS_REGION', 'us-east-1') 72 | }, 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Beanstalkd 77 | |-------------------------------------------------------------------------- 78 | | 79 | | Beanstalkd configuration must include beanstalkd server host and port 80 | | 81 | | NOTE: Ironium appears to treat beanstalkd as only for local use, not 82 | | exposing a config option for the webhook url. 83 | | 84 | */ 85 | beanstalkd: { 86 | host: Env.get('QUEUE_HOST', 'localhost'), 87 | port: Env.get('QUEUE_PORT', '11300') 88 | }, 89 | 90 | /* 91 | |-------------------------------------------------------------------------- 92 | | IronMQ 93 | |-------------------------------------------------------------------------- 94 | | 95 | | IronMQ configuration must include host, project id, and token 96 | | 97 | */ 98 | ironmq: { 99 | host: Env.get('QUEUE_HOST', 'ChangeMe'), 100 | project_id: Env.get('QUEUE_PORT', 'ChangeMe'), 101 | token: Env.get('QUEUE_TOKEN', 'ChangeMe') 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test/functional/app/Example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Example { 4 | async handle (data) { 5 | console.log('Job Processed') 6 | return 'test result'; 7 | } 8 | } 9 | 10 | module.exports = Example 11 | -------------------------------------------------------------------------------- /test/functional/commands.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('japa') 4 | const path = require('path') 5 | const ace = require('@adonisjs/ace') 6 | const { ioc, registrar, resolver } = require('@adonisjs/fold') 7 | const { Helpers, Config } = require('@adonisjs/sink') 8 | const fs = require('fs') 9 | 10 | test.group('Commands', group => { 11 | group.before(async () => { 12 | resolver.appNamespace('App') 13 | ioc.autoload(path.join(__dirname, './app'), 'App') 14 | registrar 15 | .providers([ 16 | '@adonisjs/framework/providers/AppProvider', 17 | path.join(__dirname, '../../providers/IroniumProvider'), 18 | path.join(__dirname, '../../providers/CommandsProvider') 19 | ]) 20 | .register() 21 | ioc.bind('Adonis/Src/Helpers', () => { 22 | return new Helpers(__dirname) 23 | }) 24 | ioc.alias('Adonis/Src/Helpers', 'Helpers') 25 | ioc.bind('Adonis/Src/Config', () => { 26 | const config = new Config() 27 | config.set('ironium', { 28 | service: 'beanstalkd', 29 | concurrency: 1, 30 | prefix: 'test-', 31 | beanstalkd: { 32 | host: 'localhost', 33 | port: '11300' 34 | } 35 | }) 36 | config.set('app', { 37 | logger: { 38 | transport: 'console', 39 | console: { 40 | driver: 'console' 41 | } 42 | } 43 | }) 44 | return config 45 | }) 46 | ioc.alias('Adonis/Src/Config', 'Config') 47 | await registrar.boot() 48 | }) 49 | 50 | group.beforeEach(() => { 51 | ioc.restore() 52 | }) 53 | 54 | test('Create a job', async assert => { 55 | await ace.call('make:job', { name: 'Test' }) 56 | assert.isTrue( 57 | fs.existsSync(path.join(__dirname, '../../app/Jobs/Test.js')) 58 | ) 59 | }) 60 | 61 | test('Create a job with the same name', async assert => { 62 | await ace.call('make:job', { name: 'Test' }) 63 | const filePath = path.join(__dirname, '../../app/Jobs/Test.js') 64 | assert.isTrue(fs.existsSync(filePath)) 65 | fs.unlinkSync(filePath) 66 | }) 67 | 68 | test('Job Queuing & Processing', async assert => { 69 | const Ironium = use('Ironium') 70 | const data = { test: 'data' } 71 | const jobId = await Ironium.dispatch('Example', data) 72 | assert.isFalse(Array.isArray(jobId)) 73 | await Ironium.runOnce() 74 | const data2 = [{ test: 'data' }, { more: 'tests' }] 75 | const jobIds = await Ironium.dispatch('Example', data2) 76 | assert.isTrue(Array.isArray(jobIds)) 77 | await Ironium.runOnce() 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/functional/providers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const test = require('japa') 5 | const { ioc, registrar, resolver } = require('@adonisjs/fold') 6 | const { Helpers, Config } = require('@adonisjs/sink') 7 | 8 | test.group('Providers', group => { 9 | group.before(async () => { 10 | resolver.appNamespace('App') 11 | registrar 12 | .providers([ 13 | '@adonisjs/framework/providers/AppProvider', 14 | path.join(__dirname, '../../providers/IroniumProvider'), 15 | path.join(__dirname, '../../providers/CommandsProvider') 16 | ]) 17 | .register() 18 | ioc.bind('Adonis/Src/Helpers', () => { 19 | return new Helpers(__dirname) 20 | }) 21 | ioc.alias('Adonis/Src/Helpers', 'Helpers') 22 | ioc.bind('Adonis/Src/Config', () => { 23 | const config = new Config() 24 | config.set('ironium', { 25 | service: 'beanstalkd', 26 | concurrency: 1, 27 | prefix: '', 28 | beanstalkd: { 29 | host: 'localhost', 30 | port: '11300' 31 | } 32 | }) 33 | config.set('app', { 34 | logger: { 35 | transport: 'console', 36 | console: { 37 | driver: 'console' 38 | } 39 | } 40 | }) 41 | return config 42 | }) 43 | ioc.alias('Adonis/Src/Config', 'Config') 44 | await registrar.boot() 45 | }) 46 | 47 | group.beforeEach(() => { 48 | ioc.restore() 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/functional/start/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | jobs: [ 5 | 'App/Example' 6 | ] 7 | } 8 | --------------------------------------------------------------------------------