diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 4fc877d6..13fc7bcb --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules *.rdb test/incomplete *.swp +/.project diff --git a/.npmignore b/.npmignore old mode 100755 new mode 100644 diff --git a/.travis.yml b/.travis.yml old mode 100755 new mode 100644 diff --git a/History.md b/History.md old mode 100755 new mode 100644 diff --git a/Makefile b/Makefile old mode 100755 new mode 100644 index 329a847f..ab2c91cf --- a/Makefile +++ b/Makefile @@ -4,11 +4,13 @@ all: build build: @./node_modules/coffee-script/bin/coffee \ + --exit \ -c \ -o lib src test-tdd: @./node_modules/.bin/mocha \ + --exit \ --reporter $(REPORTER) \ --require should \ --require sinon \ @@ -17,6 +19,7 @@ test-tdd: test-bdd: @./node_modules/.bin/mocha \ + --exit \ --reporter $(REPORTER) \ --require should \ --ui bdd \ @@ -24,10 +27,10 @@ test-bdd: test-bdd-coffee: @./node_modules/.bin/mocha \ - --compilers coffee:coffee-script \ + --exit \ --reporter $(REPORTER) \ - --require should \ --require coffee-script/register \ + --require should \ --ui bdd \ test/*.coffee diff --git a/Readme.md b/Readme.md index 5c8afe1e..a41eb6de 100644 --- a/Readme.md +++ b/Readme.md @@ -251,7 +251,7 @@ queue.on('job enqueue', function(id, type){ console.log( 'Job %s got queued of type %s', id, type ); }).on('job complete', function(id, result){ - kue.Job.get(id, function(err, job){ + queue.Job.get(id, function(err, job){ if (err) return; job.remove(function(err){ if (err) throw err; @@ -490,14 +490,14 @@ queue.inactive( function( err, ids ) { // others are active, complete, failed, d however the second one doesn't scale to large deployments, there you can use more specific `Job` static methods: ```js -kue.Job.rangeByState( 'failed', 0, n, 'asc', function( err, jobs ) { +queue.Job.rangeByState( 'failed', 0, n, 'asc', function( err, jobs ) { // you have an array of maximum n Job objects here }); ``` or ```js -kue.Job.rangeByType( 'my-job-type', 'failed', 0, n, 'asc', function( err, jobs ) { +queue.Job.rangeByType( 'my-job-type', 'failed', 0, n, 'asc', function( err, jobs ) { // you have an array of maximum n Job objects here }); ``` @@ -512,7 +512,7 @@ If you did none of above in [Error Handling](#error-handling) section or your pr ```js queue.active( function( err, ids ) { ids.forEach( function( id ) { - kue.Job.get( id, function( err, job ) { + queue.Job.get( id, function( err, job ) { // Your application should check if job is a stuck one job.inactive(); }); @@ -533,7 +533,7 @@ queue.create( ... ).removeOnComplete( true ).save() But if you eventually/temporally need completed job data, you can setup an on-demand job removal script like below to remove top `n` completed jobs: ```js -kue.Job.rangeByState( 'complete', 0, n, 'asc', function( err, jobs ) { +queue.Job.rangeByState( 'complete', 0, n, 'asc', function( err, jobs ) { jobs.forEach( function( job ) { job.remove( function(){ console.log( 'removed ', job.id ); diff --git a/bin/kue-dashboard b/bin/kue-dashboard index 60e8b959..1044833a 100755 --- a/bin/kue-dashboard +++ b/bin/kue-dashboard @@ -1,20 +1,20 @@ #!/usr/bin/env node var kue = require('kue'); var argv = require('yargs') - .usage('Usage: $0 [options]') - .example('$0 -p 3050 -r redis://10.0.0.4:6379 -q q') - .describe('r', 'Redis url') - .describe('p', 'Dashboard port') - .describe('q', 'Prefix to use') - .default('p', 3000) - .default('r', 'redis://127.0.0.1:6379') - .default('q', 'q') - .help('h') + .usage('Usage: $0 [options]') + .example('$0 -p 3050 -r redis://10.0.0.4:6379 -q q') + .describe('r', 'Redis url') + .describe('p', 'Dashboard port') + .describe('q', 'Prefix to use') + .default('p', 3000) + .default('r', 'redis://127.0.0.1:6379') + .default('q', 'q') + .help('h') .alias('h', 'help') .argv ; -kue.createQueue({ +kue.getQueue({ redis: argv.r, prefix: argv.q }); diff --git a/examples/stale.js b/examples/stale.js index dc9a5092..ac881369 100755 --- a/examples/stale.js +++ b/examples/stale.js @@ -4,7 +4,7 @@ var kue = require( '../' ) // create our job queue var jobs = kue.createQueue() - , Job = kue.Job; + , Job = jobs.Job; // start redis with $ redis-server @@ -62,4 +62,4 @@ jobs.on( 'job complete', function ( id ) { var app = express.createServer(); app.use( kue.app ); app.listen( 3000 ); -console.log( 'UI started on port 3000' ); \ No newline at end of file +console.log( 'UI started on port 3000' ); diff --git a/index.js b/index.js old mode 100755 new mode 100644 diff --git a/lib/http/routes/index.js b/lib/http/routes/index.js index 5fefeecf..e9577c90 100755 --- a/lib/http/routes/index.js +++ b/lib/http/routes/index.js @@ -9,8 +9,7 @@ */ var Queue = require('../../kue') - , Job = require('../../queue/job') - , queue = Queue.createQueue(); + , queue = Queue.getQueue(); /** * Serve the index page. diff --git a/lib/http/routes/json.js b/lib/http/routes/json.js index c0ae8641..6ca05004 100755 --- a/lib/http/routes/json.js +++ b/lib/http/routes/json.js @@ -9,9 +9,9 @@ */ var Queue = require('../../kue') - , Job = require('../../queue/job') , lodash = require('lodash') - , queue = Queue.createQueue(); + , queue = Queue.getQueue() + , Job = queue.Job; /** * Search instance. @@ -21,7 +21,7 @@ var search; function getSearch() { if( search ) return search; var reds = require('reds'); - reds.createClient = require('../../redis').createClient; + reds.createClient = queue.redis.createClient; return search = reds.createSearch(queue.client.getKey('search')); } @@ -158,7 +158,7 @@ exports.createJob = function( req, res ) { function _create( args, next ) { if( !args.type ) return next({ error: 'Must provide job type' }, null, 400); - var job = new Job(args.type, args.data || {}); + var job = queue.createJob(args.type, args.data || {}); var options = args.options || {}; if( options.attempts ) job.attempts(parseInt(options.attempts)); if( options.priority ) job.priority(options.priority); @@ -167,7 +167,7 @@ exports.createJob = function( req, res ) { if( options.backoff ) job.backoff(options.backoff); if( options.removeOnComplete ) job.removeOnComplete(options.removeOnComplete); if( options.ttl ) job.ttl(options.ttl); - + job.save(function( err ) { if( err ) { return next({ error: err.message }, null, 500); diff --git a/lib/queue/job.js b/lib/job.js similarity index 65% rename from lib/queue/job.js rename to lib/job.js index 204aff27..d6a8c310 100644 --- a/lib/queue/job.js +++ b/lib/job.js @@ -1,283 +1,3 @@ -/*! - * kue - Job - * Copyright (c) 2013 Automattic - * Copyright (c) 2011 LearnBoost - * MIT Licensed - */ - -/** - * Module dependencies. - */ - -var EventEmitter = require('events').EventEmitter - , events = require('./events') - , redis = require('../redis') - , _ = require('lodash') - , util = require('util') - , noop = function() {}; - -/** - * Expose `Job`. - */ - -exports = module.exports = Job; - - -exports.disableSearch = true; - - -exports.jobEvents = true; - -/** - * Search instance. - */ -var search; -function getSearch() { - if( search ) return search; - var reds = require('reds'); - reds.createClient = require('../redis').createClient; - return search = reds.createSearch(redis.client().getKey('search')); -} - -/** - * Default job priority map. - */ - -var priorities = exports.priorities = { - low: 10, normal: 0, medium: -5, high: -10, critical: -15 -}; - -/** - * Map `jobs` by the given array of `ids`. - * - * @param {Object} jobs - * @param {Array} ids - * @return {Array} - * @api private - */ - -function map( jobs, ids ) { - var ret = []; - ids.forEach(function( id ) { - if( jobs[ id ] ) ret.push(jobs[ id ]); - }); - ret = ret.sort(function( a, b ) { - return parseInt(a.id) - parseInt(b.id); - }); - return ret; -} - -/** - * Return a function that handles fetching - * of jobs by the ids fetched. - * - * @param {Function} fn - * @param {String} order - * @param {String} jobType - * @return {Function} - * @api private - */ - -function get( fn, order, jobType) { - return function( err, ids ) { - if( err ) return fn(err); - var pending = ids.length - , jobs = {}; - if( !pending ) return fn(null, ids); - ids.forEach(function( id ) { - id = redis.client().stripFIFO(id); // turn zid back to regular job id - exports.get(id, jobType, function( err, job ) { - if( err ) { - console.error(err); - } else { - jobs[ redis.client().createFIFO(job.id) ] = job; - } - --pending || fn(null, 'desc' == order - ? map(jobs, ids).reverse() - : map(jobs, ids)); - - }); - }); - } -} - -/** - * Get with the range `from`..`to` - * and invoke callback `fn(err, ids)`. - * - * @param {Number} from - * @param {Number} to - * @param {String} order - * @param {Function} fn - * @api public - */ - -exports.range = function( from, to, order, fn ) { - redis.client().zrange(redis.client().getKey('jobs'), from, to, get(fn, order)); -}; - -/** - * Get jobs of `state`, with the range `from`..`to` - * and invoke callback `fn(err, ids)`. - * - * @param {String} state - * @param {Number} from - * @param {Number} to - * @param {String} order - * @param {Function} fn - * @api public - */ - -exports.rangeByState = function( state, from, to, order, fn ) { - redis.client().zrange(redis.client().getKey('jobs:' + state), from, to, get(fn, order)); -}; - -/** - * Get jobs of `type` and `state`, with the range `from`..`to` - * and invoke callback `fn(err, ids)`. - * - * @param {String} type - * @param {String} state - * @param {Number} from - * @param {Number} to - * @param {String} order - * @param {Function} fn - * @api public - */ - -exports.rangeByType = function( type, state, from, to, order, fn ) { - redis.client().zrange(redis.client().getKey('jobs:' + type + ':' + state), from, to, get(fn, order, type)); -}; - -/** - * Get job with `id` and callback `fn(err, job)`. - * - * @param {Number} id - * @param {String} jobType is optional - * @param {Function} fn - * @api public - */ - -exports.get = function( id, jobType, fn ) { - if (id === null || id === undefined) { - return fn(new Error('invalid id param')); - } - if (typeof jobType === 'function' && !fn) { - fn = jobType; - jobType = ''; - } - var client = redis.client() - , job = new Job; - - job.id = id; - job.zid = client.createFIFO(id); - client.hgetall(client.getKey('job:' + job.id), function( err, hash ) { - if( err ) return fn(err); - if( !hash ) { - exports.removeBadJob(job.id, jobType); - return fn(new Error('job "' + job.id + '" doesnt exist')); - } - if( !hash.type ) { - exports.removeBadJob(job.id, jobType); - return fn(new Error('job "' + job.id + '" is invalid')) - } - // TODO: really lame, change some methods so - // we can just merge these - job.type = hash.type; - job._ttl = hash.ttl; - job._delay = hash.delay; - job.priority(Number(hash.priority)); - job._progress = hash.progress; - job._attempts = Number(hash.attempts); - job._max_attempts = Number(hash.max_attempts); - job._state = hash.state; - job._error = hash.error; - job.created_at = hash.created_at; - job.promote_at = hash.promote_at; - job.updated_at = hash.updated_at; - job.failed_at = hash.failed_at; - job.started_at = hash.started_at; - job.duration = hash.duration; - job.workerId = hash.workerId; - job._removeOnComplete = hash.removeOnComplete; - try { - if( hash.data ) job.data = JSON.parse(hash.data); - if( hash.result ) job.result = JSON.parse(hash.result); - if( hash.progress_data ) job.progress_data = JSON.parse(hash.progress_data); - if( hash.backoff ) { - var source = 'job._backoff = ' + hash.backoff + ';'; -// require('vm').runInContext( source ); - eval(source); - } - } catch(e) { - err = e; - } - fn(err, job); - }); -}; - -/** - * Remove all references to an invalid job. Will remove leaky keys in redis keys:TYPE:STATE when - * exports.rangeByType is used. - * - * @param {Number} id - * @param {String} jobType - */ - -exports.removeBadJob = function( id, jobType) { - var client = redis.client(); - var zid = client.createFIFO(id); - client.multi() - .del(client.getKey('job:' + id + ':log')) - .del(client.getKey('job:' + id)) - .zrem(client.getKey('jobs:inactive'), zid) - .zrem(client.getKey('jobs:active'), zid) - .zrem(client.getKey('jobs:complete'), zid) - .zrem(client.getKey('jobs:failed'), zid) - .zrem(client.getKey('jobs:delayed'), zid) - .zrem(client.getKey('jobs'), zid) - .zrem(client.getKey('jobs:' + jobType + ':inactive'), zid) - .zrem(client.getKey('jobs:' + jobType+ ':active'), zid) - .zrem(client.getKey('jobs:' + jobType + ':complete'), zid) - .zrem(client.getKey('jobs:' + jobType + ':failed'), zid) - .zrem(client.getKey('jobs:' + jobType + ':delayed'), zid) - .exec(); - if( !exports.disableSearch ) { - getSearch().remove(id); - } -}; - -/** - * Remove job `id` if it exists and invoke callback `fn(err)`. - * - * @param {Number} id - * @param {Function} fn - * @api public - */ - -exports.remove = function( id, fn ) { - fn = fn || noop; - exports.get(id, function( err, job ) { - if( err ) return fn(err); - if( !job ) return fn(new Error('failed to find job ' + id)); - job.remove(fn); - }); -}; - -/** - * Get log for job `id` and callback `fn(err, log)`. - * - * @param {Number} id - * @param {Function} fn - * @return {Type} - * @api public - */ - -exports.log = function( id, fn ) { - /*redis*/ - Job.client/*()*/.lrange(Job.client.getKey('job:' + id + ':log'), 0, -1, fn); -}; - /** * Initialize a new `Job` with the given `type` and `data`. * @@ -286,13 +6,20 @@ exports.log = function( id, fn ) { * @api public */ -function Job( type, data ) { +var EventEmitter = require('events').EventEmitter + , _ = require('lodash') + , util = require('util') + , noop = function() {}; + +function Job( type, data, queueJob ) { this.type = type; this.data = data || {}; this._max_attempts = 1; - this._jobEvents = exports.jobEvents; -// this.client = redis.client(); - this.client = Job.client/* || (Job.client = redis.client())*/; + var queue = queueJob.queue + this._jobEvents = queueJob.jobEvents; + this.queue = queue; + this.queueJob = queueJob; + this.client = queue.redis.client(); this.priority('normal'); this.on('error', function( err ) { });// prevent uncaught exceptions on failed job errors @@ -433,7 +160,7 @@ Job.prototype.progress = function( complete, total, data ) { this.set('updated_at', Date.now()); this.refreshTtl(); - events.emit(this.id, 'progress', n, data); + this.queue.events.emit(this.id, 'progress', n, data); return this; }; @@ -458,7 +185,7 @@ Job.prototype.delay = function( ms ) { /** * Sets the jobEvents flag for the job. - * Can be used to override the global exports.jobEvents setting + * Can be used to override the global self.jobEvents setting * * @param {Boolean} events True if job events should be emitted, false if job events should not be emitted. * @return {Job} Returns `this` for chaining @@ -649,6 +376,7 @@ Job.prototype.searchKeys = function( keys ) { Job.prototype.remove = function( fn ) { var client = this.client; + var self = this client.multi() .zrem(client.getKey('jobs:' + this.state()), this.zid) .zrem(client.getKey('jobs:' + this.type + ':' + this.state()), this.zid) @@ -657,9 +385,9 @@ Job.prototype.remove = function( fn ) { .del(client.getKey('job:' + this.id)) .exec(function( err ) { // events.remove(this); - events.emit(this.id, 'remove', this.type); - if( !exports.disableSearch ) { - getSearch().remove(this.id, fn); + self.queue.events.emit(this.id, 'remove', this.type); + if( !self.queueJob.disableSearch ) { + self.queueJob.getSearch().remove(this.id, fn); } else { fn && fn(err); } @@ -681,6 +409,7 @@ Job.prototype.state = function( state, fn ) { , fn = fn || noop; var oldState = this._state; var multi = client.multi(); + var self = this if( oldState && oldState != '' && oldState != state ) { multi .zrem(client.getKey('jobs:' + oldState), this.zid) @@ -701,7 +430,7 @@ Job.prototype.state = function( state, fn ) { this._state = state; multi.exec(function( err, replies ) { if( !err ) { - (this._state === 'inactive') ? events.emit(this.id, 'enqueue', this.type) : noop(); + (this._state === 'inactive') ? self.queue.events.emit(this.id, 'enqueue', this.type) : noop(); } return fn(err); }.bind(this)); @@ -731,9 +460,9 @@ Job.prototype.error = function( err ) { } summary = ('string' === typeof str) ? str.split('\n')[ 0 ] : ''; } - this.set('error', str); + this.set('error', str || ''); this.log('%s', summary); - events.emit(this.id, 'error', str); + this.queue.events.emit(this.id, 'error', str); return this; }; @@ -791,7 +520,6 @@ Job.prototype.save = function( fn ) { , fn = fn || noop , max = this._max_attempts , self = this; - // update if( this.id ) return this.update(fn); @@ -826,7 +554,7 @@ Job.prototype.save = function( fn ) { Job.prototype.update = function( fn ) { var json; - + var self = this // serialize json data try { json = JSON.stringify(this.data); @@ -869,17 +597,17 @@ Job.prototype.update = function( fn ) { this.state(this._state, fn); }.bind(this)); - if( !exports.disableSearch ) { + if( !this.queue.disableSearch ) { if( this.searchKeys() ) { this.searchKeys().forEach(function( key ) { var value = _.get(this.data, key); if( !_.isString(value) ) { value = JSON.stringify(value); } - getSearch().index(value, this.id); + self.queueJob.getSearch().index(value, this.id); }.bind(this)); } else { - getSearch().index(json, this.id); + self.queueJob.getSearch().index(json, this.id); } } return this; @@ -894,9 +622,11 @@ Job.prototype.update = function( fn ) { Job.prototype.subscribe = function( callback ) { if( this._jobEvents ) { - events.add(this, callback); + this.queue.events.add(this, callback); } else { callback && callback(); } return this; }; + +module.exports = Job diff --git a/lib/kue.js b/lib/kue.js old mode 100755 new mode 100644 index 533bf20c..431665f2 --- a/lib/kue.js +++ b/lib/kue.js @@ -9,14 +9,7 @@ * Module dependencies. */ -var EventEmitter = require('events').EventEmitter - , Worker = require('./queue/worker') - , events = require('./queue/events') - , Job = require('./queue/job') - , Warlock = require('node-redis-warlock') - , _ = require('lodash') - , redis = require('./redis') - , noop = function(){}; +var Queue = require('./queue') /** * Expose `Queue`. @@ -30,34 +23,6 @@ exports = module.exports = Queue; exports.version = require('../package.json').version; -/** - * Expose `Job`. - */ - -exports.Job = Job; - -/** - * Server instance (that is lazily required) - */ - -var app; - -/** - * Expose the server. - */ - -Object.defineProperty(exports, 'app', { - get: function() { - return app || (app = require('./http')); - } -}); - -/** - * Expose the RedisClient factory. - */ - -exports.redis = redis; - /** * Create a new `Queue`. * @@ -66,603 +31,35 @@ exports.redis = redis; */ exports.createQueue = function( options ) { - if( !Queue.singleton ) { - Queue.singleton = new Queue(options); - } - events.subscribe(); - return Queue.singleton; - -}; - -/** - * Store workers - */ -exports.workers = []; - -/** - * Initialize a new job `Queue`. - * - * @api public - */ - -function Queue( options ) { - options = options || {}; - this.name = options.name || 'kue'; - this.id = [ 'kue', require('os').hostname(), process.pid ].join(':'); - this._options = options; - this.promoter = null; - this.workers = exports.workers; - this.shuttingDown = false; - Job.disableSearch = options.disableSearch !== false; - options.jobEvents !== undefined ? Job.jobEvents = options.jobEvents : ''; - redis.configureFactory(options, this); - this.client = Worker.client = Job.client = redis.createClient(); -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Queue.prototype.__proto__ = EventEmitter.prototype; - -/** - * Create a `Job` with the given `type` and `data`. - * - * @param {String} type - * @param {Object} data - * @return {Job} - * @api public - */ - -Queue.prototype.create = - Queue.prototype.createJob = function( type, data ) { - return new Job(type, data); - }; - -/** - * Proxy to auto-subscribe to events. - * - * @api public - */ - -var on = EventEmitter.prototype.on; -Queue.prototype.on = function( event ) { - if( 0 == event.indexOf('job') ) events.subscribe(); - return on.apply(this, arguments); -}; - -/** - * Promote delayed jobs, checking every `ms`, - * defaulting to 1 second. - * - * @params {Number} ms - * @deprecated - */ - -Queue.prototype.promote = function( ms, l ) { - console.warn('promote method is deprecated, you don\'t need to call this anymore. You can safely remove it from your code now.'); -}; - -/** - * sets up promotion & ttl timers - */ + var queue = new Queue(options); + queue.events.subscribe(); + return queue; -Queue.prototype.setupTimers = function() { - if( this.warlock === undefined ) { - this.lockClient = redis.createClient(); - this.warlock = new Warlock(this.lockClient); - } - this.checkJobPromotion(this._options.promotion); - this.checkActiveJobTtl(this._options.promotion); }; /** - * This new method is called by Kue when created - * - * Promote delayed jobs, checking every `ms`, - * defaulting to 1 second. + * Get `Queue` singleton. * - * @params {Number} ms - */ - -Queue.prototype.checkJobPromotion = function( promotionOptions ) { - promotionOptions = promotionOptions || {}; - var client = this.client - , self = this - , timeout = promotionOptions.interval || 1000 - , lockTtl = promotionOptions.lockTtl || 2000 - //, lockTtl = timeout - , limit = promotionOptions.limit || 1000; - clearInterval(this.promoter); - this.promoter = setInterval(function() { - self.warlock.lock('promotion', lockTtl, function( err, unlock ) { - if( err ) { - // Something went wrong and we weren't able to set a lock - self.emit('error', err); - return; - } - if( typeof unlock === 'function' ) { - // If the lock is set successfully by this process, an unlock function is passed to our callback. - client.zrangebyscore(client.getKey('jobs:delayed'), 0, Date.now(), 'LIMIT', 0, limit, function( err, ids ) { - if( err || !ids.length ) return unlock(); - //TODO do a ZREMRANGEBYRANK jobs:delayed 0 ids.length-1 - var doUnlock = _.after(ids.length, unlock); - ids.forEach(function( id ) { - id = client.stripFIFO(id); - Job.get(id, function( err, job ) { - if( err ) return doUnlock(); - events.emit(id, 'promotion'); - job.inactive(doUnlock); - }); - }); - }); - } else { - // The lock was not established by us, be silent - } - }); - }, timeout); -}; - - -Queue.prototype.checkActiveJobTtl = function( ttlOptions ) { - ttlOptions = ttlOptions || {}; - var client = this.client - , self = this - , timeout = ttlOptions.interval || 1000 - , lockTtl = 2000 - , limit = ttlOptions.limit || 1000; - clearInterval(this.activeJobsTtlTimer); - this.activeJobsTtlTimer = setInterval(function() { - self.warlock.lock('activeJobsTTL', lockTtl, function( err, unlock ) { - if( err ) { - // Something went wrong and we weren't able to set a lock - self.emit('error', err); - return; - } - if( typeof unlock === 'function' ) { - // If the lock is set successfully by this process, an unlock function is passed to our callback. - // filter only jobs set with a ttl (timestamped) between a large number and current time - client.zrangebyscore(client.getKey('jobs:active'), 100000, Date.now(), 'LIMIT', 0, limit, function( err, ids ) { - if( err || !ids.length ) return unlock(); - - var idsRemaining = ids.slice(); - var doUnlock = _.after(ids.length, function(){ - self.removeAllListeners( 'job ttl exceeded ack' ); - waitForAcks && clearTimeout( waitForAcks ); - unlock && unlock(); - }); - - self.on( 'job ttl exceeded ack', function( id ) { - idsRemaining.splice( idsRemaining.indexOf( id ), 1 ); - doUnlock(); - }); - - var waitForAcks = setTimeout( function(){ - idsRemaining.forEach( function( id ){ - id = client.stripFIFO(id); - Job.get(id, function( err, job ) { - if( err ) return doUnlock(); - job.failedAttempt( { error: true, message: 'TTL exceeded' }, doUnlock ); - }); - }); - }, 1000 ); - - ids.forEach(function( id ) { - id = client.stripFIFO(id); - events.emit(id, 'ttl exceeded'); - }); - }); - } else { - // The lock was not established by us, be silent - } - }); - }, timeout); -}; - -/** - * Runs a LUA script to diff inactive jobs ZSET cardinality - * and helper pop LIST length each `ms` milliseconds and syncs helper LIST. - * - * @param {Number} ms interval for periodical script runs - * @api public - */ - -Queue.prototype.watchStuckJobs = function( ms ) { - var client = this.client - , self = this - , ms = ms || 1000; - var prefix = this.client.prefix; - - if( this.client.constructor.name == 'Redis' || this.client.constructor.name == 'Cluster') { - // {prefix}:jobs format is needed in using ioredis cluster to keep they keys in same node - prefix = '{' + prefix + '}'; - } - var script = - 'local msg = redis.call( "keys", "' + prefix + ':jobs:*:inactive" )\n\ - local need_fix = 0\n\ - for i,v in ipairs(msg) do\n\ - local queue = redis.call( "zcard", v )\n\ - local jt = string.match(v, "' + prefix + ':jobs:(.*):inactive")\n\ - local pending = redis.call( "LLEN", "' + prefix + ':" .. jt .. ":jobs" )\n\ - if queue > pending then\n\ - need_fix = need_fix + 1\n\ - for j=1,(queue-pending) do\n\ - redis.call( "lpush", "' + prefix + ':"..jt..":jobs", 1 )\n\ - end\n\ - end\n\ - end\n\ - return need_fix'; - clearInterval(this.stuck_job_watch); - client.script('LOAD', script, function( err, sha ) { - if( err ) { - return self.emit('error', err); - } - this.stuck_job_watch = setInterval(function() { - client.evalsha(sha, 0, function( err, fixes ) { - if( err ) return clearInterval(this.stuck_job_watch); - }.bind(this)); - }.bind(this), ms); - - }.bind(this)); -}; - -/** - * Get setting `name` and invoke `fn(err, res)`. - * - * @param {String} name - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.setting = function( name, fn ) { - fn = fn || noop; - this.client.hget(this.client.getKey('settings'), name, fn); - return this; -}; - -/** - * Process jobs with the given `type`, invoking `fn(job)`. - * - * @param {String} type - * @param {Number|Function} n - * @param {Function} fn - * @api public - */ - -Queue.prototype.process = function( type, n, fn ) { - var self = this; - - if( 'function' == typeof n ) fn = n, n = 1; - - while( n-- ) { - var worker = new Worker(this, type).start(fn); - worker.id = [ self.id, type, self.workers.length + 1 ].join(':'); - worker.on('error', function( err ) { - self.emit('error', err); - }); - worker.on('job complete', function( job ) { - // guard against emit after shutdown - if( self.client ) { - self.client.incrby(self.client.getKey('stats:work-time'), job.duration, noop); - } - }); - // Save worker so we can access it later - self.workers.push(worker); - } - this.setupTimers(); -}; - -/** - * Graceful shutdown - * - * @param {Number} timeout in milliseconds to wait for workers to finish - * @param {String} type specific worker type to shutdown - * @param {Function} fn callback - * @return {Queue} for chaining + * @return {Queue} * @api public */ -Queue.prototype.shutdown = function( timeout, type, fn ) { - var self = this - , n = self.workers.length; - if( arguments.length === 1 ) { - fn = timeout; - type = ''; - timeout = null; - } else if( arguments.length === 2 ) { - fn = type; - type = ''; - } - var origFn = fn || function() { - }; - - if( this.shuttingDown && type === '' ) { // a global shutdown already has been called - return fn(new Error('Shutdown already in progress')); - } - - if( type === '' ) { // this is a global shutdown call - this.shuttingDown = true; - } - - var cleanup = function() { - if( self.shuttingDown ) { - self.workers = []; - exports.workers = []; - self.removeAllListeners(); - Queue.singleton = null; - events.unsubscribe(); - // destroy redis client and pubsub - redis.reset(); - self.client && self.client.quit(); - self.client = null; - self.lockClient && self.lockClient.quit(); - self.lockClient = null; - } - }; - - // Wrap `fn` to only call after all workers finished - fn = function( err ) { - if( err ) { - return origFn(err); - } - if( !--n ) { - cleanup(); - origFn.apply(null, arguments); - } - }; - - // shut down promoter interval - if( self.shuttingDown ) { - if( self.promoter ) { - clearInterval(self.promoter); - self.promoter = null; - } - if( self.activeJobsTtlTimer ) { - clearInterval(self.activeJobsTtlTimer); - self.activeJobsTtlTimer = null; - } - - } - - if( !self.workers.length ) { - cleanup(); - origFn(); - } else { - // Shut down workers 1 by 1 - self.workers.forEach(function( worker ) { - if( self.shuttingDown || worker.type == type ) { - worker.shutdown(timeout, fn); - } else { - fn && fn(); - } - }); +exports.getQueue = function( options ) { + if(!Queue.singleton){ + Queue.singleton = new Queue(options); } + Queue.singleton.events.subscribe(); + return Queue.singleton; - return this; -}; - -/** - * Get the job types present and callback `fn(err, types)`. - * - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.types = function( fn ) { - fn = fn || noop; - this.client.smembers(this.client.getKey('job:types'), fn); - return this; -}; - -/** - * Return job ids with the given `state`, and callback `fn(err, ids)`. - * - * @param {String} state - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.state = function( state, fn ) { - var self = this; - this.client.zrange(this.client.getKey('jobs:' + state), 0, -1, function(err,ids){ - var fixedIds = []; - ids.forEach(function(id){ - fixedIds.push(self.client.stripFIFO(id)); - }); - fn(err,fixedIds); - }); - return this; -}; - -/** - * Get queue work time in milliseconds and invoke `fn(err, ms)`. - * - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.workTime = function( fn ) { - this.client.get(this.client.getKey('stats:work-time'), function( err, n ) { - if( err ) return fn(err); - fn(null, parseInt(n, 10)); - }); - return this; -}; - -/** - * Get cardinality of jobs with given `state` and `type` and callback `fn(err, n)`. - * - * @param {String} type - * @param {String} state - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.cardByType = function( type, state, fn ) { - fn = fn || noop; - this.client.zcard(this.client.getKey('jobs:' + type + ':' + state), fn); - return this; }; /** - * Get cardinality of `state` and callback `fn(err, n)`. + * Shutdown the singleton `Queue`. * - * @param {String} state - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.card = function( state, fn ) { - fn = fn || noop; - this.client.zcard(this.client.getKey('jobs:' + state), fn); - return this; -}; - -/** - * Completed jobs. - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.complete = function( fn ) { - return this.state('complete', fn); -}; - -/** - * Failed jobs. - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.failed = function( fn ) { - return this.state('failed', fn); -}; - -/** - * Inactive jobs (queued). - * @param {Function} fn - * @return {Queue} for chaining * @api public */ -Queue.prototype.inactive = function( fn ) { - return this.state('inactive', fn); +exports.shutdown = function( ) { + var singleton = this.getQueue() + return singleton.shutdown.apply(singleton, arguments) }; - -/** - * Active jobs (mid-process). - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.active = function( fn ) { - return this.state('active', fn); -}; - -/** - * Delayed jobs. - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.delayed = function( fn ) { - return this.state('delayed', fn); -}; - -/** - * Completed jobs of type `type` count. - * @param {String} type is optional - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.completeCount = function( type, fn ) { - if( 1 == arguments.length ) { - fn = type; - return this.card('complete', fn); - } - return this.cardByType(type, 'complete', fn); -}; - - -/** - * Failed jobs of type `type` count. - * @param {String} type is optional - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.failedCount = function( type, fn ) { - if( 1 == arguments.length ) { - fn = type; - return this.card('failed', fn); - } - return this.cardByType(type, 'failed', fn); -}; - -/** - * Inactive jobs (queued) of type `type` count. - * @param {String} type is optional - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.inactiveCount = function( type, fn ) { - if( 1 == arguments.length ) { - fn = type; - return this.card('inactive', fn); - } - return this.cardByType(type, 'inactive', fn); -}; - -/** - * Active jobs (mid-process) of type `type` count. - * @param {String} type is optional - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.activeCount = function( type, fn ) { - if( 1 == arguments.length ) { - fn = type; - return this.card('active', fn); - } - return this.cardByType(type, 'active', fn); -}; - -/** - * Delayed jobs of type `type` count. - * @param {String} type is optional - * @param {Function} fn - * @return {Queue} for chaining - * @api public - */ - -Queue.prototype.delayedCount = function( type, fn ) { - if( 1 == arguments.length ) { - fn = type; - return this.card('delayed', fn); - } - return this.cardByType(type, 'delayed', fn); -}; - -/** - * Test mode for convenience in test suites - * @api public - */ - -Queue.prototype.testMode = require('./queue/test_mode'); diff --git a/lib/queue.js b/lib/queue.js new file mode 100644 index 00000000..3dc0dd39 --- /dev/null +++ b/lib/queue.js @@ -0,0 +1,629 @@ +/** + * Initialize a new job `Queue`. + * + * @api public + */ + +var EventEmitter = require('events').EventEmitter + , Worker = require('./worker') + , QueueEvents = require('./queueEvents') + , QueueJob = require('./queueJob') + , Warlock = require('node-redis-warlock') + , _ = require('lodash') + , redis = require('./redis') + , noop = function(){}; + +function Queue( options ) { + options = options || {}; + this.name = options.name || 'kue'; + this.id = [ 'kue', require('os').hostname(), process.pid ].join(':'); + this._options = options; + this.promoter = null; + this.shuttingDown = false; + + this.workers = []; + + this.redis = redis(); + this.redis.configureFactory(options, this); + + this.client = this.redis.createClient(); + + this.events = QueueEvents(this); + + this.Job = QueueJob(this); + +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Queue.prototype.__proto__ = EventEmitter.prototype; + +/** + * Create a `Job` with the given `type` and `data`. + * + * @param {String} type + * @param {Object} data + * @return {Job} + * @api public + */ + +Queue.prototype.create = Queue.prototype.createJob = function( type, data ) { + return this.Job.createJob(type, data); +}; + +/** + * Proxy to auto-subscribe to events. + * + * @api public + */ + +var on = EventEmitter.prototype.on; +Queue.prototype.on = function( event ) { + if( 0 == event.indexOf('job') ) this.events.subscribe(); + return on.apply(this, arguments); +}; + +/** + * Promote delayed jobs, checking every `ms`, + * defaulting to 1 second. + * + * @params {Number} ms + * @deprecated + */ + +Queue.prototype.promote = function( ms, l ) { + console.warn('promote method is deprecated, you don\'t need to call this anymore. You can safely remove it from your code now.'); +}; + +/** + * sets up promotion & ttl timers + */ + +Queue.prototype.setupTimers = function() { + if( this.warlock === undefined ) { + this.lockClient = this.redis.createClient(); + this.warlock = new Warlock(this.lockClient); + } + this.checkJobPromotion(this._options.promotion); + this.checkActiveJobTtl(this._options.promotion); +}; + +/** + * This new method is called by Kue when created + * + * Promote delayed jobs, checking every `ms`, + * defaulting to 1 second. + * + * @params {Number} ms + */ + +Queue.prototype.checkJobPromotion = function( promotionOptions ) { + promotionOptions = promotionOptions || {}; + var self = this + , client = this.client + , self = this + , timeout = promotionOptions.interval || 1000 + , lockTtl = promotionOptions.lockTtl || 2000 + //, lockTtl = timeout + , limit = promotionOptions.limit || 1000; + clearInterval(this.promoter); + this.promoter = setInterval(function() { + self.warlock.lock('promotion', lockTtl, function( err, unlock ) { + if( err ) { + // Something went wrong and we weren't able to set a lock + self.emit('error', err); + return; + } + if( typeof unlock === 'function' ) { + // If the lock is set successfully by this process, an unlock function is passed to our callback. + client.zrangebyscore(client.getKey('jobs:delayed'), 0, Date.now(), 'LIMIT', 0, limit, function( err, ids ) { + if( err || !ids.length ) return unlock(); + //TODO do a ZREMRANGEBYRANK jobs:delayed 0 ids.length-1 + var doUnlock = _.after(ids.length, unlock); + ids.forEach(function( id ) { + id = client.stripFIFO(id); + self.Job.get(id, function( err, job ) { + if( err ) return doUnlock(); + self.events.emit(id, 'promotion'); + job.inactive(doUnlock); + }); + }); + }); + } else { + // The lock was not established by us, be silent + } + }); + }, timeout); +}; + + +Queue.prototype.checkActiveJobTtl = function( ttlOptions ) { + ttlOptions = ttlOptions || {}; + var self = this + , client = this.client + , self = this + , timeout = ttlOptions.interval || 1000 + , lockTtl = 2000 + , limit = ttlOptions.limit || 1000; + clearInterval(this.activeJobsTtlTimer); + this.activeJobsTtlTimer = setInterval(function() { + self.warlock.lock('activeJobsTTL', lockTtl, function( err, unlock ) { + if( err ) { + // Something went wrong and we weren't able to set a lock + self.emit('error', err); + return; + } + if( typeof unlock === 'function' ) { + // If the lock is set successfully by this process, an unlock function is passed to our callback. + // filter only jobs set with a ttl (timestamped) between a large number and current time + client.zrangebyscore(client.getKey('jobs:active'), 100000, Date.now(), 'LIMIT', 0, limit, function( err, ids ) { + if( err || !ids.length ) return unlock(); + + var idsRemaining = ids.slice(); + var doUnlock = _.after(ids.length, function(){ + self.removeAllListeners( 'job ttl exceeded ack' ); + waitForAcks && clearTimeout( waitForAcks ); + unlock && unlock(); + }); + + self.on( 'job ttl exceeded ack', function( id ) { + idsRemaining.splice( idsRemaining.indexOf( id ), 1 ); + doUnlock(); + }); + + var waitForAcks = setTimeout( function(){ + idsRemaining.forEach( function( id ){ + id = client.stripFIFO(id); + self.Job.get(id, function( err, job ) { + if( err ) return doUnlock(); + job.failedAttempt( { error: true, message: 'TTL exceeded' }, doUnlock ); + }); + }); + }, 1000 ); + + ids.forEach(function( id ) { + id = client.stripFIFO(id); + self.events.emit(id, 'ttl exceeded'); + }); + }); + } else { + // The lock was not established by us, be silent + } + }); + }, timeout); +}; + +/** + * Runs a LUA script to diff inactive jobs ZSET cardinality + * and helper pop LIST length each `ms` milliseconds and syncs helper LIST. + * + * @param {Number} ms interval for periodical script runs + * @api public + */ + +Queue.prototype.watchStuckJobs = function( ms ) { + var client = this.client + , self = this + , ms = ms || 1000; + var prefix = this.client.prefix; + + if( this.client.constructor.name == 'Redis' || this.client.constructor.name == 'Cluster') { + // {prefix}:jobs format is needed in using ioredis cluster to keep they keys in same node + prefix = '{' + prefix + '}'; + } + var script = + 'local msg = self.redis.call( "keys", "' + prefix + ':jobs:*:inactive" )\n\ + local need_fix = 0\n\ + for i,v in ipairs(msg) do\n\ + local queue = self.redis.call( "zcard", v )\n\ + local jt = string.match(v, "' + prefix + ':jobs:(.*):inactive")\n\ + local pending = self.redis.call( "LLEN", "' + prefix + ':" .. jt .. ":jobs" )\n\ + if queue > pending then\n\ + need_fix = need_fix + 1\n\ + for j=1,(queue-pending) do\n\ + self.redis.call( "lpush", "' + prefix + ':"..jt..":jobs", 1 )\n\ + end\n\ + end\n\ + end\n\ + return need_fix'; + clearInterval(this.stuck_job_watch); + client.script('LOAD', script, function( err, sha ) { + if( err ) { + return self.emit('error', err); + } + this.stuck_job_watch = setInterval(function() { + client.evalsha(sha, 0, function( err, fixes ) { + if( err ) return clearInterval(this.stuck_job_watch); + }.bind(this)); + }.bind(this), ms); + + }.bind(this)); +}; + +/** + * Get setting `name` and invoke `fn(err, res)`. + * + * @param {String} name + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.setting = function( name, fn ) { + fn = fn || noop; + this.client.hget(this.client.getKey('settings'), name, fn); + return this; +}; + +/** + * Process jobs with the given `type`, invoking `fn(job)`. + * + * @param {String} type + * @param {Number|Function} n + * @param {Function} fn + * @api public + */ + +Queue.prototype.process = function( type, n, fn ) { + var self = this; + + if( 'function' == typeof n ) fn = n, n = 1; + + while( n-- ) { + var worker = new Worker(this, type).start(fn); + worker.id = [ self.id, type, self.workers.length + 1 ].join(':'); + worker.on('error', function( err ) { + self.emit('error', err); + }); + worker.on('job complete', function( job ) { + // guard against emit after shutdown + if( self.client ) { + self.client.incrby(self.client.getKey('stats:work-time'), job.duration, noop); + } + }); + // Save worker so we can access it later + self.workers.push(worker); + } + this.setupTimers(); +}; + +/** + * Graceful shutdown + * + * @param {Number} timeout in milliseconds to wait for workers to finish + * @param {String} type specific worker type to shutdown + * @param {Function} fn callback + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.shutdown = function( timeout, type, fn ) { + var self = this + , n = self.workers.length; + if( arguments.length === 1 ) { + fn = timeout; + type = ''; + timeout = null; + } else if( arguments.length === 2 ) { + fn = type; + type = ''; + } + var origFn = fn || function() {}; + + if( this.shuttingDown && type === '' ) { // a global shutdown already has been called + return fn(new Error('Shutdown already in progress')); + } + + if( type === '' ) { // this is a global shutdown call + this.shuttingDown = true; + } + + var cleanup = function() { + if( self.shuttingDown ) { + self.workers = []; + self.removeAllListeners(); + if( self === Queue.singleton ){ + Queue.singleton = null + } + self.events.unsubscribe(); + // destroy redis client and pubsub + self.redis.reset(); + + self.client && self.client.quit(); + self.client = null; + self.lockClient && self.lockClient.quit(); + self.lockClient = null; + } + }; + + // Wrap `fn` to only call after all workers finished + fn = function( err ) { + if( err ) { + return origFn(err); + } + if( !--n ) { + cleanup(); + origFn.apply(null, arguments); + } + }; + + // shut down promoter interval + if( self.shuttingDown ) { + if( self.promoter ) { + clearInterval(self.promoter); + self.promoter = null; + } + if( self.activeJobsTtlTimer ) { + clearInterval(self.activeJobsTtlTimer); + self.activeJobsTtlTimer = null; + } + + } + if( !self.workers.length ) { + cleanup(); + origFn(); + } else { + // Shut down workers 1 by 1 + self.workers.forEach(function( worker ) { + if( self.shuttingDown || worker.type == type ) { + worker.shutdown(timeout, fn); + } else { + fn && fn(); + } + }); + } + + return this; +}; + +/** + * Get the job types present and callback `fn(err, types)`. + * + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.types = function( fn ) { + fn = fn || noop; + this.client.smembers(this.client.getKey('job:types'), fn); + return this; +}; + +/** + * Return job ids with the given `state`, and callback `fn(err, ids)`. + * + * @param {String} state + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.state = function( state, fn ) { + var self = this; + this.client.zrange(this.client.getKey('jobs:' + state), 0, -1, function(err,ids){ + var fixedIds = []; + ids.forEach(function(id){ + fixedIds.push(self.client.stripFIFO(id)); + }); + fn(err,fixedIds); + }); + return this; +}; + +/** + * Get queue work time in milliseconds and invoke `fn(err, ms)`. + * + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.workTime = function( fn ) { + this.client.get(this.client.getKey('stats:work-time'), function( err, n ) { + if( err ) return fn(err); + fn(null, parseInt(n, 10)); + }); + return this; +}; + +/** + * Get cardinality of jobs with given `state` and `type` and callback `fn(err, n)`. + * + * @param {String} type + * @param {String} state + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.cardByType = function( type, state, fn ) { + fn = fn || noop; + this.client.zcard(this.client.getKey('jobs:' + type + ':' + state), fn); + return this; +}; + +/** + * Get cardinality of `state` and callback `fn(err, n)`. + * + * @param {String} state + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.card = function( state, fn ) { + fn = fn || noop; + this.client.zcard(this.client.getKey('jobs:' + state), fn); + return this; +}; + +/** + * Completed jobs. + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.complete = function( fn ) { + return this.state('complete', fn); +}; + +/** + * Failed jobs. + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.failed = function( fn ) { + return this.state('failed', fn); +}; + +/** + * Inactive jobs (queued). + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.inactive = function( fn ) { + return this.state('inactive', fn); +}; + +/** + * Active jobs (mid-process). + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.active = function( fn ) { + return this.state('active', fn); +}; + +/** + * Delayed jobs. + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.delayed = function( fn ) { + return this.state('delayed', fn); +}; + +/** + * Completed jobs of type `type` count. + * @param {String} type is optional + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.completeCount = function( type, fn ) { + if( 1 == arguments.length ) { + fn = type; + return this.card('complete', fn); + } + return this.cardByType(type, 'complete', fn); +}; + + +/** + * Failed jobs of type `type` count. + * @param {String} type is optional + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.failedCount = function( type, fn ) { + if( 1 == arguments.length ) { + fn = type; + return this.card('failed', fn); + } + return this.cardByType(type, 'failed', fn); +}; + +/** + * Inactive jobs (queued) of type `type` count. + * @param {String} type is optional + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.inactiveCount = function( type, fn ) { + if( 1 == arguments.length ) { + fn = type; + return this.card('inactive', fn); + } + return this.cardByType(type, 'inactive', fn); +}; + +/** + * Active jobs (mid-process) of type `type` count. + * @param {String} type is optional + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.activeCount = function( type, fn ) { + if( 1 == arguments.length ) { + fn = type; + return this.card('active', fn); + } + return this.cardByType(type, 'active', fn); +}; + +/** + * Delayed jobs of type `type` count. + * @param {String} type is optional + * @param {Function} fn + * @return {Queue} for chaining + * @api public + */ + +Queue.prototype.delayedCount = function( type, fn ) { + if( 1 == arguments.length ) { + fn = type; + return this.card('delayed', fn); + } + return this.cardByType(type, 'delayed', fn); +}; + +/** + * Test mode for convenience in test suites + * @api public + */ + +var testMode = require('./queueTestMode') +Queue.prototype.testMode = function(){ + if(!this._testMode){ + this._testMode = testMode(this.Job.Job) + } + return this._testMode +}; + + +/** + * Server instance (that is lazily required) + */ + +var app; + +/** + * Expose the server. + */ + +Object.defineProperty(Queue, 'app', { + get: function() { + return app || (app = require('./http')); + } +}); + +module.exports = Queue diff --git a/lib/queue/events.js b/lib/queue/events.js deleted file mode 100755 index 1c68d4b7..00000000 --- a/lib/queue/events.js +++ /dev/null @@ -1,134 +0,0 @@ -/*! - * kue - events - * Copyright (c) 2013 Automattic - * Copyright (c) 2011 LearnBoost - * MIT Licensed - */ - -/** - * Module dependencies. - */ - -var redis = require('../redis'); - -/** - * Job map. - */ - -exports.jobs = {}; - -/** - * Pub/sub key. - */ - -exports.key = 'events'; - -/** - * Add `job` to the jobs map, used - * to grab the in-process object - * so we can emit relative events. - * - * @param {Job} job - * @api private - */ -exports.callbackQueue = []; - -exports.add = function( job, callback ) { - if( job.id ) { - if(!exports.jobs[ job.id ]) - exports.jobs[ job.id ] = []; - - exports.jobs[ job.id ].push(job); - } -// if (!exports.subscribed) exports.subscribe(); - if( !exports.subscribeStarted ) exports.subscribe(); - if( !exports.subscribed ) { - exports.callbackQueue.push(callback); - } else { - callback(); - } -}; - -/** - * Remove `job` from the jobs map. - * - * @param {Job} job - * @api private - */ - -exports.remove = function( job ) { - delete exports.jobs[ job.id ]; -}; - -/** - * Subscribe to "q:events". - * - * @api private - */ - -exports.subscribe = function() { -// if (exports.subscribed) return; - if( exports.subscribeStarted ) return; - var client = redis.pubsubClient(); - client.on('message', exports.onMessage); - client.subscribe(client.getKey(exports.key), function() { - exports.subscribed = true; - while( exports.callbackQueue.length ) { - process.nextTick(exports.callbackQueue.shift()); - } - }); - exports.queue = require('../kue').singleton; -// exports.subscribed = true; - exports.subscribeStarted = true; -}; - -exports.unsubscribe = function() { - var client = redis.pubsubClient(); - client.unsubscribe(); - client.removeAllListeners(); - exports.subscribeStarted = false; -}; - -/** - * Message handler. - * - * @api private - */ - -exports.onMessage = function( channel, msg ) { - // TODO: only subscribe on {Queue,Job}#on() - msg = JSON.parse(msg); - - // map to Job when in-process - var jobs = exports.jobs[ msg.id ]; - if( jobs && jobs.length > 0 ) { - for (var i = 0; i < jobs.length; i++) { - var job = jobs[i]; - job.emit.apply(job, msg.args); - if( [ 'complete', 'failed' ].indexOf(msg.event) !== -1 ) exports.remove(job); - } - } - // emit args on Queues - msg.args[ 0 ] = 'job ' + msg.args[ 0 ]; - msg.args.splice(1, 0, msg.id); - if( exports.queue ) { - exports.queue.emit.apply(exports.queue, msg.args); - } -}; - -/** - * Emit `event` for for job `id` with variable args. - * - * @param {Number} id - * @param {String} event - * @param {Mixed} ... - * @api private - */ - -exports.emit = function( id, event ) { - var client = redis.client() - , msg = JSON.stringify({ - id: id, event: event, args: [].slice.call(arguments, 1) - }); - client.publish(client.getKey(exports.key), msg, function () {}); -}; diff --git a/lib/queue/test_mode.js b/lib/queue/test_mode.js deleted file mode 100644 index efeefbdc..00000000 --- a/lib/queue/test_mode.js +++ /dev/null @@ -1,64 +0,0 @@ -var Job = require('./job'), - _ = require('lodash'); - -var originalJobSave = Job.prototype.save, - originalJobUpdate = Job.prototype.update, - processQueue, - jobs; - -function testJobSave( fn ) { - if(processQueue) { - jobs.push(this); - originalJobSave.call(this, fn); - } else { - this.id = _.uniqueId(); - jobs.push(this); - if( _.isFunction(fn) ) fn(); - } -}; - -function testJobUpdate( fn ) { - if(processQueue) { - originalJobUpdate.call(this, fn); - } else { - if( _.isFunction(fn) ) fn(); - } -}; - -/** - * Array of jobs added to the queue - * @api public - */ - -module.exports.jobs = jobs = []; -module.exports.processQueue = processQueue = false; - -/** - * Enable test mode. - * @api public - */ - -module.exports.enter = function(process) { - processQueue = process || false; - Job.prototype.save = testJobSave; - Job.prototype.update = testJobUpdate; -}; - -/** - * Disable test mode. - * @api public - */ - -module.exports.exit = function() { - Job.prototype.save = originalJobSave; - Job.prototype.update = originalJobUpdate; -}; - -/** - * Clear the array of queued jobs - * @api public - */ - -module.exports.clear = function() { - jobs.length = 0; -}; diff --git a/lib/queueEvents.js b/lib/queueEvents.js new file mode 100644 index 00000000..88593210 --- /dev/null +++ b/lib/queueEvents.js @@ -0,0 +1,135 @@ +/*! + * kue - events + * Copyright (c) 2013 Automattic + * Copyright (c) 2011 LearnBoost + * MIT Licensed + */ + +module.exports = function QueueEvents(queue){ + var self = this + + var redis = queue.redis + /** + * Job map. + */ + + self.jobs = {}; + + /** + * Pub/sub key. + */ + + self.key = 'events'; + + /** + * Add `job` to the jobs map, used + * to grab the in-process object + * so we can emit relative events. + * + * @param {Job} job + * @api private + */ + self.callbackQueue = []; + + self.add = function( job, callback ) { + if( job.id ) { + if(!self.jobs[ job.id ]) + self.jobs[ job.id ] = []; + + self.jobs[ job.id ].push(job); + } + // if (!self.subscribed) self.subscribe(); + if( !self.subscribeStarted ) self.subscribe(); + if( !self.subscribed ) { + self.callbackQueue.push(callback); + } else { + callback(); + } + }; + + /** + * Remove `job` from the jobs map. + * + * @param {Job} job + * @api private + */ + + self.remove = function( job ) { + delete self.jobs[ job.id ]; + }; + + /** + * Subscribe to "q:events". + * + * @api private + */ + + self.subscribe = function() { + // if (self.subscribed) return; + if( self.subscribeStarted ) return; + var client = redis.pubsubClient(); + client.on('message', self.onMessage); + client.subscribe(client.getKey(self.key), function() { + self.subscribed = true; + while( self.callbackQueue.length ) { + process.nextTick(self.callbackQueue.shift()); + } + }); + self.queue = queue; + // self.subscribed = true; + self.subscribeStarted = true; + }; + + self.unsubscribe = function() { + var client = redis.pubsubClient(); + client.unsubscribe(); + client.removeAllListeners(); + self.subscribeStarted = false; + }; + + /** + * Message handler. + * + * @api private + */ + + self.onMessage = function( channel, msg ) { + // TODO: only subscribe on {Queue,Job}#on() + msg = JSON.parse(msg); + + // map to Job when in-process + var jobs = self.jobs[ msg.id ]; + if( jobs && jobs.length > 0 ) { + for (var i = 0; i < jobs.length; i++) { + var job = jobs[i]; + job.emit.apply(job, msg.args); + if( [ 'complete', 'failed' ].indexOf(msg.event) !== -1 ) self.remove(job); + } + } + // emit args on Queues + msg.args[ 0 ] = 'job ' + msg.args[ 0 ]; + msg.args.splice(1, 0, msg.id); + if( self.queue ) { + self.queue.emit.apply(self.queue, msg.args); + } + }; + + /** + * Emit `event` for for job `id` with variable args. + * + * @param {Number} id + * @param {String} event + * @param {Mixed} ... + * @api private + */ + + self.emit = function( id, event ) { + var client = redis.client() + , msg = JSON.stringify({ + id: id, event: event, args: [].slice.call(arguments, 1) + }); + client.publish(client.getKey(self.key), msg, function () {}); + }; + + return self +} diff --git a/lib/queueJob.js b/lib/queueJob.js new file mode 100644 index 00000000..02081b07 --- /dev/null +++ b/lib/queueJob.js @@ -0,0 +1,286 @@ +/*! + * kue - Job + * Copyright (c) 2013 Automattic + * Copyright (c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Job = require('./job') + , noop = function() {}; + +/** + * Expose `Job`. + */ + +module.exports = function QueueJob(queue){ + + var self = this + var redis = queue.redis + var events = queue.events + + self.disableSearch = queue._options.disableSearch !== false; + + + self.jobEvents = queue._options.jobEvents !== undefined ? queue._options.jobEvents : true; + + /** + * Search instance. + */ + var search; + self.getSearch = function getSearch() { + if( search ) return search; + var reds = require('reds'); + reds.createClient = redis.createClient; + return search = reds.createSearch(redis.client().getKey('search')); + } + + /** + * Default job priority map. + */ + + var priorities = self.priorities = { + low: 10, normal: 0, medium: -5, high: -10, critical: -15 + }; + + /** + * Map `jobs` by the given array of `ids`. + * + * @param {Object} jobs + * @param {Array} ids + * @return {Array} + * @api private + */ + + function map( jobs, ids ) { + var ret = []; + ids.forEach(function( id ) { + if( jobs[ id ] ) ret.push(jobs[ id ]); + }); + ret = ret.sort(function( a, b ) { + return parseInt(a.id) - parseInt(b.id); + }); + return ret; + } + + /** + * Return a function that handles fetching + * of jobs by the ids fetched. + * + * @param {Function} fn + * @param {String} order + * @param {String} jobType + * @return {Function} + * @api private + */ + + function get( fn, order, jobType) { + return function( err, ids ) { + if( err ) return fn(err); + var pending = ids.length + , jobs = {}; + if( !pending ) return fn(null, ids); + ids.forEach(function( id ) { + id = redis.client().stripFIFO(id); // turn zid back to regular job id + self.get(id, jobType, function( err, job ) { + if( err ) { + console.error(err); + } else { + jobs[ redis.client().createFIFO(job.id) ] = job; + } + --pending || fn(null, 'desc' == order + ? map(jobs, ids).reverse() + : map(jobs, ids)); + + }); + }); + } + } + + /** + * Get with the range `from`..`to` + * and invoke callback `fn(err, ids)`. + * + * @param {Number} from + * @param {Number} to + * @param {String} order + * @param {Function} fn + * @api public + */ + + self.range = function( from, to, order, fn ) { + redis.client().zrange(redis.client().getKey('jobs'), from, to, get(fn, order)); + }; + + /** + * Get jobs of `state`, with the range `from`..`to` + * and invoke callback `fn(err, ids)`. + * + * @param {String} state + * @param {Number} from + * @param {Number} to + * @param {String} order + * @param {Function} fn + * @api public + */ + + self.rangeByState = function( state, from, to, order, fn ) { + redis.client().zrange(redis.client().getKey('jobs:' + state), from, to, get(fn, order)); + }; + + /** + * Get jobs of `type` and `state`, with the range `from`..`to` + * and invoke callback `fn(err, ids)`. + * + * @param {String} type + * @param {String} state + * @param {Number} from + * @param {Number} to + * @param {String} order + * @param {Function} fn + * @api public + */ + + self.rangeByType = function( type, state, from, to, order, fn ) { + redis.client().zrange(redis.client().getKey('jobs:' + type + ':' + state), from, to, get(fn, order, type)); + }; + + /** + * Get job with `id` and callback `fn(err, job)`. + * + * @param {Number} id + * @param {String} jobType is optional + * @param {Function} fn + * @api public + */ + + self.get = function( id, jobType, fn ) { + if (id === null || id === undefined) { + return fn(new Error('invalid id param')); + } + if (typeof jobType === 'function' && !fn) { + fn = jobType; + jobType = ''; + } + var client = redis.client() + , job = self.createJob(); + + job.id = id; + job.zid = client.createFIFO(id); + client.hgetall(client.getKey('job:' + job.id), function( err, hash ) { + if( err ) return fn(err); + if( !hash ) { + self.removeBadJob(job.id, jobType); + return fn(new Error('job "' + job.id + '" doesnt exist')); + } + if( !hash.type ) { + self.removeBadJob(job.id, jobType); + return fn(new Error('job "' + job.id + '" is invalid')) + } + // TODO: really lame, change some methods so + // we can just merge these + job.type = hash.type; + job._ttl = hash.ttl; + job._delay = hash.delay; + job.priority(Number(hash.priority)); + job._progress = hash.progress; + job._attempts = Number(hash.attempts); + job._max_attempts = Number(hash.max_attempts); + job._state = hash.state; + job._error = hash.error; + job.created_at = hash.created_at; + job.promote_at = hash.promote_at; + job.updated_at = hash.updated_at; + job.failed_at = hash.failed_at; + job.started_at = hash.started_at; + job.duration = hash.duration; + job.workerId = hash.workerId; + job._removeOnComplete = hash.removeOnComplete; + try { + if( hash.data ) job.data = JSON.parse(hash.data); + if( hash.result ) job.result = JSON.parse(hash.result); + if( hash.progress_data ) job.progress_data = JSON.parse(hash.progress_data); + if( hash.backoff ) { + var source = 'job._backoff = ' + hash.backoff + ';'; + // require('vm').runInContext( source ); + eval(source); + } + } catch(e) { + err = e; + } + fn(err, job); + }); + }; + + /** + * Remove all references to an invalid job. Will remove leaky keys in redis keys:TYPE:STATE when + * self.rangeByType is used. + * + * @param {Number} id + * @param {String} jobType + */ + + self.removeBadJob = function( id, jobType) { + var client = redis.client(); + var zid = client.createFIFO(id); + client.multi() + .del(client.getKey('job:' + id + ':log')) + .del(client.getKey('job:' + id)) + .zrem(client.getKey('jobs:inactive'), zid) + .zrem(client.getKey('jobs:active'), zid) + .zrem(client.getKey('jobs:complete'), zid) + .zrem(client.getKey('jobs:failed'), zid) + .zrem(client.getKey('jobs:delayed'), zid) + .zrem(client.getKey('jobs'), zid) + .zrem(client.getKey('jobs:' + jobType + ':inactive'), zid) + .zrem(client.getKey('jobs:' + jobType+ ':active'), zid) + .zrem(client.getKey('jobs:' + jobType + ':complete'), zid) + .zrem(client.getKey('jobs:' + jobType + ':failed'), zid) + .zrem(client.getKey('jobs:' + jobType + ':delayed'), zid) + .exec(); + if( !self.disableSearch ) { + self.getSearch().remove(id); + } + }; + + /** + * Remove job `id` if it exists and invoke callback `fn(err)`. + * + * @param {Number} id + * @param {Function} fn + * @api public + */ + + self.remove = function( id, fn ) { + fn = fn || noop; + self.get(id, function( err, job ) { + if( err ) return fn(err); + if( !job ) return fn(new Error('failed to find job ' + id)); + job.remove(fn); + }); + }; + + /** + * Get log for job `id` and callback `fn(err, log)`. + * + * @param {Number} id + * @param {Function} fn + * @return {Type} + * @api public + */ + + self.log = function( id, fn ) { + /*redis*/ + queue.client/*()*/.lrange(queue.client.getKey('job:' + id + ':log'), 0, -1, fn); + }; + + self.queue = queue + self.createJob = function( type, data ) { + return new Job(type, data, this); + } + + return self +} diff --git a/lib/queueTestMode.js b/lib/queueTestMode.js new file mode 100644 index 00000000..2636007b --- /dev/null +++ b/lib/queueTestMode.js @@ -0,0 +1,65 @@ +var _ = require('lodash'); + +module.exports = function(Job){ + + var originalJobSave = Job.prototype.save, + originalJobUpdate = Job.prototype.update, + processQueue = false, + jobs = []; + + function testJobSave( fn ) { + if(processQueue) { + jobs.push(this); + originalJobSave.call(this, fn); + } else { + this.id = _.uniqueId(); + jobs.push(this); + if( _.isFunction(fn) ) fn(); + } + }; + + function testJobUpdate( fn ) { + if(processQueue) { + originalJobUpdate.call(this, fn); + } else { + if( _.isFunction(fn) ) fn(); + } + }; + + var testMode = {} + + /** + * Enable test mode. + * @api public + */ + + testMode.enter = function(process) { + processQueue = process || false; + Job.prototype.save = testJobSave; + Job.prototype.update = testJobUpdate; + }; + + /** + * Disable test mode. + * @api public + */ + + testMode.exit = function() { + Job.prototype.save = originalJobSave; + Job.prototype.update = originalJobUpdate; + }; + + /** + * Clear the array of queued jobs + * @api public + */ + + testMode.clear = function() { + jobs.length = 0; + }; + + testMode.jobs = jobs + testMode.processQueue = processQueue + + return testMode +} diff --git a/lib/redis.js b/lib/redis.js old mode 100755 new mode 100644 index a77ff9ba..8a6c1534 --- a/lib/redis.js +++ b/lib/redis.js @@ -19,139 +19,146 @@ var url = require('url'); * @param queue */ -exports.configureFactory = function( options, queue ) { - options.prefix = options.prefix || 'q'; - - if( typeof options.redis === 'string' ) { - // parse the url - var conn_info = url.parse(options.redis, true /* parse query string */); - if( conn_info.protocol !== 'redis:' ) { - throw new Error('kue connection string must use the redis: protocol'); - } +module.exports = function redisInstance(){ - options.redis = { - port: conn_info.port || 6379, - host: conn_info.hostname, - db: (conn_info.pathname ? conn_info.pathname.substr(1) : null) || conn_info.query.db || 0, - // see https://github.com/mranney/node_redis#rediscreateclient - options: conn_info.query - }; + var self = this - if( conn_info.auth ) { - options.redis.auth = conn_info.auth.replace(/.*?:/, ''); - } + self.configureFactory = function( options, queue ) { + options.prefix = options.prefix || 'q'; - } + if( typeof options.redis === 'string' ) { + // parse the url + var conn_info = url.parse(options.redis, true /* parse query string */); + if( conn_info.protocol !== 'redis:' ) { + throw new Error('kue connection string must use the redis: protocol'); + } - options.redis = options.redis || {}; + options.redis = { + port: conn_info.port || 6379, + host: conn_info.hostname, + db: (conn_info.pathname ? conn_info.pathname.substr(1) : null) || conn_info.query.db || 0, + // see https://github.com/mranney/node_redis#rediscreateclient + options: conn_info.query + }; - // guarantee that redis._client has not been populated. - // may warrant some more testing - i was running into cases where shutdown - // would call redis.reset but an event would be emitted after the reset - // which would re-create the client and cache it in the redis module. - exports.reset(); + if( conn_info.auth ) { + options.redis.auth = conn_info.auth.replace(/.*?:/, ''); + } + + } + + options.redis = options.redis || {}; + + // guarantee that redis._client has not been populated. + // may warrant some more testing - i was running into cases where shutdown + // would call redis.reset but an event would be emitted after the reset + // which would re-create the client and cache it in the redis module. + self.reset(); + + /** + * Create a RedisClient. + * + * @return {RedisClient} + * @api private + */ + self.createClient = function() { + var clientFactoryMethod = options.redis.createClientFactory || self.createClientFactory; + var client = clientFactoryMethod(options); + + client.on('error', function( err ) { + queue.emit('error', err); + }); + + client.prefix = options.prefix; + + // redefine getKey to use the configured prefix + client.getKey = function( key ) { + if( client.constructor.name == 'Redis' || client.constructor.name == 'Cluster') { + // {prefix}:jobs format is needed in using ioredis cluster to keep they keys in same node + // otherwise multi commands fail, since they use ioredis's pipeline. + return '{' + this.prefix + '}:' + key; + } + return this.prefix + ':' + key; + }; + + client.createFIFO = function( id ) { + //Create an id for the zset to preserve FIFO order + var idLen = '' + id.toString().length; + var len = 2 - idLen.length; + while (len--) idLen = '0' + idLen; + return idLen + '|' + id; + }; + + // Parse out original ID from zid + client.stripFIFO = function( zid ) { + if ( typeof zid === 'string' ) { + return +zid.substr(zid.indexOf('|')+1); + } else { + // Sometimes this gets called with an undefined + // it seems to be OK to have that not resolve to an id + return zid; + } + }; + + return client; + }; + }; /** - * Create a RedisClient. - * + * Create a RedisClient from options + * @param options * @return {RedisClient} * @api private */ - exports.createClient = function() { - var clientFactoryMethod = options.redis.createClientFactory || exports.createClientFactory; - var client = clientFactoryMethod(options); - - client.on('error', function( err ) { - queue.emit('error', err); - }); - - client.prefix = options.prefix; - - // redefine getKey to use the configured prefix - client.getKey = function( key ) { - if( client.constructor.name == 'Redis' || client.constructor.name == 'Cluster') { - // {prefix}:jobs format is needed in using ioredis cluster to keep they keys in same node - // otherwise multi commands fail, since they use ioredis's pipeline. - return '{' + this.prefix + '}:' + key; - } - return this.prefix + ':' + key; - }; - - client.createFIFO = function( id ) { - //Create an id for the zset to preserve FIFO order - var idLen = '' + id.toString().length; - var len = 2 - idLen.length; - while (len--) idLen = '0' + idLen; - return idLen + '|' + id; - }; - - // Parse out original ID from zid - client.stripFIFO = function( zid ) { - if ( typeof zid === 'string' ) { - return +zid.substr(zid.indexOf('|')+1); - } else { - // Sometimes this gets called with an undefined - // it seems to be OK to have that not resolve to an id - return zid; - } - }; + self.createClientFactory = function( options ) { + var socket = options.redis.socket; + var port = !socket ? (options.redis.port || 6379) : null; + var host = !socket ? (options.redis.host || '127.0.0.1') : null; + var db = !socket ? (options.redis.db || 0) : null; + var client = redis.createClient(socket || port, host, options.redis.options); + if( options.redis.auth ) { + client.auth(options.redis.auth); + } + if( db >= 0 ){ + client.select(db); + } return client; }; -}; - -/** - * Create a RedisClient from options - * @param options - * @return {RedisClient} - * @api private - */ -exports.createClientFactory = function( options ) { - var socket = options.redis.socket; - var port = !socket ? (options.redis.port || 6379) : null; - var host = !socket ? (options.redis.host || '127.0.0.1') : null; - var db = !socket ? (options.redis.db || 0) : null; - var client = redis.createClient(socket || port, host, options.redis.options); - if( options.redis.auth ) { - client.auth(options.redis.auth); - } - if( db >= 0 ){ - client.select(db); - } - return client; -}; + /** + * Create or return the existing RedisClient. + * + * @return {RedisClient} + * @api private + */ -/** - * Create or return the existing RedisClient. - * - * @return {RedisClient} - * @api private - */ + self.client = function() { + return self._client || (self._client = self.createClient()); + }; -exports.client = function() { - return exports._client || (exports._client = exports.createClient()); -}; + /** + * Return the pubsub-specific redis client. + * + * @return {RedisClient} + * @api private + */ -/** - * Return the pubsub-specific redis client. - * - * @return {RedisClient} - * @api private - */ + self.pubsubClient = function() { + return self._pubsub || (self._pubsub = self.createClient()); + }; -exports.pubsubClient = function() { - return exports._pubsub || (exports._pubsub = exports.createClient()); -}; + /** + * Resets internal variables to initial state + * + * @api private + */ + self.reset = function() { + self._client && self._client.quit(); + self._pubsub && self._pubsub.quit(); + self._client = null; + self._pubsub = null; + }; -/** - * Resets internal variables to initial state - * - * @api private - */ -exports.reset = function() { - exports._client && exports._client.quit(); - exports._pubsub && exports._pubsub.quit(); - exports._client = null; - exports._pubsub = null; -}; + return self +} diff --git a/lib/queue/worker.js b/lib/worker.js similarity index 91% rename from lib/queue/worker.js rename to lib/worker.js index 2213dfcf..70213df1 100644 --- a/lib/queue/worker.js +++ b/lib/worker.js @@ -11,9 +11,6 @@ */ var EventEmitter = require('events').EventEmitter - , redis = require('../redis') - , events = require('./events') - , Job = require('./job') , noop = function() {}; /** @@ -22,11 +19,6 @@ var EventEmitter = require('events').EventEmitter module.exports = Worker; -/** - * Redis connections used by `getJob()` when blocking. - */ - -var clients = {}; /** * Initialize a new `Worker` with the given Queue @@ -40,9 +32,18 @@ var clients = {}; function Worker( queue, type ) { this.queue = queue; this.type = type; - this.client = Worker.client || (Worker.client = redis.createClient()); this.running = true; this.job = null; + + /** + * Redis connections used by `getJob()` when blocking. + */ + this.clients = {}; + + this.redis = queue.redis; + this.client = queue.client; + this.Job = queue.Job; + this.events = queue.events; } /** @@ -70,7 +71,7 @@ Worker.prototype.start = function( fn ) { self.ttlExceededCb = function(id) { if( self.job && self.job.id && self.job.id === id ) { self.failed( self.job, { error: true, message: 'TTL exceeded' }, fn ); - events.emit(id, 'ttl exceeded ack'); + self.events.emit(id, 'ttl exceeded ack'); } } @@ -245,14 +246,16 @@ Worker.prototype.process = function( job, fn ) { */ Worker.prototype.zpop = function( key, fn ) { - this.client + // var client = this.client + var client = this.client || this.redis.createClient() + client .multi() .zrange(key, 0, 0) .zremrangebyrank(key, 0, 0) .exec(function( err, res ) { if( err || !res || !res[ 0 ] || !res[ 0 ].length ) return fn(err); var id = res[ 0 ][ 0 ] || res[ 0 ][ 1 ][ 0 ]; - fn(null, this.client.stripFIFO(id)); + fn(null, client.stripFIFO(id)); }.bind(this)); }; @@ -269,23 +272,23 @@ Worker.prototype.getJob = function( fn ) { return fn('Already Shutdown'); } // alloc a client for this job type - var client = clients[ self.type ] || (clients[ self.type ] = redis.createClient()); + var client = self.clients[ self.type ] || (self.clients[ self.type ] = self.redis.createClient()); // BLPOP indicates we have a new inactive job to process client.blpop(client.getKey(self.type + ':jobs'), 0, function( err ) { if( err || !self.running ) { if( self.client && self.client.connected && !self.client.closing ) { - self.client.lpush(self.client.getKey(self.type + ':jobs'), 1, noop); + self.client.lpush(client.getKey(self.type + ':jobs'), 1, noop); } - return fn(err); // SAE: Added to avoid crashing redis on zpop + return fn(err); // SAE: Added to avoid crashing redis on zpop } // Set job to a temp value so shutdown() knows to wait self.job = true; - self.zpop(self.client.getKey('jobs:' + self.type + ':inactive'), function( err, id ) { + self.zpop(client.getKey('jobs:' + self.type + ':inactive'), function( err, id ) { if( err || !id ) { self.idle(); return fn(err /*|| "No job to pop!"*/); } - Job.get(id, fn); + self.Job.get(id, fn); }); }); }; @@ -324,8 +327,8 @@ Worker.prototype.shutdown = function( timeout, fn ) { self.removeAllListeners(); self.job = null; //Safeyly kill any blpop's that are waiting. - (self.type in clients) && clients[ self.type ].quit(); - delete clients[ self.type ]; + (self.type in self.clients) && self.clients[ self.type ].quit(); + delete self.clients[ self.type ]; self.cleaned_up = true; //fix half-blob job fetches if any self.client.lpush(self.client.getKey(self.type + ':jobs'), 1, fn || noop); @@ -367,7 +370,7 @@ Worker.prototype.shutdown = function( timeout, fn ) { Worker.prototype.emitJobEvent = function( event, job, arg1, arg2 ) { if( this.cleaned_up ) return; - events.emit(job.id, event, arg1, arg2); + this.events.emit(job.id, event, arg1, arg2); this.emit('job ' + event, job); }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f40c1814 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1976 @@ +{ + "name": "kue", + "version": "0.11.5", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sinonjs/commons": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.0.2.tgz", + "integrity": "sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + }, + "dependencies": { + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } + } + }, + "@sinonjs/formatio": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.0.0.tgz", + "integrity": "sha512-vdjoYLDptCgvtJs57ULshak3iJe4NW3sJ3g36xVDGff5AE8P30S6A093EIEPjdi2noGhfuNOEkbxt3J3awFW1w==", + "dev": true, + "requires": { + "@sinonjs/samsam": "2.1.0" + }, + "dependencies": { + "@sinonjs/samsam": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.0.tgz", + "integrity": "sha512-5x2kFgJYupaF1ns/RmharQ90lQkd2ELS8A9X0ymkAAdemYHGtI2KiUHG8nX2WU0T1qgnOU5YMqnBM2V7NUanNw==", + "dev": true, + "requires": { + "array-from": "^2.1.1" + } + } + } + }, + "@sinonjs/samsam": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.2.tgz", + "integrity": "sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw==", + "dev": true + }, + "@types/babel-types": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.2.tgz", + "integrity": "sha512-ylggu8DwwxT6mk3jVoJeohWAePWMNWEYm06MSoJ19kwp3hT9eY2Z4NNZn3oevzgFmClgNQ2GQF500hPDvNsGHg==" + }, + "@types/babylon": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.2.tgz", + "integrity": "sha512-+Jty46mPaWe1VAyZbfvgJM4BAdklLWxrT5tc/RjvCgLrtk6gzRY6AOnoWFv4p6hVxhJshDdr2hGVn56alBp97Q==", + "requires": { + "@types/babel-types": "*" + } + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "^4.0.4" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "apparatus": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/apparatus/-/apparatus-0.0.10.tgz", + "integrity": "sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==", + "optional": true, + "requires": { + "sylvester": ">= 0.0.8" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, + "clean-css": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", + "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", + "requires": { + "source-map": "0.5.x" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coffee-script": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", + "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "requires": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + } + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "extend": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz", + "integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg=" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "ipaddr.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "requires": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "just-extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz", + "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lolex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", + "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "natural": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/natural/-/natural-0.2.1.tgz", + "integrity": "sha1-HrUVap2QtFkZSeIOlOvHe7Iznto=", + "optional": true, + "requires": { + "apparatus": ">= 0.0.9", + "sylvester": ">= 0.0.12", + "underscore": ">=1.3.1" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "nib": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/nib/-/nib-1.1.2.tgz", + "integrity": "sha1-amnt5AgblcDe+L4CSkyK4MLLtsc=", + "requires": { + "stylus": "0.54.5" + } + }, + "nise": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.6.tgz", + "integrity": "sha512-1GedetLKzmqmgwabuMSqPsT7oumdR77SBpDfNNJhADRIeA3LN/2RVqR4fFqwvzhAqcTef6PPCzQwITE/YQ8S8A==", + "dev": true, + "requires": { + "@sinonjs/formatio": "3.0.0", + "just-extend": "^3.0.0", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "node-redis-scripty": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/node-redis-scripty/-/node-redis-scripty-0.0.5.tgz", + "integrity": "sha1-S/LTZattqyAswIt6xj+PVarcliU=", + "requires": { + "extend": "^1.2.1", + "lru-cache": "^2.5.0" + } + }, + "node-redis-warlock": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-redis-warlock/-/node-redis-warlock-0.2.0.tgz", + "integrity": "sha1-VjlbmUyCjo4y9qrlO5O27fzZeZA=", + "requires": { + "node-redis-scripty": "0.0.5", + "uuid": "^2.0.1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "proxy-addr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.6.0" + } + }, + "pug": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.3.tgz", + "integrity": "sha1-ccuoJTfJWl6rftBGluQiH1Oqh44=", + "requires": { + "pug-code-gen": "^2.0.1", + "pug-filters": "^3.1.0", + "pug-lexer": "^4.0.0", + "pug-linker": "^3.0.5", + "pug-load": "^2.0.11", + "pug-parser": "^5.0.0", + "pug-runtime": "^2.0.4", + "pug-strip-comments": "^1.0.3" + } + }, + "pug-attrs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.3.tgz", + "integrity": "sha1-owlflw5kFR972tlX7vVftdeQXRU=", + "requires": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.4" + } + }, + "pug-code-gen": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.1.tgz", + "integrity": "sha1-CVHsgyJddNjPxHan+Zolm199BQw=", + "requires": { + "constantinople": "^3.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.3", + "pug-error": "^1.3.2", + "pug-runtime": "^2.0.4", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "pug-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", + "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" + }, + "pug-filters": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.0.tgz", + "integrity": "sha1-JxZVVbwEwjbkqisDZiRt+gIbYm4=", + "requires": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.2", + "pug-walk": "^1.1.7", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + } + }, + "pug-lexer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.0.0.tgz", + "integrity": "sha1-IQwYRX7y4XYCQnQMXmR715TOwng=", + "requires": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.2" + } + }, + "pug-linker": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.5.tgz", + "integrity": "sha1-npp65ABWgtAn3uuWsAD4juuDoC8=", + "requires": { + "pug-error": "^1.3.2", + "pug-walk": "^1.1.7" + } + }, + "pug-load": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.11.tgz", + "integrity": "sha1-5kjlftET/iwfRdV4WOorrWvAFSc=", + "requires": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.7" + } + }, + "pug-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.0.tgz", + "integrity": "sha1-45Stmz/KkxI5QK/4hcBuRKt+aOQ=", + "requires": { + "pug-error": "^1.3.2", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.4.tgz", + "integrity": "sha1-4XjhvaaKsujArPybztLFT9iM61g=" + }, + "pug-strip-comments": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.3.tgz", + "integrity": "sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8=", + "requires": { + "pug-error": "^1.3.2" + } + }, + "pug-walk": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.7.tgz", + "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "requires": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.6.0" + } + }, + "redis-commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz", + "integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==" + }, + "redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + }, + "reds": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/reds/-/reds-0.2.5.tgz", + "integrity": "sha1-OKdn92Y810kDaEhpfYLHT9KbwB8=", + "optional": true, + "requires": { + "natural": "^0.2.0", + "redis": "^0.12.1" + }, + "dependencies": { + "redis": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz", + "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=", + "optional": true + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "requires": { + "path-parse": "^1.0.5" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "^0.1.1" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, + "sinon": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.0.0.tgz", + "integrity": "sha512-8OrSYFPZ9xaECfi1ayVMd0ihYCW2OZYgX3rBczrB990gHZMM+aftvhNTJazGz/luS0Us9NWgD5P3KGQ7kYZvGg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "@sinonjs/formatio": "^3.0.0", + "@sinonjs/samsam": "^2.1.2", + "diff": "^3.5.0", + "lodash.get": "^4.4.2", + "lolex": "^3.0.0", + "nise": "^1.4.5", + "supports-color": "^5.5.0", + "type-detect": "^4.0.8" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": ">=0.0.4" + } + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + } + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "supertest": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.3.0.tgz", + "integrity": "sha512-dMQSzYdaZRSANH5LL8kX3UpgK9G1LRh/jnggs/TI0W2Sz7rkMx9Y48uia3K9NgcaWEV28tYkBnXE4tiFC77ygQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "sylvester": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/sylvester/-/sylvester-0.0.21.tgz", + "integrity": "sha1-KYexzivS84sNzio0OIiEv6RADqc=" + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "optional": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + }, + "validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "requires": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.0.3", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.1", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^2.4.1" + }, + "dependencies": { + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + } + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + } + } + } + } +} diff --git a/package.json b/package.json index 9621ed20..f39369cf 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "nib": "~1.1.2", "node-redis-warlock": "~0.2.0", "pug": "^2.0.0-beta3", - "redis": "~2.6.0-2", + "redis": "^2.8.0", "stylus": "~0.54.5", "yargs": "^4.0.0" }, @@ -39,17 +39,20 @@ "async": "^1.4.2", "chai": "^3.3.0", "coffee-script": "~1.10.0", - "mocha": "^2.3.3", - "should": "^3.1.0", - "sinon": "^1.17.2", - "supertest": "^1.1.0" + "mocha": "^5.2.0", + "should": "^13.2.3", + "sinon": "^7.0.0", + "supertest": "^3.3.0" }, "main": "index", "bin": { "kue-dashboard": "bin/kue-dashboard" }, "scripts": { - "test": "make test-all" + "test": "make test-all", + "test:dev:tdd": "mocha --require should --require sinon test/tdd/*.js --watch", + "test:dev:bdd": "mocha --require should test/*.js --watch", + "test:dev:bdd-coffee": "mocha --require should --require coffee-script/register test/*.coffee --watch" }, "optionalDependencies": { "reds": "^0.2.5" diff --git a/test/jsonapi.js b/test/jsonapi.js index 5ce536c3..63a68565 100644 --- a/test/jsonapi.js +++ b/test/jsonapi.js @@ -2,11 +2,10 @@ var request = require( 'supertest' ), kue = require( '../index' ), async = require( 'async' ), chai = require( 'chai' ), - queue = kue.createQueue( { disableSearch: false } ), //customize queue before accessing kue.app + queue = kue.getQueue( { disableSearch: false } ), //customize queue before accessing kue.app app = kue.app, type = 'test:inserts'; - expect = chai.expect; @@ -33,7 +32,7 @@ function jobsPopulate( count ) { } -describe( 'JSON API', function () { +describe.only( 'JSON API', function () { var scope = {}; @@ -41,7 +40,7 @@ describe( 'JSON API', function () { scope.queue = queue; // delete all jobs to get a clean state - kue.Job.rangeByType( type, 'inactive', 0, 100, 'asc', function ( err, jobs ) { + queue.Job.rangeByType( type, 'inactive', 0, 100, 'asc', function ( err, jobs ) { if ( err ) return done( err ); if ( !jobs.length ) return done(); async.each( jobs, function ( job, asyncDone ) { @@ -103,7 +102,7 @@ describe( 'JSON API', function () { request( app ) .get( '/job/' + scope.jobId ) .expect( function ( res ) { - res.body.id.should.eql( scope.jobId ); + res.body.id.should.eql( scope.jobId.toString() ); res.body.type.should.eql( type ); res.body.state.should.eql( 'inactive' ); } ) @@ -125,7 +124,7 @@ describe( 'JSON API', function () { request( app ) .get( '/job/' + scope.jobId ) .expect( function ( res ) { - res.body.id.should.eql( scope.jobId ); + res.body.id.should.eql( scope.jobId.toString() ); res.body.type.should.eql( type ); res.body.state.should.eql( 'active' ); } ) diff --git a/test/mocha.opts b/test/mocha.opts old mode 100755 new mode 100644 index 02c0dc43..ccf9e01e --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,5 +1,5 @@ ---compilers coffee:coffee-script +--require coffee-script/register --require should --reporter spec --ui bdd ---timeout 10000 \ No newline at end of file +--timeout 10000 diff --git a/test/prefix.coffee b/test/prefix.coffee index 0772f535..aa0c9bbf 100644 --- a/test/prefix.coffee +++ b/test/prefix.coffee @@ -7,7 +7,7 @@ describe 'Kue - Prefix', -> prefix: queueName promotion: interval: 10 - jobs = kue.createQueue opts + jobs = kue.getQueue opts return jobs stopJobs = (jobs, callback) -> @@ -18,7 +18,7 @@ describe 'Kue - Prefix', -> # 1397744169.196792 "subscribe" "q:events" # 1397744169.196852 "unsubscribe" it 'should use prefix q by default', (done) -> - jobs = kue.createQueue() + jobs = kue.getQueue() jobs.client.prefix.should.equal 'q' stopJobs jobs, done @@ -89,7 +89,7 @@ describe 'Kue - Prefix', -> it 'should properly switch back to default queue', (testDone) -> jobs = makeJobs('notDefault') stopJobs jobs, (err) -> - jobs = kue.createQueue() + jobs = kue.getQueue() job = jobs.create('defaultPrefixJob') job.on 'complete', () -> @@ -98,4 +98,3 @@ describe 'Kue - Prefix', -> jobs.process 'defaultPrefixJob', (job, done) -> done() - diff --git a/test/shutdown.coffee b/test/shutdown.coffee index c986137a..aca7683c 100644 --- a/test/shutdown.coffee +++ b/test/shutdown.coffee @@ -1,4 +1,5 @@ should = require 'should' +assert = require 'assert' kue = require '../' @@ -6,34 +7,35 @@ kue = require '../' describe 'Kue', -> before (done) -> - jobs = kue.createQueue() + jobs = kue.getQueue() jobs.client.flushdb done after (done) -> - jobs = kue.createQueue() + jobs = kue.getQueue() jobs.client.flushdb done describe 'Shutdown', -> - it 'should return singleton from createQueue', (done) -> - jobs = kue.createQueue() - jobsToo = kue.createQueue() + it 'should return singleton from getQueue', (done) -> + jobs = kue.getQueue() + jobsToo = kue.getQueue() jobs.should.equal jobsToo jobs.shutdown done - it 'should destroy singleton on shutdown', (done) -> - jobs = kue.createQueue() + it 'should return new instance from getQueue', (done) -> + jobs = kue.getQueue() jobs.shutdown (err) -> # test that new jobs object is a different reference - newJobs = kue.createQueue() - newJobs.should.not.equal jobs + newJobs = kue.getQueue() + # newJobs.should.not.equal jobs + assert( newJobs != jobs) newJobs.shutdown done it 'should clear properties on shutdown', (done) -> - jobs = kue.createQueue({promotion:{interval:200}}) + jobs = kue.getQueue({promotion:{interval:200}}) jobs.shutdown (err) -> should(jobs.workers).be.empty should(jobs.client).be.empty @@ -43,7 +45,7 @@ describe 'Kue', -> it 'should be able to pause/resume the worker', (done) -> - jobs = kue.createQueue() + jobs = kue.getQueue() job_data = title: 'resumable jobs' to: 'tj@learnboost.com' @@ -62,7 +64,7 @@ describe 'Kue', -> it 'should not clear properties on single type shutdown', (testDone) -> - jobs = kue.createQueue() + jobs = kue.getQueue() fn = (err) -> jobs.client.should.not.be.empty jobs.shutdown 10, testDone @@ -72,7 +74,7 @@ describe 'Kue', -> it 'should shutdown one worker type on single type shutdown', (testDone) -> - jobs = kue.createQueue() + jobs = kue.getQueue() # set up two worker types jobs.process 'runningTask', (job, done) -> done() @@ -98,7 +100,7 @@ describe 'Kue', -> it 'should fail active job when shutdown timer expires', (testDone) -> - jobs = kue.createQueue() + jobs = kue.getQueue() jobId = null jobs.process 'long-task', (job, done) -> jobId = job.id @@ -110,7 +112,7 @@ describe 'Kue', -> # need to make sure long-task has had enough time to get into active state waitForJobToRun = -> fn = (err) -> - kue.Job.get jobId, (err, job) -> + jobs.Job.get jobId, (err, job) -> job.should.have.property '_state', "failed" job.should.have.property '_error', "Shutdown" testDone() @@ -123,7 +125,7 @@ describe 'Kue', -> it 'should not call graceful shutdown twice on subsequent calls', (testDone) -> - jobs = kue.createQueue() + jobs = kue.getQueue() jobs.process 'test-subsequent-shutdowns', (job, done) -> done() setTimeout ()-> @@ -144,7 +146,7 @@ describe 'Kue', -> it 'should fail active re-attemptable job when shutdown timer expires', (testDone) -> - jobs = kue.createQueue() + jobs = kue.getQueue() jobId = null jobs.process 'shutdown-reattemptable-jobs', (job, done) -> jobId = job.id @@ -155,10 +157,10 @@ describe 'Kue', -> # need to make sure long-task has had enough time to get into active state waitForJobToRun = -> fn = (err) -> - kue.Job.get jobId, (err, job) -> + jobs.Job.get jobId, (err, job) -> job.should.have.property '_state', "inactive" - job.should.have.property '_attempts', "1" job.should.have.property '_error', "Shutdown" + job.should.have.property '_attempts', 1 testDone() # shutdown timer is shorter than job length diff --git a/test/tdd/kue.spec.js b/test/tdd/kue.spec.js index 2a3cb054..32fa0b85 100644 --- a/test/tdd/kue.spec.js +++ b/test/tdd/kue.spec.js @@ -1,46 +1,44 @@ var sinon = require('sinon'); var kue = require('../../lib/kue'); -var redis = require('../../lib/redis'); -var events = require('../../lib/queue/events'); -var Job = require('../../lib/queue/job'); -var Worker = require('../../lib/queue/worker'); +var Worker = require('../../lib/worker'); var _ = require('lodash'); var EventEmitter = require('events').EventEmitter; var redisClient = {}; describe('Kue', function () { + var queue = kue.getQueue() beforeEach(function(){ - sinon.stub(events, 'subscribe'); - sinon.stub(redis, 'configureFactory', function () { - redis.createClient = sinon.stub(); + sinon.stub(queue.events, 'subscribe'); + sinon.stub(queue.redis, 'configureFactory').callsFake( function () { + queue.redis.createClient = sinon.stub(); }); }); afterEach(function(){ - events.subscribe.restore(); - redis.configureFactory.restore(); + queue.events.subscribe.restore && queue.events.subscribe.restore(); + queue.redis.configureFactory.restore && queue.redis.configureFactory.restore(); }); - describe('Function: createQueue', function () { + describe('Function: getQueue', function () { it('should subscribe to queue events', function () { - var queue = kue.createQueue(); - events.subscribe.called.should.be.true; + queue = kue.getQueue(); + queue.events.subscribe.called.should.be.true; }); it('should set the correct default values', function () { - var queue = kue.createQueue(); + queue = kue.getQueue(); queue.name.should.equal('kue'); queue.id.should.equal([ 'kue', require("os").hostname(), process.pid ].join(':')); (queue.promoter === null).should.be.true; - queue.workers.should.eql(kue.workers); + queue.workers.should.eql([]); queue.shuttingDown.should.be.false; }); it('should allow a custom name option', function () { it('should set the correct default values', function () { - var queue = kue.createQueue({ + queue = kue.getQueue({ name: 'name' }); queue.name.should.equal('name'); @@ -49,9 +47,8 @@ describe('Kue', function () { }); describe('Function: create', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); }); it('should return a new Job instance', function () { @@ -66,16 +63,16 @@ describe('Kue', function () { }); describe('Function: on', function() { - var queue, noop; + var noop; beforeEach(function(){ - queue = kue.createQueue(); - events.subscribe.reset(); + queue = kue.getQueue(); + queue.events.subscribe.reset(); noop = function () {}; }); it('should subscribe to events when subscribing to the job event', function () { queue.on('job', noop); - events.subscribe.called.should.be.true; + queue.events.subscribe.called.should.be.true; }); it('should proxy the event listener', function (done) { @@ -88,9 +85,8 @@ describe('Kue', function () { }); describe('Function: setupTimers', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'checkJobPromotion'); sinon.stub(queue, 'checkActiveJobTtl'); }); @@ -118,7 +114,7 @@ describe('Kue', function () { }); describe('Function: checkJobPromotion', function() { - var queue, unlock, clock, timeout, client, ids, job; + var unlock, clock, timeout, client, ids, job; beforeEach(function(){ unlock = sinon.spy(); @@ -133,19 +129,19 @@ describe('Kue', function () { inactive: sinon.stub().callsArg(0) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; - sinon.stub(Job, 'get').callsArgWith(1, null, job); + sinon.stub(queue.Job, 'get').callsArgWith(1, null, job); sinon.stub(queue.warlock, 'lock').callsArgWith(2, null, unlock); - sinon.stub(events, 'emit'); + sinon.stub(queue.events, 'emit'); clock = sinon.useFakeTimers(); }); afterEach(function(){ - Job.get.restore(); + queue.Job.get.restore(); queue.warlock.lock.restore(); - events.emit.restore(); + queue.events.emit.restore(); clock.restore(); }); @@ -164,25 +160,25 @@ describe('Kue', function () { it('should load all delayed jobs that should be run job', function () { queue.checkJobPromotion(); clock.tick(timeout); - client.zrangebyscore.calledWith(client.getKey('jobs:delayed'), 0, sinon.match.any, "LIMIT", 0, 1000).should.be.true; + queue.client.zrangebyscore.calledWith(queue.client.getKey('jobs:delayed'), 0, sinon.match.any, "LIMIT", 0, 1000).should.be.true; }); it('should get each job', function () { queue.checkJobPromotion(); clock.tick(timeout); - Job.get.callCount.should.equal(3); - Job.get.calledWith(ids[0]).should.be.true; - Job.get.calledWith(ids[1]).should.be.true; - Job.get.calledWith(ids[2]).should.be.true; + queue.Job.get.callCount.should.equal(3); + queue.Job.get.calledWith(ids[0]).should.be.true; + queue.Job.get.calledWith(ids[1]).should.be.true; + queue.Job.get.calledWith(ids[2]).should.be.true; }); it('should emit promotion for each job', function () { queue.checkJobPromotion(); clock.tick(timeout); - events.emit.callCount.should.equal(3); - events.emit.calledWith(ids[0], 'promotion').should.be.true; - events.emit.calledWith(ids[1], 'promotion').should.be.true; - events.emit.calledWith(ids[2], 'promotion').should.be.true; + queue.events.emit.callCount.should.equal(3); + queue.events.emit.calledWith(ids[0], 'promotion').should.be.true; + queue.events.emit.calledWith(ids[1], 'promotion').should.be.true; + queue.events.emit.calledWith(ids[2], 'promotion').should.be.true; }); it('should set each job to inactive', function () { @@ -200,7 +196,7 @@ describe('Kue', function () { }); describe('Function: checkActiveJobTtl', function() { - var queue, unlock, clock, timeout, client, ids, job; + var unlock, clock, timeout, client, ids, job; beforeEach(function(){ unlock = sinon.spy(); @@ -215,21 +211,21 @@ describe('Kue', function () { failedAttempt: sinon.stub().callsArg(1) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; sinon.spy(queue, 'removeAllListeners'); - sinon.stub(Job, 'get').callsArgWith(1, null, job); + sinon.stub(queue.Job, 'get').callsArgWith(1, null, job); sinon.stub(queue.warlock, 'lock').callsArgWith(2, null, unlock); - sinon.stub(events, 'emit'); + sinon.stub(queue.events, 'emit'); clock = sinon.useFakeTimers(); }); afterEach(function(){ queue.removeAllListeners.restore(); - Job.get.restore(); + queue.Job.get.restore(); queue.warlock.lock.restore(); - events.emit.restore(); + queue.events.emit.restore(); clock.restore(); }); @@ -242,16 +238,16 @@ describe('Kue', function () { it('should load all expired jobs', function () { queue.checkActiveJobTtl(); clock.tick(timeout); - client.zrangebyscore.calledWith(client.getKey('jobs:active'), 100000, sinon.match.any, "LIMIT", 0, 1000).should.be.true; + queue.client.zrangebyscore.calledWith(queue.client.getKey('jobs:active'), 100000, sinon.match.any, "LIMIT", 0, 1000).should.be.true; }); it('should emit ttl exceeded for each job', function () { queue.checkActiveJobTtl(); clock.tick(timeout); - events.emit.callCount.should.equal(3); - events.emit.calledWith(ids[0], 'ttl exceeded'); - events.emit.calledWith(ids[1], 'ttl exceeded'); - events.emit.calledWith(ids[2], 'ttl exceeded'); + queue.events.emit.callCount.should.equal(3); + queue.events.emit.calledWith(ids[0], 'ttl exceeded'); + queue.events.emit.calledWith(ids[1], 'ttl exceeded'); + queue.events.emit.calledWith(ids[2], 'ttl exceeded'); }); it('should unlock after all the job ttl exceeded acks have been received', function () { @@ -276,7 +272,7 @@ describe('Kue', function () { queue.emit('job ttl exceeded ack', id); }); clock.tick(timeout); - Job.get.calledWith(id).should.be.true; + queue.Job.get.calledWith(id).should.be.true; job.failedAttempt.calledOnce.should.be.true; job.failedAttempt.calledWith({ error: true, @@ -286,7 +282,7 @@ describe('Kue', function () { }); describe('Function: watchStuckJobs', function() { - var queue, clock, client, sha; + var clock, client, sha; beforeEach(function(){ sha = 'sha'; @@ -295,7 +291,7 @@ describe('Kue', function () { evalsha: sinon.stub().callsArg(2) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; clock = sinon.useFakeTimers(); @@ -307,22 +303,22 @@ describe('Kue', function () { it('should load the script', function () { queue.watchStuckJobs(); - client.script.calledWith('LOAD').should.be.true; + queue.client.script.calledWith('LOAD').should.be.true; }); it('should run the script on an interval', function () { queue.watchStuckJobs(); clock.tick(1000); - client.evalsha.calledWith(sha, 0).should.be.true; - client.evalsha.callCount.should.equal(1); + queue.client.evalsha.calledWith(sha, 0).should.be.true; + queue.client.evalsha.callCount.should.equal(1); clock.tick(1000); - client.evalsha.callCount.should.equal(2); + queue.client.evalsha.callCount.should.equal(2); }); }); describe('Function: setting', function() { - var queue, client; + var client; beforeEach(function(){ client = { @@ -330,13 +326,13 @@ describe('Kue', function () { hget: sinon.stub().callsArg(2) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; }); it('should get the requested setting', function (done) { queue.setting('name', function () { - client.hget.calledWith(client.getKey('settings'), 'name').should.be.true; + queue.client.hget.calledWith(queue.client.getKey('settings'), 'name').should.be.true; done(); }); }); @@ -344,7 +340,7 @@ describe('Kue', function () { }); describe('Function: process', function() { - var queue, client, worker; + var client, worker; beforeEach(function(){ client = { @@ -352,9 +348,10 @@ describe('Kue', function () { incrby: sinon.stub() }; worker = new EventEmitter(); - queue = kue.createQueue(); + queue = kue.getQueue(); queue.workers = []; queue.client = client; + worker.client = queue.client; sinon.stub(queue, 'setupTimers'); sinon.stub(Worker.prototype, 'start').returns(worker); @@ -394,7 +391,7 @@ describe('Kue', function () { }; queue.process('type', 3, sinon.stub()); worker.emit('job complete', job); - client.incrby.calledWith(client.getKey('stats:work-time'), job.duration).should.be.true; + queue.client.incrby.calledWith(queue.client.getKey('stats:work-time'), job.duration).should.be.true; }); it('should setup timers', function () { @@ -405,7 +402,7 @@ describe('Kue', function () { }); describe('Function: shutdown', function() { - var queue, client, worker, lockClient; + var client, worker, lockClient; beforeEach(function(){ client = { @@ -417,19 +414,19 @@ describe('Kue', function () { worker = { shutdown: sinon.stub().callsArg(1) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.shuttingDown = false; queue.workers = [worker, worker, worker]; queue.client = client; queue.lockClient = lockClient; - sinon.stub(events, 'unsubscribe'); - sinon.stub(redis, 'reset'); + sinon.stub(queue.events, 'unsubscribe'); + sinon.stub(queue.redis, 'reset'); }); afterEach(function(){ - events.unsubscribe.restore(); - redis.reset.restore(); + queue.events.unsubscribe.restore(); + queue.redis.reset.restore(); }); it('should return an error if it is already shutting down', function (done) { @@ -450,8 +447,8 @@ describe('Kue', function () { it('should clean things up', function (done) { queue.shutdown(function () { queue.workers.length.should.equal(0); - events.unsubscribe.called.should.be.true; - redis.reset.called.should.be.true; + queue.events.unsubscribe.called.should.be.true; + queue.redis.reset.called.should.be.true; client.quit.called.should.be.true; (queue.client == null).should.be.true; lockClient.quit.called.should.be.true; @@ -463,7 +460,7 @@ describe('Kue', function () { }); describe('Function: types', function() { - var queue, client, types; + var client, types; beforeEach(function(){ types = ['type1', 'type2']; @@ -471,7 +468,7 @@ describe('Kue', function () { getKey: sinon.stub().returnsArg(0), smembers: sinon.stub().callsArgWith(1, null, types) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; }); @@ -484,7 +481,7 @@ describe('Kue', function () { }); describe('Function: state', function() { - var queue, client, jobIds, state; + var client, jobIds, state; beforeEach(function(){ jobIds = [1, 2]; @@ -494,7 +491,7 @@ describe('Kue', function () { stripFIFO: sinon.stub().returnsArg(0), zrange: sinon.stub().callsArgWith(3, null, jobIds) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; }); @@ -508,7 +505,7 @@ describe('Kue', function () { }); describe('Function: workTime', function() { - var queue, client, n; + var client, n; beforeEach(function(){ n = 20; @@ -516,7 +513,7 @@ describe('Kue', function () { getKey: sinon.stub().returnsArg(0), get: sinon.stub().callsArgWith(1, null, n) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; }); @@ -530,7 +527,7 @@ describe('Kue', function () { }); describe('Function: cardByType', function() { - var queue, client, type, state, total; + var client, type, state, total; beforeEach(function(){ type = 'type'; @@ -540,7 +537,7 @@ describe('Kue', function () { getKey: sinon.stub().returnsArg(0), zcard: sinon.stub().callsArgWith(1, null, total) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; }); @@ -553,7 +550,7 @@ describe('Kue', function () { }); describe('function: card', function() { - var queue, client, state, total; + var client, state, total; beforeEach(function(){ state = 'state'; @@ -562,7 +559,7 @@ describe('Kue', function () { getKey: sinon.stub().returnsArg(0), zcard: sinon.stub().callsArgWith(1, null, total) }; - queue = kue.createQueue(); + queue = kue.getQueue(); queue.client = client; }); @@ -575,10 +572,9 @@ describe('Kue', function () { }); describe('Function: complete', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'state').callsArg(1); }); @@ -595,10 +591,9 @@ describe('Kue', function () { }); describe('Function: failed', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'state').callsArg(1); }); @@ -615,10 +610,9 @@ describe('Kue', function () { }); describe('Function: inactive', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'state').callsArg(1); }); @@ -635,10 +629,9 @@ describe('Kue', function () { }); describe('Function: active', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'state').callsArg(1); }); @@ -655,10 +648,9 @@ describe('Kue', function () { }); describe('Function: delayed', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'state').callsArg(1); }); @@ -675,10 +667,9 @@ describe('Kue', function () { }); describe('Function: completeCount', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'card').callsArg(1); sinon.stub(queue, 'cardByType').callsArg(2); }); @@ -704,10 +695,9 @@ describe('Kue', function () { }); describe('Function: failedCount', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'card').callsArg(1); sinon.stub(queue, 'cardByType').callsArg(2); }); @@ -733,10 +723,9 @@ describe('Kue', function () { }); describe('Function: inactiveCount', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'card').callsArg(1); sinon.stub(queue, 'cardByType').callsArg(2); }); @@ -762,10 +751,9 @@ describe('Kue', function () { }); describe('Function: activeCount', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'card').callsArg(1); sinon.stub(queue, 'cardByType').callsArg(2); }); @@ -791,10 +779,9 @@ describe('Kue', function () { }); describe('Function: delayedCount', function() { - var queue; beforeEach(function(){ - queue = kue.createQueue(); + queue = kue.getQueue(); sinon.stub(queue, 'card').callsArg(1); sinon.stub(queue, 'cardByType').callsArg(2); }); @@ -819,4 +806,4 @@ describe('Kue', function () { }); }); -}); \ No newline at end of file +}); diff --git a/test/tdd/redis.spec.js b/test/tdd/redis.spec.js index e1ae02f7..25c2e0e4 100644 --- a/test/tdd/redis.spec.js +++ b/test/tdd/redis.spec.js @@ -1,6 +1,6 @@ var sinon = require('sinon'); var r = require('redis'); -var redis = require('../../lib/redis'); +var redis = require('../../lib/redis')(); describe('redis', function() { @@ -218,4 +218,4 @@ describe('redis', function() { }); -}); \ No newline at end of file +}); diff --git a/test/test.coffee b/test/test.coffee old mode 100755 new mode 100644 index 03202314..41cc7334 --- a/test/test.coffee +++ b/test/test.coffee @@ -10,18 +10,18 @@ describe 'Kue Tests', -> Job = null beforeEach -> - jobs = kue.createQueue({promotion:{interval:50}}) - Job = kue.Job + jobs = kue.getQueue({promotion:{interval:50}}) + Job = jobs.Job afterEach (done) -> jobs.shutdown 50, done # before (done) -> - # jobs = kue.createQueue({promotion:{interval:100}}) + # jobs = kue.getQueue({promotion:{interval:100}}) # jobs.client.flushdb done # after (done) -> - # jobs = kue.createQueue({promotion:{interval:100}}) + # jobs = kue.getQueue({promotion:{interval:100}}) # jobs.client.flushdb done diff --git a/test/test.js b/test/test.js old mode 100755 new mode 100644 index d1fab1b3..dd944ad6 --- a/test/test.js +++ b/test/test.js @@ -1,151 +1,151 @@ var kue = require( '../' ); describe('CONNECTION', function(){ - var jobs = null; + var jobs = null; - afterEach( function ( done ) { - jobs.shutdown( 50, function () { - done() - } ); - } ); + afterEach( function ( done ) { + jobs.shutdown( 50, function () { + done() + } ); + } ); it( 'should configure properly with string', function ( done ) { - jobs = new kue( { - redis: 'redis://localhost:6379/15?foo=bar' - } ); - - jobs.client.options.port.should.be.eql( 6379 ); - jobs.client.options.host.should.be.eql( 'localhost' ); - jobs.client.options.foo.should.be.eql( 'bar' ); - - var jobData = { - title: 'welcome email for tj', - to: '"TJ" ', - template: 'welcome-email' - }; - jobs.create( 'email-should-be-processed-3', jobData ).priority( 'high' ).save(); - jobs.process( 'email-should-be-processed-3', function ( job, jdone ) { - job.data.should.be.eql( jobData ); - job.log( '

This is a formatted log

' ); - // Needs to be here to support the async client.select statement where the return happens sync but the call is async - jobs.client.selected_db.should.be.eql(15); - jdone(); - done(); - } ); + jobs = new kue( { + redis: 'redis://localhost:6379/15?foo=bar' + } ); + + jobs.client.options.port.should.be.eql( 6379 ); + jobs.client.options.host.should.be.eql( 'localhost' ); + jobs.client.options.foo.should.be.eql( 'bar' ); + + var jobData = { + title: 'welcome email for tj', + to: '"TJ" ', + template: 'welcome-email' + }; + jobs.create( 'email-should-be-processed-3', jobData ).priority( 'high' ).save(); + jobs.process( 'email-should-be-processed-3', function ( job, jdone ) { + job.data.should.be.eql( jobData ); + job.log( '

This is a formatted log

' ); + // Needs to be here to support the async client.select statement where the return happens sync but the call is async + jobs.client.selected_db.should.be.eql(15); + jdone(); + done(); + } ); }); - it( 'should configure properly with dictionary', function ( done ) { - jobs = new kue( { - redis: { - host: 'localhost', - port: 6379, - db: 15, - options: { - foo: 'bar' - } - } - } ); - - jobs.client.options.port.should.be.eql( 6379 ); - jobs.client.options.host.should.be.eql( 'localhost' ); - jobs.client.options.foo.should.be.eql( 'bar' ); - - var jobData = { - title: 'welcome email for tj', - to: '"TJ" ', - template: 'welcome-email' - }; - jobs.create( 'email-should-be-processed-4', jobData ).priority( 'high' ).save(); - jobs.process( 'email-should-be-processed-4', function ( job, jdone ) { - job.data.should.be.eql( jobData ); - job.log( '

This is a formatted log

' ); - // Needs to be here to support the async client.select statement where the return happens sync but the call is async - jobs.client.selected_db.should.be.eql(15); - jdone(); - done(); - } ); - }); - - it( 'should default to 0 db with string', function ( done ) { - var jobs = new kue( { - redis: 'redis://localhost:6379/?foo=bar' - } ); - - jobs.client.options.port.should.be.eql( 6379 ); - jobs.client.options.host.should.be.eql( 'localhost' ); - jobs.client.options.foo.should.be.eql( 'bar' ); - - var jobData = { - title: 'welcome email for tj', - to: '"TJ" ', - template: 'welcome-email' - }; - jobs.create( 'email-should-be-processed-5', jobData ).priority( 'high' ).save(); - jobs.process( 'email-should-be-processed-5', function ( job, jdone ) { - job.data.should.be.eql( jobData ); - job.log( '

This is a formatted log

' ); - jobs.client.selected_db.should.be.eql(0); - jdone(); - done(); - } ); - - }); - - it( 'should default to 0 db with string and no /', function ( done ) { - var jobs = new kue( { - redis: 'redis://localhost:6379?foo=bar' - } ); - - jobs.client.options.port.should.be.eql( 6379 ); - jobs.client.options.host.should.be.eql( 'localhost' ); - jobs.client.options.foo.should.be.eql( 'bar' ); - - var jobData = { - title: 'welcome email for tj', - to: '"TJ" ', - template: 'welcome-email' - }; - jobs.create( 'email-should-be-processed-6', jobData ).priority( 'high' ).save(); - jobs.process( 'email-should-be-processed-6', function ( job, jdone ) { - job.data.should.be.eql( jobData ); - job.log( '

This is a formatted log

' ); - jobs.client.selected_db.should.be.eql(0); - jdone(); - done(); - } ); - - }); - - it( 'should configure properly with dictionary', function ( done ) { - jobs = new kue( { - redis: { - host: 'localhost', - port: 6379, - options: { - foo: 'bar' - } - } - } ); - - jobs.client.options.port.should.be.eql( 6379 ); - jobs.client.options.host.should.be.eql( 'localhost' ); - jobs.client.options.foo.should.be.eql( 'bar' ); - - var jobData = { - title: 'welcome email for tj', - to: '"TJ" ', - template: 'welcome-email' - }; - jobs.create( 'email-should-be-processed-7', jobData ).priority( 'high' ).save(); - jobs.process( 'email-should-be-processed-7', function ( job, jdone ) { - job.data.should.be.eql( jobData ); - job.log( '

This is a formatted log

' ); - // Needs to be here to support the async client.select statement where the return happens sync but the call is async - jobs.client.selected_db.should.be.eql(0); - jdone(); - done(); - } ); - }); + it( 'should configure properly with dictionary', function ( done ) { + jobs = new kue( { + redis: { + host: 'localhost', + port: 6379, + db: 15, + options: { + foo: 'bar' + } + } + } ); + + jobs.client.options.port.should.be.eql( 6379 ); + jobs.client.options.host.should.be.eql( 'localhost' ); + jobs.client.options.foo.should.be.eql( 'bar' ); + + var jobData = { + title: 'welcome email for tj', + to: '"TJ" ', + template: 'welcome-email' + }; + jobs.create( 'email-should-be-processed-4', jobData ).priority( 'high' ).save(); + jobs.process( 'email-should-be-processed-4', function ( job, jdone ) { + job.data.should.be.eql( jobData ); + job.log( '

This is a formatted log

' ); + // Needs to be here to support the async client.select statement where the return happens sync but the call is async + jobs.client.selected_db.should.be.eql(15); + jdone(); + done(); + } ); + }); + + it( 'should default to 0 db with string', function ( done ) { + var jobs = new kue( { + redis: 'redis://localhost:6379/?foo=bar' + } ); + + jobs.client.options.port.should.be.eql( 6379 ); + jobs.client.options.host.should.be.eql( 'localhost' ); + jobs.client.options.foo.should.be.eql( 'bar' ); + + var jobData = { + title: 'welcome email for tj', + to: '"TJ" ', + template: 'welcome-email' + }; + jobs.create( 'email-should-be-processed-5', jobData ).priority( 'high' ).save(); + jobs.process( 'email-should-be-processed-5', function ( job, jdone ) { + job.data.should.be.eql( jobData ); + job.log( '

This is a formatted log

' ); + jobs.client.selected_db.should.be.eql(0); + jdone(); + done(); + } ); + + }); + + it( 'should default to 0 db with string and no /', function ( done ) { + var jobs = new kue( { + redis: 'redis://localhost:6379?foo=bar' + } ); + + jobs.client.options.port.should.be.eql( 6379 ); + jobs.client.options.host.should.be.eql( 'localhost' ); + jobs.client.options.foo.should.be.eql( 'bar' ); + + var jobData = { + title: 'welcome email for tj', + to: '"TJ" ', + template: 'welcome-email' + }; + jobs.create( 'email-should-be-processed-6', jobData ).priority( 'high' ).save(); + jobs.process( 'email-should-be-processed-6', function ( job, jdone ) { + job.data.should.be.eql( jobData ); + job.log( '

This is a formatted log

' ); + jobs.client.selected_db.should.be.eql(0); + jdone(); + done(); + } ); + + }); + + it( 'should configure properly with dictionary', function ( done ) { + jobs = new kue( { + redis: { + host: 'localhost', + port: 6379, + options: { + foo: 'bar' + } + } + } ); + + jobs.client.options.port.should.be.eql( 6379 ); + jobs.client.options.host.should.be.eql( 'localhost' ); + jobs.client.options.foo.should.be.eql( 'bar' ); + + var jobData = { + title: 'welcome email for tj', + to: '"TJ" ', + template: 'welcome-email' + }; + jobs.create( 'email-should-be-processed-7', jobData ).priority( 'high' ).save(); + jobs.process( 'email-should-be-processed-7', function ( job, jdone ) { + job.data.should.be.eql( jobData ); + job.log( '

This is a formatted log

' ); + // Needs to be here to support the async client.select statement where the return happens sync but the call is async + jobs.client.selected_db.should.be.eql(0); + jdone(); + done(); + } ); + }); }); describe( 'JOBS', function () { @@ -153,7 +153,7 @@ describe( 'JOBS', function () { var jobs = null; beforeEach( function ( done ) { - jobs = kue.createQueue( { promotion: { interval: 100 } } ); + jobs = kue.getQueue( { promotion: { interval: 100 } } ); done(); } ); @@ -179,6 +179,15 @@ describe( 'JOBS', function () { } ); it( 'should retry on failure if attempts is set', function ( testDone ) { + var attempts = 0; + jobs.process( 'failure-attempts', function ( job, done ) { + attempts++; + if ( attempts == 5 ) + done(); + else + done( new Error( "error" ) ); + } ); + var job = jobs.create( 'failure-attempts', {} ); var failures = 0; job.attempts( 5 ) @@ -191,14 +200,6 @@ describe( 'JOBS', function () { failures++; } ) .save(); - var attempts = 0; - jobs.process( 'failure-attempts', function ( job, done ) { - attempts++; - if ( attempts == 5 ) - done(); - else - done( new Error( "error" ) ); - } ); } ); it( 'should accept url strings for redis when making an new queue', function ( done ) { diff --git a/test/test_mode.js b/test/test_mode.js index 4c009cb5..0ddb3eb8 100644 --- a/test/test_mode.js +++ b/test/test_mode.js @@ -1,21 +1,21 @@ var kue = require('../'), _ = require('lodash'), - queue = kue.createQueue(); + queue = kue.getQueue(); describe('Test Mode', function() { context('when enabled', function() { before(function() { - queue.testMode.enter(); + queue.testMode().enter(); }); afterEach(function() { - queue.testMode.clear(); + queue.testMode().clear(); }); it('adds jobs to an array in memory', function() { queue.createJob('myJob', { foo: 'bar' }).save(); - var jobs = queue.testMode.jobs; + var jobs = queue.testMode().jobs; expect(jobs.length).to.equal(1); var job = _.last(jobs); @@ -24,12 +24,13 @@ describe('Test Mode', function() { }); it('adds jobs to an array in memory and processes them when processQueue is true', function(done) { - queue.testMode.exit(); - queue.testMode.enter(true); + + queue.testMode().exit(); + queue.testMode().enter(true); queue.createJob('test-testMode-process', { foo: 'bar' }).save(); - var jobs = queue.testMode.jobs; + var jobs = queue.testMode().jobs; expect(jobs.length).to.equal(1); var job = _.last(jobs); @@ -37,8 +38,8 @@ describe('Test Mode', function() { expect(job.data).to.eql({ foo: 'bar' }); job.on('complete', function() { - queue.testMode.exit(); - queue.testMode.enter(); + queue.testMode().exit(); + queue.testMode().enter(); done(); }); @@ -52,9 +53,9 @@ describe('Test Mode', function() { describe('#clear', function() { it('resets the list of jobs', function() { queue.createJob('myJob', { foo: 'bar' }).save(); - queue.testMode.clear(); + queue.testMode().clear(); - var jobs = queue.testMode.jobs; + var jobs = queue.testMode().jobs; expect(jobs.length).to.equal(0); }); }); @@ -64,14 +65,14 @@ describe('Test Mode', function() { before(function() { // Simulate entering and exiting test mode to ensure // state is restored correctly. - queue.testMode.enter(); - queue.testMode.exit(); + queue.testMode().enter(); + queue.testMode().exit(); }); it('processes jobs regularly', function(done) { queue.createJob('myJob', { foo: 'bar' }).save(); - var jobs = queue.testMode.jobs; + var jobs = queue.testMode().jobs; expect(jobs.length).to.equal(0); queue.process('myJob', function (job, jdone) {