├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── timed-queue.png └── timed-tasks-on-redis.md ├── examples └── simple.js ├── index.js ├── package-lock.json ├── package.json ├── queue.lua └── test ├── index.js └── performance.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | debug 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | - "12" 6 | cache: 7 | directories: 8 | - node_modules 9 | sudo: false 10 | services: 11 | - redis-server 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2019 teambition 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # timed-queue 2 | 3 | Distributed timed job queue, backed by redis. 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Build Status][travis-image]][travis-url] 7 | [![Downloads][downloads-image]][downloads-url] 8 | 9 | ![timed-queue](https://raw.githubusercontent.com/teambition/timed-queue/master/docs/timed-queue.png) 10 | 11 | ## Features 12 | 13 | - Support redis cluster. 14 | - Support one or more `timed-queue` instance in a redis instance. Each `timed-queue` instance are segregated by `prefix` option 15 | - Support one or more job queues in a timed-queue instance. 16 | - Support one or more timed-queue clients for a timed-queue instance. 17 | 18 | ## Demo 19 | 20 | ```js 21 | const TimedQueue = require('timed-queue') 22 | const timedQueue = new TimedQueue({prefix: 'TQ1', interval: 1000 * 60}) 23 | 24 | 25 | // connect to redis cluster. 26 | timedQueue.connect([7000, 7001, 7002]) 27 | .on('error', function (error) { 28 | console.error(error) 29 | }) 30 | 31 | 32 | // create 'event' job queue in timed-queue instance 33 | const eventQueue = timedQueue.queue('event') 34 | 35 | // add 'job' listener 36 | eventQueue.on('job', function (jobObj) { 37 | // ... just do some thing 38 | // ACK the job 39 | eventQueue.ackjob(jobObj.job)() 40 | }) 41 | 42 | // add job to queue 43 | eventQueue.addjob(eventObj.id, new Date(eventObj.startDate).getTime() - 10 * 60 * 1000)(function (err, res) { 44 | console.log(err, res) 45 | }) 46 | ``` 47 | 48 | 49 | ## Installation 50 | 51 | ```bash 52 | npm install timed-queue 53 | ``` 54 | 55 | ## Job 56 | 57 | `Job` Class: 58 | 59 | ```js 60 | function Job (queue, job, timing, active, retryCount) { 61 | this.queue = queue 62 | this.job = job 63 | this.timing = timing 64 | this.active = active 65 | this.retryCount = retryCount 66 | } 67 | ``` 68 | 69 | - `this.queue`: {String} Queue name 70 | - `this.job`: {String} The job's name 71 | - `this.timing`: {Number} The time in millisecond when the job should be actived 72 | - `this.active`: {Number} The actual time in millisecond that the job be actived 73 | - `this.retryCount`: {Number} A job that has been actived but has not been ACK in `retry` time will be actived again. `retryCount` is times that the job re-actived. 74 | 75 | ## API 76 | 77 | ```js 78 | const TimedQueue = require('timed-queue') 79 | ``` 80 | 81 | ### new TimedQueue([options]) => `timedQueue` object 82 | 83 | Return a `timedQueue` client. It is an EventEmitter instance. 84 | 85 | - `options.prefix`: {String} Redis key's prefix, or namespace. Default to `"TIMEDQ"` 86 | - `options.count`: {Number} The maximum job count for queue's `getjobs` method. Default to `64` 87 | - `options.interval`: {Number} Interval time for scanning. Default to `1000 * 60` ms 88 | - `options.retry`: {Number} Retry time for a job. A job that has been actived but has not been ACK in `retry` time will be actived again. Default to `interval / 2` ms 89 | - `options.expire`: {Number} Expiration time for a job. A job that has been actived and has not been ACK in `expire` time will be removed from the queue. Default to `interval * 5` ms 90 | - `options.accuracy`: {Number} Scanning accuracy. Default to `interval / 5` 91 | - `options.autoScan`: {Boolean} The flag to enable or disable automatic scan. Default to `true`. It can be set to `false` if automatic scan is not desired. 92 | 93 | ```js 94 | const timedQueue = new TimedQueue() 95 | ``` 96 | 97 | #### TimedQueue Events 98 | 99 | - timedQueue.on('connect', function () {}) 100 | - timedQueue.on('error', function (error) {}) 101 | - timedQueue.on('close', function () {}) 102 | - timedQueue.on('scanStart', function (queuesLength) {}) 103 | - timedQueue.on('scanEnd', function (queuesLength, timeConsuming) {}) 104 | 105 | ### TimedQueue.prototype.connect([host, options]) => `this` 106 | 107 | ### TimedQueue.prototype.connect(redisClient) => `this` 108 | 109 | Connect to redis. Arguments are the same as [thunk-redis](https://github.com/thunks/thunk-redis)'s `createClient`, or give a thunk-redis instance. 110 | 111 | ```js 112 | timedQueue.connect() 113 | ``` 114 | 115 | ### TimedQueue.prototype.scan() => `this` 116 | 117 | Start scanning. It automatically starts after `connect` method is called unless `autoScan` is set to `false`. 118 | 119 | ### TimedQueue.prototype.stop() => `this` 120 | 121 | Stop scanning. 122 | 123 | ### TimedQueue.prototype.close() => `this` 124 | 125 | Close the `timedQueue`. It closes redis client of the `timedQueue` accordingly. 126 | 127 | ### TimedQueue.prototype.regulateFreq(factor) => `this` 128 | 129 | It is used to regulate the automatic scanning frequency. 130 | 131 | ### TimedQueue.prototype.destroyQueue(queue[, options]) => `this` 132 | 133 | Remove the queue. It deletes all data in the queue from redis. 134 | 135 | ### TimedQueue.prototype.queue(queue[, options]) => `Queue` instance 136 | 137 | Return a `Queue` instance if one exists. Otherwise it creates a `Queue` instance and return it. `Queue` instance is a EventEmitter instance. 138 | 139 | - `queue`: {String} The queue's name 140 | - `options.count`: {Number} The maximum job count for queue's `getjobs` method. Default to timedQueue's `count` 141 | - `options.retry`: {Number} Retry time for a job. A job that has been actived and has not been ACK in `retry` time will be actived again. Default to timedQueue's `retry` 142 | - `options.expire`: {Number} Expiration time for job. A job that has been actived and has not been ACK in `expire` time will be removed from the queue. Default to timedQueue's `expire` 143 | - `options.accuracy`: {Number} Scanning accuracy, Default to timedQueue's `accuracy` 144 | 145 | ```js 146 | const eventQueue = timedQueue.queue('event', {retry: 1000, expire: 5000}) 147 | ``` 148 | 149 | #### Queue Events 150 | 151 | - queue.on('job', function (job) {}) 152 | 153 | If no `job` listener on queue, queue scanning will not run. 154 | 155 | ### Queue.prototype.init([options]) => `this` 156 | 157 | - `options.count`: {Number} The maximum job count for queue's `getjobs` method. Default to timedQueue's `count` 158 | - `options.retry`: {Number} Retry time for a job. A job that has been actived and has not been ACK in `retry` time will be actived again. Default to timedQueue's `retry` 159 | - `options.expire`: {Number} Expire time for a job. A job that has been actived and has not been ACK in `expire` time will be removed from queue. Default to timedQueue's `expire` 160 | - `options.accuracy`: {Number} Scanning accuracy. Default to timedQueue's `accuracy` 161 | 162 | ### Queue.prototype.addjob(job, timing[, job, timing, ...]) => `thunk` function 163 | 164 | ### Queue.prototype.addjob([job, timing, job, timing, ...]) => `thunk` function 165 | 166 | Add one or more jobs to the queue. It can be used to update the job's timing. 167 | 168 | - `job`: {String} The job's name 169 | - `timing`: {Number} The time in millisecond when the job should be actived. It should greater than `Date.now()` 170 | 171 | ```js 172 | eventQueue.addjob('52b3b5f49c2238313600015d', 1441552050409)(function (err, res) { 173 | console.log(err, res) 174 | // null, 1 175 | }) 176 | ``` 177 | 178 | ### Queue.prototype.show(job) => `thunk` function 179 | 180 | Show the job info. 181 | 182 | - `job`: {String} job 183 | 184 | ```js 185 | eventQueue.show('52b3b5f49c2238313600015d')(function (err, res) { 186 | console.log(err, res) 187 | // { 188 | // queue: 'event', 189 | // job: '52b3b5f49c2238313600015d', 190 | // timing: 1441552050409 191 | // active: 0, 192 | // retryCount: 0 193 | // } 194 | }) 195 | ``` 196 | 197 | ### Queue.prototype.deljob(job[, job, ...]) => `thunk` function 198 | 199 | ### Queue.prototype.deljob([job, job, ...]) => `thunk` function 200 | 201 | Delete one or more jobs. 202 | 203 | - `job`: {String} job 204 | 205 | ```js 206 | eventQueue.deljob('52b3b5f49c2238313600015d')(function (err, res) { 207 | console.log(err, res) // null, 1 208 | }) 209 | ``` 210 | 211 | ### Queue.prototype.getjobs([scanActive]) => `thunk` function 212 | 213 | It is called by `Queue.prototype.scan`. It should not be called explicitly unless you know what you are doing. 214 | 215 | ### Queue.prototype.ackjob(job[, job, ...]) => `thunk` function 216 | ### Queue.prototype.ackjob([job, job, ...]) => `thunk` function 217 | 218 | ACK one or more jobs. 219 | 220 | - `job`: {String} job 221 | 222 | ```js 223 | eventQueue.ackjob('52b3b5f49c2238313600015d')(function (err, res) { 224 | console.log(err, res) // null, 1 225 | }) 226 | ``` 227 | 228 | ### Queue.prototype.scan() => `thunk` function 229 | 230 | It is called by `TimedQueue.prototype.scan`. It should not be called explicitly unless you know what you are doing. 231 | 232 | ### Queue.prototype.len() => `thunk` function 233 | 234 | Return the queue' length. 235 | 236 | ```js 237 | eventQueue.len()(function (err, res) { 238 | console.log(err, res) // null, 3 239 | }) 240 | ``` 241 | 242 | ### Queue.prototype.showActive() => `thunk` function 243 | 244 | Return actived jobs in the queue. 245 | 246 | ```js 247 | eventQueue.showActive()(function (err, res) { 248 | console.log(err, res) // null, [jobs...] 249 | }) 250 | ``` 251 | 252 | [npm-url]: https://npmjs.org/package/timed-queue 253 | [npm-image]: http://img.shields.io/npm/v/timed-queue.svg 254 | 255 | [travis-url]: https://travis-ci.org/teambition/timed-queue 256 | [travis-image]: http://img.shields.io/travis/teambition/timed-queue.svg 257 | 258 | [downloads-url]: https://npmjs.org/package/timed-queue 259 | [downloads-image]: http://img.shields.io/npm/dm/timed-queue.svg?style=flat-square 260 | -------------------------------------------------------------------------------- /docs/timed-queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teambition/timed-queue/46b2739f1eecff61f035336f01588d03eb575f3c/docs/timed-queue.png -------------------------------------------------------------------------------- /docs/timed-tasks-on-redis.md: -------------------------------------------------------------------------------- 1 | 用 redis 实现一个分布式定时任务系统 2 | ==== 3 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // **Github:** https://github.com/teambition/timed-queue 4 | // 5 | // **License:** MIT 6 | 7 | const TimedQueue = require('../index.js') 8 | const timedQueue = new TimedQueue({ prefix: 'TQ', interval: 1000 * 2 }) 9 | const addLog = console.log.bind(console, 'addjob: ') 10 | const ackLog = console.log.bind(console, 'ackjob: ') 11 | 12 | // connect to redis. 13 | timedQueue 14 | .connect() 15 | .on('error', function (error) { 16 | console.error(error) 17 | }) 18 | 19 | // create 'event' job queue in timed-queue instance 20 | const eventQueue = timedQueue.queue('event') 21 | 22 | // add 'job' listener 23 | eventQueue.on('job', function (job) { 24 | console.log('\n', job.job + ' at ' + new Date(job.timing) + ', actived: ' + new Date(job.active), '\n') 25 | this.ackjob(job.job)(ackLog) 26 | if (job.job === 'repeat event') this.addjob(job.job, job.timing + 10 * 1000)(addLog) 27 | }) 28 | 29 | // add job to queue 30 | eventQueue.addjob('repeat event', Date.now() + 5 * 1000)(addLog) 31 | eventQueue.addjob('simple event', Date.now() + 10 * 1000)(addLog) 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // **Github:** https://github.com/teambition/timed-queue 4 | // 5 | // **License:** MIT 6 | 7 | const fs = require('fs') 8 | const path = require('path') 9 | const thunks = require('thunks') 10 | const redis = require('thunk-redis') 11 | const EventEmitter = require('events').EventEmitter 12 | 13 | const thunk = thunks() 14 | const slice = Array.prototype.slice 15 | const luaScript = fs.readFileSync(path.join(__dirname, 'queue.lua'), { encoding: 'utf8' }) 16 | 17 | class TimedQueue extends EventEmitter { 18 | constructor (options) { 19 | super() 20 | options = options || {} 21 | 22 | this.prefix = options.prefix || 'TIMEDQ' 23 | this.queuesKey = '{' + this.prefix + '}:QUEUES' 24 | this.count = Math.floor(options.count) || 64 25 | this.interval = Math.floor(options.interval) || 1000 * 60 26 | this.expire = Math.floor(options.expire) || this.interval * 5 27 | this.retry = Math.floor(options.retry) || Math.floor(this.interval / 2) 28 | this.accuracy = Math.floor(options.accuracy) || Math.floor(this.interval / 5) 29 | this.autoScan = options.autoScan !== false 30 | 31 | this.redis = null 32 | this.timer = null 33 | this.scanTime = 0 34 | this.scanning = false 35 | this.delay = this.interval 36 | this.queues = Object.create(null) 37 | this.thunk = thunks((err) => err && this.emit('error', err)) 38 | } 39 | 40 | connect (redisClient) { 41 | if (this.redis) return this 42 | if (redisClient && redisClient.info && typeof redisClient.evalauto === 'function') { 43 | this.redis = redisClient 44 | } else { 45 | this.redis = redis.createClient.apply(null, arguments) 46 | } 47 | 48 | this.redis.on('connect', () => this.emit('connect')) 49 | .on('error', (err) => this.emit('error', err)) 50 | .on('close', () => this.emit('close')) 51 | 52 | // auto scan jobs 53 | if (this.autoScan) { 54 | thunk.delay(Math.random() * this.interval)(() => this.scan()) 55 | } 56 | return this 57 | } 58 | 59 | queue (queueName, options) { 60 | validateString(queueName) 61 | if (!this.queues[queueName]) this.queues[queueName] = new Queue(this, queueName, options) 62 | else if (options) this.queues[queueName].init(options) 63 | return this.queues[queueName] 64 | } 65 | 66 | destroyQueue (queueName) { 67 | return thunk.call(this, function * () { 68 | let redis = this.redis 69 | let queue = this.queue(queueName) 70 | 71 | delete this.queues[queueName] 72 | yield [ 73 | redis.srem(this.queuesKey, queue.name), 74 | redis.del(queue.queueOptionsKey), 75 | redis.del(queue.activeQueueKey), 76 | redis.del(queue.queueKey) 77 | ] 78 | }) 79 | } 80 | 81 | scan () { 82 | if (this.scanning || !this.redis) return this 83 | 84 | let scanStart 85 | this.scanning = true 86 | this.scanTime = scanStart = Date.now() 87 | 88 | this.thunk(function * () { 89 | let queues = yield this.redis.smembers(this.queuesKey) 90 | queues = queues.filter((queueName) => this.queues[queueName]) 91 | this.emit('scanStart', queues.length) 92 | 93 | let queueScores = yield queues.map((queue) => this.queue(queue).scan()) 94 | this.emit('scanEnd', queueScores.length, Date.now() - scanStart) 95 | this.regulateFreq(scoresDeviation(queueScores)) 96 | 97 | if (!this.timer && this.scanning) { 98 | this.scanning = false 99 | this.scan() 100 | } else this.scanning = false 101 | })() 102 | 103 | clearTimeout(this.timer) 104 | this.timer = setTimeout(() => { 105 | this.timer = null 106 | if (!this.scanning) this.scan() 107 | }, this.delay) 108 | 109 | return this 110 | } 111 | 112 | stop () { 113 | clearTimeout(this.timer) 114 | this.scanning = false 115 | this.timer = null 116 | return this 117 | } 118 | 119 | close () { 120 | this.stop() 121 | this.redis.clientEnd() 122 | this.redis = null 123 | return this 124 | } 125 | 126 | // x > 0 | 2 - 1 / (1 + x) 127 | // x < 0 | 1 / (1 - x) 128 | regulateFreq (factor) { 129 | if (factor < -0.05) this.delay = Math.max(this.delay / (1 - factor), this.interval / 10) 130 | else if (factor > 0.05) this.delay = Math.min(this.delay * (2 - 1 / (1 + factor)), this.interval) 131 | return this 132 | } 133 | } 134 | 135 | class Queue extends EventEmitter { 136 | constructor (timedQueue, queueName, options) { 137 | super() 138 | 139 | this.root = timedQueue 140 | this.name = queueName 141 | this.queueKey = '{' + timedQueue.prefix + ':' + queueName + '}' // hash tag 142 | this.activeQueueKey = this.queueKey + ':-' 143 | this.queueOptionsKey = this.queueKey + ':O' 144 | this.init(options) 145 | } 146 | 147 | init (options) { 148 | let root = this.root 149 | root.thunk(root.redis.sadd(root.queuesKey, this.name))() 150 | 151 | if (!options) return this 152 | root.thunk(root.redis.hmset(this.queueOptionsKey, { 153 | count: options.count || root.count, 154 | retry: options.retry || root.retry, 155 | expire: options.expire || root.expire, 156 | accuracy: options.accuracy || root.accuracy 157 | }))() 158 | return this 159 | } 160 | 161 | addjob (job, timing) { 162 | let args = slice.call(Array.isArray(job) ? job : arguments) 163 | return thunk.call(this, function * () { 164 | let data = [this.queueKey] 165 | let current = Date.now() 166 | 167 | for (let i = 0, l = args.length || 2; i < l; i += 2) { 168 | validateString(args[i]) 169 | timing = Math.floor(args[i + 1]) 170 | if (!timing || timing <= current) throw new Error(`${String(args[i + 1])} is invalid time in "${job}".`) 171 | data.push(timing, args[i]) 172 | } 173 | 174 | return yield this.root.redis.zadd(data) 175 | }) 176 | } 177 | 178 | show (job) { 179 | return thunk.call(this, function * () { 180 | validateString(job) 181 | 182 | let timing = yield this.root.redis.zscore(this.queueKey, job) 183 | if (timing) return new Job(this.name, job, timing, 0, 0) 184 | 185 | let times = yield this.root.redis.hget(this.activeQueueKey, job) 186 | if (!times) return null 187 | times = times.split(':') 188 | return new Job(this.name, job, times[0], times[1], times.length - 2) 189 | }) 190 | } 191 | 192 | deljob (job) { 193 | let args = slice.call(Array.isArray(job) ? job : arguments) 194 | return thunk.call(this, function * () { 195 | for (let i = 0, l = args.length || 1; i < l; i++) validateString(args[i]) 196 | 197 | args.unshift(this.queueKey) 198 | let count = yield this.root.redis.zrem(args) 199 | args[0] = this.activeQueueKey 200 | let _count = yield this.root.redis.hdel(args) 201 | return count + _count 202 | }) 203 | } 204 | 205 | getjobs (scanActive) { 206 | return thunk.call(this, function * () { 207 | let root = this.root 208 | let timestamp = Date.now() 209 | 210 | let res = yield root.redis.evalauto(luaScript, 3, 211 | this.queueKey, this.activeQueueKey, this.queueOptionsKey, 212 | root.count, root.retry, root.expire, root.accuracy, timestamp, +(scanActive !== false)) 213 | return new ScanResult(this.name, timestamp, res) 214 | }) 215 | } 216 | 217 | ackjob (job) { 218 | let args = slice.call(Array.isArray(job) ? job : arguments) 219 | return thunk.call(this, function * () { 220 | for (let i = 0, l = args.length || 1; i < l; i++) validateString(args[i]) 221 | args.unshift(this.activeQueueKey) 222 | return yield this.root.redis.hdel(args) 223 | }) 224 | } 225 | 226 | scan () { 227 | return thunk.call(this, function * () { 228 | let scores = [] 229 | if (!this.listenerCount('job')) { 230 | return // Don't scan if no 'job' listener 231 | } 232 | 233 | let res = yield this.getjobs(true) // get active jobs firstly 234 | while (true) { 235 | res.jobs.map((job) => { 236 | if (!job.retryCount) scores.push((job.timing - job.active) / res.retry) 237 | process.nextTick(() => this.emit('job', job)) 238 | }) 239 | if (!res.hasMore) return scores 240 | res = yield this.getjobs(false) 241 | } 242 | }) 243 | } 244 | 245 | len () { 246 | return thunk.call(this, function * () { 247 | return yield this.root.redis.zcard(this.queueKey) 248 | }) 249 | } 250 | 251 | showActive () { 252 | return thunk.call(this, function * () { 253 | let res = yield this.root.redis.hgetall(this.activeQueueKey) 254 | return Object.keys(res) 255 | .map((job) => { 256 | let times = res[job].split(':') 257 | return new Job(this.name, job, times[0], times[1], times.length - 2) 258 | }).sort((a, b) => a.timing - b.timing) 259 | }) 260 | } 261 | } 262 | 263 | class Job { 264 | constructor (queue, job, timing, active, retryCount) { 265 | this.queue = queue 266 | this.job = job 267 | this.timing = +timing 268 | this.active = +active 269 | this.retryCount = +retryCount 270 | } 271 | } 272 | 273 | function ScanResult (name, timestamp, res) { 274 | let jobs = res[0] 275 | this.retry = +res[1] 276 | this.hasMore = +res[2] 277 | this.jobs = [] 278 | for (let i = 0, l = jobs.length - 2; i < l; i += 3) { 279 | this.jobs.push(new Job(name, jobs[i], jobs[i + 1], timestamp, jobs[i + 2])) 280 | } 281 | } 282 | 283 | function scoresDeviation (queueScores) { 284 | let count = 0 285 | let score = queueScores.reduce((prev, scores) => { 286 | return prev + scores.reduce((p, s) => { 287 | count++ 288 | return p + s 289 | }, 0) 290 | }, 0) 291 | return count ? (score / count) : 0 292 | } 293 | 294 | function validateString (str) { 295 | if (typeof str !== 'string' || !str) throw new TypeError(`${String(str)} is invalid string`) 296 | } 297 | 298 | module.exports = TimedQueue 299 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timed-queue", 3 | "version": "1.4.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "acorn": { 8 | "version": "6.1.1", 9 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", 10 | "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", 11 | "dev": true 12 | }, 13 | "acorn-jsx": { 14 | "version": "5.0.1", 15 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", 16 | "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", 17 | "dev": true 18 | }, 19 | "ajv": { 20 | "version": "6.10.0", 21 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 22 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 23 | "dev": true, 24 | "requires": { 25 | "fast-deep-equal": "^2.0.1", 26 | "fast-json-stable-stringify": "^2.0.0", 27 | "json-schema-traverse": "^0.4.1", 28 | "uri-js": "^4.2.2" 29 | } 30 | }, 31 | "ajv-keywords": { 32 | "version": "3.4.0", 33 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", 34 | "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", 35 | "dev": true 36 | }, 37 | "ansi-escapes": { 38 | "version": "3.2.0", 39 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", 40 | "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", 41 | "dev": true 42 | }, 43 | "ansi-regex": { 44 | "version": "2.1.1", 45 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 46 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 47 | "dev": true 48 | }, 49 | "ansi-styles": { 50 | "version": "2.2.1", 51 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 52 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 53 | "dev": true 54 | }, 55 | "argparse": { 56 | "version": "1.0.10", 57 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 58 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 59 | "dev": true, 60 | "requires": { 61 | "sprintf-js": "~1.0.2" 62 | } 63 | }, 64 | "array-includes": { 65 | "version": "3.0.3", 66 | "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", 67 | "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", 68 | "dev": true, 69 | "requires": { 70 | "define-properties": "^1.1.2", 71 | "es-abstract": "^1.7.0" 72 | } 73 | }, 74 | "babel-code-frame": { 75 | "version": "6.26.0", 76 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 77 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 78 | "dev": true, 79 | "requires": { 80 | "chalk": "^1.1.3", 81 | "esutils": "^2.0.2", 82 | "js-tokens": "^3.0.2" 83 | }, 84 | "dependencies": { 85 | "chalk": { 86 | "version": "1.1.3", 87 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 88 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 89 | "dev": true, 90 | "requires": { 91 | "ansi-styles": "^2.2.1", 92 | "escape-string-regexp": "^1.0.2", 93 | "has-ansi": "^2.0.0", 94 | "strip-ansi": "^3.0.0", 95 | "supports-color": "^2.0.0" 96 | } 97 | }, 98 | "strip-ansi": { 99 | "version": "3.0.1", 100 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 101 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 102 | "dev": true, 103 | "requires": { 104 | "ansi-regex": "^2.0.0" 105 | } 106 | } 107 | } 108 | }, 109 | "balanced-match": { 110 | "version": "1.0.0", 111 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 112 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 113 | "dev": true 114 | }, 115 | "brace-expansion": { 116 | "version": "1.1.11", 117 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 118 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 119 | "dev": true, 120 | "requires": { 121 | "balanced-match": "^1.0.0", 122 | "concat-map": "0.0.1" 123 | } 124 | }, 125 | "caller-path": { 126 | "version": "0.1.0", 127 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", 128 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", 129 | "dev": true, 130 | "requires": { 131 | "callsites": "^0.2.0" 132 | } 133 | }, 134 | "callsites": { 135 | "version": "0.2.0", 136 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", 137 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", 138 | "dev": true 139 | }, 140 | "chalk": { 141 | "version": "2.4.2", 142 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 143 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 144 | "dev": true, 145 | "requires": { 146 | "ansi-styles": "^3.2.1", 147 | "escape-string-regexp": "^1.0.5", 148 | "supports-color": "^5.3.0" 149 | }, 150 | "dependencies": { 151 | "ansi-styles": { 152 | "version": "3.2.1", 153 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 154 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 155 | "dev": true, 156 | "requires": { 157 | "color-convert": "^1.9.0" 158 | } 159 | }, 160 | "supports-color": { 161 | "version": "5.5.0", 162 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 163 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 164 | "dev": true, 165 | "requires": { 166 | "has-flag": "^3.0.0" 167 | } 168 | } 169 | } 170 | }, 171 | "chardet": { 172 | "version": "0.4.2", 173 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", 174 | "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", 175 | "dev": true 176 | }, 177 | "circular-json": { 178 | "version": "0.3.3", 179 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 180 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 181 | "dev": true 182 | }, 183 | "cli-cursor": { 184 | "version": "2.1.0", 185 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", 186 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", 187 | "dev": true, 188 | "requires": { 189 | "restore-cursor": "^2.0.0" 190 | } 191 | }, 192 | "cli-width": { 193 | "version": "2.2.0", 194 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 195 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 196 | "dev": true 197 | }, 198 | "color-convert": { 199 | "version": "1.9.3", 200 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 201 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 202 | "dev": true, 203 | "requires": { 204 | "color-name": "1.1.3" 205 | } 206 | }, 207 | "color-name": { 208 | "version": "1.1.3", 209 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 210 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 211 | "dev": true 212 | }, 213 | "commander": { 214 | "version": "2.20.0", 215 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", 216 | "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", 217 | "dev": true 218 | }, 219 | "concat-map": { 220 | "version": "0.0.1", 221 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 222 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 223 | "dev": true 224 | }, 225 | "contains-path": { 226 | "version": "0.1.0", 227 | "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", 228 | "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", 229 | "dev": true 230 | }, 231 | "cross-spawn": { 232 | "version": "6.0.5", 233 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 234 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 235 | "dev": true, 236 | "requires": { 237 | "nice-try": "^1.0.4", 238 | "path-key": "^2.0.1", 239 | "semver": "^5.5.0", 240 | "shebang-command": "^1.2.0", 241 | "which": "^1.2.9" 242 | } 243 | }, 244 | "debug": { 245 | "version": "3.2.6", 246 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 247 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 248 | "dev": true, 249 | "requires": { 250 | "ms": "^2.1.1" 251 | } 252 | }, 253 | "debug-log": { 254 | "version": "1.0.1", 255 | "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", 256 | "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", 257 | "dev": true 258 | }, 259 | "deep-is": { 260 | "version": "0.1.3", 261 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 262 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 263 | "dev": true 264 | }, 265 | "define-properties": { 266 | "version": "1.1.3", 267 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 268 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 269 | "dev": true, 270 | "requires": { 271 | "object-keys": "^1.0.12" 272 | } 273 | }, 274 | "deglob": { 275 | "version": "2.1.1", 276 | "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", 277 | "integrity": "sha512-2kjwuGGonL7gWE1XU4Fv79+vVzpoQCl0V+boMwWtOQJV2AGDabCwez++nB1Nli/8BabAfZQ/UuHPlp6AymKdWw==", 278 | "dev": true, 279 | "requires": { 280 | "find-root": "^1.0.0", 281 | "glob": "^7.0.5", 282 | "ignore": "^3.0.9", 283 | "pkg-config": "^1.1.0", 284 | "run-parallel": "^1.1.2", 285 | "uniq": "^1.0.1" 286 | }, 287 | "dependencies": { 288 | "ignore": { 289 | "version": "3.3.10", 290 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", 291 | "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", 292 | "dev": true 293 | } 294 | } 295 | }, 296 | "diff": { 297 | "version": "4.0.1", 298 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", 299 | "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", 300 | "dev": true 301 | }, 302 | "doctrine": { 303 | "version": "2.1.0", 304 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", 305 | "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", 306 | "dev": true, 307 | "requires": { 308 | "esutils": "^2.0.2" 309 | } 310 | }, 311 | "error-ex": { 312 | "version": "1.3.2", 313 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 314 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 315 | "dev": true, 316 | "requires": { 317 | "is-arrayish": "^0.2.1" 318 | } 319 | }, 320 | "es-abstract": { 321 | "version": "1.13.0", 322 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 323 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 324 | "dev": true, 325 | "requires": { 326 | "es-to-primitive": "^1.2.0", 327 | "function-bind": "^1.1.1", 328 | "has": "^1.0.3", 329 | "is-callable": "^1.1.4", 330 | "is-regex": "^1.0.4", 331 | "object-keys": "^1.0.12" 332 | } 333 | }, 334 | "es-to-primitive": { 335 | "version": "1.2.0", 336 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 337 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 338 | "dev": true, 339 | "requires": { 340 | "is-callable": "^1.1.4", 341 | "is-date-object": "^1.0.1", 342 | "is-symbol": "^1.0.2" 343 | } 344 | }, 345 | "escape-string-regexp": { 346 | "version": "1.0.5", 347 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 348 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 349 | "dev": true 350 | }, 351 | "eslint": { 352 | "version": "5.4.0", 353 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", 354 | "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", 355 | "dev": true, 356 | "requires": { 357 | "ajv": "^6.5.0", 358 | "babel-code-frame": "^6.26.0", 359 | "chalk": "^2.1.0", 360 | "cross-spawn": "^6.0.5", 361 | "debug": "^3.1.0", 362 | "doctrine": "^2.1.0", 363 | "eslint-scope": "^4.0.0", 364 | "eslint-utils": "^1.3.1", 365 | "eslint-visitor-keys": "^1.0.0", 366 | "espree": "^4.0.0", 367 | "esquery": "^1.0.1", 368 | "esutils": "^2.0.2", 369 | "file-entry-cache": "^2.0.0", 370 | "functional-red-black-tree": "^1.0.1", 371 | "glob": "^7.1.2", 372 | "globals": "^11.7.0", 373 | "ignore": "^4.0.2", 374 | "imurmurhash": "^0.1.4", 375 | "inquirer": "^5.2.0", 376 | "is-resolvable": "^1.1.0", 377 | "js-yaml": "^3.11.0", 378 | "json-stable-stringify-without-jsonify": "^1.0.1", 379 | "levn": "^0.3.0", 380 | "lodash": "^4.17.5", 381 | "minimatch": "^3.0.4", 382 | "mkdirp": "^0.5.1", 383 | "natural-compare": "^1.4.0", 384 | "optionator": "^0.8.2", 385 | "path-is-inside": "^1.0.2", 386 | "pluralize": "^7.0.0", 387 | "progress": "^2.0.0", 388 | "regexpp": "^2.0.0", 389 | "require-uncached": "^1.0.3", 390 | "semver": "^5.5.0", 391 | "strip-ansi": "^4.0.0", 392 | "strip-json-comments": "^2.0.1", 393 | "table": "^4.0.3", 394 | "text-table": "^0.2.0" 395 | } 396 | }, 397 | "eslint-config-standard": { 398 | "version": "12.0.0", 399 | "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", 400 | "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", 401 | "dev": true 402 | }, 403 | "eslint-config-standard-jsx": { 404 | "version": "6.0.2", 405 | "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz", 406 | "integrity": "sha512-D+YWAoXw+2GIdbMBRAzWwr1ZtvnSf4n4yL0gKGg7ShUOGXkSOLerI17K4F6LdQMJPNMoWYqepzQD/fKY+tXNSg==", 407 | "dev": true 408 | }, 409 | "eslint-import-resolver-node": { 410 | "version": "0.3.2", 411 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", 412 | "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", 413 | "dev": true, 414 | "requires": { 415 | "debug": "^2.6.9", 416 | "resolve": "^1.5.0" 417 | }, 418 | "dependencies": { 419 | "debug": { 420 | "version": "2.6.9", 421 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 422 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 423 | "dev": true, 424 | "requires": { 425 | "ms": "2.0.0" 426 | } 427 | }, 428 | "ms": { 429 | "version": "2.0.0", 430 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 431 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 432 | "dev": true 433 | } 434 | } 435 | }, 436 | "eslint-module-utils": { 437 | "version": "2.4.0", 438 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", 439 | "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", 440 | "dev": true, 441 | "requires": { 442 | "debug": "^2.6.8", 443 | "pkg-dir": "^2.0.0" 444 | }, 445 | "dependencies": { 446 | "debug": { 447 | "version": "2.6.9", 448 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 449 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 450 | "dev": true, 451 | "requires": { 452 | "ms": "2.0.0" 453 | } 454 | }, 455 | "ms": { 456 | "version": "2.0.0", 457 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 458 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 459 | "dev": true 460 | } 461 | } 462 | }, 463 | "eslint-plugin-es": { 464 | "version": "1.4.0", 465 | "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz", 466 | "integrity": "sha512-XfFmgFdIUDgvaRAlaXUkxrRg5JSADoRC8IkKLc/cISeR3yHVMefFHQZpcyXXEUUPHfy5DwviBcrfqlyqEwlQVw==", 467 | "dev": true, 468 | "requires": { 469 | "eslint-utils": "^1.3.0", 470 | "regexpp": "^2.0.1" 471 | } 472 | }, 473 | "eslint-plugin-import": { 474 | "version": "2.14.0", 475 | "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", 476 | "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", 477 | "dev": true, 478 | "requires": { 479 | "contains-path": "^0.1.0", 480 | "debug": "^2.6.8", 481 | "doctrine": "1.5.0", 482 | "eslint-import-resolver-node": "^0.3.1", 483 | "eslint-module-utils": "^2.2.0", 484 | "has": "^1.0.1", 485 | "lodash": "^4.17.4", 486 | "minimatch": "^3.0.3", 487 | "read-pkg-up": "^2.0.0", 488 | "resolve": "^1.6.0" 489 | }, 490 | "dependencies": { 491 | "debug": { 492 | "version": "2.6.9", 493 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 494 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 495 | "dev": true, 496 | "requires": { 497 | "ms": "2.0.0" 498 | } 499 | }, 500 | "doctrine": { 501 | "version": "1.5.0", 502 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", 503 | "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", 504 | "dev": true, 505 | "requires": { 506 | "esutils": "^2.0.2", 507 | "isarray": "^1.0.0" 508 | } 509 | }, 510 | "ms": { 511 | "version": "2.0.0", 512 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 513 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 514 | "dev": true 515 | } 516 | } 517 | }, 518 | "eslint-plugin-node": { 519 | "version": "7.0.1", 520 | "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz", 521 | "integrity": "sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw==", 522 | "dev": true, 523 | "requires": { 524 | "eslint-plugin-es": "^1.3.1", 525 | "eslint-utils": "^1.3.1", 526 | "ignore": "^4.0.2", 527 | "minimatch": "^3.0.4", 528 | "resolve": "^1.8.1", 529 | "semver": "^5.5.0" 530 | } 531 | }, 532 | "eslint-plugin-promise": { 533 | "version": "4.0.1", 534 | "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", 535 | "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", 536 | "dev": true 537 | }, 538 | "eslint-plugin-react": { 539 | "version": "7.11.1", 540 | "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", 541 | "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", 542 | "dev": true, 543 | "requires": { 544 | "array-includes": "^3.0.3", 545 | "doctrine": "^2.1.0", 546 | "has": "^1.0.3", 547 | "jsx-ast-utils": "^2.0.1", 548 | "prop-types": "^15.6.2" 549 | } 550 | }, 551 | "eslint-plugin-standard": { 552 | "version": "4.0.0", 553 | "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz", 554 | "integrity": "sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA==", 555 | "dev": true 556 | }, 557 | "eslint-scope": { 558 | "version": "4.0.3", 559 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", 560 | "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", 561 | "dev": true, 562 | "requires": { 563 | "esrecurse": "^4.1.0", 564 | "estraverse": "^4.1.1" 565 | } 566 | }, 567 | "eslint-utils": { 568 | "version": "1.3.1", 569 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", 570 | "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", 571 | "dev": true 572 | }, 573 | "eslint-visitor-keys": { 574 | "version": "1.0.0", 575 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", 576 | "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", 577 | "dev": true 578 | }, 579 | "espree": { 580 | "version": "4.1.0", 581 | "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", 582 | "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", 583 | "dev": true, 584 | "requires": { 585 | "acorn": "^6.0.2", 586 | "acorn-jsx": "^5.0.0", 587 | "eslint-visitor-keys": "^1.0.0" 588 | } 589 | }, 590 | "esprima": { 591 | "version": "4.0.1", 592 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 593 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 594 | "dev": true 595 | }, 596 | "esquery": { 597 | "version": "1.0.1", 598 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", 599 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", 600 | "dev": true, 601 | "requires": { 602 | "estraverse": "^4.0.0" 603 | } 604 | }, 605 | "esrecurse": { 606 | "version": "4.2.1", 607 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", 608 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", 609 | "dev": true, 610 | "requires": { 611 | "estraverse": "^4.1.0" 612 | } 613 | }, 614 | "estraverse": { 615 | "version": "4.2.0", 616 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 617 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 618 | "dev": true 619 | }, 620 | "esutils": { 621 | "version": "2.0.2", 622 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 623 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 624 | "dev": true 625 | }, 626 | "external-editor": { 627 | "version": "2.2.0", 628 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", 629 | "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", 630 | "dev": true, 631 | "requires": { 632 | "chardet": "^0.4.0", 633 | "iconv-lite": "^0.4.17", 634 | "tmp": "^0.0.33" 635 | } 636 | }, 637 | "fast-deep-equal": { 638 | "version": "2.0.1", 639 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 640 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 641 | "dev": true 642 | }, 643 | "fast-json-stable-stringify": { 644 | "version": "2.0.0", 645 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 646 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 647 | "dev": true 648 | }, 649 | "fast-levenshtein": { 650 | "version": "2.0.6", 651 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 652 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 653 | "dev": true 654 | }, 655 | "figures": { 656 | "version": "2.0.0", 657 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 658 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", 659 | "dev": true, 660 | "requires": { 661 | "escape-string-regexp": "^1.0.5" 662 | } 663 | }, 664 | "file-entry-cache": { 665 | "version": "2.0.0", 666 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", 667 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", 668 | "dev": true, 669 | "requires": { 670 | "flat-cache": "^1.2.1", 671 | "object-assign": "^4.0.1" 672 | } 673 | }, 674 | "find-root": { 675 | "version": "1.1.0", 676 | "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", 677 | "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", 678 | "dev": true 679 | }, 680 | "find-up": { 681 | "version": "2.1.0", 682 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", 683 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", 684 | "dev": true, 685 | "requires": { 686 | "locate-path": "^2.0.0" 687 | } 688 | }, 689 | "flat-cache": { 690 | "version": "1.3.4", 691 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", 692 | "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", 693 | "dev": true, 694 | "requires": { 695 | "circular-json": "^0.3.1", 696 | "graceful-fs": "^4.1.2", 697 | "rimraf": "~2.6.2", 698 | "write": "^0.2.1" 699 | } 700 | }, 701 | "fs.realpath": { 702 | "version": "1.0.0", 703 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 704 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 705 | "dev": true 706 | }, 707 | "function-bind": { 708 | "version": "1.1.1", 709 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 710 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 711 | "dev": true 712 | }, 713 | "functional-red-black-tree": { 714 | "version": "1.0.1", 715 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 716 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 717 | "dev": true 718 | }, 719 | "get-stdin": { 720 | "version": "6.0.0", 721 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", 722 | "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", 723 | "dev": true 724 | }, 725 | "glob": { 726 | "version": "7.1.3", 727 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 728 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 729 | "dev": true, 730 | "requires": { 731 | "fs.realpath": "^1.0.0", 732 | "inflight": "^1.0.4", 733 | "inherits": "2", 734 | "minimatch": "^3.0.4", 735 | "once": "^1.3.0", 736 | "path-is-absolute": "^1.0.0" 737 | } 738 | }, 739 | "globals": { 740 | "version": "11.12.0", 741 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 742 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 743 | "dev": true 744 | }, 745 | "graceful-fs": { 746 | "version": "4.1.15", 747 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 748 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", 749 | "dev": true 750 | }, 751 | "has": { 752 | "version": "1.0.3", 753 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 754 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 755 | "dev": true, 756 | "requires": { 757 | "function-bind": "^1.1.1" 758 | } 759 | }, 760 | "has-ansi": { 761 | "version": "2.0.0", 762 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 763 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 764 | "dev": true, 765 | "requires": { 766 | "ansi-regex": "^2.0.0" 767 | } 768 | }, 769 | "has-flag": { 770 | "version": "3.0.0", 771 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 772 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 773 | "dev": true 774 | }, 775 | "has-symbols": { 776 | "version": "1.0.0", 777 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 778 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 779 | "dev": true 780 | }, 781 | "hosted-git-info": { 782 | "version": "2.7.1", 783 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", 784 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", 785 | "dev": true 786 | }, 787 | "iconv-lite": { 788 | "version": "0.4.24", 789 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 790 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 791 | "dev": true, 792 | "requires": { 793 | "safer-buffer": ">= 2.1.2 < 3" 794 | } 795 | }, 796 | "ignore": { 797 | "version": "4.0.6", 798 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 799 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 800 | "dev": true 801 | }, 802 | "imurmurhash": { 803 | "version": "0.1.4", 804 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 805 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 806 | "dev": true 807 | }, 808 | "inflight": { 809 | "version": "1.0.6", 810 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 811 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 812 | "dev": true, 813 | "requires": { 814 | "once": "^1.3.0", 815 | "wrappy": "1" 816 | } 817 | }, 818 | "inherits": { 819 | "version": "2.0.3", 820 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 821 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 822 | "dev": true 823 | }, 824 | "inquirer": { 825 | "version": "5.2.0", 826 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", 827 | "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", 828 | "dev": true, 829 | "requires": { 830 | "ansi-escapes": "^3.0.0", 831 | "chalk": "^2.0.0", 832 | "cli-cursor": "^2.1.0", 833 | "cli-width": "^2.0.0", 834 | "external-editor": "^2.1.0", 835 | "figures": "^2.0.0", 836 | "lodash": "^4.3.0", 837 | "mute-stream": "0.0.7", 838 | "run-async": "^2.2.0", 839 | "rxjs": "^5.5.2", 840 | "string-width": "^2.1.0", 841 | "strip-ansi": "^4.0.0", 842 | "through": "^2.3.6" 843 | } 844 | }, 845 | "is-arrayish": { 846 | "version": "0.2.1", 847 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 848 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 849 | "dev": true 850 | }, 851 | "is-callable": { 852 | "version": "1.1.4", 853 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 854 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 855 | "dev": true 856 | }, 857 | "is-date-object": { 858 | "version": "1.0.1", 859 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 860 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 861 | "dev": true 862 | }, 863 | "is-fullwidth-code-point": { 864 | "version": "2.0.0", 865 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 866 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 867 | "dev": true 868 | }, 869 | "is-promise": { 870 | "version": "2.1.0", 871 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 872 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 873 | "dev": true 874 | }, 875 | "is-regex": { 876 | "version": "1.0.4", 877 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 878 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 879 | "dev": true, 880 | "requires": { 881 | "has": "^1.0.1" 882 | } 883 | }, 884 | "is-resolvable": { 885 | "version": "1.1.0", 886 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", 887 | "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", 888 | "dev": true 889 | }, 890 | "is-symbol": { 891 | "version": "1.0.2", 892 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 893 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 894 | "dev": true, 895 | "requires": { 896 | "has-symbols": "^1.0.0" 897 | } 898 | }, 899 | "isarray": { 900 | "version": "1.0.0", 901 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 902 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 903 | "dev": true 904 | }, 905 | "isexe": { 906 | "version": "2.0.0", 907 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 908 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 909 | "dev": true 910 | }, 911 | "js-tokens": { 912 | "version": "3.0.2", 913 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 914 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 915 | "dev": true 916 | }, 917 | "js-yaml": { 918 | "version": "3.13.1", 919 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 920 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 921 | "dev": true, 922 | "requires": { 923 | "argparse": "^1.0.7", 924 | "esprima": "^4.0.0" 925 | } 926 | }, 927 | "json-parse-better-errors": { 928 | "version": "1.0.2", 929 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 930 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 931 | "dev": true 932 | }, 933 | "json-schema-traverse": { 934 | "version": "0.4.1", 935 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 936 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 937 | "dev": true 938 | }, 939 | "json-stable-stringify-without-jsonify": { 940 | "version": "1.0.1", 941 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 942 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 943 | "dev": true 944 | }, 945 | "jsx-ast-utils": { 946 | "version": "2.1.0", 947 | "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz", 948 | "integrity": "sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==", 949 | "dev": true, 950 | "requires": { 951 | "array-includes": "^3.0.3" 952 | } 953 | }, 954 | "levn": { 955 | "version": "0.3.0", 956 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 957 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 958 | "dev": true, 959 | "requires": { 960 | "prelude-ls": "~1.1.2", 961 | "type-check": "~0.3.2" 962 | } 963 | }, 964 | "load-json-file": { 965 | "version": "2.0.0", 966 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", 967 | "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", 968 | "dev": true, 969 | "requires": { 970 | "graceful-fs": "^4.1.2", 971 | "parse-json": "^2.2.0", 972 | "pify": "^2.0.0", 973 | "strip-bom": "^3.0.0" 974 | } 975 | }, 976 | "locate-path": { 977 | "version": "2.0.0", 978 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", 979 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", 980 | "dev": true, 981 | "requires": { 982 | "p-locate": "^2.0.0", 983 | "path-exists": "^3.0.0" 984 | } 985 | }, 986 | "lodash": { 987 | "version": "4.17.11", 988 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 989 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 990 | "dev": true 991 | }, 992 | "loose-envify": { 993 | "version": "1.4.0", 994 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 995 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 996 | "dev": true, 997 | "requires": { 998 | "js-tokens": "^3.0.0 || ^4.0.0" 999 | } 1000 | }, 1001 | "mimic-fn": { 1002 | "version": "1.2.0", 1003 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 1004 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", 1005 | "dev": true 1006 | }, 1007 | "minimatch": { 1008 | "version": "3.0.4", 1009 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1010 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1011 | "dev": true, 1012 | "requires": { 1013 | "brace-expansion": "^1.1.7" 1014 | } 1015 | }, 1016 | "minimist": { 1017 | "version": "0.0.8", 1018 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1019 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1020 | "dev": true 1021 | }, 1022 | "mkdirp": { 1023 | "version": "0.5.1", 1024 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1025 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1026 | "dev": true, 1027 | "requires": { 1028 | "minimist": "0.0.8" 1029 | } 1030 | }, 1031 | "ms": { 1032 | "version": "2.1.1", 1033 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1034 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 1035 | "dev": true 1036 | }, 1037 | "mute-stream": { 1038 | "version": "0.0.7", 1039 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 1040 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 1041 | "dev": true 1042 | }, 1043 | "natural-compare": { 1044 | "version": "1.4.0", 1045 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1046 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1047 | "dev": true 1048 | }, 1049 | "nice-try": { 1050 | "version": "1.0.5", 1051 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1052 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 1053 | "dev": true 1054 | }, 1055 | "normalize-package-data": { 1056 | "version": "2.5.0", 1057 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 1058 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 1059 | "dev": true, 1060 | "requires": { 1061 | "hosted-git-info": "^2.1.4", 1062 | "resolve": "^1.10.0", 1063 | "semver": "2 || 3 || 4 || 5", 1064 | "validate-npm-package-license": "^3.0.1" 1065 | } 1066 | }, 1067 | "object-assign": { 1068 | "version": "4.1.1", 1069 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1070 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1071 | "dev": true 1072 | }, 1073 | "object-keys": { 1074 | "version": "1.1.1", 1075 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1076 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 1077 | "dev": true 1078 | }, 1079 | "once": { 1080 | "version": "1.4.0", 1081 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1082 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1083 | "dev": true, 1084 | "requires": { 1085 | "wrappy": "1" 1086 | } 1087 | }, 1088 | "onetime": { 1089 | "version": "2.0.1", 1090 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", 1091 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", 1092 | "dev": true, 1093 | "requires": { 1094 | "mimic-fn": "^1.0.0" 1095 | } 1096 | }, 1097 | "optionator": { 1098 | "version": "0.8.2", 1099 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 1100 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 1101 | "dev": true, 1102 | "requires": { 1103 | "deep-is": "~0.1.3", 1104 | "fast-levenshtein": "~2.0.4", 1105 | "levn": "~0.3.0", 1106 | "prelude-ls": "~1.1.2", 1107 | "type-check": "~0.3.2", 1108 | "wordwrap": "~1.0.0" 1109 | } 1110 | }, 1111 | "os-tmpdir": { 1112 | "version": "1.0.2", 1113 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1114 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1115 | "dev": true 1116 | }, 1117 | "p-limit": { 1118 | "version": "1.3.0", 1119 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", 1120 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", 1121 | "dev": true, 1122 | "requires": { 1123 | "p-try": "^1.0.0" 1124 | } 1125 | }, 1126 | "p-locate": { 1127 | "version": "2.0.0", 1128 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", 1129 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", 1130 | "dev": true, 1131 | "requires": { 1132 | "p-limit": "^1.1.0" 1133 | } 1134 | }, 1135 | "p-try": { 1136 | "version": "1.0.0", 1137 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", 1138 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", 1139 | "dev": true 1140 | }, 1141 | "parse-json": { 1142 | "version": "2.2.0", 1143 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 1144 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 1145 | "dev": true, 1146 | "requires": { 1147 | "error-ex": "^1.2.0" 1148 | } 1149 | }, 1150 | "path-exists": { 1151 | "version": "3.0.0", 1152 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 1153 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 1154 | "dev": true 1155 | }, 1156 | "path-is-absolute": { 1157 | "version": "1.0.1", 1158 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1159 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1160 | "dev": true 1161 | }, 1162 | "path-is-inside": { 1163 | "version": "1.0.2", 1164 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 1165 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 1166 | "dev": true 1167 | }, 1168 | "path-key": { 1169 | "version": "2.0.1", 1170 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1171 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1172 | "dev": true 1173 | }, 1174 | "path-parse": { 1175 | "version": "1.0.6", 1176 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1177 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1178 | "dev": true 1179 | }, 1180 | "path-type": { 1181 | "version": "2.0.0", 1182 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", 1183 | "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", 1184 | "dev": true, 1185 | "requires": { 1186 | "pify": "^2.0.0" 1187 | } 1188 | }, 1189 | "pify": { 1190 | "version": "2.3.0", 1191 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 1192 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 1193 | "dev": true 1194 | }, 1195 | "pkg-conf": { 1196 | "version": "2.1.0", 1197 | "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", 1198 | "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", 1199 | "dev": true, 1200 | "requires": { 1201 | "find-up": "^2.0.0", 1202 | "load-json-file": "^4.0.0" 1203 | }, 1204 | "dependencies": { 1205 | "load-json-file": { 1206 | "version": "4.0.0", 1207 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 1208 | "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", 1209 | "dev": true, 1210 | "requires": { 1211 | "graceful-fs": "^4.1.2", 1212 | "parse-json": "^4.0.0", 1213 | "pify": "^3.0.0", 1214 | "strip-bom": "^3.0.0" 1215 | } 1216 | }, 1217 | "parse-json": { 1218 | "version": "4.0.0", 1219 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 1220 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 1221 | "dev": true, 1222 | "requires": { 1223 | "error-ex": "^1.3.1", 1224 | "json-parse-better-errors": "^1.0.1" 1225 | } 1226 | }, 1227 | "pify": { 1228 | "version": "3.0.0", 1229 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 1230 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 1231 | "dev": true 1232 | } 1233 | } 1234 | }, 1235 | "pkg-config": { 1236 | "version": "1.1.1", 1237 | "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", 1238 | "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", 1239 | "dev": true, 1240 | "requires": { 1241 | "debug-log": "^1.0.0", 1242 | "find-root": "^1.0.0", 1243 | "xtend": "^4.0.1" 1244 | } 1245 | }, 1246 | "pkg-dir": { 1247 | "version": "2.0.0", 1248 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", 1249 | "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", 1250 | "dev": true, 1251 | "requires": { 1252 | "find-up": "^2.1.0" 1253 | } 1254 | }, 1255 | "pluralize": { 1256 | "version": "7.0.0", 1257 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", 1258 | "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", 1259 | "dev": true 1260 | }, 1261 | "prelude-ls": { 1262 | "version": "1.1.2", 1263 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1264 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1265 | "dev": true 1266 | }, 1267 | "progress": { 1268 | "version": "2.0.3", 1269 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 1270 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 1271 | "dev": true 1272 | }, 1273 | "prop-types": { 1274 | "version": "15.7.2", 1275 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 1276 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 1277 | "dev": true, 1278 | "requires": { 1279 | "loose-envify": "^1.4.0", 1280 | "object-assign": "^4.1.1", 1281 | "react-is": "^16.8.1" 1282 | } 1283 | }, 1284 | "punycode": { 1285 | "version": "2.1.1", 1286 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1287 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1288 | "dev": true 1289 | }, 1290 | "react-is": { 1291 | "version": "16.8.6", 1292 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", 1293 | "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", 1294 | "dev": true 1295 | }, 1296 | "read-pkg": { 1297 | "version": "2.0.0", 1298 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", 1299 | "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", 1300 | "dev": true, 1301 | "requires": { 1302 | "load-json-file": "^2.0.0", 1303 | "normalize-package-data": "^2.3.2", 1304 | "path-type": "^2.0.0" 1305 | } 1306 | }, 1307 | "read-pkg-up": { 1308 | "version": "2.0.0", 1309 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", 1310 | "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", 1311 | "dev": true, 1312 | "requires": { 1313 | "find-up": "^2.0.0", 1314 | "read-pkg": "^2.0.0" 1315 | } 1316 | }, 1317 | "regexpp": { 1318 | "version": "2.0.1", 1319 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", 1320 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", 1321 | "dev": true 1322 | }, 1323 | "require-uncached": { 1324 | "version": "1.0.3", 1325 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", 1326 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", 1327 | "dev": true, 1328 | "requires": { 1329 | "caller-path": "^0.1.0", 1330 | "resolve-from": "^1.0.0" 1331 | } 1332 | }, 1333 | "resolve": { 1334 | "version": "1.10.1", 1335 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", 1336 | "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", 1337 | "dev": true, 1338 | "requires": { 1339 | "path-parse": "^1.0.6" 1340 | } 1341 | }, 1342 | "resolve-from": { 1343 | "version": "1.0.1", 1344 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", 1345 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", 1346 | "dev": true 1347 | }, 1348 | "respjs": { 1349 | "version": "4.1.5", 1350 | "resolved": "https://registry.npmjs.org/respjs/-/respjs-4.1.5.tgz", 1351 | "integrity": "sha512-TJIaj4HvCVuxKGaXyCeDdCKy+7yXtdzVXuMxyrmAHBcVtmYwloLpTAYA58TCOEvKKvm1Yjfg0bCce63+/p/mfA==" 1352 | }, 1353 | "restore-cursor": { 1354 | "version": "2.0.0", 1355 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", 1356 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", 1357 | "dev": true, 1358 | "requires": { 1359 | "onetime": "^2.0.0", 1360 | "signal-exit": "^3.0.2" 1361 | } 1362 | }, 1363 | "rimraf": { 1364 | "version": "2.6.3", 1365 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 1366 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 1367 | "dev": true, 1368 | "requires": { 1369 | "glob": "^7.1.3" 1370 | } 1371 | }, 1372 | "run-async": { 1373 | "version": "2.3.0", 1374 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 1375 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 1376 | "dev": true, 1377 | "requires": { 1378 | "is-promise": "^2.1.0" 1379 | } 1380 | }, 1381 | "run-parallel": { 1382 | "version": "1.1.9", 1383 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", 1384 | "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", 1385 | "dev": true 1386 | }, 1387 | "rxjs": { 1388 | "version": "5.5.12", 1389 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", 1390 | "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", 1391 | "dev": true, 1392 | "requires": { 1393 | "symbol-observable": "1.0.1" 1394 | } 1395 | }, 1396 | "safer-buffer": { 1397 | "version": "2.1.2", 1398 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1399 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1400 | "dev": true 1401 | }, 1402 | "semver": { 1403 | "version": "5.7.0", 1404 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 1405 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", 1406 | "dev": true 1407 | }, 1408 | "shebang-command": { 1409 | "version": "1.2.0", 1410 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1411 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1412 | "dev": true, 1413 | "requires": { 1414 | "shebang-regex": "^1.0.0" 1415 | } 1416 | }, 1417 | "shebang-regex": { 1418 | "version": "1.0.0", 1419 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1420 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1421 | "dev": true 1422 | }, 1423 | "signal-exit": { 1424 | "version": "3.0.2", 1425 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1426 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 1427 | "dev": true 1428 | }, 1429 | "slice-ansi": { 1430 | "version": "1.0.0", 1431 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", 1432 | "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", 1433 | "dev": true, 1434 | "requires": { 1435 | "is-fullwidth-code-point": "^2.0.0" 1436 | } 1437 | }, 1438 | "spdx-correct": { 1439 | "version": "3.1.0", 1440 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 1441 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 1442 | "dev": true, 1443 | "requires": { 1444 | "spdx-expression-parse": "^3.0.0", 1445 | "spdx-license-ids": "^3.0.0" 1446 | } 1447 | }, 1448 | "spdx-exceptions": { 1449 | "version": "2.2.0", 1450 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 1451 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 1452 | "dev": true 1453 | }, 1454 | "spdx-expression-parse": { 1455 | "version": "3.0.0", 1456 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 1457 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 1458 | "dev": true, 1459 | "requires": { 1460 | "spdx-exceptions": "^2.1.0", 1461 | "spdx-license-ids": "^3.0.0" 1462 | } 1463 | }, 1464 | "spdx-license-ids": { 1465 | "version": "3.0.4", 1466 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", 1467 | "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", 1468 | "dev": true 1469 | }, 1470 | "sprintf-js": { 1471 | "version": "1.0.3", 1472 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1473 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1474 | "dev": true 1475 | }, 1476 | "standard": { 1477 | "version": "12.0.1", 1478 | "resolved": "https://registry.npmjs.org/standard/-/standard-12.0.1.tgz", 1479 | "integrity": "sha512-UqdHjh87OG2gUrNCSM4QRLF5n9h3TFPwrCNyVlkqu31Hej0L/rc8hzKqVvkb2W3x0WMq7PzZdkLfEcBhVOR6lg==", 1480 | "dev": true, 1481 | "requires": { 1482 | "eslint": "~5.4.0", 1483 | "eslint-config-standard": "12.0.0", 1484 | "eslint-config-standard-jsx": "6.0.2", 1485 | "eslint-plugin-import": "~2.14.0", 1486 | "eslint-plugin-node": "~7.0.1", 1487 | "eslint-plugin-promise": "~4.0.0", 1488 | "eslint-plugin-react": "~7.11.1", 1489 | "eslint-plugin-standard": "~4.0.0", 1490 | "standard-engine": "~9.0.0" 1491 | } 1492 | }, 1493 | "standard-engine": { 1494 | "version": "9.0.0", 1495 | "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-9.0.0.tgz", 1496 | "integrity": "sha512-ZfNfCWZ2Xq67VNvKMPiVMKHnMdvxYzvZkf1AH8/cw2NLDBm5LRsxMqvEJpsjLI/dUosZ3Z1d6JlHDp5rAvvk2w==", 1497 | "dev": true, 1498 | "requires": { 1499 | "deglob": "^2.1.0", 1500 | "get-stdin": "^6.0.0", 1501 | "minimist": "^1.1.0", 1502 | "pkg-conf": "^2.0.0" 1503 | }, 1504 | "dependencies": { 1505 | "minimist": { 1506 | "version": "1.2.0", 1507 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1508 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1509 | "dev": true 1510 | } 1511 | } 1512 | }, 1513 | "string-width": { 1514 | "version": "2.1.1", 1515 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1516 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1517 | "dev": true, 1518 | "requires": { 1519 | "is-fullwidth-code-point": "^2.0.0", 1520 | "strip-ansi": "^4.0.0" 1521 | } 1522 | }, 1523 | "strip-ansi": { 1524 | "version": "4.0.0", 1525 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1526 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1527 | "dev": true, 1528 | "requires": { 1529 | "ansi-regex": "^3.0.0" 1530 | }, 1531 | "dependencies": { 1532 | "ansi-regex": { 1533 | "version": "3.0.0", 1534 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 1535 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 1536 | "dev": true 1537 | } 1538 | } 1539 | }, 1540 | "strip-bom": { 1541 | "version": "3.0.0", 1542 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1543 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1544 | "dev": true 1545 | }, 1546 | "strip-json-comments": { 1547 | "version": "2.0.1", 1548 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1549 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1550 | "dev": true 1551 | }, 1552 | "supports-color": { 1553 | "version": "2.0.0", 1554 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1555 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1556 | "dev": true 1557 | }, 1558 | "symbol-observable": { 1559 | "version": "1.0.1", 1560 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", 1561 | "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", 1562 | "dev": true 1563 | }, 1564 | "table": { 1565 | "version": "4.0.3", 1566 | "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", 1567 | "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", 1568 | "dev": true, 1569 | "requires": { 1570 | "ajv": "^6.0.1", 1571 | "ajv-keywords": "^3.0.0", 1572 | "chalk": "^2.1.0", 1573 | "lodash": "^4.17.4", 1574 | "slice-ansi": "1.0.0", 1575 | "string-width": "^2.1.1" 1576 | } 1577 | }, 1578 | "text-table": { 1579 | "version": "0.2.0", 1580 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1581 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1582 | "dev": true 1583 | }, 1584 | "through": { 1585 | "version": "2.3.8", 1586 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1587 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1588 | "dev": true 1589 | }, 1590 | "thunk-queue": { 1591 | "version": "1.2.0", 1592 | "resolved": "https://registry.npmjs.org/thunk-queue/-/thunk-queue-1.2.0.tgz", 1593 | "integrity": "sha1-8o8jLf+aV720J/GedjpJGs3aLMA=", 1594 | "dev": true, 1595 | "requires": { 1596 | "thunks": "^4.8.0" 1597 | } 1598 | }, 1599 | "thunk-redis": { 1600 | "version": "2.2.4", 1601 | "resolved": "https://registry.npmjs.org/thunk-redis/-/thunk-redis-2.2.4.tgz", 1602 | "integrity": "sha512-Mlo9MQHo+6m+zhDrk/ybjqXMa7dEWkYFUiVasDmmkUwCRSs6Wyt2kyzo8ITW3LTfWovBSPYYmn6KG76gjQ+TIA==", 1603 | "requires": { 1604 | "respjs": "^4.1.5", 1605 | "thunks": "^4.9.3" 1606 | } 1607 | }, 1608 | "thunks": { 1609 | "version": "4.9.4", 1610 | "resolved": "https://registry.npmjs.org/thunks/-/thunks-4.9.4.tgz", 1611 | "integrity": "sha512-T4WxGsyXtG5QrR68nBule2eBsJ5ppuNcRPkiasdbwx0kLiZbLVchwgFKhJ6RawYa5TbCjfEJcKuS/1918+R18A==" 1612 | }, 1613 | "tman": { 1614 | "version": "1.9.0", 1615 | "resolved": "https://registry.npmjs.org/tman/-/tman-1.9.0.tgz", 1616 | "integrity": "sha512-u6PlJFoeIAJQoFdnsYi9bR8fi6AHE++dvOIsHjmct5M41+s+9LdAK/NqNy+0zKvAsFsMVUX2FnITIEoS7xb3Vw==", 1617 | "dev": true, 1618 | "requires": { 1619 | "commander": "^2.19.0", 1620 | "diff": "~4.0.1", 1621 | "glob": "~7.1.3", 1622 | "supports-color": "^6.1.0", 1623 | "thunks": "~4.9.4" 1624 | }, 1625 | "dependencies": { 1626 | "supports-color": { 1627 | "version": "6.1.0", 1628 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", 1629 | "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", 1630 | "dev": true, 1631 | "requires": { 1632 | "has-flag": "^3.0.0" 1633 | } 1634 | } 1635 | } 1636 | }, 1637 | "tmp": { 1638 | "version": "0.0.33", 1639 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 1640 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 1641 | "dev": true, 1642 | "requires": { 1643 | "os-tmpdir": "~1.0.2" 1644 | } 1645 | }, 1646 | "type-check": { 1647 | "version": "0.3.2", 1648 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1649 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1650 | "dev": true, 1651 | "requires": { 1652 | "prelude-ls": "~1.1.2" 1653 | } 1654 | }, 1655 | "uniq": { 1656 | "version": "1.0.1", 1657 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", 1658 | "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", 1659 | "dev": true 1660 | }, 1661 | "uri-js": { 1662 | "version": "4.2.2", 1663 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1664 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1665 | "dev": true, 1666 | "requires": { 1667 | "punycode": "^2.1.0" 1668 | } 1669 | }, 1670 | "validate-npm-package-license": { 1671 | "version": "3.0.4", 1672 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1673 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1674 | "dev": true, 1675 | "requires": { 1676 | "spdx-correct": "^3.0.0", 1677 | "spdx-expression-parse": "^3.0.0" 1678 | } 1679 | }, 1680 | "which": { 1681 | "version": "1.3.1", 1682 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1683 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1684 | "dev": true, 1685 | "requires": { 1686 | "isexe": "^2.0.0" 1687 | } 1688 | }, 1689 | "wordwrap": { 1690 | "version": "1.0.0", 1691 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1692 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 1693 | "dev": true 1694 | }, 1695 | "wrappy": { 1696 | "version": "1.0.2", 1697 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1698 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1699 | "dev": true 1700 | }, 1701 | "write": { 1702 | "version": "0.2.1", 1703 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 1704 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 1705 | "dev": true, 1706 | "requires": { 1707 | "mkdirp": "^0.5.1" 1708 | } 1709 | }, 1710 | "xtend": { 1711 | "version": "4.0.1", 1712 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1713 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 1714 | "dev": true 1715 | } 1716 | } 1717 | } 1718 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timed-queue", 3 | "description": "Distributed timed job queue, backed by Redis.", 4 | "authors": [ 5 | "Yan Qing " 6 | ], 7 | "version": "1.4.0", 8 | "main": "index.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:teambition/timed-queue.git" 12 | }, 13 | "homepage": "https://github.com/teambition/timed-queue", 14 | "engines": { 15 | "node": ">= 6" 16 | }, 17 | "keywords": [ 18 | "timed", 19 | "timer", 20 | "queue", 21 | "job", 22 | "redis", 23 | "distributed" 24 | ], 25 | "dependencies": { 26 | "thunk-redis": "^2.2.4", 27 | "thunks": "^4.9.4" 28 | }, 29 | "devDependencies": { 30 | "standard": "^12.0.1", 31 | "thunk-queue": "^1.2.0", 32 | "tman": "^1.9.0" 33 | }, 34 | "scripts": { 35 | "test": "standard && tman test/index.js" 36 | }, 37 | "files": [ 38 | "README.md", 39 | "index.js", 40 | "queue.lua" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /queue.lua: -------------------------------------------------------------------------------- 1 | -- KEYS[3] queueKey, activeQueueKey, queueOptionsKey 2 | -- ARGV[6] count, retry time, expire time, time accuracy, current timestamp, scanActive 3 | 4 | local options = redis.call('hmget', KEYS[3], 'count', 'retry', 'expire', 'accuracy') 5 | options = {tonumber(options[1] or ARGV[1]), tonumber(options[2] or ARGV[2]), tonumber(options[3] or ARGV[3]), tonumber(options[4] or ARGV[4])} 6 | 7 | local jobs = {} -- {job, timing, retryCount, job, timing, retryCount, ...} 8 | local res = {jobs, options[2], 0} -- {jobs, retry, hasMore} 9 | local current = tonumber(ARGV[5]) 10 | local dueTime = options[4] + current 11 | 12 | if ARGV[6] == '1' then 13 | local activeQueue = redis.call('hgetall', KEYS[2]) 14 | for i = 1, #activeQueue, 2 do 15 | local times = {} 16 | for time in string.gmatch(activeQueue[i + 1], '%d+') do 17 | times[#times + 1] = tonumber(time) 18 | end 19 | 20 | if (current - times[2]) >= options[3] then 21 | redis.call('hdel', KEYS[2], activeQueue[i]) 22 | elseif (current - times[#times]) >= options[2] then 23 | jobs[#jobs + 1] = activeQueue[i] 24 | jobs[#jobs + 1] = times[1] 25 | jobs[#jobs + 1] = #times - 1 26 | redis.call('hset', KEYS[2], activeQueue[i], activeQueue[i + 1] .. ':' .. ARGV[5]) 27 | end 28 | end 29 | end 30 | 31 | local queue = redis.call('zrangebyscore', KEYS[1], 0, dueTime, 'WITHSCORES', 'LIMIT', 0, options[1]) 32 | if #queue == options[1] * 2 then res[3] = 1 end 33 | 34 | for i = 1, #queue, 2 do 35 | jobs[#jobs + 1] = queue[i] 36 | jobs[#jobs + 1] = queue[i + 1] 37 | jobs[#jobs + 1] = 0 38 | redis.call('hset', KEYS[2], queue[i], queue[i + 1] .. ':' .. ARGV[5]) 39 | redis.call('zrem', KEYS[1], queue[i]) 40 | end 41 | 42 | return res 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // **Github:** https://github.com/teambition/timed-queue 4 | // 5 | // **License:** MIT 6 | 7 | const tman = require('tman') 8 | const assert = require('assert') 9 | const thunk = require('thunks')() 10 | const redis = require('thunk-redis') 11 | const thunkQueue = require('thunk-queue') 12 | const TimedQueue = require('../index') 13 | 14 | tman.suite('timed-queue', function () { 15 | this.timeout(50000) 16 | 17 | const queueNames = [] 18 | function getName () { 19 | let name = `test_${Date.now()}_${queueNames.length}` 20 | queueNames.push(name) 21 | return name 22 | } 23 | 24 | tman.afterEach(function * () { 25 | let timedQueue = new TimedQueue({ autoScan: false }).connect() 26 | // ensure to clean the test queue 27 | for (let name of queueNames) { 28 | yield timedQueue.destroyQueue(name) 29 | } 30 | queueNames.length = 0 31 | timedQueue.close() 32 | }) 33 | 34 | tman.it('new TimedQueue()', function (done) { 35 | const timedQueue = new TimedQueue({ interval: 2000 }) 36 | const events = [] 37 | timedQueue 38 | .on('connect', function () { 39 | events.push('connect') 40 | }) 41 | .on('scanStart', function (n) { 42 | events.push('scanStart') 43 | }) 44 | .on('scanEnd', function (queues, time) { 45 | events.push('scanEnd') 46 | assert.strictEqual(queues, 0) 47 | assert.strictEqual(time >= 0, true) 48 | assert.deepStrictEqual(events, ['connect', 'scanStart', 'scanEnd']) 49 | timedQueue.close() 50 | done() 51 | }) 52 | timedQueue.connect(redis.createClient()) 53 | }) 54 | 55 | tman.it('timedQueue.scan, timedQueue.regulateFreq, timedQueue.close', function (done) { 56 | const timedQueue = new TimedQueue({ interval: 1000 }) 57 | let scanCount = 0 58 | assert.strictEqual(timedQueue.delay, 1000) 59 | timedQueue.regulateFreq(-0.05) 60 | assert.strictEqual(timedQueue.delay, 1000) 61 | timedQueue.regulateFreq(-0.06) 62 | assert.strictEqual(timedQueue.delay < 1000, true) 63 | timedQueue.delay = 1000 64 | timedQueue.regulateFreq(0.05) 65 | assert.strictEqual(timedQueue.delay, 1000) 66 | timedQueue.regulateFreq(0.06) 67 | assert.strictEqual(timedQueue.delay, 1000) 68 | 69 | timedQueue 70 | .on('scanEnd', function () { 71 | if (++scanCount === 10) { 72 | timedQueue.close() 73 | done() 74 | } 75 | if (scanCount > 10) throw new Error('Should stopped!') 76 | }) 77 | timedQueue.connect(redis.createClient()) 78 | }) 79 | 80 | tman.it('timedQueue.queue, queue.addjob, queue.show, queue.deljob, timedQueue.destroyQueue', function * () { 81 | const timedQueue = new TimedQueue().connect() 82 | const name = getName() 83 | const queue = timedQueue.queue(name) 84 | 85 | yield queue.show(1)((err) => assert(err instanceof Error)) 86 | 87 | let res = yield queue.show('123') 88 | assert.strictEqual(res, null) 89 | 90 | yield queue.addjob('123', Date.now())((err) => assert(err instanceof Error)) 91 | res = yield queue.addjob('123', Date.now() + 1000) 92 | assert.strictEqual(res, 1) 93 | 94 | res = yield queue.show('123') 95 | assert.strictEqual(res.queue, name) 96 | assert.strictEqual(res.job, '123') 97 | assert.strictEqual(res.timing > Date.now(), true) 98 | assert.strictEqual(res.active, 0) 99 | 100 | res = yield queue.deljob('123') 101 | assert.strictEqual(res, 1) 102 | 103 | res = yield queue.show('123') 104 | assert.strictEqual(res, null) 105 | timedQueue.close() 106 | }) 107 | 108 | tman.it('queue.init, queue.getjobs, queue.showActive, queue.len, queue.ackjob', function * () { 109 | let time = Date.now() 110 | const timedQueue = new TimedQueue({ autoScan: false }).connect(redis.createClient()) 111 | const queue = timedQueue.queue(getName(), { 112 | count: 3, 113 | retry: 1000, 114 | expire: 3000, 115 | accuracy: 200 116 | }) 117 | 118 | let res = yield queue.getjobs() 119 | assert.deepStrictEqual(Object.assign({}, res), { 120 | retry: 1000, 121 | hasMore: 0, 122 | jobs: [] 123 | }) 124 | 125 | res = yield queue.addjob( 126 | 'job0', time + 1000, 127 | 'job1', time + 1010, 128 | 'job2', time + 1020, 129 | 'job3', time + 1030, 130 | 'job4', time + 1100, 131 | 'job5', time + 1300, 132 | 'job6', time + 1320, 133 | 'job7', time + 1340, 134 | 'job8', time + 1360, 135 | 'job9', time + 3000 136 | ) 137 | assert.strictEqual(res, 10) 138 | 139 | res = yield queue.len() 140 | assert.strictEqual(res, 10) 141 | 142 | yield thunk.delay(1020) 143 | 144 | res = yield queue.getjobs() 145 | assert.strictEqual(res.hasMore, 1) 146 | assert.deepStrictEqual(res.jobs.map((job) => { 147 | assert.strictEqual(job.retryCount, 0) 148 | assert.strictEqual(job.timing > time, true) 149 | assert.strictEqual(job.active > time, true) 150 | return job.job 151 | }), ['job0', 'job1', 'job2']) 152 | 153 | res = yield queue.showActive() 154 | assert.deepStrictEqual(res.map((job) => { 155 | assert.strictEqual(job.retryCount, 0) 156 | assert.strictEqual(job.timing > time, true) 157 | assert.strictEqual(job.active > time, true) 158 | return job.job 159 | }), ['job0', 'job1', 'job2']) 160 | 161 | res = yield queue.len() 162 | assert.strictEqual(res, 7) 163 | 164 | res = yield queue.getjobs() 165 | assert.strictEqual(res.hasMore, 0) 166 | assert.deepStrictEqual(res.jobs.map((job) => { 167 | assert.strictEqual(job.retryCount, 0) 168 | assert.strictEqual(job.timing > time, true) 169 | assert.strictEqual(job.active > time, true) 170 | return job.job 171 | }), ['job3', 'job4']) 172 | 173 | yield thunk.delay(1000) 174 | res = yield queue.getjobs() 175 | assert.strictEqual(res.hasMore, 1) 176 | let retryJob = ['job0', 'job1', 'job2', 'job3', 'job4'] 177 | assert.deepStrictEqual(res.jobs.map((job) => { 178 | assert.strictEqual(job.retryCount, retryJob.indexOf(job.job) >= 0 ? 1 : 0) 179 | assert.strictEqual(job.timing > time, true) 180 | assert.strictEqual(job.active > time, true) 181 | return job.job 182 | }), retryJob.concat('job5', 'job6', 'job7')) 183 | 184 | res = yield queue.getjobs() 185 | assert.strictEqual(res.hasMore, 0) 186 | assert.deepStrictEqual(res.jobs.map((job) => { 187 | assert.strictEqual(job.retryCount, 0) 188 | assert.strictEqual(job.timing > time, true) 189 | assert.strictEqual(job.active > time, true) 190 | return job.job 191 | }), ['job8']) 192 | 193 | res = yield queue.ackjob('job0', 'job1', 'job2', 'job3', 'job4', 'job5', 'job6', 'job7') 194 | assert.strictEqual(res, 8) 195 | 196 | yield thunk.delay(1000) 197 | res = yield queue.getjobs() 198 | assert.strictEqual(res.hasMore, 0) 199 | assert.deepStrictEqual(res.jobs.map((job) => { 200 | assert.strictEqual(job.retryCount, job.job === 'job8' ? 1 : 0) 201 | assert.strictEqual(job.timing > time, true) 202 | assert.strictEqual(job.active > time, true) 203 | return job.job 204 | }), ['job8', 'job9']) 205 | timedQueue.close() 206 | }) 207 | 208 | tman.it('queue.scan', function * () { 209 | const jobs = [] 210 | const tasks = [] 211 | 212 | let time = Date.now() + 100 213 | const timedQueue = new TimedQueue({ autoScan: false }).connect(redis.createClient()) 214 | const queue = timedQueue.queue(getName(), { 215 | count: 8, 216 | retry: 1000, 217 | expire: 3000, 218 | accuracy: 300 219 | }) 220 | 221 | for (let i = 0; i < 100; i++) tasks.push(i) 222 | 223 | yield queue.scan()((err) => assert.strictEqual(err, null)) 224 | 225 | queue.on('job', function (job) { 226 | jobs.push(job) 227 | }) 228 | yield queue.scan() 229 | assert.deepStrictEqual(jobs, []) 230 | 231 | yield tasks.map((index) => queue.addjob(String(index), time + index * 100)) 232 | let res = yield queue.len() 233 | assert.strictEqual(res, 100) 234 | 235 | res = yield queue.scan() 236 | assert.strictEqual(res.length, 3) 237 | assert.strictEqual(Math.abs(res.reduce((m, v) => { return m + v }, 0) / res.length) < 1, true) 238 | assert.strictEqual(jobs.length, 0) // jobs should be emit in next tick 239 | 240 | yield thunk.delay(1000) 241 | assert.deepStrictEqual(jobs.map((job) => { 242 | assert.strictEqual(job.timing >= time, true) 243 | return job.job 244 | }), ['0', '1', '2']) 245 | 246 | res = yield queue.scan() 247 | assert.strictEqual(res.length, 10) 248 | assert.strictEqual(Math.abs(res.reduce((m, v) => { return m + v }, 0) / res.length) < 1, true) 249 | 250 | yield thunk.delay(2000) 251 | assert.deepStrictEqual(jobs.map((job) => { 252 | assert.strictEqual(job.timing >= time, true) 253 | return job.job 254 | }), [ 255 | '0', '1', '2', '0', '1', '2', '3', '4', '5', '6', '7', 256 | '8', '9', '10', '11', '12' 257 | ]) 258 | jobs.length = 0 259 | 260 | res = yield queue.scan() 261 | assert.strictEqual(res.length, 20) 262 | assert.strictEqual(Math.abs(res.reduce((m, v) => { return m + v }, 0) / res.length) < 1, true) 263 | yield thunk.delay() 264 | // '0', '1', '2' should expired 265 | let currentJobs = [ 266 | '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', 267 | '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', 268 | '28', '29', '30', '31', '32' 269 | ] 270 | assert.deepStrictEqual(jobs.map((job) => { 271 | assert.strictEqual(job.timing >= time, true) 272 | return job.job 273 | }), currentJobs) 274 | 275 | res = yield queue.ackjob(currentJobs) 276 | assert.strictEqual(res, 30) 277 | 278 | res = yield queue.scan() 279 | assert.deepStrictEqual(res, []) 280 | timedQueue.close() 281 | }) 282 | 283 | tman.suite('chaos: 100000 random jobs', function () { 284 | const jobs = [] 285 | const check = new Map() 286 | 287 | tman.it('ok', function (done) { 288 | const queueT = thunkQueue() 289 | const timedQueue = new TimedQueue({ interval: 1000 }).connect(redis.createClient()) 290 | const queues = [timedQueue.queue(getName()), timedQueue.queue(getName()), timedQueue.queue(getName())] 291 | 292 | let i = 100000 293 | while (i--) jobs.push(String(i)) 294 | 295 | for (let queue of queues) { 296 | queue.on('job', (job) => { 297 | if (check.get(job.job) !== job.timing) { 298 | queueT.push(() => { throw new Error(`uncaughtException: ${check[job.job]}, ${JSON.stringify(job)}`) }) 299 | } else { 300 | check.delete(job.job) 301 | queueT.push(queue.ackjob(job.job)((err) => { 302 | if (err) throw err 303 | })) 304 | } 305 | if (!check.size) queueT.end() 306 | }) 307 | } 308 | 309 | thunk(function * () { 310 | while (jobs.length) { 311 | let i = 0 312 | let list = [] 313 | let time = Date.now() + 2000 314 | let seed = Math.ceil(Math.random() * 10000) 315 | // push random jobs 316 | while (jobs.length && i++ < seed) { 317 | let job = jobs.pop() 318 | check.set(job, time + (i < 3000 ? i : Math.floor(i / 3))) 319 | list.push(queues[i % 3].addjob(job, check.get(job))) 320 | } 321 | 322 | yield list 323 | yield thunk.delay(seed / 10) 324 | } 325 | 326 | // wait for jobs ACK 327 | yield queueT 328 | })(done) 329 | }) 330 | }) 331 | }) 332 | -------------------------------------------------------------------------------- /test/performance.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // **Github:** https://github.com/teambition/timed-queue 4 | // 5 | // **License:** MIT 6 | 7 | const thunk = require('thunks')() 8 | const TimedQueue = require('../index.js') 9 | const timedQueue = new TimedQueue({ autoScan: false }) 10 | 11 | timedQueue.connect() 12 | 13 | const queue = timedQueue.queue('performance').on('job', function (job) { /* console.log(job) */ }) 14 | 15 | thunk(function * () { 16 | console.log('jobs:', yield queue.len()) 17 | console.log('add jobs:') 18 | let id = 0 19 | let batch = 10000 20 | 21 | let array = [] 22 | while (array.length < 200) array.push(1) 23 | let time = Date.now() 24 | while (batch--) { 25 | yield array.map(function () { 26 | return queue.addjob(`performance_test:${id++}`, time + id * 1000) 27 | }) 28 | if (!(batch % 100)) process.stdout.write('.') 29 | } 30 | 31 | console.log(`\n${id} jobs added, ${Date.now() - time} ms.`) 32 | console.log('current jobs:', yield queue.len()) 33 | console.log('scan active jobs:') 34 | 35 | time = Date.now() 36 | let scan = yield queue.scan() 37 | console.log(`${scan.length} jobs scaned, ${Date.now() - time} ms.`) 38 | console.log('remain jobs:', yield queue.len()) 39 | 40 | let activedJobs = yield queue.showActive() 41 | console.log('actived jobs:', activedJobs.length, 'a actived job:\n', activedJobs[activedJobs.length - 1]) 42 | 43 | yield timedQueue.destroyQueue('performance') 44 | timedQueue.close() 45 | })() 46 | 47 | // jobs: 0 48 | // add jobs: 49 | // .................................................................................................... 50 | // 2000000 jobs added, 68622 ms. 51 | // current jobs: 2000000 52 | // scan active jobs: 53 | // 80 jobs scaned, 7 ms. 54 | // remain jobs: 1999920 55 | // actived jobs: 80 a actived job: 56 | // Job { 57 | // queue: 'performance', 58 | // job: 'performance_test:79', 59 | // timing: 1494938200330, 60 | // active: 1494938188959, 61 | // retryCount: 0 } 62 | --------------------------------------------------------------------------------