From ea8905429e826801ee7c28dfb4c29f7a521c2979 Mon Sep 17 00:00:00 2001 From: takion Date: Thu, 7 Jun 2018 11:01:06 +0200 Subject: [PATCH 1/4] fix chmod --- lib/kue.js | 0 lib/queue/events.js | 0 lib/redis.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 lib/kue.js mode change 100755 => 100644 lib/queue/events.js mode change 100755 => 100644 lib/redis.js diff --git a/lib/kue.js b/lib/kue.js old mode 100755 new mode 100644 diff --git a/lib/queue/events.js b/lib/queue/events.js old mode 100755 new mode 100644 diff --git a/lib/redis.js b/lib/redis.js old mode 100755 new mode 100644 From 66f2161ccafd293a2ffab0823da777ac8956960f Mon Sep 17 00:00:00 2001 From: takion Date: Thu, 7 Jun 2018 11:01:33 +0200 Subject: [PATCH 2/4] add giteye's .project file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4fc877d6..13fc7bcb 100755 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules *.rdb test/incomplete *.swp +/.project From 4f044fe39f2e8c0f1e3a3bda25a83ffe9ddee1a1 Mon Sep 17 00:00:00 2001 From: takion Date: Thu, 7 Jun 2018 11:02:02 +0200 Subject: [PATCH 3/4] add test helpers --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9621ed20..4b2bb92c 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,10 @@ "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" From 0262c665a9167e4414dd477a647968f8e0659e1f Mon Sep 17 00:00:00 2001 From: takion Date: Thu, 7 Jun 2018 18:38:46 +0200 Subject: [PATCH 4/4] refacto decouple singleton ! --- .gitignore | 0 .npmignore | 0 .travis.yml | 0 History.md | 0 Makefile | 0 Readme.md | 10 +- bin/kue-dashboard | 20 +- examples/stale.js | 4 +- index.js | 0 lib/http/routes/index.js | 3 +- lib/http/routes/json.js | 10 +- lib/kue.js | 138 +-- lib/queue/events.js | 251 +++--- lib/queue/job.js | 1596 ++++++++++++++++----------------- lib/queue/test_mode.js | 107 +-- lib/queue/worker.js | 43 +- lib/redis.js | 241 ++--- package-lock.json | 1833 ++++++++++++++++++++++++++++++++++++++ test/jsonapi.js | 7 +- test/mocha.opts | 2 +- test/prefix.coffee | 7 +- test/shutdown.coffee | 38 +- test/tdd/kue.spec.js | 187 ++-- test/tdd/redis.spec.js | 4 +- test/test.coffee | 8 +- test/test.js | 299 +++---- test/test_mode.js | 29 +- 27 files changed, 3349 insertions(+), 1488 deletions(-) mode change 100755 => 100644 .gitignore mode change 100755 => 100644 .npmignore mode change 100755 => 100644 .travis.yml mode change 100755 => 100644 History.md mode change 100755 => 100644 Makefile mode change 100755 => 100644 index.js create mode 100644 package-lock.json mode change 100755 => 100644 test/mocha.opts mode change 100755 => 100644 test/test.coffee mode change 100755 => 100644 test/test.js diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 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 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..c0b83cb1 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 = new Job.Job(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/kue.js b/lib/kue.js index 533bf20c..3bf546f3 100644 --- a/lib/kue.js +++ b/lib/kue.js @@ -11,8 +11,8 @@ var EventEmitter = require('events').EventEmitter , Worker = require('./queue/worker') - , events = require('./queue/events') - , Job = require('./queue/job') + , QueueEvents = require('./queue/events') + , QueueJob = require('./queue/job') , Warlock = require('node-redis-warlock') , _ = require('lodash') , redis = require('./redis') @@ -31,53 +31,45 @@ 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. + * Create a new `Queue`. + * + * @return {Queue} + * @api public */ -Object.defineProperty(exports, 'app', { - get: function() { - return app || (app = require('./http')); - } -}); - -/** - * Expose the RedisClient factory. - */ +exports.createQueue = function( options ) { + var queue = new Queue(options); + queue.events.subscribe(); + return queue; -exports.redis = redis; +}; /** - * Create a new `Queue`. + * Get `Queue` singleton. * * @return {Queue} * @api public */ -exports.createQueue = function( options ) { - if( !Queue.singleton ) { +exports.getQueue = function( options ) { + if(!Queue.singleton){ Queue.singleton = new Queue(options); } - events.subscribe(); + Queue.singleton.events.subscribe(); return Queue.singleton; }; /** - * Store workers + * Shutdown the singleton `Queue`. + * + * @api public */ -exports.workers = []; + +exports.shutdown = function( ) { + var singleton = this.getQueue() + return singleton.shutdown.apply(singleton, arguments) +}; /** * Initialize a new job `Queue`. @@ -91,12 +83,19 @@ function Queue( options ) { 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(); + + this.workers = []; + + this.redis = redis(); + this.redis.configureFactory(options, this); + + this.client = this.redis.createClient(); + + this.events = QueueEvents(this); + + this.Job = QueueJob(this); + } /** @@ -116,7 +115,7 @@ Queue.prototype.__proto__ = EventEmitter.prototype; Queue.prototype.create = Queue.prototype.createJob = function( type, data ) { - return new Job(type, data); + return new this.Job.Job(type, data); }; /** @@ -127,7 +126,7 @@ Queue.prototype.create = var on = EventEmitter.prototype.on; Queue.prototype.on = function( event ) { - if( 0 == event.indexOf('job') ) events.subscribe(); + if( 0 == event.indexOf('job') ) this.events.subscribe(); return on.apply(this, arguments); }; @@ -149,7 +148,7 @@ Queue.prototype.promote = function( ms, l ) { Queue.prototype.setupTimers = function() { if( this.warlock === undefined ) { - this.lockClient = redis.createClient(); + this.lockClient = this.redis.createClient(); this.warlock = new Warlock(this.lockClient); } this.checkJobPromotion(this._options.promotion); @@ -167,7 +166,8 @@ Queue.prototype.setupTimers = function() { Queue.prototype.checkJobPromotion = function( promotionOptions ) { promotionOptions = promotionOptions || {}; - var client = this.client + var self = this + , client = this.client , self = this , timeout = promotionOptions.interval || 1000 , lockTtl = promotionOptions.lockTtl || 2000 @@ -189,9 +189,9 @@ Queue.prototype.checkJobPromotion = function( promotionOptions ) { var doUnlock = _.after(ids.length, unlock); ids.forEach(function( id ) { id = client.stripFIFO(id); - Job.get(id, function( err, job ) { + self.Job.get(id, function( err, job ) { if( err ) return doUnlock(); - events.emit(id, 'promotion'); + self.events.emit(id, 'promotion'); job.inactive(doUnlock); }); }); @@ -206,7 +206,8 @@ Queue.prototype.checkJobPromotion = function( promotionOptions ) { Queue.prototype.checkActiveJobTtl = function( ttlOptions ) { ttlOptions = ttlOptions || {}; - var client = this.client + var self = this + , client = this.client , self = this , timeout = ttlOptions.interval || 1000 , lockTtl = 2000 @@ -240,7 +241,7 @@ Queue.prototype.checkActiveJobTtl = function( ttlOptions ) { var waitForAcks = setTimeout( function(){ idsRemaining.forEach( function( id ){ id = client.stripFIFO(id); - Job.get(id, function( err, job ) { + self.Job.get(id, function( err, job ) { if( err ) return doUnlock(); job.failedAttempt( { error: true, message: 'TTL exceeded' }, doUnlock ); }); @@ -249,7 +250,7 @@ Queue.prototype.checkActiveJobTtl = function( ttlOptions ) { ids.forEach(function( id ) { id = client.stripFIFO(id); - events.emit(id, 'ttl exceeded'); + self.events.emit(id, 'ttl exceeded'); }); }); } else { @@ -278,16 +279,16 @@ Queue.prototype.watchStuckJobs = function( ms ) { prefix = '{' + prefix + '}'; } var script = - 'local msg = redis.call( "keys", "' + prefix + ':jobs:*:inactive" )\n\ + '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 = redis.call( "zcard", v )\n\ + local queue = self.redis.call( "zcard", v )\n\ local jt = string.match(v, "' + prefix + ':jobs:(.*):inactive")\n\ - local pending = redis.call( "LLEN", "' + prefix + ':" .. jt .. ":jobs" )\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\ - redis.call( "lpush", "' + prefix + ':"..jt..":jobs", 1 )\n\ + self.redis.call( "lpush", "' + prefix + ':"..jt..":jobs", 1 )\n\ end\n\ end\n\ end\n\ @@ -374,8 +375,7 @@ Queue.prototype.shutdown = function( timeout, type, fn ) { fn = type; type = ''; } - var origFn = fn || function() { - }; + var origFn = fn || function() {}; if( this.shuttingDown && type === '' ) { // a global shutdown already has been called return fn(new Error('Shutdown already in progress')); @@ -388,12 +388,14 @@ Queue.prototype.shutdown = function( timeout, type, fn ) { var cleanup = function() { if( self.shuttingDown ) { self.workers = []; - exports.workers = []; self.removeAllListeners(); - Queue.singleton = null; - events.unsubscribe(); + if( self === Queue.singleton ){ + Queue.singleton = null + } + self.events.unsubscribe(); // destroy redis client and pubsub - redis.reset(); + self.redis.reset(); + self.client && self.client.quit(); self.client = null; self.lockClient && self.lockClient.quit(); @@ -424,7 +426,6 @@ Queue.prototype.shutdown = function( timeout, type, fn ) { } } - if( !self.workers.length ) { cleanup(); origFn(); @@ -665,4 +666,27 @@ Queue.prototype.delayedCount = function( type, fn ) { * @api public */ -Queue.prototype.testMode = require('./queue/test_mode'); +var testMode = require('./queue/test_mode') +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')); + } +}); diff --git a/lib/queue/events.js b/lib/queue/events.js index 1c68d4b7..88593210 100644 --- a/lib/queue/events.js +++ b/lib/queue/events.js @@ -5,130 +5,131 @@ * 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()); +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); } - }); - 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); + // if (!self.subscribed) self.subscribe(); + if( !self.subscribeStarted ) self.subscribe(); + if( !self.subscribed ) { + self.callbackQueue.push(callback); + } else { + callback(); } - } - // 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 () {}); -}; + }; + + /** + * 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/queue/job.js b/lib/queue/job.js index 204aff27..13bd4cc5 100644 --- a/lib/queue/job.js +++ b/lib/queue/job.js @@ -10,8 +10,6 @@ */ var EventEmitter = require('events').EventEmitter - , events = require('./events') - , redis = require('../redis') , _ = require('lodash') , util = require('util') , noop = function() {}; @@ -20,883 +18,889 @@ var EventEmitter = require('events').EventEmitter * Expose `Job`. */ -exports = module.exports = Job; +module.exports = function QueueJob(queue){ + var self = this + var redis = queue.redis + var events = queue.events -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')); -} + self.disableSearch = queue._options.disableSearch !== false; -/** - * Default job priority map. - */ -var priorities = exports.priorities = { - low: 10, normal: 0, medium: -5, high: -10, critical: -15 -}; + self.jobEvents = queue._options.jobEvents !== undefined ? queue._options.jobEvents : true; -/** - * Map `jobs` by the given array of `ids`. - * - * @param {Object} jobs - * @param {Array} ids - * @return {Array} - * @api private - */ + /** + * Search instance. + */ + var search; + function getSearch() { + if( search ) return search; + var reds = require('reds'); + reds.createClient = redis.createClient; + return search = reds.createSearch(redis.client().getKey('search')); + } -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; -} + /** + * Default job priority map. + */ -/** - * 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 - */ + var priorities = self.priorities = { + low: 10, normal: 0, medium: -5, high: -10, critical: -15 + }; -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); + /** + * 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 ) { - 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)); - - }); + if( jobs[ id ] ) ret.push(jobs[ id ]); }); + ret = ret.sort(function( a, b ) { + return parseInt(a.id) - parseInt(b.id); + }); + return ret; } -} - -/** - * 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 - */ + /** + * 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)); -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 - */ + /** + * 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)); + }; -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 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 job with `id` and callback `fn(err, job)`. - * - * @param {Number} id - * @param {String} jobType is optional - * @param {Function} fn - * @api public - */ + /** + * 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)); + }; -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')); + /** + * 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( !hash.type ) { - exports.removeBadJob(job.id, jobType); - return fn(new Error('job "' + job.id + '" is invalid')) + if (typeof jobType === 'function' && !fn) { + fn = jobType; + jobType = ''; } - // 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); + 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 ) { + self.removeBadJob(job.id, jobType); + return fn(new Error('job "' + job.id + '" doesnt exist')); } - } 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`. - * - * @param {String} type - * @param {Object} data - * @api public - */ - -function Job( type, data ) { - 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())*/; - this.priority('normal'); - this.on('error', function( err ) { - });// prevent uncaught exceptions on failed job errors -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Job.prototype.__proto__ = EventEmitter.prototype; - -/** - * Return JSON-friendly object. - * - * @return {Object} - * @api public - */ + 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); + }); + }; -Job.prototype.toJSON = function() { - return { - id: this.id - , type: this.type - , data: this.data - , result: this.result - , priority: this._priority - , progress: this._progress || 0 - , progress_data: this.progress_data - , state: this._state - , error: this._error - , created_at: this.created_at - , promote_at: this.promote_at - , updated_at: this.updated_at - , failed_at: this.failed_at - , started_at: this.started_at - , duration: this.duration - , delay: this._delay - , workerId: this.workerId - , ttl: this._ttl - , attempts: { - made: Number(this._attempts) || 0 - , remaining: this._attempts > 0 ? this._max_attempts - this._attempts : Number(this._max_attempts) || 1 - , max: Number(this._max_attempts) || 1 + /** + * 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 ) { + getSearch().remove(id); } }; -}; - - -Job.prototype.refreshTtl = function() { - ('active' === this.state() && this._ttl > 0) - ? - this.client.zadd(this.client.getKey('jobs:' + this.state()), Date.now() + parseInt(this._ttl), this.zid, noop) - : - noop(); -}; + /** + * 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); + }); + }; -/** - * Log `str` with sprintf-style variable args or anything (objects,arrays,numbers,etc). - * - * Examples: - * - * job.log('preparing attachments'); - * job.log('sending email to %s at %s', user.name, user.email); - * job.log({key: 'some key', value: 10}); - * job.log([1,2,3]); - * - * Specifiers: - * - * - %s : string - * - %d : integer - * - * @param {String} str - * @param {Mixed} ... - * @return {Job} for chaining - * @api public - */ + /** + * 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); + }; -Job.prototype.log = function( str ) { - if(typeof str === 'string') { - var formatted = util.format.apply(util, arguments); - }else{ - var formatted = util.inspect(str); + /** + * Initialize a new `Job` with the given `type` and `data`. + * + * @param {String} type + * @param {Object} data + * @api public + */ + + function Job( type, data ) { + this.type = type; + this.data = data || {}; + this._max_attempts = 1; + this._jobEvents = self.jobEvents; + this.client = redis.client(); + this.priority('normal'); + this.on('error', function( err ) { + });// prevent uncaught exceptions on failed job errors } - this.client.rpush(this.client.getKey('job:' + this.id + ':log'), formatted, noop); - this.set('updated_at', Date.now()); - return this; -}; - -/** - * Set job `key` to `val`. - * - * @param {String} key - * @param {String} val - * @param {String} fn - * @return {Job} for chaining - * @api public - */ - -Job.prototype.set = function( key, val, fn ) { - this.client.hset(this.client.getKey('job:' + this.id), key, val, fn || noop); - return this; -}; - -/** - * Get job `key` - * - * @param {String} key - * @param {Function} fn - * @return {Job} for chaining - * @api public - */ -Job.prototype.get = function( key, fn ) { - this.client.hget(this.client.getKey('job:' + this.id), key, fn || noop); - return this; -}; - -/** - * Set the job progress by telling the job - * how `complete` it is relative to `total`. - * data can be used to pass extra data to job subscribers - * - * @param {Number} complete - * @param {Number} total - * @param {Object} data - * @return {Job} for chaining - * @api public - */ + /** + * Inherit from `EventEmitter.prototype`. + */ + + Job.prototype.__proto__ = EventEmitter.prototype; + + /** + * Return JSON-friendly object. + * + * @return {Object} + * @api public + */ + + Job.prototype.toJSON = function() { + return { + id: this.id + , type: this.type + , data: this.data + , result: this.result + , priority: this._priority + , progress: this._progress || 0 + , progress_data: this.progress_data + , state: this._state + , error: this._error + , created_at: this.created_at + , promote_at: this.promote_at + , updated_at: this.updated_at + , failed_at: this.failed_at + , started_at: this.started_at + , duration: this.duration + , delay: this._delay + , workerId: this.workerId + , ttl: this._ttl + , attempts: { + made: Number(this._attempts) || 0 + , remaining: this._attempts > 0 ? this._max_attempts - this._attempts : Number(this._max_attempts) || 1 + , max: Number(this._max_attempts) || 1 + } + }; + }; -Job.prototype.progress = function( complete, total, data ) { - if( 0 == arguments.length ) return this._progress; - var n = Math.min(100, complete * 100 / total | 0); - this.set('progress', n); - // If this stringify fails because of a circular structure, even the one in events.emit would. - // So it does not make sense to try/catch this. - if( data ) this.set('progress_data', JSON.stringify(data)); + Job.prototype.refreshTtl = function() { + ('active' === this.state() && this._ttl > 0) + ? + this.client.zadd(this.client.getKey('jobs:' + this.state()), Date.now() + parseInt(this._ttl), this.zid, noop) + : + noop(); + }; - this.set('updated_at', Date.now()); - this.refreshTtl(); - events.emit(this.id, 'progress', n, data); - return this; -}; -/** - * Set the job delay in `ms`. - * - * @param {Number|Date} delay in ms or execution date - * @return {Job|Number} - * @api public - */ + /** + * Log `str` with sprintf-style variable args or anything (objects,arrays,numbers,etc). + * + * Examples: + * + * job.log('preparing attachments'); + * job.log('sending email to %s at %s', user.name, user.email); + * job.log({key: 'some key', value: 10}); + * job.log([1,2,3]); + * + * Specifiers: + * + * - %s : string + * - %d : integer + * + * @param {String} str + * @param {Mixed} ... + * @return {Job} for chaining + * @api public + */ + + Job.prototype.log = function( str ) { + if(typeof str === 'string') { + var formatted = util.format.apply(util, arguments); + }else{ + var formatted = util.inspect(str); + } + this.client.rpush(this.client.getKey('job:' + this.id + ':log'), formatted, noop); + this.set('updated_at', Date.now()); + return this; + }; -Job.prototype.delay = function( ms ) { - if( 0 == arguments.length ) return this._delay; - if( _.isDate(ms) ) { - ms = parseInt(ms.getTime() - Date.now()) - } - if( ms > 0 ) { - this._delay = ms; - } - return this; -}; + /** + * Set job `key` to `val`. + * + * @param {String} key + * @param {String} val + * @param {String} fn + * @return {Job} for chaining + * @api public + */ + + Job.prototype.set = function( key, val, fn ) { + this.client.hset(this.client.getKey('job:' + this.id), key, val, fn || noop); + return this; + }; -/** - * Sets the jobEvents flag for the job. - * Can be used to override the global exports.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 - */ -Job.prototype.events = function (events) { - this._jobEvents = !!events; - return this; -}; - -Job.prototype.removeOnComplete = function( param ) { - if( 0 == arguments.length ) return this._removeOnComplete; - this._removeOnComplete = param; - return this; -}; - -Job.prototype.backoff = function( param ) { - if( 0 == arguments.length ) return this._backoff; - this._backoff = param; - return this; -}; + /** + * Get job `key` + * + * @param {String} key + * @param {Function} fn + * @return {Job} for chaining + * @api public + */ + + Job.prototype.get = function( key, fn ) { + this.client.hget(this.client.getKey('job:' + this.id), key, fn || noop); + return this; + }; -/** - * - * @param param - * @returns {*} - */ -Job.prototype.ttl = function( param ) { - if( 0 == arguments.length ) return this._ttl; - if( param > 0 ) { - this._ttl = param; - } - return this; -}; + /** + * Set the job progress by telling the job + * how `complete` it is relative to `total`. + * data can be used to pass extra data to job subscribers + * + * @param {Number} complete + * @param {Number} total + * @param {Object} data + * @return {Job} for chaining + * @api public + */ + + Job.prototype.progress = function( complete, total, data ) { + if( 0 == arguments.length ) return this._progress; + var n = Math.min(100, complete * 100 / total | 0); + this.set('progress', n); + + // If this stringify fails because of a circular structure, even the one in events.emit would. + // So it does not make sense to try/catch this. + if( data ) this.set('progress_data', JSON.stringify(data)); + + this.set('updated_at', Date.now()); + this.refreshTtl(); + events.emit(this.id, 'progress', n, data); + return this; + }; -Job.prototype._getBackoffImpl = function() { - var self = this - var supported_backoffs = { - fixed: function( delay ) { - return function( attempts ) { - return delay; - }; + /** + * Set the job delay in `ms`. + * + * @param {Number|Date} delay in ms or execution date + * @return {Job|Number} + * @api public + */ + + Job.prototype.delay = function( ms ) { + if( 0 == arguments.length ) return this._delay; + if( _.isDate(ms) ) { + ms = parseInt(ms.getTime() - Date.now()) } - , exponential: function( delay ) { - return function( attempts ) { - return Math.round(Math.min( - (self._backoff.maxDelay || Infinity) - (delay * 0.5), - delay * 0.5 * ( Math.pow(2, attempts) - 1) - )); - }; + if( ms > 0 ) { + this._delay = ms; } + return this; }; - if( _.isPlainObject(this._backoff) ) { - return supported_backoffs[ this._backoff.type ](this._backoff.delay || this._delay); - } else { - return this._backoff; - } -}; - -/** - * Set or get the priority `level`, which is one - * of "low", "normal", "medium", and "high", or - * a number in the range of -10..10. - * - * @param {String|Number} level - * @return {Job|Number} for chaining - * @api public - */ -Job.prototype.priority = function( level ) { - if( 0 == arguments.length ) return this._priority; - this._priority = null == priorities[ level ] - ? level - : priorities[ level ]; - return this; -}; - -/** - * Increment attempts, invoking callback `fn(remaining, attempts, max)`. - * - * @param {Function} fn - * @return {Job} for chaining - * @api public - */ - -Job.prototype.attempt = function( fn ) { - var client = this.client - , id = this.id - , key = client.getKey('job:' + id); + /** + * Sets the jobEvents flag for the job. + * 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 + */ + Job.prototype.events = function (events) { + this._jobEvents = !!events; + return this; + }; - this._attempts = this._attempts || 0; - if( this._attempts < this._max_attempts ) { - client.hincrby(key, 'attempts', 1, function( err, attempts ) { - this._attempts = attempts; - fn(err, Math.max(0, this._max_attempts - attempts), attempts, this._max_attempts); - }.bind(this)); - } else { - fn(null, 0, this._attempts, this._max_attempts); - } - return this; -}; + Job.prototype.removeOnComplete = function( param ) { + if( 0 == arguments.length ) return this._removeOnComplete; + this._removeOnComplete = param; + return this; + }; + Job.prototype.backoff = function( param ) { + if( 0 == arguments.length ) return this._backoff; + this._backoff = param; + return this; + }; -/** - * Try to reattempt the job seand called onFailedAttempt, or call onFailed - * @param remaining total left attempts - * @param attempts - * @param onFailedAttempt - * @param onFailed - * @param clbk - */ + /** + * + * @param param + * @returns {*} + */ + Job.prototype.ttl = function( param ) { + if( 0 == arguments.length ) return this._ttl; + if( param > 0 ) { + this._ttl = param; + } + return this; + }; -Job.prototype.reattempt = function( attempts, clbk ) { - clbk = clbk || noop; - if( this.backoff() ) { - var delay = this.delay(); - if( _.isFunction(this._getBackoffImpl()) ) { - try { - delay = this._getBackoffImpl().apply(this, [ attempts, delay ]); - } catch(e) { - clbk(e); + Job.prototype._getBackoffImpl = function() { + var self = this + var supported_backoffs = { + fixed: function( delay ) { + return function( attempts ) { + return delay; + }; } + , exponential: function( delay ) { + return function( attempts ) { + return Math.round(Math.min( + (self._backoff.maxDelay || Infinity) - (delay * 0.5), + delay * 0.5 * ( Math.pow(2, attempts) - 1) + )); + }; + } + }; + if( _.isPlainObject(this._backoff) ) { + return supported_backoffs[ this._backoff.type ](this._backoff.delay || this._delay); + } else { + return this._backoff; } - var self = this; - this.delay(delay).update(function( err ) { - if( err ) return clbk(err); - self.delayed(clbk); - }); - } else { - this.inactive(clbk); - } -}; + }; -/** - * Set max attempts to `n`. - * - * @param {Number} n - * @return {Job} for chaining - * @api public - */ + /** + * Set or get the priority `level`, which is one + * of "low", "normal", "medium", and "high", or + * a number in the range of -10..10. + * + * @param {String|Number} level + * @return {Job|Number} for chaining + * @api public + */ + + Job.prototype.priority = function( level ) { + if( 0 == arguments.length ) return this._priority; + this._priority = null == priorities[ level ] + ? level + : priorities[ level ]; + return this; + }; -Job.prototype.attempts = function( n ) { - this._max_attempts = n; - return this; -}; + /** + * Increment attempts, invoking callback `fn(remaining, attempts, max)`. + * + * @param {Function} fn + * @return {Job} for chaining + * @api public + */ + + Job.prototype.attempt = function( fn ) { + var client = this.client + , id = this.id + , key = client.getKey('job:' + id); + + this._attempts = this._attempts || 0; + if( this._attempts < this._max_attempts ) { + client.hincrby(key, 'attempts', 1, function( err, attempts ) { + this._attempts = attempts; + fn(err, Math.max(0, this._max_attempts - attempts), attempts, this._max_attempts); + }.bind(this)); + } else { + fn(null, 0, this._attempts, this._max_attempts); + } + return this; + }; -Job.prototype.failedAttempt = function( theErr, fn ) { - this.error(theErr).failed(function() { - this.attempt(function( error, remaining, attempts/*, max*/ ) { - if( error ) { - this.emit( 'error', error ); - return fn && fn( error ); - } - if( remaining > 0 ) { - this.reattempt(attempts, function( err ) { - if( err ) { - this.emit( 'error', err ); - return fn && fn( err ); - } - fn && fn( err, true, attempts ); - }.bind(this)); - } else if( remaining === 0 ) { - fn && fn( null, false, attempts ); - } else { - fn && fn( new Error('Attempts Exceeded') ); + /** + * Try to reattempt the job seand called onFailedAttempt, or call onFailed + * @param remaining total left attempts + * @param attempts + * @param onFailedAttempt + * @param onFailed + * @param clbk + */ + + Job.prototype.reattempt = function( attempts, clbk ) { + clbk = clbk || noop; + if( this.backoff() ) { + var delay = this.delay(); + if( _.isFunction(this._getBackoffImpl()) ) { + try { + delay = this._getBackoffImpl().apply(this, [ attempts, delay ]); + } catch(e) { + clbk(e); + } } - }.bind(this)); - }.bind(this)); - return this; -}; - -Job.prototype.searchKeys = function( keys ) { - if( 0 == arguments.length ) return this._searchKeys; - this._searchKeys = keys || []; - if( !_.isArray(this._searchKeys) ) { - this._searchKeys = [ this._searchKeys ]; - } - return this; -}; + var self = this; + this.delay(delay).update(function( err ) { + if( err ) return clbk(err); + self.delayed(clbk); + }); + } else { + this.inactive(clbk); + } + }; -/** - * Remove the job and callback `fn(err)`. - * - * @param {Function} fn - * @return {Job} for chaining - * @api public - */ + /** + * Set max attempts to `n`. + * + * @param {Number} n + * @return {Job} for chaining + * @api public + */ -Job.prototype.remove = function( fn ) { - var client = this.client; - client.multi() - .zrem(client.getKey('jobs:' + this.state()), this.zid) - .zrem(client.getKey('jobs:' + this.type + ':' + this.state()), this.zid) - .zrem(client.getKey('jobs'), this.zid) - .del(client.getKey('job:' + this.id + ':log')) - .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); - } else { - fn && fn(err); - } + Job.prototype.attempts = function( n ) { + this._max_attempts = n; + return this; + }; + + + Job.prototype.failedAttempt = function( theErr, fn ) { + this.error(theErr).failed(function() { + this.attempt(function( error, remaining, attempts/*, max*/ ) { + if( error ) { + this.emit( 'error', error ); + return fn && fn( error ); + } + if( remaining > 0 ) { + this.reattempt(attempts, function( err ) { + if( err ) { + this.emit( 'error', err ); + return fn && fn( err ); + } + fn && fn( err, true, attempts ); + }.bind(this)); + } else if( remaining === 0 ) { + fn && fn( null, false, attempts ); + } else { + fn && fn( new Error('Attempts Exceeded') ); + } + }.bind(this)); }.bind(this)); - return this; -}; + return this; + }; -/** - * Set state to `state`. - * - * @param {String} state - * @param fn - * @return {Job} for chaining - * @api public - */ -Job.prototype.state = function( state, fn ) { - if( 0 == arguments.length ) return this._state; - var client = this.client - , fn = fn || noop; - var oldState = this._state; - var multi = client.multi(); - if( oldState && oldState != '' && oldState != state ) { - multi - .zrem(client.getKey('jobs:' + oldState), this.zid) - .zrem(client.getKey('jobs:' + this.type + ':' + oldState), this.zid); - } - multi - .hset(client.getKey('job:' + this.id), 'state', state) - .zadd(client.getKey('jobs:' + state), this._priority, this.zid) - .zadd(client.getKey('jobs:' + this.type + ':' + state), this._priority, this.zid); - - // use promote_at as score when job moves to delayed - ('delayed' === state) ? multi.zadd(client.getKey('jobs:' + state), parseInt(this.promote_at), this.zid) : noop(); - ('active' === state && this._ttl > 0) ? multi.zadd(client.getKey('jobs:' + state), Date.now() + parseInt(this._ttl), this.zid) : noop(); - ('active' === state && !this._ttl) ? multi.zadd(client.getKey('jobs:' + state), this._priority<0?this._priority:-this._priority, this.zid) : noop(); - ('inactive' === state) ? multi.lpush(client.getKey(this.type + ':jobs'), 1) : noop(); - - this.set('updated_at', Date.now()); - this._state = state; - multi.exec(function( err, replies ) { - if( !err ) { - (this._state === 'inactive') ? events.emit(this.id, 'enqueue', this.type) : noop(); + Job.prototype.searchKeys = function( keys ) { + if( 0 == arguments.length ) return this._searchKeys; + this._searchKeys = keys || []; + if( !_.isArray(this._searchKeys) ) { + this._searchKeys = [ this._searchKeys ]; } - return fn(err); - }.bind(this)); - return this; -}; + return this; + }; -/** - * Set the job's failure `err`. - * - * @param {Error} err - * @return {Job} for chaining - * @api public - */ + /** + * Remove the job and callback `fn(err)`. + * + * @param {Function} fn + * @return {Job} for chaining + * @api public + */ + + Job.prototype.remove = function( fn ) { + var client = this.client; + client.multi() + .zrem(client.getKey('jobs:' + this.state()), this.zid) + .zrem(client.getKey('jobs:' + this.type + ':' + this.state()), this.zid) + .zrem(client.getKey('jobs'), this.zid) + .del(client.getKey('job:' + this.id + ':log')) + .del(client.getKey('job:' + this.id)) + .exec(function( err ) { + // events.remove(this); + events.emit(this.id, 'remove', this.type); + if( !self.disableSearch ) { + getSearch().remove(this.id, fn); + } else { + fn && fn(err); + } + }.bind(this)); + return this; + }; -Job.prototype.error = function( err ) { - var str, summary; - if( 0 == arguments.length ) return this._error; - - if( 'string' == typeof err ) { - str = err; - summary = ''; - } else { - if( err.stack && 'string' === typeof err.stack ) { - str = err.stack - } else { //TODO what happens to CallSite[] err.stack? - str = err.message + /** + * Set state to `state`. + * + * @param {String} state + * @param fn + * @return {Job} for chaining + * @api public + */ + Job.prototype.state = function( state, fn ) { + if( 0 == arguments.length ) return this._state; + var client = this.client + , fn = fn || noop; + var oldState = this._state; + var multi = client.multi(); + if( oldState && oldState != '' && oldState != state ) { + multi + .zrem(client.getKey('jobs:' + oldState), this.zid) + .zrem(client.getKey('jobs:' + this.type + ':' + oldState), this.zid); } - summary = ('string' === typeof str) ? str.split('\n')[ 0 ] : ''; - } - this.set('error', str); - this.log('%s', summary); - events.emit(this.id, 'error', str); - return this; -}; + multi + .hset(client.getKey('job:' + this.id), 'state', state) + .zadd(client.getKey('jobs:' + state), this._priority, this.zid) + .zadd(client.getKey('jobs:' + this.type + ':' + state), this._priority, this.zid); + + // use promote_at as score when job moves to delayed + ('delayed' === state) ? multi.zadd(client.getKey('jobs:' + state), parseInt(this.promote_at), this.zid) : noop(); + ('active' === state && this._ttl > 0) ? multi.zadd(client.getKey('jobs:' + state), Date.now() + parseInt(this._ttl), this.zid) : noop(); + ('active' === state && !this._ttl) ? multi.zadd(client.getKey('jobs:' + state), this._priority<0?this._priority:-this._priority, this.zid) : noop(); + ('inactive' === state) ? multi.lpush(client.getKey(this.type + ':jobs'), 1) : noop(); + + this.set('updated_at', Date.now()); + this._state = state; + multi.exec(function( err, replies ) { + if( !err ) { + (this._state === 'inactive') ? events.emit(this.id, 'enqueue', this.type) : noop(); + } + return fn(err); + }.bind(this)); + return this; + }; -/** - * Set state to "complete", and progress to 100%. - */ + /** + * Set the job's failure `err`. + * + * @param {Error} err + * @return {Job} for chaining + * @api public + */ + + Job.prototype.error = function( err ) { + var str, summary; + if( 0 == arguments.length ) return this._error; + + if( 'string' == typeof err ) { + str = err; + summary = ''; + } else { + if( err.stack && 'string' === typeof err.stack ) { + str = err.stack + } else { //TODO what happens to CallSite[] err.stack? + str = err.message + } + summary = ('string' === typeof str) ? str.split('\n')[ 0 ] : ''; + } + this.set('error', str); + this.log('%s', summary); + events.emit(this.id, 'error', str); + return this; + }; -Job.prototype.complete = function( clbk ) { - return this.set('progress', 100).state('complete', clbk); -}; + /** + * Set state to "complete", and progress to 100%. + */ -/** - * Set state to "failed". - */ + Job.prototype.complete = function( clbk ) { + return this.set('progress', 100).state('complete', clbk); + }; -Job.prototype.failed = function( clbk ) { - this.failed_at = Date.now(); - return this.set('failed_at', this.failed_at).state('failed', clbk); -}; + /** + * Set state to "failed". + */ -/** - * Set state to "inactive". - */ + Job.prototype.failed = function( clbk ) { + this.failed_at = Date.now(); + return this.set('failed_at', this.failed_at).state('failed', clbk); + }; -Job.prototype.inactive = function( clbk ) { - return this.state('inactive', clbk); -}; + /** + * Set state to "inactive". + */ -/** - * Set state to "active". - */ + Job.prototype.inactive = function( clbk ) { + return this.state('inactive', clbk); + }; -Job.prototype.active = function( clbk ) { - return this.state('active', clbk); -}; + /** + * Set state to "active". + */ -/** - * Set state to "delayed". - */ + Job.prototype.active = function( clbk ) { + return this.state('active', clbk); + }; -Job.prototype.delayed = function( clbk ) { - return this.state('delayed', clbk); -}; + /** + * Set state to "delayed". + */ -/** - * Save the job, optionally invoking the callback `fn(err)`. - * - * @param {Function} fn - * @return {Job} for chaining - * @api public - */ + Job.prototype.delayed = function( clbk ) { + return this.state('delayed', clbk); + }; -Job.prototype.save = function( fn ) { - var client = this.client - , fn = fn || noop - , max = this._max_attempts - , self = this; - - // update - if( this.id ) return this.update(fn); - - // incr id - client.incr(client.getKey('ids'), function( err, id ) { - if( err ) return fn(err); - // add the job for event mapping - self.id = id; - self.zid = client.createFIFO(id); - self.subscribe(function() { - self._state = self._state || (this._delay ? 'delayed' : 'inactive'); - if( max ) { self.set('max_attempts', max); } - client.sadd(client.getKey('job:types'), self.type, noop); - self.set('type', self.type); - var now = Date.now(); - self.created_at = now; - self.set('created_at', self.created_at); - self.promote_at = now + (self._delay || 0); - self.set('promote_at', self.promote_at); - self.update(fn); + /** + * Save the job, optionally invoking the callback `fn(err)`. + * + * @param {Function} fn + * @return {Job} for chaining + * @api public + */ + + Job.prototype.save = function( fn ) { + var client = this.client + , fn = fn || noop + , max = this._max_attempts + , self = this; + // update + if( this.id ) return this.update(fn); + + // incr id + client.incr(client.getKey('ids'), function( err, id ) { + if( err ) return fn(err); + // add the job for event mapping + self.id = id; + self.zid = client.createFIFO(id); + self.subscribe(function() { + self._state = self._state || (this._delay ? 'delayed' : 'inactive'); + if( max ) { self.set('max_attempts', max); } + client.sadd(client.getKey('job:types'), self.type, noop); + self.set('type', self.type); + var now = Date.now(); + self.created_at = now; + self.set('created_at', self.created_at); + self.promote_at = now + (self._delay || 0); + self.set('promote_at', self.promote_at); + self.update(fn); + }.bind(this)); }.bind(this)); - }.bind(this)); - return this; -}; + return this; + }; -/** - * Update the job and callback `fn(err)`. - * - * @param {Function} fn - * @api public - */ + /** + * Update the job and callback `fn(err)`. + * + * @param {Function} fn + * @api public + */ -Job.prototype.update = function( fn ) { - var json; + Job.prototype.update = function( fn ) { + var json; - // serialize json data - try { - json = JSON.stringify(this.data); - } catch(err) { - fn(err); - return this; - } + // serialize json data + try { + json = JSON.stringify(this.data); + } catch(err) { + fn(err); + return this; + } - // delay - if( this._delay ) { - this.set('delay', this._delay); - if( this.created_at ) { - var timestamp = parseInt(this.failed_at || this.created_at, 10) - , delay = parseInt(this._delay); - this.promote_at = timestamp + delay; - this.set('promote_at', this.promote_at); + // delay + if( this._delay ) { + this.set('delay', this._delay); + if( this.created_at ) { + var timestamp = parseInt(this.failed_at || this.created_at, 10) + , delay = parseInt(this._delay); + this.promote_at = timestamp + delay; + this.set('promote_at', this.promote_at); + } + } + if( this._ttl ) { + this.set('ttl', this._ttl); + } + if( this._removeOnComplete ) this.set('removeOnComplete', this._removeOnComplete); + if( this._backoff ) { + if( _.isPlainObject(this._backoff) ) this.set('backoff', JSON.stringify(this._backoff)); + else this.set('backoff', this._backoff.toString()); } - } - if( this._ttl ) { - this.set('ttl', this._ttl); - } - if( this._removeOnComplete ) this.set('removeOnComplete', this._removeOnComplete); - if( this._backoff ) { - if( _.isPlainObject(this._backoff) ) this.set('backoff', JSON.stringify(this._backoff)); - else this.set('backoff', this._backoff.toString()); - } - // updated timestamp - this.set('updated_at', Date.now()); - this.refreshTtl(); + // updated timestamp + this.set('updated_at', Date.now()); + this.refreshTtl(); - // priority - this.set('priority', this._priority); + // priority + this.set('priority', this._priority); - this.client.zadd(this.client.getKey('jobs'), this._priority, this.zid, noop); + this.client.zadd(this.client.getKey('jobs'), this._priority, this.zid, noop); - // data - this.set('data', json, function() { - // state - this.state(this._state, fn); - }.bind(this)); + // data + this.set('data', json, function() { + // state + this.state(this._state, fn); + }.bind(this)); - if( !exports.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); - }.bind(this)); + if( !self.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); + }.bind(this)); + } else { + getSearch().index(json, this.id); + } + } + return this; + }; + + /** + * Subscribe this job for event mapping. + * + * @return {Job} for chaining + * @api public + */ + + Job.prototype.subscribe = function( callback ) { + if( this._jobEvents ) { + events.add(this, callback); } else { - getSearch().index(json, this.id); + callback && callback(); } - } - return this; -}; + return this; + }; -/** - * Subscribe this job for event mapping. - * - * @return {Job} for chaining - * @api public - */ + self.Job = Job -Job.prototype.subscribe = function( callback ) { - if( this._jobEvents ) { - events.add(this, callback); - } else { - callback && callback(); - } - return this; -}; + return self +} diff --git a/lib/queue/test_mode.js b/lib/queue/test_mode.js index efeefbdc..2636007b 100644 --- a/lib/queue/test_mode.js +++ b/lib/queue/test_mode.js @@ -1,64 +1,65 @@ -var Job = require('./job'), - _ = require('lodash'); +var _ = require('lodash'); -var originalJobSave = Job.prototype.save, - originalJobUpdate = Job.prototype.update, - processQueue, - jobs; +module.exports = function(Job){ -function testJobSave( fn ) { - if(processQueue) { - jobs.push(this); - originalJobSave.call(this, fn); - } else { - this.id = _.uniqueId(); - jobs.push(this); - if( _.isFunction(fn) ) fn(); - } -}; + var originalJobSave = Job.prototype.save, + originalJobUpdate = Job.prototype.update, + processQueue = false, + jobs = []; -function testJobUpdate( fn ) { - if(processQueue) { - originalJobUpdate.call(this, fn); - } else { - if( _.isFunction(fn) ) fn(); - } -}; + function testJobSave( fn ) { + if(processQueue) { + jobs.push(this); + originalJobSave.call(this, fn); + } else { + this.id = _.uniqueId(); + jobs.push(this); + if( _.isFunction(fn) ) fn(); + } + }; -/** - * Array of jobs added to the queue - * @api public - */ + function testJobUpdate( fn ) { + if(processQueue) { + originalJobUpdate.call(this, fn); + } else { + if( _.isFunction(fn) ) fn(); + } + }; -module.exports.jobs = jobs = []; -module.exports.processQueue = processQueue = false; + var testMode = {} -/** - * Enable test mode. - * @api public - */ + /** + * Enable test mode. + * @api public + */ -module.exports.enter = function(process) { - processQueue = process || false; - Job.prototype.save = testJobSave; - Job.prototype.update = testJobUpdate; -}; + testMode.enter = function(process) { + processQueue = process || false; + Job.prototype.save = testJobSave; + Job.prototype.update = testJobUpdate; + }; -/** - * Disable test mode. - * @api public - */ + /** + * Disable test mode. + * @api public + */ -module.exports.exit = function() { - Job.prototype.save = originalJobSave; - Job.prototype.update = originalJobUpdate; -}; + testMode.exit = function() { + Job.prototype.save = originalJobSave; + Job.prototype.update = originalJobUpdate; + }; -/** - * Clear the array of queued jobs - * @api public - */ + /** + * Clear the array of queued jobs + * @api public + */ -module.exports.clear = function() { - jobs.length = 0; -}; + testMode.clear = function() { + jobs.length = 0; + }; + + testMode.jobs = jobs + testMode.processQueue = processQueue + + return testMode +} diff --git a/lib/queue/worker.js b/lib/queue/worker.js index 2213dfcf..70213df1 100644 --- a/lib/queue/worker.js +++ b/lib/queue/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/lib/redis.js b/lib/redis.js index a77ff9ba..8a6c1534 100644 --- 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/package-lock.json b/package-lock.json new file mode 100644 index 00000000..7178d6cf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1833 @@ +{ + "name": "kue", + "version": "0.11.5", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@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=" + }, + "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 + }, + "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" + } + }, + "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.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "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.0.6", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.6.tgz", + "integrity": "sha1-Cr81atANHFohnYjURRgEbdAmrP4=", + "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": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "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.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "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": "1.0.0-rc3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz", + "integrity": "sha1-01vGLn+8KTeuePlIqqDTjZBgdXc=", + "dev": true, + "requires": { + "async": "^1.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.3" + } + }, + "formatio": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "dev": true, + "requires": { + "samsam": "~1.1" + } + }, + "formidable": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.16.tgz", + "integrity": "sha1-SRbP38TL7QILJXpqlQWpqzjCzQ4=", + "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.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "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" + } + }, + "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": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "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" + } + }, + "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=" + }, + "lolex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", + "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "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": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "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" + } + }, + "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" + } + }, + "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": "1.0.27-1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz", + "integrity": "sha1-a2eYPCA1fO/QfwFlABoW1xDZEHg=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "redis": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.6.5.tgz", + "integrity": "sha1-h8Hv9KSJ+Utwhx89CLaYjyOpVoc=", + "requires": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.0.0" + } + }, + "redis-commands": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", + "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" + }, + "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 + } + } + }, + "reduce-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz", + "integrity": "sha1-4Mk1QsV0UhvqE98PlIjtgqt3xdo=", + "dev": 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==" + }, + "samsam": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", + "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "dev": true + }, + "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": "3.3.2", + "resolved": "https://registry.npmjs.org/should/-/should-3.3.2.tgz", + "integrity": "sha1-yIPdQJtTu98bVewNj8OGXysofmQ=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "sinon": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", + "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "dev": true, + "requires": { + "formatio": "1.1.1", + "lolex": "1.3.2", + "samsam": "1.1.2", + "util": ">=0.10.3 <1" + } + }, + "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": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "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": "1.8.5", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-1.8.5.tgz", + "integrity": "sha1-HA3cOvMOgOuE68BcshItqP6UC1U=", + "dev": true, + "requires": { + "component-emitter": "~1.2.0", + "cookiejar": "2.0.6", + "debug": "2", + "extend": "3.0.0", + "form-data": "1.0.0-rc3", + "formidable": "~1.0.14", + "methods": "~1.1.1", + "mime": "1.3.4", + "qs": "2.3.3", + "readable-stream": "1.0.27-1", + "reduce-component": "1.0.1" + }, + "dependencies": { + "extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=", + "dev": true + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", + "dev": true + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "dev": true + } + } + }, + "supertest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-1.2.0.tgz", + "integrity": "sha1-hQp5X5Bo0vrxngF5n/CZYuDOQ74=", + "dev": true, + "requires": { + "methods": "1.x", + "superagent": "^1.7.2" + } + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + }, + "sylvester": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/sylvester/-/sylvester-0.0.21.tgz", + "integrity": "sha1-KYexzivS84sNzio0OIiEv6RADqc=" + }, + "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=" + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, + "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": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "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/test/jsonapi.js b/test/jsonapi.js index 5ce536c3..57dbf5d2 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'; - - +console.log(queue._options) expect = chai.expect; @@ -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 ) { diff --git a/test/mocha.opts b/test/mocha.opts old mode 100755 new mode 100644 index 02c0dc43..6a3f4981 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -2,4 +2,4 @@ --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..30144840 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,7 +157,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', "inactive" job.should.have.property '_attempts', "1" job.should.have.property '_error', "Shutdown" diff --git a/test/tdd/kue.spec.js b/test/tdd/kue.spec.js index 2a3cb054..0452b6e8 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 _ = 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', 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) {