diff --git a/README.md b/README.md index 0163a1d..6407fad 100644 --- a/README.md +++ b/README.md @@ -472,9 +472,13 @@ Clustering lets many limiters access the same shared state, stored in a Redis se ##### Enabling Clustering -__IMPORTANT:__ Add `redis` to your application's dependencies. -``` +__IMPORTANT:__ Add `redis` or `ioredis` to your application's dependencies. +```bash +# To use https://github.com/NodeRedis/node_redis npm install --save redis + +# To use https://github.com/luin/ioredis +npm install --save ioredis ``` ```js @@ -485,11 +489,13 @@ const limiter = new Bottleneck({ id: "my-super-app" // Should be unique for every limiter in the same Redis db /* Clustering options */ - datastore: "redis", + datastore: "redis", // or "ioredis" clearDatastore: false, clientOptions: { - // node-redis client options, passed to redis.createClient() - // See https://github.com/NodeRedis/node_redis#options-object-properties + // Redis client options + // For NodeRedis, see https://github.com/NodeRedis/node_redis#options-object-properties + // For ioredis, see https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options + host: "127.0.0.1", port: 6379 // "db" is another useful option @@ -499,9 +505,10 @@ const limiter = new Bottleneck({ | Option | Default | Description | |--------|---------|-------------| -| `datastore` | `"local"` | Where the limiter stores its internal state. The default (`local`) keeps the state in the limiter itself. Set it to `redis` to enable Clustering. | +| `datastore` | `"local"` | Where the limiter stores its internal state. The default (`"local"`) keeps the state in the limiter itself. Set it to `"redis"` or `"ioredis"` to enable Clustering. | | `clearDatastore` | `false` | When set to `true`, on initial startup, the limiter will wipe any existing Bottleneck state data on the Redis db. | -| `clientOptions` | `{}` | This object is passed directly to NodeRedis's `redis.createClient()` method. [See all the valid client options.](https://github.com/NodeRedis/node_redis#options-object-properties) | +| `clientOptions` | `{}` | This object is passed directly to the redis client library you've selected. | +| `clusterNodes` | `null` | **ioredis only.** When `clusterNodes` is not null, the client will be instantiated by calling `new Redis.Cluster(clusterNodes, clientOptions)` instead of `new Redis(clientOptions)`. | | `timeout` | `null` | The Redis TTL in milliseconds ([TTL](https://redis.io/commands/ttl)) for the keys created by the limiter. When `timeout` is set, the limiter's state will be automatically removed from Redis after `timeout` milliseconds of inactivity. **Note:** `timeout` is `300000` (5 minutes) by default when using a Group. | ###### `.ready()` @@ -520,11 +527,11 @@ limiter.ready() }) ``` -The `.ready()` method also exists when using the `local` datastore, for code compatibility reasons: code written for `redis` will always work with `local`. +The `.ready()` method also exists when using the `local` datastore, for code compatibility reasons: code written for `redis`/`ioredis` will also work with `local`. ###### `.disconnect(flush)` -This helper method calls the `.end(flush)` method on the Redis clients used by a limiter. +This helper method disconnects the limiter's client from the Redis server. ```js limiter.disconnect(true) @@ -591,8 +598,8 @@ clusterLimiter.ready() - As of v2.7.0, each Group will create 2 connections to Redis, one for commands and one for pub/sub. All limiters within the Group will share those connections. - Each standalone limiter has its own 2 connections. - Redis connectivity errors trigger an `error` event on the owner of the connection (the Group or the limiter). -- Bottleneck itself is compatible with [Redis Clusters](https://redis.io/topics/cluster-tutorial), but `NodeRedis` may not support it at the moment. Future versions of Bottleneck will support the `ioredis` driver. -- Bottleneck's data is stored in Redis keys starting with `b_`. It also uses pub/sub channels starting with `b_` It will not interfere with any other data stored on the server. +- Bottleneck is compatible with [Redis Clusters](https://redis.io/topics/cluster-tutorial) and Redis Sentinel, but you must use the `ioredis` datastore and pass the `clusterNodes` option. +- Bottleneck's data is stored in Redis keys starting with `b_`. It also uses pub/sub channels starting with `bottleneck_` It will not interfere with any other data stored on the server. - Bottleneck loads a few Lua scripts on the Redis server using the `SCRIPT LOAD` command. These scripts only take up a few Kb of memory. Running the `SCRIPT FLUSH` command will cause any connected limiters to experience critical errors until a new limiter connects to Redis and loads the scripts again. - The Lua scripts are highly optimized and designed to use as few resources (CPU, especially) as possible. @@ -753,13 +760,11 @@ This README is always in need of improvements. If wording can be clearer and sim Suggestions and bug reports are also welcome. -To work on the Bottleneck code, simply clone the repo, and run `./scripts/build.sh && npm test` to ensure that everything is set up correctly. - -Make your changes to the files located in `src/` only, then run `./scripts/build.sh && npm test` to build and test them. +To work on the Bottleneck code, simply clone the repo, makes your changes to the files located in `src/` only, then run `./scripts/build.sh && npm test` to ensure that everything is set up correctly. To speed up compilation time during development, run `./scripts/build.sh dev` instead. Make sure to build and test without `dev` before submitting a PR. -The tests must also pass in Clustering mode. You'll need a Redis server running on `127.0.0.1:6379`, then run `./scripts/build.sh && DATASTORE=redis npm test`. +The tests must also pass in Clustering mode. You'll need a Redis server running on `127.0.0.1:6379`, then run `./scripts/build.sh && DATASTORE=redis npm test && DATASTORE=ioredis npm test`. All contributions are appreciated and will be considered. diff --git a/bottleneck.d.ts b/bottleneck.d.ts index 8c3664b..a8276b7 100644 --- a/bottleneck.d.ts +++ b/bottleneck.d.ts @@ -46,9 +46,13 @@ declare module "bottleneck" { */ readonly Promise?: any; /** - * This object is passed directly to NodeRedis's `redis.createClient()` method. + * This object is passed directly to the redis client library you've selected. */ readonly clientOptions?: any; + /** + * **ioredis only.** When `clusterNodes` is not null, the client will be instantiated by calling `new Redis.Cluster(clusterNodes, clientOptions)`. + */ + readonly clusterNodes?: any; /** * When set to `true`, on initial startup, the limiter will wipe any existing Bottleneck state data on the Redis db. */ diff --git a/bottleneck.d.ts.ejs b/bottleneck.d.ts.ejs index 592ec3f..a1ce37e 100644 --- a/bottleneck.d.ts.ejs +++ b/bottleneck.d.ts.ejs @@ -46,9 +46,13 @@ declare module "bottleneck" { */ readonly Promise?: any; /** - * This object is passed directly to NodeRedis's `redis.createClient()` method. + * This object is passed directly to the redis client library you've selected. */ readonly clientOptions?: any; + /** + * **ioredis only.** When `clusterNodes` is not null, the client will be instantiated by calling `new Redis.Cluster(clusterNodes, clientOptions)`. + */ + readonly clusterNodes?: any; /** * When set to `true`, on initial startup, the limiter will wipe any existing Bottleneck state data on the Redis db. */ diff --git a/bottleneck.js b/bottleneck.js index 33ecc0f..11b6224 100644 --- a/bottleneck.js +++ b/bottleneck.js @@ -95,7 +95,8 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a var _this = this; return _asyncToGenerator(function* () { - return yield _this._store.__disconnect__(flush); + yield _this._store.__disconnect__(flush); + return _this; })(); } @@ -580,6 +581,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a Bottleneck.prototype.storeInstanceDefaults = { clientOptions: {}, + clusterNodes: null, clearDatastore: false, Promise: Promise, timeout: null, @@ -780,7 +782,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a Group = function () { class Group { constructor(limiterOptions = {}) { - var ref, ref1, ref2, ref3; + var ref, ref1, ref2, ref3, ref4; this.key = this.key.bind(this); this.deleteKey = this.deleteKey.bind(this); this.limiters = this.limiters.bind(this); @@ -796,7 +798,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a if (this.limiterOptions.datastore === "redis") { this._connection = new RedisConnection((ref = this.limiterOptions.clientOptions) != null ? ref : {}, (ref1 = this.limiterOptions.Promise) != null ? ref1 : Promise, this.Events); } else if (this.limiterOptions.datastore === "ioredis") { - this._connection = new IORedisConnection((ref2 = this.limiterOptions.clientOptions) != null ? ref2 : {}, (ref3 = this.limiterOptions.Promise) != null ? ref3 : Promise, this.Events); + this._connection = new IORedisConnection((ref2 = this.limiterOptions.clusterNodes) != null ? ref2 : null, (ref3 = this.limiterOptions.clientOptions) != null ? ref3 : {}, (ref4 = this.limiterOptions.Promise) != null ? ref4 : Promise, this.Events); } } @@ -901,14 +903,20 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a Scripts = require("./Scripts"); IORedisConnection = class IORedisConnection { - constructor(clientOptions, Promise, Events) { + constructor(clusterNodes, clientOptions, Promise, Events) { var Redis; + this.clusterNodes = clusterNodes; this.clientOptions = clientOptions; this.Promise = Promise; this.Events = Events; Redis = eval("require")("ioredis"); // Obfuscated or else Webpack/Angular will try to inline the optional ioredis module - this.client = new Redis(this.clientOptions); - this.subClient = new Redis(this.clientOptions); + if (this.clusterNodes != null) { + this.client = new Redis.Cluster(this.clusterNodes, this.clientOptions); + this.subClient = new Redis.Cluster(this.clusterNodes, this.clientOptions); + } else { + this.client = new Redis(this.clientOptions); + this.subClient = new Redis(this.clientOptions); + } this.pubsubs = {}; this.ready = new this.Promise((resolve, reject) => { var count, done, errorListener; @@ -981,8 +989,13 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a } disconnect(flush) { - this.client.end(flush); - return this.subClient.end(flush); + if (flush) { + return this.Promise.all([this.client.quit(), this.subClient.quit()]); + } else { + this.client.disconnect(); + this.subClient.disconnect(); + return this.Promise.resolve(); + } } }; @@ -1016,7 +1029,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a } __disconnect__(flush) { - return this; + return this.Promise.resolve(); } yieldLoop(t = 0) { @@ -1194,13 +1207,13 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a RedisConnection = class RedisConnection { constructor(clientOptions, Promise, Events) { - var redis; + var Redis; this.clientOptions = clientOptions; this.Promise = Promise; this.Events = Events; - redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module - this.client = redis.createClient(this.clientOptions); - this.subClient = redis.createClient(this.clientOptions); + Redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module + this.client = Redis.createClient(this.clientOptions); + this.subClient = Redis.createClient(this.clientOptions); this.pubsubs = {}; this.shas = {}; this.ready = new this.Promise((resolve, reject) => { @@ -1285,7 +1298,8 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a disconnect(flush) { this.client.end(flush); - return this.subClient.end(flush); + this.subClient.end(flush); + return this.Promise.resolve(); } }; @@ -1320,11 +1334,11 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a this.originalId = this.instance.id; parser.load(options, options, this); this.isReady = false; - this.connection = this._groupConnection ? this._groupConnection : this.instance.datastore === "redis" ? new RedisConnection(this.clientOptions, this.Promise, this.instance.Events) : this.instance.datastore === "ioredis" ? new IORedisConnection(this.clientOptions, this.Promise, this.instance.Events) : void 0; + this.connection = this._groupConnection ? this._groupConnection : this.instance.datastore === "redis" ? new RedisConnection(this.clientOptions, this.Promise, this.instance.Events) : this.instance.datastore === "ioredis" ? new IORedisConnection(this.clusterNodes, this.clientOptions, this.Promise, this.instance.Events) : void 0; this.ready = this.connection.ready.then(clients => { var args; this.clients = clients; - args = this.prepareInitSettings(options.clearDatastore); + args = this.prepareInitSettings(this.clearDatastore); this.isReady = true; return this.runScript("init", args); }).then(() => { diff --git a/bottleneck.min.js b/bottleneck.min.js index 0801963..5a35c38 100644 --- a/bottleneck.min.js +++ b/bottleneck.min.js @@ -1 +1 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i=ref;i=1<=ref?++j:--j){results.push(new DLList)}return results}_sanitizePriority(priority){var sProperty;sProperty=~~priority!==priority?DEFAULT_PRIORITY:priority;if(sProperty<0){return 0}else if(sProperty>NUM_PRIORITIES-1){return NUM_PRIORITIES-1}else{return sProperty}}_find(arr,fn){var ref;return(ref=function(){var i,j,len,x;for(i=j=0,len=arr.length;j0})}_randomIndex(){return Math.random().toString(36).slice(2)}check(weight=1){var _this3=this;return _asyncToGenerator(function*(){return yield _this3._store.__check__(weight)})()}_run(next,wait,index){var _this4=this;var completed,done;this.Events.trigger("debug",[`Scheduling ${next.options.id}`,{args:next.args,options:next.options}]);done=false;completed=(()=>{var _ref=_asyncToGenerator(function*(...args){var e,ref,running;if(!done){try{done=true;_this4._states.next(next.options.id);clearTimeout(_this4._scheduled[index].expiration);delete _this4._scheduled[index];_this4.Events.trigger("debug",[`Completed ${next.options.id}`,{args:next.args,options:next.options}]);_this4.Events.trigger("done",[`Completed ${next.options.id}`,{args:next.args,options:next.options}]);var _ref2=yield _this4._store.__free__(index,next.options.weight);running=_ref2.running;_this4.Events.trigger("debug",[`Freed ${next.options.id}`,{args:next.args,options:next.options}]);_this4._drainAll().catch(function(e){return _this4.Events.trigger("error",[e])});if(running===0&&_this4.empty()){_this4.Events.trigger("idle",[])}return(ref=next.cb)!=null?ref.apply({},args):void 0}catch(error){e=error;return _this4.Events.trigger("error",[e])}}});return function completed(){return _ref.apply(this,arguments)}})();this._states.next(next.options.id);return this._scheduled[index]={timeout:setTimeout(()=>{this.Events.trigger("debug",[`Executing ${next.options.id}`,{args:next.args,options:next.options}]);this._states.next(next.options.id);if(this._limiter!=null){return this._limiter.submit.apply(this._limiter,Array.prototype.concat(next.options,next.task,next.args,completed))}else{return next.task.apply({},next.args.concat(completed))}},wait),expiration:next.options.expiration!=null?setTimeout(()=>{return completed(new Bottleneck.prototype.BottleneckError(`This job timed out after ${next.options.expiration} ms.`))},wait+next.options.expiration):void 0,job:next}}_drainOne(freed){return this._registerLock.schedule(()=>{var args,index,options,queue;if(this.queued()===0){return this.Promise.resolve(false)}queue=this._getFirst(this._queues);var _queue$first=queue.first();options=_queue$first.options;args=_queue$first.args;if(freed!=null&&options.weight>freed){return this.Promise.resolve(false)}this.Events.trigger("debug",[`Draining ${options.id}`,{args:args,options:options}]);index=this._randomIndex();return this._store.__register__(index,options.weight,options.expiration).then(({success:success,wait:wait,reservoir:reservoir})=>{var empty,next;this.Events.trigger("debug",[`Drained ${options.id}`,{success:success,args:args,options:options}]);if(success){next=queue.shift();empty=this.empty();if(empty){this.Events.trigger("empty",[])}if(reservoir===0){this.Events.trigger("depleted",[empty])}this._run(next,wait,index)}return this.Promise.resolve(success)})})}_drainAll(freed){return this._drainOne(freed).then(success=>{if(success){return this._drainAll()}else{return this.Promise.resolve(success)}}).catch(e=>{return this.Events.trigger("error",[e])})}_drop(job,message="This job has been dropped by Bottleneck"){var ref;this._states.remove(job.options.id);if(this.rejectOnDrop){if((ref=job.cb)!=null){ref.apply({},[new Bottleneck.prototype.BottleneckError(message)])}}return this.Events.trigger("dropped",[job])}stop(options={}){var done,waitForExecuting;options=parser.load(options,this.stopDefaults);waitForExecuting=(at=>{var finished;finished=(()=>{var counts;counts=this._states.counts;return counts[0]+counts[1]+counts[2]+counts[3]===at});return new this.Promise((resolve,reject)=>{if(finished()){return resolve()}else{return this.on("done",()=>{if(finished()){this.removeAllListeners("done");return resolve()}})}})});done=options.dropWaitingJobs?(this._run=(next=>{return this._drop(next,options.dropErrorMessage)}),this._drainOne=(()=>{return Promise.resolve(false)}),this.Promise.all([this._registerLock.schedule(()=>{return Promise.resolve(true)},this._submitLock.schedule(()=>{return Promise.resolve(true)}))]).then(()=>{var k,ref,v;ref=this._scheduled;for(k in ref){v=ref[k];if(this.jobStatus(v.job.options.id)==="RUNNING"){clearTimeout(v.timeout);clearTimeout(v.expiration);this._drop(v.job,options.dropErrorMessage)}}this._queues.forEach(queue=>{return queue.forEachShift(job=>{return this._drop(job,options.dropErrorMessage)})});return waitForExecuting(0)})):this.schedule({priority:NUM_PRIORITIES-1,weight:0},()=>{return waitForExecuting(1)});this.submit=((...args)=>{var _ref3,_ref4,_splice$call,_splice$call2;var cb,ref;ref=args,_ref3=ref,_ref4=_toArray(_ref3),args=_ref4.slice(0),_ref3,_splice$call=splice.call(args,-1),_splice$call2=_slicedToArray(_splice$call,1),cb=_splice$call2[0],_splice$call;return cb!=null?cb.apply({},[new Bottleneck.prototype.BottleneckError(options.enqueueErrorMessage)]):void 0});return done}submit(...args){var _this5=this;var cb,job,options,ref,ref1,ref2,task;if(typeof args[0]==="function"){var _ref5,_ref6,_splice$call3,_splice$call4;ref=args,_ref5=ref,_ref6=_toArray(_ref5),task=_ref6[0],args=_ref6.slice(1),_ref5,_splice$call3=splice.call(args,-1),_splice$call4=_slicedToArray(_splice$call3,1),cb=_splice$call4[0],_splice$call3;options=parser.load({},this.jobDefaults,{})}else{var _ref7,_ref8,_splice$call5,_splice$call6;ref1=args,_ref7=ref1,_ref8=_toArray(_ref7),options=_ref8[0],task=_ref8[1],args=_ref8.slice(2),_ref7,_splice$call5=splice.call(args,-1),_splice$call6=_slicedToArray(_splice$call5,1),cb=_splice$call6[0],_splice$call5;options=parser.load(options,this.jobDefaults)}job={options:options,task:task,args:args,cb:cb};options.priority=this._sanitizePriority(options.priority);if(options.id===this.jobDefaults.id){options.id=`${options.id}-${this._randomIndex()}`}if(this.jobStatus(options.id)!=null){if((ref2=job.cb)!=null){ref2.apply({},[new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${options.id})`)])}return false}this._states.start(options.id);this.Events.trigger("debug",[`Queueing ${options.id}`,{args:args,options:options}]);return this._submitLock.schedule(_asyncToGenerator(function*(){var blocked,e,reachedHWM,ref3,shifted,strategy;try{var _ref10=yield _this5._store.__submit__(_this5.queued(),options.weight);reachedHWM=_ref10.reachedHWM;blocked=_ref10.blocked;strategy=_ref10.strategy;_this5.Events.trigger("debug",[`Queued ${options.id}`,{args:args,options:options,reachedHWM:reachedHWM,blocked:blocked}])}catch(error){e=error;_this5._states.remove(options.id);_this5.Events.trigger("debug",[`Could not queue ${options.id}`,{args:args,options:options,error:e}]);if((ref3=job.cb)!=null){ref3.apply({},[e])}return false}if(blocked){_this5._queues=_this5._makeQueues();_this5._drop(job);return true}else if(reachedHWM){shifted=strategy===Bottleneck.prototype.strategy.LEAK?_this5._getFirst(_this5._queues.slice(options.priority).reverse()).shift():strategy===Bottleneck.prototype.strategy.OVERFLOW_PRIORITY?_this5._getFirst(_this5._queues.slice(options.priority+1).reverse()).shift():strategy===Bottleneck.prototype.strategy.OVERFLOW?job:void 0;if(shifted!=null){_this5._drop(shifted)}if(shifted==null||strategy===Bottleneck.prototype.strategy.OVERFLOW){if(shifted==null){_this5._drop(job)}return reachedHWM}}_this5._states.next(job.options.id);_this5._queues[options.priority].push(job);yield _this5._drainAll();return reachedHWM}))}schedule(...args){var options,task,wrapped;if(typeof args[0]==="function"){var _args=args;var _args2=_toArray(_args);task=_args2[0];args=_args2.slice(1);options=parser.load({},this.jobDefaults,{})}else{var _args3=args;var _args4=_toArray(_args3);options=_args4[0];task=_args4[1];args=_args4.slice(2);options=parser.load(options,this.jobDefaults)}wrapped=function wrapped(...args){var _ref11,_ref12,_splice$call7,_splice$call8;var cb,ref,returned;ref=args,_ref11=ref,_ref12=_toArray(_ref11),args=_ref12.slice(0),_ref11,_splice$call7=splice.call(args,-1),_splice$call8=_slicedToArray(_splice$call7,1),cb=_splice$call8[0],_splice$call7;returned=task.apply({},args);return(!((returned!=null?returned.then:void 0)!=null&&typeof returned.then==="function")?Promise.resolve(returned):returned).then(function(...args){return cb.apply({},Array.prototype.concat(null,args))}).catch(function(...args){return cb.apply({},args)})};return new this.Promise((resolve,reject)=>{return this.submit.apply({},Array.prototype.concat(options,wrapped,args,function(...args){return(args[0]!=null?reject:(args.shift(),resolve)).apply({},args)})).catch(e=>{return this.Events.trigger("error",[e])})})}wrap(fn){var ret;ret=((...args)=>{return this.schedule.apply({},Array.prototype.concat(fn,args))});ret.withOptions=((options,...args)=>{return this.schedule.apply({},Array.prototype.concat(options,fn,args))});return ret}updateSettings(options={}){var _this6=this;return _asyncToGenerator(function*(){yield _this6._store.__updateSettings__(parser.overwrite(options,_this6.storeDefaults));parser.overwrite(options,_this6.instanceDefaults,_this6);_this6._drainAll().catch(function(e){return _this6.Events.trigger("error",[e])});return _this6})()}currentReservoir(){var _this7=this;return _asyncToGenerator(function*(){return yield _this7._store.__currentReservoir__()})()}incrementReservoir(incr=0){var _this8=this;return _asyncToGenerator(function*(){yield _this8._store.__incrementReservoir__(incr);_this8._drainAll().catch(function(e){return _this8.Events.trigger("error",[e])});return _this8})()}}Bottleneck.default=Bottleneck;Bottleneck.version=Bottleneck.prototype.version=packagejson.version;Bottleneck.strategy=Bottleneck.prototype.strategy={LEAK:1,OVERFLOW:2,OVERFLOW_PRIORITY:4,BLOCK:3};Bottleneck.BottleneckError=Bottleneck.prototype.BottleneckError=require("./BottleneckError");Bottleneck.Group=Bottleneck.prototype.Group=require("./Group");Bottleneck.prototype.jobDefaults={priority:DEFAULT_PRIORITY,weight:1,expiration:null,id:""};Bottleneck.prototype.storeDefaults={maxConcurrent:null,minTime:0,highWater:null,strategy:Bottleneck.prototype.strategy.LEAK,penalty:null,reservoir:null};Bottleneck.prototype.storeInstanceDefaults={clientOptions:{},clearDatastore:false,Promise:Promise,timeout:null,_groupConnection:null};Bottleneck.prototype.instanceDefaults={datastore:"local",id:"",rejectOnDrop:true,trackDoneStatus:false,Promise:Promise};Bottleneck.prototype.stopDefaults={enqueueErrorMessage:"This limiter has been stopped and cannot accept new jobs.",dropWaitingJobs:true,dropErrorMessage:"This limiter has been stopped."};return Bottleneck}.call(this);module.exports=Bottleneck}).call(undefined)},{"../package.json":16,"./BottleneckError":2,"./DLList":3,"./Events":4,"./Group":5,"./LocalDatastore":7,"./RedisDatastore":9,"./States":11,"./Sync":12,"./parser":15}],2:[function(require,module,exports){"use strict";(function(){var BottleneckError;BottleneckError=class BottleneckError extends Error{};module.exports=BottleneckError}).call(undefined)},{}],3:[function(require,module,exports){"use strict";(function(){var DLList;DLList=class DLList{constructor(){this._first=null;this._last=null;this.length=0}push(value){var node;this.length++;node={value:value,next:null};if(this._last!=null){this._last.next=node;this._last=node}else{this._first=this._last=node}return void 0}shift(){var ref1,value;if(this._first==null){return void 0}else{this.length--}value=this._first.value;this._first=(ref1=this._first.next)!=null?ref1:this._last=null;return value}first(){if(this._first!=null){return this._first.value}}getArray(){var node,ref,results;node=this._first;results=[];while(node!=null){results.push((ref=node,node=node.next,ref.value))}return results}forEachShift(cb){var node;node=this.shift();while(node!=null){cb(node),node=this.shift()}return void 0}};module.exports=DLList}).call(undefined)},{}],4:[function(require,module,exports){"use strict";(function(){var Events;Events=class Events{constructor(instance){this.instance=instance;this._events={};this.instance.on=((name,cb)=>{return this._addListener(name,"many",cb)});this.instance.once=((name,cb)=>{return this._addListener(name,"once",cb)});this.instance.removeAllListeners=((name=null)=>{if(name!=null){return delete this._events[name]}else{return this._events={}}})}_addListener(name,status,cb){var base;if((base=this._events)[name]==null){base[name]=[]}this._events[name].push({cb:cb,status:status});return this.instance}trigger(name,args){if(name!=="debug"){this.trigger("debug",[`Event triggered: ${name}`,args])}if(this._events[name]==null){return}this._events[name]=this._events[name].filter(function(listener){return listener.status!=="none"});return this._events[name].forEach(listener=>{var e,ret;if(listener.status==="none"){return}if(listener.status==="once"){listener.status="none"}try{ret=listener.cb.apply({},args);if(typeof(ret!=null?ret.then:void 0)==="function"){return ret.then(function(){}).catch(e=>{return this.trigger("error",[e])})}}catch(error){e=error;if("name"!=="error"){return this.trigger("error",[e])}}})}};module.exports=Events}).call(undefined)},{}],5:[function(require,module,exports){"use strict";function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step("next",value)},function(err){step("throw",err)})}}return step("next")})}}(function(){var Events,Group,IORedisConnection,RedisConnection,parser;parser=require("./parser");Events=require("./Events");RedisConnection=require("./RedisConnection");IORedisConnection=require("./IORedisConnection");Group=function(){class Group{constructor(limiterOptions={}){var ref,ref1,ref2,ref3;this.key=this.key.bind(this);this.deleteKey=this.deleteKey.bind(this);this.limiters=this.limiters.bind(this);this.keys=this.keys.bind(this);this._startAutoCleanup=this._startAutoCleanup.bind(this);this.updateSettings=this.updateSettings.bind(this);this.limiterOptions=limiterOptions;parser.load(this.limiterOptions,this.defaults,this);this.Events=new Events(this);this.instances={};this.Bottleneck=require("./Bottleneck");this._startAutoCleanup();if(this.limiterOptions.datastore==="redis"){this._connection=new RedisConnection((ref=this.limiterOptions.clientOptions)!=null?ref:{},(ref1=this.limiterOptions.Promise)!=null?ref1:Promise,this.Events)}else if(this.limiterOptions.datastore==="ioredis"){this._connection=new IORedisConnection((ref2=this.limiterOptions.clientOptions)!=null?ref2:{},(ref3=this.limiterOptions.Promise)!=null?ref3:Promise,this.Events)}}key(key=""){var ref;return(ref=this.instances[key])!=null?ref:(()=>{var limiter;limiter=this.instances[key]=new this.Bottleneck(Object.assign(this.limiterOptions,{id:`group-key-${key}`,timeout:this.timeout,_groupConnection:this._connection}));this.Events.trigger("created",[limiter,key]);return limiter})()}deleteKey(key=""){var ref;if((ref=this.instances[key])!=null){ref.disconnect()}return delete this.instances[key]}limiters(){var k,ref,results,v;ref=this.instances;results=[];for(k in ref){v=ref[k];results.push({key:k,limiter:v})}return results}keys(){return Object.keys(this.instances)}_startAutoCleanup(){var _this=this;var base;clearInterval(this.interval);return typeof(base=this.interval=setInterval(_asyncToGenerator(function*(){var e,k,ref,results,time,v;time=Date.now();ref=_this.instances;results=[];for(k in ref){v=ref[k];try{if(yield v._store.__groupCheck__(time)){results.push(_this.deleteKey(k))}else{results.push(void 0)}}catch(error){e=error;results.push(v.Events.trigger("error",[e]))}}return results}),this.timeout/2)).unref==="function"?base.unref():void 0}updateSettings(options={}){parser.overwrite(options,this.defaults,this);parser.overwrite(options,options,this.limiterOptions);if(options.timeout!=null){return this._startAutoCleanup()}}disconnect(flush){var ref;return(ref=this._connection)!=null?ref.disconnect(flush):void 0}}Group.prototype.defaults={timeout:1e3*60*5};return Group}.call(this);module.exports=Group}).call(undefined)},{"./Bottleneck":1,"./Events":4,"./IORedisConnection":6,"./RedisConnection":8,"./parser":15}],6:[function(require,module,exports){"use strict";(function(){var IORedisConnection,Scripts;Scripts=require("./Scripts");IORedisConnection=class IORedisConnection{constructor(clientOptions,Promise,Events){var Redis;this.clientOptions=clientOptions;this.Promise=Promise;this.Events=Events;Redis=eval("require")("ioredis");this.client=new Redis(this.clientOptions);this.subClient=new Redis(this.clientOptions);this.pubsubs={};this.ready=new this.Promise((resolve,reject)=>{var count,done,errorListener;errorListener=(e=>{[this.client,this.subClient].forEach(client=>{return client.removeListener("error",errorListener)});return reject(e)});count=0;done=(()=>{count++;if(count===2){[this.client,this.subClient].forEach(client=>{client.removeListener("error",errorListener);return client.on("error",e=>{return this.Events.trigger("error",[e])})});return resolve({client:this.client,subscriber:this.subClient})}});this.client.on("error",errorListener);this.client.on("ready",function(){return done()});this.subClient.on("error",errorListener);this.subClient.on("ready",()=>{return this.subClient.psubscribe("bottleneck_*",function(){return done()})});return this.subClient.on("pmessage",(pattern,channel,message)=>{var base;return typeof(base=this.pubsubs)[channel]==="function"?base[channel](message):void 0})}).then(()=>{return Scripts.names.forEach(name=>{return this.client.defineCommand(name,{lua:Scripts.payload(name)})})}).then(()=>{return this.Promise.resolve({client:this.client,subscriber:this.subClient})})}addLimiter(instance,pubsub){return this.pubsubs[`bottleneck_${instance.id}`]=pubsub}removeLimiter(instance){return delete this.pubsubs[`bottleneck_${instance.id}`]}scriptArgs(name,id,args,cb){var keys;keys=Scripts.keys(name,id);return[keys.length].concat(keys,args,cb)}scriptFn(name){return this.client[name].bind(this.client)}disconnect(flush){this.client.end(flush);return this.subClient.end(flush)}};module.exports=IORedisConnection}).call(undefined)},{"./Scripts":10}],7:[function(require,module,exports){"use strict";function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step("next",value)},function(err){step("throw",err)})}}return step("next")})}}(function(){var BottleneckError,DLList,LocalDatastore,parser;parser=require("./parser");DLList=require("./DLList");BottleneckError=require("./BottleneckError");LocalDatastore=class LocalDatastore{constructor(options){parser.load(options,options,this);this._nextRequest=Date.now();this._running=0;this._executing={};this._unblockTime=0;this.ready=this.yieldLoop();this.clients={}}__disconnect__(flush){return this}yieldLoop(t=0){return new this.Promise(function(resolve,reject){return setTimeout(resolve,t)})}computePenalty(){var ref;return(ref=this.penalty)!=null?ref:15*this.minTime||5e3}__updateSettings__(options){var _this=this;return _asyncToGenerator(function*(){yield _this.yieldLoop();parser.overwrite(options,options,_this);return true})()}__running__(){var _this2=this;return _asyncToGenerator(function*(){yield _this2.yieldLoop();return _this2._running})()}__groupCheck__(time){var _this3=this;return _asyncToGenerator(function*(){yield _this3.yieldLoop();return _this3._nextRequest+_this3.timeout=0)}__incrementReservoir__(incr){var _this4=this;return _asyncToGenerator(function*(){yield _this4.yieldLoop();return _this4.reservoir+=incr})()}__currentReservoir__(){var _this5=this;return _asyncToGenerator(function*(){yield _this5.yieldLoop();return _this5.reservoir})()}isBlocked(now){return this._unblockTime>=now}check(weight,now){return this.conditionsCheck(weight)&&this._nextRequest-now<=0}__check__(weight){var _this6=this;return _asyncToGenerator(function*(){var now;yield _this6.yieldLoop();now=Date.now();return _this6.check(weight,now)})()}__register__(index,weight,expiration){var _this7=this;return _asyncToGenerator(function*(){var now,wait;yield _this7.yieldLoop();now=Date.now();if(_this7.conditionsCheck(weight)){_this7._running+=weight;_this7._executing[index]={timeout:expiration!=null?setTimeout(function(){if(!_this7._executing[index].freed){_this7._executing[index].freed=true;return _this7._running-=weight}},expiration):void 0,freed:false};if(_this7.reservoir!=null){_this7.reservoir-=weight}wait=Math.max(_this7._nextRequest-now,0);_this7._nextRequest=now+wait+_this7.minTime;return{success:true,wait:wait,reservoir:_this7.reservoir}}else{return{success:false}}})()}strategyIsBlock(){return this.strategy===3}__submit__(queueLength,weight){var _this8=this;return _asyncToGenerator(function*(){var blocked,now,reachedHWM;yield _this8.yieldLoop();if(_this8.maxConcurrent!=null&&weight>_this8.maxConcurrent){throw new BottleneckError(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${_this8.maxConcurrent}`)}now=Date.now();reachedHWM=_this8.highWater!=null&&queueLength===_this8.highWater&&!_this8.check(weight,now);blocked=_this8.strategyIsBlock()&&(reachedHWM||_this8.isBlocked(now));if(blocked){_this8._unblockTime=now+_this8.computePenalty();_this8._nextRequest=_this8._unblockTime+_this8.minTime}return{reachedHWM:reachedHWM,blocked:blocked,strategy:_this8.strategy}})()}__free__(index,weight){var _this9=this;return _asyncToGenerator(function*(){yield _this9.yieldLoop();clearTimeout(_this9._executing[index].timeout);if(!_this9._executing[index].freed){_this9._executing[index].freed=true;_this9._running-=weight}return{running:_this9._running}})()}};module.exports=LocalDatastore}).call(undefined)},{"./BottleneckError":2,"./DLList":3,"./parser":15}],8:[function(require,module,exports){"use strict";(function(){var RedisConnection,Scripts;Scripts=require("./Scripts");RedisConnection=class RedisConnection{constructor(clientOptions,Promise,Events){var redis;this.clientOptions=clientOptions;this.Promise=Promise;this.Events=Events;redis=eval("require")("redis");this.client=redis.createClient(this.clientOptions);this.subClient=redis.createClient(this.clientOptions);this.pubsubs={};this.shas={};this.ready=new this.Promise((resolve,reject)=>{var count,done,errorListener;errorListener=(e=>{[this.client,this.subClient].forEach(client=>{return client.removeListener("error",errorListener)});return reject(e)});count=0;done=(()=>{count++;if(count===2){[this.client,this.subClient].forEach(client=>{client.removeListener("error",errorListener);return client.on("error",e=>{return this.Events.trigger("error",[e])})});return resolve()}});this.client.on("error",errorListener);this.client.on("ready",function(){return done()});this.subClient.on("error",errorListener);this.subClient.on("ready",()=>{this.subClient.on("psubscribe",function(){return done()});return this.subClient.psubscribe("bottleneck_*")});return this.subClient.on("pmessage",(pattern,channel,message)=>{var base;return typeof(base=this.pubsubs)[channel]==="function"?base[channel](message):void 0})}).then(()=>{return this.Promise.all(Scripts.names.map(k=>{return this._loadScript(k)}))}).then(()=>{return this.Promise.resolve({client:this.client,subscriber:this.subClient})})}_loadScript(name){return new this.Promise((resolve,reject)=>{var payload;payload=Scripts.payload(name);return this.client.multi([["script","load",payload]]).exec((err,replies)=>{if(err!=null){return reject(err)}this.shas[name]=replies[0];return resolve(replies[0])})})}addLimiter(instance,pubsub){return this.pubsubs[`bottleneck_${instance.id}`]=pubsub}removeLimiter(instance){return delete this.pubsubs[`bottleneck_${instance.id}`]}scriptArgs(name,id,args,cb){var keys;keys=Scripts.keys(name,id);return[this.shas[name],keys.length].concat(keys,args,cb)}scriptFn(name){return this.client.evalsha.bind(this.client)}disconnect(flush){this.client.end(flush);return this.subClient.end(flush)}};module.exports=RedisConnection}).call(undefined)},{"./Scripts":10}],9:[function(require,module,exports){"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i["return"])_i["return"]()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError("Invalid attempt to destructure non-iterable instance")}}}();function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step("next",value)},function(err){step("throw",err)})}}return step("next")})}}(function(){var BottleneckError,IORedisConnection,RedisConnection,RedisDatastore,Scripts,parser;parser=require("./parser");BottleneckError=require("./BottleneckError");RedisConnection=require("./RedisConnection");IORedisConnection=require("./IORedisConnection");Scripts=require("./Scripts");RedisDatastore=class RedisDatastore{constructor(instance,initSettings,options){this.instance=instance;this.initSettings=initSettings;this.originalId=this.instance.id;parser.load(options,options,this);this.isReady=false;this.connection=this._groupConnection?this._groupConnection:this.instance.datastore==="redis"?new RedisConnection(this.clientOptions,this.Promise,this.instance.Events):this.instance.datastore==="ioredis"?new IORedisConnection(this.clientOptions,this.Promise,this.instance.Events):void 0;this.ready=this.connection.ready.then(clients=>{var args;this.clients=clients;args=this.prepareInitSettings(options.clearDatastore);this.isReady=true;return this.runScript("init",args)}).then(()=>{this.connection.addLimiter(this.instance,message=>{var info,type;var _message$split=message.split(":");var _message$split2=_slicedToArray(_message$split,2);type=_message$split2[0];info=_message$split2[1];if(type==="freed"){return this.instance._drainAll(~~info)}});return this.clients})}__disconnect__(flush){this.connection.removeLimiter(this.instance);if(this._groupConnection==null){return this.connection.disconnect(flush)}}runScript(name,args){var keys;if(!this.isReady){return this.Promise.reject(new BottleneckError("This limiter is not done connecting to Redis yet. Wait for the '.ready()' promise to resolve before submitting requests."))}else{keys=Scripts.keys(name,this.originalId);return new this.Promise((resolve,reject)=>{var arr;this.instance.Events.trigger("debug",[`Calling Redis script: ${name}.lua`,args]);arr=this.connection.scriptArgs(name,this.originalId,args,function(err,replies){if(err!=null){return reject(err)}return resolve(replies)});return this.connection.scriptFn(name).apply({},arr)}).catch(e=>{if(e.message==="SETTINGS_KEY_NOT_FOUND"){return this.runScript("init",this.prepareInitSettings(false)).then(()=>{return this.runScript(name,args)})}else{return this.Promise.reject(e)}})}}prepareArray(arr){return arr.map(function(x){if(x!=null){return x.toString()}else{return""}})}prepareObject(obj){var arr,k,v;arr=[];for(k in obj){v=obj[k];arr.push(k,v!=null?v.toString():"")}return arr}prepareInitSettings(clear){var args;args=this.prepareObject(Object.assign({},this.initSettings,{id:this.originalId,nextRequest:Date.now(),running:0,unblockTime:0,version:this.instance.version,groupTimeout:this.timeout}));args.unshift(clear?1:0);return args}convertBool(b){return!!b}__updateSettings__(options){var _this=this;return _asyncToGenerator(function*(){return yield _this.runScript("update_settings",_this.prepareObject(options))})()}__running__(){var _this2=this;return _asyncToGenerator(function*(){return yield _this2.runScript("running",[Date.now()])})()}__groupCheck__(){var _this3=this;return _asyncToGenerator(function*(){return _this3.convertBool(yield _this3.runScript("group_check",[]))})()}__incrementReservoir__(incr){var _this4=this;return _asyncToGenerator(function*(){return yield _this4.runScript("increment_reservoir",[incr])})()}__currentReservoir__(){var _this5=this;return _asyncToGenerator(function*(){return yield _this5.runScript("current_reservoir",[])})()}__check__(weight){var _this6=this;return _asyncToGenerator(function*(){return _this6.convertBool(yield _this6.runScript("check",_this6.prepareArray([weight,Date.now()])))})()}__register__(index,weight,expiration){var _this7=this;return _asyncToGenerator(function*(){var reservoir,success,wait;var _ref=yield _this7.runScript("register",_this7.prepareArray([index,weight,expiration,Date.now()]));var _ref2=_slicedToArray(_ref,3);success=_ref2[0];wait=_ref2[1];reservoir=_ref2[2];return{success:_this7.convertBool(success),wait:wait,reservoir:reservoir}})()}__submit__(queueLength,weight){var _this8=this;return _asyncToGenerator(function*(){var blocked,e,maxConcurrent,overweight,reachedHWM,strategy;try{var _ref3=yield _this8.runScript("submit",_this8.prepareArray([queueLength,weight,Date.now()]));var _ref4=_slicedToArray(_ref3,3);reachedHWM=_ref4[0];blocked=_ref4[1];strategy=_ref4[2];return{reachedHWM:_this8.convertBool(reachedHWM),blocked:_this8.convertBool(blocked),strategy:strategy}}catch(error){e=error;if(e.message.indexOf("OVERWEIGHT")===0){var _e$message$split=e.message.split(":");var _e$message$split2=_slicedToArray(_e$message$split,3);overweight=_e$message$split2[0];weight=_e$message$split2[1];maxConcurrent=_e$message$split2[2];throw new BottleneckError(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${maxConcurrent}`)}else{throw e}}})()}__free__(index,weight){var _this9=this;return _asyncToGenerator(function*(){var result;result=yield _this9.runScript("free",_this9.prepareArray([index,Date.now()]));return{running:result}})()}};module.exports=RedisDatastore}).call(undefined)},{"./BottleneckError":2,"./IORedisConnection":6,"./RedisConnection":8,"./Scripts":10,"./parser":15}],10:[function(require,module,exports){"use strict";(function(){var libraries,lua,templates;lua=require("./lua.json");libraries={get_time:lua["get_time.lua"],refresh_running:lua["refresh_running.lua"],conditions_check:lua["conditions_check.lua"],refresh_expiration:lua["refresh_expiration.lua"],validate_keys:lua["validate_keys.lua"]};templates={init:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["refresh_expiration"],code:lua["init.lua"]},update_settings:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_expiration"],code:lua["update_settings.lua"]},running:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running"],code:lua["running.lua"]},group_check:{keys:function keys(id){return[`b_${id}_settings`]},libs:[],code:lua["group_check.lua"]},check:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running","conditions_check"],code:lua["check.lua"]},submit:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running","conditions_check","refresh_expiration"],code:lua["submit.lua"]},register:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running","conditions_check","refresh_expiration"],code:lua["register.lua"]},free:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running"],code:lua["free.lua"]},current_reservoir:{keys:function keys(id){return[`b_${id}_settings`]},libs:["validate_keys"],code:lua["current_reservoir.lua"]},increment_reservoir:{keys:function keys(id){return[`b_${id}_settings`]},libs:["validate_keys"],code:lua["increment_reservoir.lua"]}};exports.names=Object.keys(templates);exports.keys=function(name,id){return templates[name].keys(id)};exports.payload=function(name){return templates[name].libs.map(function(lib){return libraries[lib]}).join("\n")+templates[name].code}}).call(undefined)},{"./lua.json":14}],11:[function(require,module,exports){"use strict";(function(){var BottleneckError,States;BottleneckError=require("./BottleneckError");States=class States{constructor(status){this.status=status;this.jobs={};this.counts=this.status.map(function(){return 0})}next(id){var current,next;current=this.jobs[id];next=current+1;if(current!=null&&next{acc[this.status[i]]=v;return acc},{})}};module.exports=States}).call(undefined)},{"./BottleneckError":2}],12:[function(require,module,exports){"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i["return"])_i["return"]()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError("Invalid attempt to destructure non-iterable instance")}}}();function _toArray(arr){return Array.isArray(arr)?arr:Array.from(arr)}(function(){var DLList,Sync,splice=[].splice;DLList=require("./DLList");Sync=class Sync{constructor(name){this.submit=this.submit.bind(this);this.schedule=this.schedule.bind(this);this.name=name;this._running=0;this._queue=new DLList}isEmpty(){return this._queue.length===0}_tryToRun(){var next;if(this._running<1&&this._queue.length>0){this._running++;next=this._queue.shift();return next.task.apply({},next.args.concat((...args)=>{var ref;this._running--;this._tryToRun();return(ref=next.cb)!=null?ref.apply({},args):void 0}))}}submit(task,...args){var _ref,_ref2,_splice$call,_splice$call2;var cb,ref;ref=args,_ref=ref,_ref2=_toArray(_ref),args=_ref2.slice(0),_ref,_splice$call=splice.call(args,-1),_splice$call2=_slicedToArray(_splice$call,1),cb=_splice$call2[0],_splice$call;this._queue.push({task:task,args:args,cb:cb});return this._tryToRun()}schedule(task,...args){var wrapped;wrapped=function wrapped(...args){var _ref3,_ref4,_splice$call3,_splice$call4;var cb,ref;ref=args,_ref3=ref,_ref4=_toArray(_ref3),args=_ref4.slice(0),_ref3,_splice$call3=splice.call(args,-1),_splice$call4=_slicedToArray(_splice$call3,1),cb=_splice$call4[0],_splice$call3;return task.apply({},args).then(function(...args){return cb.apply({},Array.prototype.concat(null,args))}).catch(function(...args){return cb.apply({},args)})};return new Promise((resolve,reject)=>{return this.submit.apply({},Array.prototype.concat(wrapped,args,function(...args){return(args[0]!=null?reject:(args.shift(),resolve)).apply({},args)}))})}};module.exports=Sync}).call(undefined)},{"./DLList":3}],13:[function(require,module,exports){"use strict";(function(){module.exports=require("./Bottleneck")}).call(undefined)},{"./Bottleneck":1}],14:[function(require,module,exports){module.exports={"check.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal weight = tonumber(ARGV[1])\nlocal now = tonumber(ARGV[2])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'reservoir',\n 'nextRequest'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal reservoir = tonumber(settings[2])\nlocal nextRequest = tonumber(settings[3])\n\nlocal conditionsCheck = conditions_check(weight, maxConcurrent, running, reservoir)\n\nlocal result = conditionsCheck and nextRequest - now <= 0\n\nreturn result\n","conditions_check.lua":"local conditions_check = function (weight, maxConcurrent, running, reservoir)\n return (\n (maxConcurrent == nil or running + weight <= maxConcurrent) and\n (reservoir == nil or reservoir - weight >= 0)\n )\nend\n","current_reservoir.lua":"local settings_key = KEYS[1]\n\nreturn tonumber(redis.call('hget', settings_key, 'reservoir'))\n","free.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal index = ARGV[1]\nlocal now = ARGV[2]\n\nredis.call('zadd', executing_key, 0, index)\n\nreturn refresh_running(executing_key, running_key, settings_key, now)\n","get_time.lua":"redis.replicate_commands()\n\nlocal get_time = function ()\n local time = redis.call('time')\n\n return tonumber(time[1]..string.sub(time[2], 1, 3))\nend\n","group_check.lua":"local settings_key = KEYS[1]\n\nreturn not (redis.call('exists', settings_key) == 1)\n","increment_reservoir.lua":"local settings_key = KEYS[1]\nlocal incr = ARGV[1]\n\nreturn redis.call('hincrby', settings_key, 'reservoir', incr)\n","init.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal clear = tonumber(ARGV[1])\n\nif clear == 1 then\n redis.call('del', settings_key, running_key, executing_key)\nend\n\nif redis.call('exists', settings_key) == 0 then\n local args = {'hmset', settings_key}\n\n for i = 2, #ARGV do\n table.insert(args, ARGV[i])\n end\n\n redis.call(unpack(args))\nend\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn {}\n","refresh_expiration.lua":"local refresh_expiration = function (executing_key, running_key, settings_key, now, nextRequest, groupTimeout)\n\n if groupTimeout ~= nil then\n local ttl = (nextRequest + groupTimeout) - now\n\n redis.call('pexpire', executing_key, ttl)\n redis.call('pexpire', running_key, ttl)\n redis.call('pexpire', settings_key, ttl)\n end\n\nend\n","refresh_running.lua":"local refresh_running = function (executing_key, running_key, settings_key, now)\n\n local expired = redis.call('zrangebyscore', executing_key, '-inf', '('..now)\n\n if #expired == 0 then\n return redis.call('hget', settings_key, 'running')\n else\n redis.call('zremrangebyscore', executing_key, '-inf', '('..now)\n\n local args = {'hmget', running_key}\n for i = 1, #expired do\n table.insert(args, expired[i])\n end\n\n local weights = redis.call(unpack(args))\n\n args[1] = 'hdel'\n local deleted = redis.call(unpack(args))\n\n local total = 0\n for i = 1, #weights do\n total = total + (tonumber(weights[i]) or 0)\n end\n local incr = -total\n if total == 0 then\n incr = 0\n else\n local id = redis.call('hget', settings_key, 'id')\n redis.call('publish', 'bottleneck_'..id, 'freed:'..total)\n end\n\n return redis.call('hincrby', settings_key, 'running', incr)\n end\n\nend\n","register.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal index = ARGV[1]\nlocal weight = tonumber(ARGV[2])\nlocal expiration = tonumber(ARGV[3])\nlocal now = tonumber(ARGV[4])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'reservoir',\n 'nextRequest',\n 'minTime',\n 'groupTimeout'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal reservoir = tonumber(settings[2])\nlocal nextRequest = tonumber(settings[3])\nlocal minTime = tonumber(settings[4])\nlocal groupTimeout = tonumber(settings[5])\n\nif conditions_check(weight, maxConcurrent, running, reservoir) then\n\n if expiration ~= nil then\n redis.call('zadd', executing_key, now + expiration, index)\n end\n redis.call('hset', running_key, index, weight)\n redis.call('hincrby', settings_key, 'running', weight)\n\n local wait = math.max(nextRequest - now, 0)\n local newNextRequest = now + wait + minTime\n\n if reservoir == nil then\n redis.call('hset', settings_key,\n 'nextRequest', newNextRequest\n )\n else\n reservoir = reservoir - weight\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'nextRequest', newNextRequest\n )\n end\n\n refresh_expiration(executing_key, running_key, settings_key, now, newNextRequest, groupTimeout)\n\n return {true, wait, reservoir}\n\nelse\n return {false}\nend\n","running.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\nlocal now = ARGV[1]\n\nreturn tonumber(refresh_running(executing_key, running_key, settings_key, now))\n","submit.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal queueLength = tonumber(ARGV[1])\nlocal weight = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'highWater',\n 'reservoir',\n 'nextRequest',\n 'strategy',\n 'unblockTime',\n 'penalty',\n 'minTime',\n 'groupTimeout'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal highWater = tonumber(settings[2])\nlocal reservoir = tonumber(settings[3])\nlocal nextRequest = tonumber(settings[4])\nlocal strategy = tonumber(settings[5])\nlocal unblockTime = tonumber(settings[6])\nlocal penalty = tonumber(settings[7])\nlocal minTime = tonumber(settings[8])\nlocal groupTimeout = tonumber(settings[9])\n\nif maxConcurrent ~= nil and weight > maxConcurrent then\n return redis.error_reply('OVERWEIGHT:'..weight..':'..maxConcurrent)\nend\n\nlocal reachedHWM = (highWater ~= nil and queueLength == highWater\n and not (\n conditions_check(weight, maxConcurrent, running, reservoir)\n and nextRequest - now <= 0\n )\n)\n\nlocal blocked = strategy == 3 and (reachedHWM or unblockTime >= now)\n\nif blocked then\n local computedPenalty = penalty\n if computedPenalty == nil then\n if minTime == 0 then\n computedPenalty = 5000\n else\n computedPenalty = 15 * minTime\n end\n end\n\n local newNextRequest = now + computedPenalty + minTime\n\n redis.call('hmset', settings_key,\n 'unblockTime', now + computedPenalty,\n 'nextRequest', newNextRequest\n )\n\n refresh_expiration(executing_key, running_key, settings_key, now, newNextRequest, groupTimeout)\nend\n\nreturn {reachedHWM, blocked, strategy}\n","update_settings.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal args = {'hmset', settings_key}\n\nfor i = 1, #ARGV do\n table.insert(args, ARGV[i])\nend\n\nredis.call(unpack(args))\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn {}\n","validate_keys.lua":"local settings_key = KEYS[1]\n\nif not (redis.call('exists', settings_key) == 1) then\n return redis.error_reply('SETTINGS_KEY_NOT_FOUND')\nend\n"}},{}],15:[function(require,module,exports){"use strict";(function(){exports.load=function(received,defaults,onto={}){var k,ref,v;for(k in defaults){v=defaults[k];onto[k]=(ref=received[k])!=null?ref:v}return onto};exports.overwrite=function(received,defaults,onto={}){var k,v;for(k in received){v=received[k];if(defaults[k]!==void 0){onto[k]=v}}return onto}}).call(undefined)},{}],16:[function(require,module,exports){module.exports={name:"bottleneck",version:"2.7.2",description:"Distributed task scheduler and rate limiter",main:"lib/index.js",typings:"bottleneck.d.ts",scripts:{test:"./node_modules/mocha/bin/mocha test",build:"./scripts/build.sh",compile:"./scripts/build.sh compile"},repository:{type:"git",url:"https://github.com/SGrondin/bottleneck"},keywords:["async rate limiter","rate limiter","rate limiting","async","rate","limiting","limiter","throttle","throttling","load","ddos"],author:{name:"Simon Grondin"},license:"MIT",bugs:{url:"https://github.com/SGrondin/bottleneck/issues"},devDependencies:{"@types/es6-promise":"0.0.33",assert:"1.4.x","babel-core":"^6.26.0","babel-preset-env":"^1.6.1",browserify:"*",coffeescript:"2.2.x","ejs-cli":"2.0.1",ioredis:"^4.0.0",mocha:"4.x",redis:"^2.8.0",typescript:"^2.6.2","uglify-es":"3.x"},dependencies:{}}},{}]},{},[13]); \ No newline at end of file +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i=ref;i=1<=ref?++j:--j){results.push(new DLList)}return results}_sanitizePriority(priority){var sProperty;sProperty=~~priority!==priority?DEFAULT_PRIORITY:priority;if(sProperty<0){return 0}else if(sProperty>NUM_PRIORITIES-1){return NUM_PRIORITIES-1}else{return sProperty}}_find(arr,fn){var ref;return(ref=function(){var i,j,len,x;for(i=j=0,len=arr.length;j0})}_randomIndex(){return Math.random().toString(36).slice(2)}check(weight=1){var _this3=this;return _asyncToGenerator(function*(){return yield _this3._store.__check__(weight)})()}_run(next,wait,index){var _this4=this;var completed,done;this.Events.trigger("debug",[`Scheduling ${next.options.id}`,{args:next.args,options:next.options}]);done=false;completed=(()=>{var _ref=_asyncToGenerator(function*(...args){var e,ref,running;if(!done){try{done=true;_this4._states.next(next.options.id);clearTimeout(_this4._scheduled[index].expiration);delete _this4._scheduled[index];_this4.Events.trigger("debug",[`Completed ${next.options.id}`,{args:next.args,options:next.options}]);_this4.Events.trigger("done",[`Completed ${next.options.id}`,{args:next.args,options:next.options}]);var _ref2=yield _this4._store.__free__(index,next.options.weight);running=_ref2.running;_this4.Events.trigger("debug",[`Freed ${next.options.id}`,{args:next.args,options:next.options}]);_this4._drainAll().catch(function(e){return _this4.Events.trigger("error",[e])});if(running===0&&_this4.empty()){_this4.Events.trigger("idle",[])}return(ref=next.cb)!=null?ref.apply({},args):void 0}catch(error){e=error;return _this4.Events.trigger("error",[e])}}});return function completed(){return _ref.apply(this,arguments)}})();this._states.next(next.options.id);return this._scheduled[index]={timeout:setTimeout(()=>{this.Events.trigger("debug",[`Executing ${next.options.id}`,{args:next.args,options:next.options}]);this._states.next(next.options.id);if(this._limiter!=null){return this._limiter.submit.apply(this._limiter,Array.prototype.concat(next.options,next.task,next.args,completed))}else{return next.task.apply({},next.args.concat(completed))}},wait),expiration:next.options.expiration!=null?setTimeout(()=>{return completed(new Bottleneck.prototype.BottleneckError(`This job timed out after ${next.options.expiration} ms.`))},wait+next.options.expiration):void 0,job:next}}_drainOne(freed){return this._registerLock.schedule(()=>{var args,index,options,queue;if(this.queued()===0){return this.Promise.resolve(false)}queue=this._getFirst(this._queues);var _queue$first=queue.first();options=_queue$first.options;args=_queue$first.args;if(freed!=null&&options.weight>freed){return this.Promise.resolve(false)}this.Events.trigger("debug",[`Draining ${options.id}`,{args:args,options:options}]);index=this._randomIndex();return this._store.__register__(index,options.weight,options.expiration).then(({success:success,wait:wait,reservoir:reservoir})=>{var empty,next;this.Events.trigger("debug",[`Drained ${options.id}`,{success:success,args:args,options:options}]);if(success){next=queue.shift();empty=this.empty();if(empty){this.Events.trigger("empty",[])}if(reservoir===0){this.Events.trigger("depleted",[empty])}this._run(next,wait,index)}return this.Promise.resolve(success)})})}_drainAll(freed){return this._drainOne(freed).then(success=>{if(success){return this._drainAll()}else{return this.Promise.resolve(success)}}).catch(e=>{return this.Events.trigger("error",[e])})}_drop(job,message="This job has been dropped by Bottleneck"){var ref;this._states.remove(job.options.id);if(this.rejectOnDrop){if((ref=job.cb)!=null){ref.apply({},[new Bottleneck.prototype.BottleneckError(message)])}}return this.Events.trigger("dropped",[job])}stop(options={}){var done,waitForExecuting;options=parser.load(options,this.stopDefaults);waitForExecuting=(at=>{var finished;finished=(()=>{var counts;counts=this._states.counts;return counts[0]+counts[1]+counts[2]+counts[3]===at});return new this.Promise((resolve,reject)=>{if(finished()){return resolve()}else{return this.on("done",()=>{if(finished()){this.removeAllListeners("done");return resolve()}})}})});done=options.dropWaitingJobs?(this._run=(next=>{return this._drop(next,options.dropErrorMessage)}),this._drainOne=(()=>{return Promise.resolve(false)}),this.Promise.all([this._registerLock.schedule(()=>{return Promise.resolve(true)},this._submitLock.schedule(()=>{return Promise.resolve(true)}))]).then(()=>{var k,ref,v;ref=this._scheduled;for(k in ref){v=ref[k];if(this.jobStatus(v.job.options.id)==="RUNNING"){clearTimeout(v.timeout);clearTimeout(v.expiration);this._drop(v.job,options.dropErrorMessage)}}this._queues.forEach(queue=>{return queue.forEachShift(job=>{return this._drop(job,options.dropErrorMessage)})});return waitForExecuting(0)})):this.schedule({priority:NUM_PRIORITIES-1,weight:0},()=>{return waitForExecuting(1)});this.submit=((...args)=>{var _ref3,_ref4,_splice$call,_splice$call2;var cb,ref;ref=args,_ref3=ref,_ref4=_toArray(_ref3),args=_ref4.slice(0),_ref3,_splice$call=splice.call(args,-1),_splice$call2=_slicedToArray(_splice$call,1),cb=_splice$call2[0],_splice$call;return cb!=null?cb.apply({},[new Bottleneck.prototype.BottleneckError(options.enqueueErrorMessage)]):void 0});return done}submit(...args){var _this5=this;var cb,job,options,ref,ref1,ref2,task;if(typeof args[0]==="function"){var _ref5,_ref6,_splice$call3,_splice$call4;ref=args,_ref5=ref,_ref6=_toArray(_ref5),task=_ref6[0],args=_ref6.slice(1),_ref5,_splice$call3=splice.call(args,-1),_splice$call4=_slicedToArray(_splice$call3,1),cb=_splice$call4[0],_splice$call3;options=parser.load({},this.jobDefaults,{})}else{var _ref7,_ref8,_splice$call5,_splice$call6;ref1=args,_ref7=ref1,_ref8=_toArray(_ref7),options=_ref8[0],task=_ref8[1],args=_ref8.slice(2),_ref7,_splice$call5=splice.call(args,-1),_splice$call6=_slicedToArray(_splice$call5,1),cb=_splice$call6[0],_splice$call5;options=parser.load(options,this.jobDefaults)}job={options:options,task:task,args:args,cb:cb};options.priority=this._sanitizePriority(options.priority);if(options.id===this.jobDefaults.id){options.id=`${options.id}-${this._randomIndex()}`}if(this.jobStatus(options.id)!=null){if((ref2=job.cb)!=null){ref2.apply({},[new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${options.id})`)])}return false}this._states.start(options.id);this.Events.trigger("debug",[`Queueing ${options.id}`,{args:args,options:options}]);return this._submitLock.schedule(_asyncToGenerator(function*(){var blocked,e,reachedHWM,ref3,shifted,strategy;try{var _ref10=yield _this5._store.__submit__(_this5.queued(),options.weight);reachedHWM=_ref10.reachedHWM;blocked=_ref10.blocked;strategy=_ref10.strategy;_this5.Events.trigger("debug",[`Queued ${options.id}`,{args:args,options:options,reachedHWM:reachedHWM,blocked:blocked}])}catch(error){e=error;_this5._states.remove(options.id);_this5.Events.trigger("debug",[`Could not queue ${options.id}`,{args:args,options:options,error:e}]);if((ref3=job.cb)!=null){ref3.apply({},[e])}return false}if(blocked){_this5._queues=_this5._makeQueues();_this5._drop(job);return true}else if(reachedHWM){shifted=strategy===Bottleneck.prototype.strategy.LEAK?_this5._getFirst(_this5._queues.slice(options.priority).reverse()).shift():strategy===Bottleneck.prototype.strategy.OVERFLOW_PRIORITY?_this5._getFirst(_this5._queues.slice(options.priority+1).reverse()).shift():strategy===Bottleneck.prototype.strategy.OVERFLOW?job:void 0;if(shifted!=null){_this5._drop(shifted)}if(shifted==null||strategy===Bottleneck.prototype.strategy.OVERFLOW){if(shifted==null){_this5._drop(job)}return reachedHWM}}_this5._states.next(job.options.id);_this5._queues[options.priority].push(job);yield _this5._drainAll();return reachedHWM}))}schedule(...args){var options,task,wrapped;if(typeof args[0]==="function"){var _args=args;var _args2=_toArray(_args);task=_args2[0];args=_args2.slice(1);options=parser.load({},this.jobDefaults,{})}else{var _args3=args;var _args4=_toArray(_args3);options=_args4[0];task=_args4[1];args=_args4.slice(2);options=parser.load(options,this.jobDefaults)}wrapped=function wrapped(...args){var _ref11,_ref12,_splice$call7,_splice$call8;var cb,ref,returned;ref=args,_ref11=ref,_ref12=_toArray(_ref11),args=_ref12.slice(0),_ref11,_splice$call7=splice.call(args,-1),_splice$call8=_slicedToArray(_splice$call7,1),cb=_splice$call8[0],_splice$call7;returned=task.apply({},args);return(!((returned!=null?returned.then:void 0)!=null&&typeof returned.then==="function")?Promise.resolve(returned):returned).then(function(...args){return cb.apply({},Array.prototype.concat(null,args))}).catch(function(...args){return cb.apply({},args)})};return new this.Promise((resolve,reject)=>{return this.submit.apply({},Array.prototype.concat(options,wrapped,args,function(...args){return(args[0]!=null?reject:(args.shift(),resolve)).apply({},args)})).catch(e=>{return this.Events.trigger("error",[e])})})}wrap(fn){var ret;ret=((...args)=>{return this.schedule.apply({},Array.prototype.concat(fn,args))});ret.withOptions=((options,...args)=>{return this.schedule.apply({},Array.prototype.concat(options,fn,args))});return ret}updateSettings(options={}){var _this6=this;return _asyncToGenerator(function*(){yield _this6._store.__updateSettings__(parser.overwrite(options,_this6.storeDefaults));parser.overwrite(options,_this6.instanceDefaults,_this6);_this6._drainAll().catch(function(e){return _this6.Events.trigger("error",[e])});return _this6})()}currentReservoir(){var _this7=this;return _asyncToGenerator(function*(){return yield _this7._store.__currentReservoir__()})()}incrementReservoir(incr=0){var _this8=this;return _asyncToGenerator(function*(){yield _this8._store.__incrementReservoir__(incr);_this8._drainAll().catch(function(e){return _this8.Events.trigger("error",[e])});return _this8})()}}Bottleneck.default=Bottleneck;Bottleneck.version=Bottleneck.prototype.version=packagejson.version;Bottleneck.strategy=Bottleneck.prototype.strategy={LEAK:1,OVERFLOW:2,OVERFLOW_PRIORITY:4,BLOCK:3};Bottleneck.BottleneckError=Bottleneck.prototype.BottleneckError=require("./BottleneckError");Bottleneck.Group=Bottleneck.prototype.Group=require("./Group");Bottleneck.prototype.jobDefaults={priority:DEFAULT_PRIORITY,weight:1,expiration:null,id:""};Bottleneck.prototype.storeDefaults={maxConcurrent:null,minTime:0,highWater:null,strategy:Bottleneck.prototype.strategy.LEAK,penalty:null,reservoir:null};Bottleneck.prototype.storeInstanceDefaults={clientOptions:{},clusterNodes:null,clearDatastore:false,Promise:Promise,timeout:null,_groupConnection:null};Bottleneck.prototype.instanceDefaults={datastore:"local",id:"",rejectOnDrop:true,trackDoneStatus:false,Promise:Promise};Bottleneck.prototype.stopDefaults={enqueueErrorMessage:"This limiter has been stopped and cannot accept new jobs.",dropWaitingJobs:true,dropErrorMessage:"This limiter has been stopped."};return Bottleneck}.call(this);module.exports=Bottleneck}).call(undefined)},{"../package.json":16,"./BottleneckError":2,"./DLList":3,"./Events":4,"./Group":5,"./LocalDatastore":7,"./RedisDatastore":9,"./States":11,"./Sync":12,"./parser":15}],2:[function(require,module,exports){"use strict";(function(){var BottleneckError;BottleneckError=class BottleneckError extends Error{};module.exports=BottleneckError}).call(undefined)},{}],3:[function(require,module,exports){"use strict";(function(){var DLList;DLList=class DLList{constructor(){this._first=null;this._last=null;this.length=0}push(value){var node;this.length++;node={value:value,next:null};if(this._last!=null){this._last.next=node;this._last=node}else{this._first=this._last=node}return void 0}shift(){var ref1,value;if(this._first==null){return void 0}else{this.length--}value=this._first.value;this._first=(ref1=this._first.next)!=null?ref1:this._last=null;return value}first(){if(this._first!=null){return this._first.value}}getArray(){var node,ref,results;node=this._first;results=[];while(node!=null){results.push((ref=node,node=node.next,ref.value))}return results}forEachShift(cb){var node;node=this.shift();while(node!=null){cb(node),node=this.shift()}return void 0}};module.exports=DLList}).call(undefined)},{}],4:[function(require,module,exports){"use strict";(function(){var Events;Events=class Events{constructor(instance){this.instance=instance;this._events={};this.instance.on=((name,cb)=>{return this._addListener(name,"many",cb)});this.instance.once=((name,cb)=>{return this._addListener(name,"once",cb)});this.instance.removeAllListeners=((name=null)=>{if(name!=null){return delete this._events[name]}else{return this._events={}}})}_addListener(name,status,cb){var base;if((base=this._events)[name]==null){base[name]=[]}this._events[name].push({cb:cb,status:status});return this.instance}trigger(name,args){if(name!=="debug"){this.trigger("debug",[`Event triggered: ${name}`,args])}if(this._events[name]==null){return}this._events[name]=this._events[name].filter(function(listener){return listener.status!=="none"});return this._events[name].forEach(listener=>{var e,ret;if(listener.status==="none"){return}if(listener.status==="once"){listener.status="none"}try{ret=listener.cb.apply({},args);if(typeof(ret!=null?ret.then:void 0)==="function"){return ret.then(function(){}).catch(e=>{return this.trigger("error",[e])})}}catch(error){e=error;if("name"!=="error"){return this.trigger("error",[e])}}})}};module.exports=Events}).call(undefined)},{}],5:[function(require,module,exports){"use strict";function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step("next",value)},function(err){step("throw",err)})}}return step("next")})}}(function(){var Events,Group,IORedisConnection,RedisConnection,parser;parser=require("./parser");Events=require("./Events");RedisConnection=require("./RedisConnection");IORedisConnection=require("./IORedisConnection");Group=function(){class Group{constructor(limiterOptions={}){var ref,ref1,ref2,ref3,ref4;this.key=this.key.bind(this);this.deleteKey=this.deleteKey.bind(this);this.limiters=this.limiters.bind(this);this.keys=this.keys.bind(this);this._startAutoCleanup=this._startAutoCleanup.bind(this);this.updateSettings=this.updateSettings.bind(this);this.limiterOptions=limiterOptions;parser.load(this.limiterOptions,this.defaults,this);this.Events=new Events(this);this.instances={};this.Bottleneck=require("./Bottleneck");this._startAutoCleanup();if(this.limiterOptions.datastore==="redis"){this._connection=new RedisConnection((ref=this.limiterOptions.clientOptions)!=null?ref:{},(ref1=this.limiterOptions.Promise)!=null?ref1:Promise,this.Events)}else if(this.limiterOptions.datastore==="ioredis"){this._connection=new IORedisConnection((ref2=this.limiterOptions.clusterNodes)!=null?ref2:null,(ref3=this.limiterOptions.clientOptions)!=null?ref3:{},(ref4=this.limiterOptions.Promise)!=null?ref4:Promise,this.Events)}}key(key=""){var ref;return(ref=this.instances[key])!=null?ref:(()=>{var limiter;limiter=this.instances[key]=new this.Bottleneck(Object.assign(this.limiterOptions,{id:`group-key-${key}`,timeout:this.timeout,_groupConnection:this._connection}));this.Events.trigger("created",[limiter,key]);return limiter})()}deleteKey(key=""){var ref;if((ref=this.instances[key])!=null){ref.disconnect()}return delete this.instances[key]}limiters(){var k,ref,results,v;ref=this.instances;results=[];for(k in ref){v=ref[k];results.push({key:k,limiter:v})}return results}keys(){return Object.keys(this.instances)}_startAutoCleanup(){var _this=this;var base;clearInterval(this.interval);return typeof(base=this.interval=setInterval(_asyncToGenerator(function*(){var e,k,ref,results,time,v;time=Date.now();ref=_this.instances;results=[];for(k in ref){v=ref[k];try{if(yield v._store.__groupCheck__(time)){results.push(_this.deleteKey(k))}else{results.push(void 0)}}catch(error){e=error;results.push(v.Events.trigger("error",[e]))}}return results}),this.timeout/2)).unref==="function"?base.unref():void 0}updateSettings(options={}){parser.overwrite(options,this.defaults,this);parser.overwrite(options,options,this.limiterOptions);if(options.timeout!=null){return this._startAutoCleanup()}}disconnect(flush){var ref;return(ref=this._connection)!=null?ref.disconnect(flush):void 0}}Group.prototype.defaults={timeout:1e3*60*5};return Group}.call(this);module.exports=Group}).call(undefined)},{"./Bottleneck":1,"./Events":4,"./IORedisConnection":6,"./RedisConnection":8,"./parser":15}],6:[function(require,module,exports){"use strict";(function(){var IORedisConnection,Scripts;Scripts=require("./Scripts");IORedisConnection=class IORedisConnection{constructor(clusterNodes,clientOptions,Promise,Events){var Redis;this.clusterNodes=clusterNodes;this.clientOptions=clientOptions;this.Promise=Promise;this.Events=Events;Redis=eval("require")("ioredis");if(this.clusterNodes!=null){this.client=new Redis.Cluster(this.clusterNodes,this.clientOptions);this.subClient=new Redis.Cluster(this.clusterNodes,this.clientOptions)}else{this.client=new Redis(this.clientOptions);this.subClient=new Redis(this.clientOptions)}this.pubsubs={};this.ready=new this.Promise((resolve,reject)=>{var count,done,errorListener;errorListener=(e=>{[this.client,this.subClient].forEach(client=>{return client.removeListener("error",errorListener)});return reject(e)});count=0;done=(()=>{count++;if(count===2){[this.client,this.subClient].forEach(client=>{client.removeListener("error",errorListener);return client.on("error",e=>{return this.Events.trigger("error",[e])})});return resolve({client:this.client,subscriber:this.subClient})}});this.client.on("error",errorListener);this.client.on("ready",function(){return done()});this.subClient.on("error",errorListener);this.subClient.on("ready",()=>{return this.subClient.psubscribe("bottleneck_*",function(){return done()})});return this.subClient.on("pmessage",(pattern,channel,message)=>{var base;return typeof(base=this.pubsubs)[channel]==="function"?base[channel](message):void 0})}).then(()=>{return Scripts.names.forEach(name=>{return this.client.defineCommand(name,{lua:Scripts.payload(name)})})}).then(()=>{return this.Promise.resolve({client:this.client,subscriber:this.subClient})})}addLimiter(instance,pubsub){return this.pubsubs[`bottleneck_${instance.id}`]=pubsub}removeLimiter(instance){return delete this.pubsubs[`bottleneck_${instance.id}`]}scriptArgs(name,id,args,cb){var keys;keys=Scripts.keys(name,id);return[keys.length].concat(keys,args,cb)}scriptFn(name){return this.client[name].bind(this.client)}disconnect(flush){if(flush){return this.Promise.all([this.client.quit(),this.subClient.quit()])}else{this.client.disconnect();this.subClient.disconnect();return this.Promise.resolve()}}};module.exports=IORedisConnection}).call(undefined)},{"./Scripts":10}],7:[function(require,module,exports){"use strict";function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step("next",value)},function(err){step("throw",err)})}}return step("next")})}}(function(){var BottleneckError,DLList,LocalDatastore,parser;parser=require("./parser");DLList=require("./DLList");BottleneckError=require("./BottleneckError");LocalDatastore=class LocalDatastore{constructor(options){parser.load(options,options,this);this._nextRequest=Date.now();this._running=0;this._executing={};this._unblockTime=0;this.ready=this.yieldLoop();this.clients={}}__disconnect__(flush){return this.Promise.resolve()}yieldLoop(t=0){return new this.Promise(function(resolve,reject){return setTimeout(resolve,t)})}computePenalty(){var ref;return(ref=this.penalty)!=null?ref:15*this.minTime||5e3}__updateSettings__(options){var _this=this;return _asyncToGenerator(function*(){yield _this.yieldLoop();parser.overwrite(options,options,_this);return true})()}__running__(){var _this2=this;return _asyncToGenerator(function*(){yield _this2.yieldLoop();return _this2._running})()}__groupCheck__(time){var _this3=this;return _asyncToGenerator(function*(){yield _this3.yieldLoop();return _this3._nextRequest+_this3.timeout=0)}__incrementReservoir__(incr){var _this4=this;return _asyncToGenerator(function*(){yield _this4.yieldLoop();return _this4.reservoir+=incr})()}__currentReservoir__(){var _this5=this;return _asyncToGenerator(function*(){yield _this5.yieldLoop();return _this5.reservoir})()}isBlocked(now){return this._unblockTime>=now}check(weight,now){return this.conditionsCheck(weight)&&this._nextRequest-now<=0}__check__(weight){var _this6=this;return _asyncToGenerator(function*(){var now;yield _this6.yieldLoop();now=Date.now();return _this6.check(weight,now)})()}__register__(index,weight,expiration){var _this7=this;return _asyncToGenerator(function*(){var now,wait;yield _this7.yieldLoop();now=Date.now();if(_this7.conditionsCheck(weight)){_this7._running+=weight;_this7._executing[index]={timeout:expiration!=null?setTimeout(function(){if(!_this7._executing[index].freed){_this7._executing[index].freed=true;return _this7._running-=weight}},expiration):void 0,freed:false};if(_this7.reservoir!=null){_this7.reservoir-=weight}wait=Math.max(_this7._nextRequest-now,0);_this7._nextRequest=now+wait+_this7.minTime;return{success:true,wait:wait,reservoir:_this7.reservoir}}else{return{success:false}}})()}strategyIsBlock(){return this.strategy===3}__submit__(queueLength,weight){var _this8=this;return _asyncToGenerator(function*(){var blocked,now,reachedHWM;yield _this8.yieldLoop();if(_this8.maxConcurrent!=null&&weight>_this8.maxConcurrent){throw new BottleneckError(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${_this8.maxConcurrent}`)}now=Date.now();reachedHWM=_this8.highWater!=null&&queueLength===_this8.highWater&&!_this8.check(weight,now);blocked=_this8.strategyIsBlock()&&(reachedHWM||_this8.isBlocked(now));if(blocked){_this8._unblockTime=now+_this8.computePenalty();_this8._nextRequest=_this8._unblockTime+_this8.minTime}return{reachedHWM:reachedHWM,blocked:blocked,strategy:_this8.strategy}})()}__free__(index,weight){var _this9=this;return _asyncToGenerator(function*(){yield _this9.yieldLoop();clearTimeout(_this9._executing[index].timeout);if(!_this9._executing[index].freed){_this9._executing[index].freed=true;_this9._running-=weight}return{running:_this9._running}})()}};module.exports=LocalDatastore}).call(undefined)},{"./BottleneckError":2,"./DLList":3,"./parser":15}],8:[function(require,module,exports){"use strict";(function(){var RedisConnection,Scripts;Scripts=require("./Scripts");RedisConnection=class RedisConnection{constructor(clientOptions,Promise,Events){var Redis;this.clientOptions=clientOptions;this.Promise=Promise;this.Events=Events;Redis=eval("require")("redis");this.client=Redis.createClient(this.clientOptions);this.subClient=Redis.createClient(this.clientOptions);this.pubsubs={};this.shas={};this.ready=new this.Promise((resolve,reject)=>{var count,done,errorListener;errorListener=(e=>{[this.client,this.subClient].forEach(client=>{return client.removeListener("error",errorListener)});return reject(e)});count=0;done=(()=>{count++;if(count===2){[this.client,this.subClient].forEach(client=>{client.removeListener("error",errorListener);return client.on("error",e=>{return this.Events.trigger("error",[e])})});return resolve()}});this.client.on("error",errorListener);this.client.on("ready",function(){return done()});this.subClient.on("error",errorListener);this.subClient.on("ready",()=>{this.subClient.on("psubscribe",function(){return done()});return this.subClient.psubscribe("bottleneck_*")});return this.subClient.on("pmessage",(pattern,channel,message)=>{var base;return typeof(base=this.pubsubs)[channel]==="function"?base[channel](message):void 0})}).then(()=>{return this.Promise.all(Scripts.names.map(k=>{return this._loadScript(k)}))}).then(()=>{return this.Promise.resolve({client:this.client,subscriber:this.subClient})})}_loadScript(name){return new this.Promise((resolve,reject)=>{var payload;payload=Scripts.payload(name);return this.client.multi([["script","load",payload]]).exec((err,replies)=>{if(err!=null){return reject(err)}this.shas[name]=replies[0];return resolve(replies[0])})})}addLimiter(instance,pubsub){return this.pubsubs[`bottleneck_${instance.id}`]=pubsub}removeLimiter(instance){return delete this.pubsubs[`bottleneck_${instance.id}`]}scriptArgs(name,id,args,cb){var keys;keys=Scripts.keys(name,id);return[this.shas[name],keys.length].concat(keys,args,cb)}scriptFn(name){return this.client.evalsha.bind(this.client)}disconnect(flush){this.client.end(flush);this.subClient.end(flush);return this.Promise.resolve()}};module.exports=RedisConnection}).call(undefined)},{"./Scripts":10}],9:[function(require,module,exports){"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i["return"])_i["return"]()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError("Invalid attempt to destructure non-iterable instance")}}}();function _asyncToGenerator(fn){return function(){var gen=fn.apply(this,arguments);return new Promise(function(resolve,reject){function step(key,arg){try{var info=gen[key](arg);var value=info.value}catch(error){reject(error);return}if(info.done){resolve(value)}else{return Promise.resolve(value).then(function(value){step("next",value)},function(err){step("throw",err)})}}return step("next")})}}(function(){var BottleneckError,IORedisConnection,RedisConnection,RedisDatastore,Scripts,parser;parser=require("./parser");BottleneckError=require("./BottleneckError");RedisConnection=require("./RedisConnection");IORedisConnection=require("./IORedisConnection");Scripts=require("./Scripts");RedisDatastore=class RedisDatastore{constructor(instance,initSettings,options){this.instance=instance;this.initSettings=initSettings;this.originalId=this.instance.id;parser.load(options,options,this);this.isReady=false;this.connection=this._groupConnection?this._groupConnection:this.instance.datastore==="redis"?new RedisConnection(this.clientOptions,this.Promise,this.instance.Events):this.instance.datastore==="ioredis"?new IORedisConnection(this.clusterNodes,this.clientOptions,this.Promise,this.instance.Events):void 0;this.ready=this.connection.ready.then(clients=>{var args;this.clients=clients;args=this.prepareInitSettings(this.clearDatastore);this.isReady=true;return this.runScript("init",args)}).then(()=>{this.connection.addLimiter(this.instance,message=>{var info,type;var _message$split=message.split(":");var _message$split2=_slicedToArray(_message$split,2);type=_message$split2[0];info=_message$split2[1];if(type==="freed"){return this.instance._drainAll(~~info)}});return this.clients})}__disconnect__(flush){this.connection.removeLimiter(this.instance);if(this._groupConnection==null){return this.connection.disconnect(flush)}}runScript(name,args){var keys;if(!this.isReady){return this.Promise.reject(new BottleneckError("This limiter is not done connecting to Redis yet. Wait for the '.ready()' promise to resolve before submitting requests."))}else{keys=Scripts.keys(name,this.originalId);return new this.Promise((resolve,reject)=>{var arr;this.instance.Events.trigger("debug",[`Calling Redis script: ${name}.lua`,args]);arr=this.connection.scriptArgs(name,this.originalId,args,function(err,replies){if(err!=null){return reject(err)}return resolve(replies)});return this.connection.scriptFn(name).apply({},arr)}).catch(e=>{if(e.message==="SETTINGS_KEY_NOT_FOUND"){return this.runScript("init",this.prepareInitSettings(false)).then(()=>{return this.runScript(name,args)})}else{return this.Promise.reject(e)}})}}prepareArray(arr){return arr.map(function(x){if(x!=null){return x.toString()}else{return""}})}prepareObject(obj){var arr,k,v;arr=[];for(k in obj){v=obj[k];arr.push(k,v!=null?v.toString():"")}return arr}prepareInitSettings(clear){var args;args=this.prepareObject(Object.assign({},this.initSettings,{id:this.originalId,nextRequest:Date.now(),running:0,unblockTime:0,version:this.instance.version,groupTimeout:this.timeout}));args.unshift(clear?1:0);return args}convertBool(b){return!!b}__updateSettings__(options){var _this=this;return _asyncToGenerator(function*(){return yield _this.runScript("update_settings",_this.prepareObject(options))})()}__running__(){var _this2=this;return _asyncToGenerator(function*(){return yield _this2.runScript("running",[Date.now()])})()}__groupCheck__(){var _this3=this;return _asyncToGenerator(function*(){return _this3.convertBool(yield _this3.runScript("group_check",[]))})()}__incrementReservoir__(incr){var _this4=this;return _asyncToGenerator(function*(){return yield _this4.runScript("increment_reservoir",[incr])})()}__currentReservoir__(){var _this5=this;return _asyncToGenerator(function*(){return yield _this5.runScript("current_reservoir",[])})()}__check__(weight){var _this6=this;return _asyncToGenerator(function*(){return _this6.convertBool(yield _this6.runScript("check",_this6.prepareArray([weight,Date.now()])))})()}__register__(index,weight,expiration){var _this7=this;return _asyncToGenerator(function*(){var reservoir,success,wait;var _ref=yield _this7.runScript("register",_this7.prepareArray([index,weight,expiration,Date.now()]));var _ref2=_slicedToArray(_ref,3);success=_ref2[0];wait=_ref2[1];reservoir=_ref2[2];return{success:_this7.convertBool(success),wait:wait,reservoir:reservoir}})()}__submit__(queueLength,weight){var _this8=this;return _asyncToGenerator(function*(){var blocked,e,maxConcurrent,overweight,reachedHWM,strategy;try{var _ref3=yield _this8.runScript("submit",_this8.prepareArray([queueLength,weight,Date.now()]));var _ref4=_slicedToArray(_ref3,3);reachedHWM=_ref4[0];blocked=_ref4[1];strategy=_ref4[2];return{reachedHWM:_this8.convertBool(reachedHWM),blocked:_this8.convertBool(blocked),strategy:strategy}}catch(error){e=error;if(e.message.indexOf("OVERWEIGHT")===0){var _e$message$split=e.message.split(":");var _e$message$split2=_slicedToArray(_e$message$split,3);overweight=_e$message$split2[0];weight=_e$message$split2[1];maxConcurrent=_e$message$split2[2];throw new BottleneckError(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${maxConcurrent}`)}else{throw e}}})()}__free__(index,weight){var _this9=this;return _asyncToGenerator(function*(){var result;result=yield _this9.runScript("free",_this9.prepareArray([index,Date.now()]));return{running:result}})()}};module.exports=RedisDatastore}).call(undefined)},{"./BottleneckError":2,"./IORedisConnection":6,"./RedisConnection":8,"./Scripts":10,"./parser":15}],10:[function(require,module,exports){"use strict";(function(){var libraries,lua,templates;lua=require("./lua.json");libraries={get_time:lua["get_time.lua"],refresh_running:lua["refresh_running.lua"],conditions_check:lua["conditions_check.lua"],refresh_expiration:lua["refresh_expiration.lua"],validate_keys:lua["validate_keys.lua"]};templates={init:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["refresh_expiration"],code:lua["init.lua"]},update_settings:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_expiration"],code:lua["update_settings.lua"]},running:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running"],code:lua["running.lua"]},group_check:{keys:function keys(id){return[`b_${id}_settings`]},libs:[],code:lua["group_check.lua"]},check:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running","conditions_check"],code:lua["check.lua"]},submit:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running","conditions_check","refresh_expiration"],code:lua["submit.lua"]},register:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running","conditions_check","refresh_expiration"],code:lua["register.lua"]},free:{keys:function keys(id){return[`b_${id}_settings`,`b_${id}_running`,`b_${id}_executing`]},libs:["validate_keys","refresh_running"],code:lua["free.lua"]},current_reservoir:{keys:function keys(id){return[`b_${id}_settings`]},libs:["validate_keys"],code:lua["current_reservoir.lua"]},increment_reservoir:{keys:function keys(id){return[`b_${id}_settings`]},libs:["validate_keys"],code:lua["increment_reservoir.lua"]}};exports.names=Object.keys(templates);exports.keys=function(name,id){return templates[name].keys(id)};exports.payload=function(name){return templates[name].libs.map(function(lib){return libraries[lib]}).join("\n")+templates[name].code}}).call(undefined)},{"./lua.json":14}],11:[function(require,module,exports){"use strict";(function(){var BottleneckError,States;BottleneckError=require("./BottleneckError");States=class States{constructor(status){this.status=status;this.jobs={};this.counts=this.status.map(function(){return 0})}next(id){var current,next;current=this.jobs[id];next=current+1;if(current!=null&&next{acc[this.status[i]]=v;return acc},{})}};module.exports=States}).call(undefined)},{"./BottleneckError":2}],12:[function(require,module,exports){"use strict";var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break}}catch(err){_d=true;_e=err}finally{try{if(!_n&&_i["return"])_i["return"]()}finally{if(_d)throw _e}}return _arr}return function(arr,i){if(Array.isArray(arr)){return arr}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i)}else{throw new TypeError("Invalid attempt to destructure non-iterable instance")}}}();function _toArray(arr){return Array.isArray(arr)?arr:Array.from(arr)}(function(){var DLList,Sync,splice=[].splice;DLList=require("./DLList");Sync=class Sync{constructor(name){this.submit=this.submit.bind(this);this.schedule=this.schedule.bind(this);this.name=name;this._running=0;this._queue=new DLList}isEmpty(){return this._queue.length===0}_tryToRun(){var next;if(this._running<1&&this._queue.length>0){this._running++;next=this._queue.shift();return next.task.apply({},next.args.concat((...args)=>{var ref;this._running--;this._tryToRun();return(ref=next.cb)!=null?ref.apply({},args):void 0}))}}submit(task,...args){var _ref,_ref2,_splice$call,_splice$call2;var cb,ref;ref=args,_ref=ref,_ref2=_toArray(_ref),args=_ref2.slice(0),_ref,_splice$call=splice.call(args,-1),_splice$call2=_slicedToArray(_splice$call,1),cb=_splice$call2[0],_splice$call;this._queue.push({task:task,args:args,cb:cb});return this._tryToRun()}schedule(task,...args){var wrapped;wrapped=function wrapped(...args){var _ref3,_ref4,_splice$call3,_splice$call4;var cb,ref;ref=args,_ref3=ref,_ref4=_toArray(_ref3),args=_ref4.slice(0),_ref3,_splice$call3=splice.call(args,-1),_splice$call4=_slicedToArray(_splice$call3,1),cb=_splice$call4[0],_splice$call3;return task.apply({},args).then(function(...args){return cb.apply({},Array.prototype.concat(null,args))}).catch(function(...args){return cb.apply({},args)})};return new Promise((resolve,reject)=>{return this.submit.apply({},Array.prototype.concat(wrapped,args,function(...args){return(args[0]!=null?reject:(args.shift(),resolve)).apply({},args)}))})}};module.exports=Sync}).call(undefined)},{"./DLList":3}],13:[function(require,module,exports){"use strict";(function(){module.exports=require("./Bottleneck")}).call(undefined)},{"./Bottleneck":1}],14:[function(require,module,exports){module.exports={"check.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal weight = tonumber(ARGV[1])\nlocal now = tonumber(ARGV[2])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'reservoir',\n 'nextRequest'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal reservoir = tonumber(settings[2])\nlocal nextRequest = tonumber(settings[3])\n\nlocal conditionsCheck = conditions_check(weight, maxConcurrent, running, reservoir)\n\nlocal result = conditionsCheck and nextRequest - now <= 0\n\nreturn result\n","conditions_check.lua":"local conditions_check = function (weight, maxConcurrent, running, reservoir)\n return (\n (maxConcurrent == nil or running + weight <= maxConcurrent) and\n (reservoir == nil or reservoir - weight >= 0)\n )\nend\n","current_reservoir.lua":"local settings_key = KEYS[1]\n\nreturn tonumber(redis.call('hget', settings_key, 'reservoir'))\n","free.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal index = ARGV[1]\nlocal now = ARGV[2]\n\nredis.call('zadd', executing_key, 0, index)\n\nreturn refresh_running(executing_key, running_key, settings_key, now)\n","get_time.lua":"redis.replicate_commands()\n\nlocal get_time = function ()\n local time = redis.call('time')\n\n return tonumber(time[1]..string.sub(time[2], 1, 3))\nend\n","group_check.lua":"local settings_key = KEYS[1]\n\nreturn not (redis.call('exists', settings_key) == 1)\n","increment_reservoir.lua":"local settings_key = KEYS[1]\nlocal incr = ARGV[1]\n\nreturn redis.call('hincrby', settings_key, 'reservoir', incr)\n","init.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal clear = tonumber(ARGV[1])\n\nif clear == 1 then\n redis.call('del', settings_key, running_key, executing_key)\nend\n\nif redis.call('exists', settings_key) == 0 then\n local args = {'hmset', settings_key}\n\n for i = 2, #ARGV do\n table.insert(args, ARGV[i])\n end\n\n redis.call(unpack(args))\nend\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn {}\n","refresh_expiration.lua":"local refresh_expiration = function (executing_key, running_key, settings_key, now, nextRequest, groupTimeout)\n\n if groupTimeout ~= nil then\n local ttl = (nextRequest + groupTimeout) - now\n\n redis.call('pexpire', executing_key, ttl)\n redis.call('pexpire', running_key, ttl)\n redis.call('pexpire', settings_key, ttl)\n end\n\nend\n","refresh_running.lua":"local refresh_running = function (executing_key, running_key, settings_key, now)\n\n local expired = redis.call('zrangebyscore', executing_key, '-inf', '('..now)\n\n if #expired == 0 then\n return redis.call('hget', settings_key, 'running')\n else\n redis.call('zremrangebyscore', executing_key, '-inf', '('..now)\n\n local args = {'hmget', running_key}\n for i = 1, #expired do\n table.insert(args, expired[i])\n end\n\n local weights = redis.call(unpack(args))\n\n args[1] = 'hdel'\n local deleted = redis.call(unpack(args))\n\n local total = 0\n for i = 1, #weights do\n total = total + (tonumber(weights[i]) or 0)\n end\n local incr = -total\n if total == 0 then\n incr = 0\n else\n local id = redis.call('hget', settings_key, 'id')\n redis.call('publish', 'bottleneck_'..id, 'freed:'..total)\n end\n\n return redis.call('hincrby', settings_key, 'running', incr)\n end\n\nend\n","register.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal index = ARGV[1]\nlocal weight = tonumber(ARGV[2])\nlocal expiration = tonumber(ARGV[3])\nlocal now = tonumber(ARGV[4])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'reservoir',\n 'nextRequest',\n 'minTime',\n 'groupTimeout'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal reservoir = tonumber(settings[2])\nlocal nextRequest = tonumber(settings[3])\nlocal minTime = tonumber(settings[4])\nlocal groupTimeout = tonumber(settings[5])\n\nif conditions_check(weight, maxConcurrent, running, reservoir) then\n\n if expiration ~= nil then\n redis.call('zadd', executing_key, now + expiration, index)\n end\n redis.call('hset', running_key, index, weight)\n redis.call('hincrby', settings_key, 'running', weight)\n\n local wait = math.max(nextRequest - now, 0)\n local newNextRequest = now + wait + minTime\n\n if reservoir == nil then\n redis.call('hset', settings_key,\n 'nextRequest', newNextRequest\n )\n else\n reservoir = reservoir - weight\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'nextRequest', newNextRequest\n )\n end\n\n refresh_expiration(executing_key, running_key, settings_key, now, newNextRequest, groupTimeout)\n\n return {true, wait, reservoir}\n\nelse\n return {false}\nend\n","running.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\nlocal now = ARGV[1]\n\nreturn tonumber(refresh_running(executing_key, running_key, settings_key, now))\n","submit.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal queueLength = tonumber(ARGV[1])\nlocal weight = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\n\nlocal running = tonumber(refresh_running(executing_key, running_key, settings_key, now))\nlocal settings = redis.call('hmget', settings_key,\n 'maxConcurrent',\n 'highWater',\n 'reservoir',\n 'nextRequest',\n 'strategy',\n 'unblockTime',\n 'penalty',\n 'minTime',\n 'groupTimeout'\n)\nlocal maxConcurrent = tonumber(settings[1])\nlocal highWater = tonumber(settings[2])\nlocal reservoir = tonumber(settings[3])\nlocal nextRequest = tonumber(settings[4])\nlocal strategy = tonumber(settings[5])\nlocal unblockTime = tonumber(settings[6])\nlocal penalty = tonumber(settings[7])\nlocal minTime = tonumber(settings[8])\nlocal groupTimeout = tonumber(settings[9])\n\nif maxConcurrent ~= nil and weight > maxConcurrent then\n return redis.error_reply('OVERWEIGHT:'..weight..':'..maxConcurrent)\nend\n\nlocal reachedHWM = (highWater ~= nil and queueLength == highWater\n and not (\n conditions_check(weight, maxConcurrent, running, reservoir)\n and nextRequest - now <= 0\n )\n)\n\nlocal blocked = strategy == 3 and (reachedHWM or unblockTime >= now)\n\nif blocked then\n local computedPenalty = penalty\n if computedPenalty == nil then\n if minTime == 0 then\n computedPenalty = 5000\n else\n computedPenalty = 15 * minTime\n end\n end\n\n local newNextRequest = now + computedPenalty + minTime\n\n redis.call('hmset', settings_key,\n 'unblockTime', now + computedPenalty,\n 'nextRequest', newNextRequest\n )\n\n refresh_expiration(executing_key, running_key, settings_key, now, newNextRequest, groupTimeout)\nend\n\nreturn {reachedHWM, blocked, strategy}\n","update_settings.lua":"local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal args = {'hmset', settings_key}\n\nfor i = 1, #ARGV do\n table.insert(args, ARGV[i])\nend\n\nredis.call(unpack(args))\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn {}\n","validate_keys.lua":"local settings_key = KEYS[1]\n\nif not (redis.call('exists', settings_key) == 1) then\n return redis.error_reply('SETTINGS_KEY_NOT_FOUND')\nend\n"}},{}],15:[function(require,module,exports){"use strict";(function(){exports.load=function(received,defaults,onto={}){var k,ref,v;for(k in defaults){v=defaults[k];onto[k]=(ref=received[k])!=null?ref:v}return onto};exports.overwrite=function(received,defaults,onto={}){var k,v;for(k in received){v=received[k];if(defaults[k]!==void 0){onto[k]=v}}return onto}}).call(undefined)},{}],16:[function(require,module,exports){module.exports={name:"bottleneck",version:"2.7.2",description:"Distributed task scheduler and rate limiter",main:"lib/index.js",typings:"bottleneck.d.ts",scripts:{test:"./node_modules/mocha/bin/mocha test",build:"./scripts/build.sh",compile:"./scripts/build.sh compile"},repository:{type:"git",url:"https://github.com/SGrondin/bottleneck"},keywords:["async rate limiter","rate limiter","rate limiting","async","rate","limiting","limiter","throttle","throttling","load","ddos"],author:{name:"Simon Grondin"},license:"MIT",bugs:{url:"https://github.com/SGrondin/bottleneck/issues"},devDependencies:{"@types/es6-promise":"0.0.33",assert:"1.4.x","babel-core":"^6.26.0","babel-preset-env":"^1.6.1",browserify:"*",coffeescript:"2.2.x","ejs-cli":"2.0.1",ioredis:"^4.0.0",mocha:"4.x",redis:"^2.8.0",typescript:"^2.6.2","uglify-es":"3.x"},dependencies:{}}},{}]},{},[13]); \ No newline at end of file diff --git a/lib/Bottleneck.js b/lib/Bottleneck.js index 0e76811..3bbf837 100644 --- a/lib/Bottleneck.js +++ b/lib/Bottleneck.js @@ -94,7 +94,8 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a var _this = this; return _asyncToGenerator(function* () { - return yield _this._store.__disconnect__(flush); + yield _this._store.__disconnect__(flush); + return _this; })(); } @@ -579,6 +580,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a Bottleneck.prototype.storeInstanceDefaults = { clientOptions: {}, + clusterNodes: null, clearDatastore: false, Promise: Promise, timeout: null, diff --git a/lib/Group.js b/lib/Group.js index d5f0e8e..44edd73 100644 --- a/lib/Group.js +++ b/lib/Group.js @@ -17,7 +17,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a Group = function () { class Group { constructor(limiterOptions = {}) { - var ref, ref1, ref2, ref3; + var ref, ref1, ref2, ref3, ref4; this.key = this.key.bind(this); this.deleteKey = this.deleteKey.bind(this); this.limiters = this.limiters.bind(this); @@ -33,7 +33,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a if (this.limiterOptions.datastore === "redis") { this._connection = new RedisConnection((ref = this.limiterOptions.clientOptions) != null ? ref : {}, (ref1 = this.limiterOptions.Promise) != null ? ref1 : Promise, this.Events); } else if (this.limiterOptions.datastore === "ioredis") { - this._connection = new IORedisConnection((ref2 = this.limiterOptions.clientOptions) != null ? ref2 : {}, (ref3 = this.limiterOptions.Promise) != null ? ref3 : Promise, this.Events); + this._connection = new IORedisConnection((ref2 = this.limiterOptions.clusterNodes) != null ? ref2 : null, (ref3 = this.limiterOptions.clientOptions) != null ? ref3 : {}, (ref4 = this.limiterOptions.Promise) != null ? ref4 : Promise, this.Events); } } diff --git a/lib/IORedisConnection.js b/lib/IORedisConnection.js index b7f68ca..a2ea55c 100644 --- a/lib/IORedisConnection.js +++ b/lib/IORedisConnection.js @@ -7,14 +7,20 @@ Scripts = require("./Scripts"); IORedisConnection = class IORedisConnection { - constructor(clientOptions, Promise, Events) { + constructor(clusterNodes, clientOptions, Promise, Events) { var Redis; + this.clusterNodes = clusterNodes; this.clientOptions = clientOptions; this.Promise = Promise; this.Events = Events; Redis = eval("require")("ioredis"); // Obfuscated or else Webpack/Angular will try to inline the optional ioredis module - this.client = new Redis(this.clientOptions); - this.subClient = new Redis(this.clientOptions); + if (this.clusterNodes != null) { + this.client = new Redis.Cluster(this.clusterNodes, this.clientOptions); + this.subClient = new Redis.Cluster(this.clusterNodes, this.clientOptions); + } else { + this.client = new Redis(this.clientOptions); + this.subClient = new Redis(this.clientOptions); + } this.pubsubs = {}; this.ready = new this.Promise((resolve, reject) => { var count, done, errorListener; @@ -87,8 +93,13 @@ } disconnect(flush) { - this.client.end(flush); - return this.subClient.end(flush); + if (flush) { + return this.Promise.all([this.client.quit(), this.subClient.quit()]); + } else { + this.client.disconnect(); + this.subClient.disconnect(); + return this.Promise.resolve(); + } } }; diff --git a/lib/LocalDatastore.js b/lib/LocalDatastore.js index 76b92c2..fd90382 100644 --- a/lib/LocalDatastore.js +++ b/lib/LocalDatastore.js @@ -24,7 +24,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a } __disconnect__(flush) { - return this; + return this.Promise.resolve(); } yieldLoop(t = 0) { diff --git a/lib/RedisConnection.js b/lib/RedisConnection.js index 37f4f3e..8cc09fc 100644 --- a/lib/RedisConnection.js +++ b/lib/RedisConnection.js @@ -8,13 +8,13 @@ RedisConnection = class RedisConnection { constructor(clientOptions, Promise, Events) { - var redis; + var Redis; this.clientOptions = clientOptions; this.Promise = Promise; this.Events = Events; - redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module - this.client = redis.createClient(this.clientOptions); - this.subClient = redis.createClient(this.clientOptions); + Redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module + this.client = Redis.createClient(this.clientOptions); + this.subClient = Redis.createClient(this.clientOptions); this.pubsubs = {}; this.shas = {}; this.ready = new this.Promise((resolve, reject) => { @@ -99,7 +99,8 @@ disconnect(flush) { this.client.end(flush); - return this.subClient.end(flush); + this.subClient.end(flush); + return this.Promise.resolve(); } }; diff --git a/lib/RedisDatastore.js b/lib/RedisDatastore.js index c2806ae..eeaeb67 100644 --- a/lib/RedisDatastore.js +++ b/lib/RedisDatastore.js @@ -25,11 +25,11 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a this.originalId = this.instance.id; parser.load(options, options, this); this.isReady = false; - this.connection = this._groupConnection ? this._groupConnection : this.instance.datastore === "redis" ? new RedisConnection(this.clientOptions, this.Promise, this.instance.Events) : this.instance.datastore === "ioredis" ? new IORedisConnection(this.clientOptions, this.Promise, this.instance.Events) : void 0; + this.connection = this._groupConnection ? this._groupConnection : this.instance.datastore === "redis" ? new RedisConnection(this.clientOptions, this.Promise, this.instance.Events) : this.instance.datastore === "ioredis" ? new IORedisConnection(this.clusterNodes, this.clientOptions, this.Promise, this.instance.Events) : void 0; this.ready = this.connection.ready.then(clients => { var args; this.clients = clients; - args = this.prepareInitSettings(options.clearDatastore); + args = this.prepareInitSettings(this.clearDatastore); this.isReady = true; return this.runScript("init", args); }).then(() => { diff --git a/src/Bottleneck.coffee b/src/Bottleneck.coffee index 5f5d779..75c48c2 100644 --- a/src/Bottleneck.coffee +++ b/src/Bottleneck.coffee @@ -28,6 +28,7 @@ class Bottleneck reservoir: null, storeInstanceDefaults: clientOptions: {}, + clusterNodes: null, clearDatastore: false, Promise: Promise, timeout: null, @@ -59,7 +60,9 @@ class Bottleneck else throw new Bottleneck::BottleneckError "Invalid datastore type: #{@datastore}" ready: => @_store.ready clients: => @_store.clients - disconnect: (flush=true) => await @_store.__disconnect__ flush + disconnect: (flush=true) => + await @_store.__disconnect__ flush + @ chain: (@_limiter) => @ queued: (priority) => if priority? then @_queues[priority].length else @_queues.reduce ((a, b) -> a+b.length), 0 empty: -> @queued() == 0 and @_submitLock.isEmpty() diff --git a/src/Group.coffee b/src/Group.coffee index cc8e503..29db14c 100644 --- a/src/Group.coffee +++ b/src/Group.coffee @@ -15,7 +15,7 @@ class Group if @limiterOptions.datastore == "redis" @_connection = new RedisConnection (@limiterOptions.clientOptions ? {}), (@limiterOptions.Promise ? Promise), @Events else if @limiterOptions.datastore == "ioredis" - @_connection = new IORedisConnection (@limiterOptions.clientOptions ? {}), (@limiterOptions.Promise ? Promise), @Events + @_connection = new IORedisConnection (@limiterOptions.clusterNodes ? null), (@limiterOptions.clientOptions ? {}), (@limiterOptions.Promise ? Promise), @Events key: (key="") => @instances[key] ? do => limiter = @instances[key] = new @Bottleneck Object.assign @limiterOptions, { diff --git a/src/IORedisConnection.coffee b/src/IORedisConnection.coffee index 79f69e5..27cd6cc 100644 --- a/src/IORedisConnection.coffee +++ b/src/IORedisConnection.coffee @@ -1,51 +1,59 @@ -Scripts = require "./Scripts" - -class IORedisConnection - constructor: (@clientOptions, @Promise, @Events) -> - Redis = eval("require")("ioredis") # Obfuscated or else Webpack/Angular will try to inline the optional ioredis module - @client = new Redis @clientOptions - @subClient = new Redis @clientOptions - @pubsubs = {} - - @ready = new @Promise (resolve, reject) => - errorListener = (e) => - [@client, @subClient].forEach (client) => - client.removeListener "error", errorListener - reject e - count = 0 - done = => - count++ - if count == 2 - [@client, @subClient].forEach (client) => - client.removeListener "error", errorListener - client.on "error", (e) => @Events.trigger "error", [e] - resolve({ client: @client, subscriber: @subClient }) - @client.on "error", errorListener - @client.on "ready", -> done() - @subClient.on "error", errorListener - @subClient.on "ready", => - @subClient.psubscribe "bottleneck_*", -> done() - @subClient.on "pmessage", (pattern, channel, message) => - @pubsubs[channel]?(message) - - .then => Scripts.names.forEach (name) => @client.defineCommand name, { lua: Scripts.payload(name) } - .then => @Promise.resolve { client: @client, subscriber: @subClient } - - addLimiter: (instance, pubsub) -> - @pubsubs["bottleneck_#{instance.id}"] = pubsub - - removeLimiter: (instance) -> - delete @pubsubs["bottleneck_#{instance.id}"] - - scriptArgs: (name, id, args, cb) -> - keys = Scripts.keys name, id - [keys.length].concat keys, args, cb - - scriptFn: (name) -> - @client[name].bind(@client) - - disconnect: (flush) -> - @client.end(flush) - @subClient.end(flush) - -module.exports = IORedisConnection +Scripts = require "./Scripts" + +class IORedisConnection + constructor: (@clusterNodes, @clientOptions, @Promise, @Events) -> + Redis = eval("require")("ioredis") # Obfuscated or else Webpack/Angular will try to inline the optional ioredis module + if @clusterNodes? + @client = new Redis.Cluster @clusterNodes, @clientOptions + @subClient = new Redis.Cluster @clusterNodes, @clientOptions + else + @client = new Redis @clientOptions + @subClient = new Redis @clientOptions + @pubsubs = {} + + @ready = new @Promise (resolve, reject) => + errorListener = (e) => + [@client, @subClient].forEach (client) => + client.removeListener "error", errorListener + reject e + count = 0 + done = => + count++ + if count == 2 + [@client, @subClient].forEach (client) => + client.removeListener "error", errorListener + client.on "error", (e) => @Events.trigger "error", [e] + resolve({ client: @client, subscriber: @subClient }) + @client.on "error", errorListener + @client.on "ready", -> done() + @subClient.on "error", errorListener + @subClient.on "ready", => + @subClient.psubscribe "bottleneck_*", -> done() + @subClient.on "pmessage", (pattern, channel, message) => + @pubsubs[channel]?(message) + + .then => Scripts.names.forEach (name) => @client.defineCommand name, { lua: Scripts.payload(name) } + .then => @Promise.resolve { client: @client, subscriber: @subClient } + + addLimiter: (instance, pubsub) -> + @pubsubs["bottleneck_#{instance.id}"] = pubsub + + removeLimiter: (instance) -> + delete @pubsubs["bottleneck_#{instance.id}"] + + scriptArgs: (name, id, args, cb) -> + keys = Scripts.keys name, id + [keys.length].concat keys, args, cb + + scriptFn: (name) -> + @client[name].bind(@client) + + disconnect: (flush) -> + if flush + @Promise.all [@client.quit(), @subClient.quit()] + else + @client.disconnect() + @subClient.disconnect() + @Promise.resolve() + +module.exports = IORedisConnection diff --git a/src/LocalDatastore.coffee b/src/LocalDatastore.coffee index cbc7018..58574c1 100644 --- a/src/LocalDatastore.coffee +++ b/src/LocalDatastore.coffee @@ -12,7 +12,7 @@ class LocalDatastore @ready = @yieldLoop() @clients = {} - __disconnect__: (flush) -> @ + __disconnect__: (flush) -> @Promise.resolve() yieldLoop: (t=0) -> new @Promise (resolve, reject) -> setTimeout resolve, t diff --git a/src/RedisConnection.coffee b/src/RedisConnection.coffee index 0321fcc..cb88f58 100644 --- a/src/RedisConnection.coffee +++ b/src/RedisConnection.coffee @@ -2,9 +2,9 @@ Scripts = require "./Scripts" class RedisConnection constructor: (@clientOptions, @Promise, @Events) -> - redis = eval("require")("redis") # Obfuscated or else Webpack/Angular will try to inline the optional redis module - @client = redis.createClient @clientOptions - @subClient = redis.createClient @clientOptions + Redis = eval("require")("redis") # Obfuscated or else Webpack/Angular will try to inline the optional redis module + @client = Redis.createClient @clientOptions + @subClient = Redis.createClient @clientOptions @pubsubs = {} @shas = {} @@ -57,5 +57,6 @@ class RedisConnection disconnect: (flush) -> @client.end flush @subClient.end flush + @Promise.resolve() module.exports = RedisConnection diff --git a/src/RedisDatastore.coffee b/src/RedisDatastore.coffee index 1daec02..f6542eb 100644 --- a/src/RedisDatastore.coffee +++ b/src/RedisDatastore.coffee @@ -12,11 +12,11 @@ class RedisDatastore @connection = if @_groupConnection then @_groupConnection else if @instance.datastore == "redis" then new RedisConnection @clientOptions, @Promise, @instance.Events - else if @instance.datastore == "ioredis" then new IORedisConnection @clientOptions, @Promise, @instance.Events + else if @instance.datastore == "ioredis" then new IORedisConnection @clusterNodes, @clientOptions, @Promise, @instance.Events @ready = @connection.ready .then (@clients) => - args = @prepareInitSettings options.clearDatastore + args = @prepareInitSettings @clearDatastore @isReady = true @runScript "init", args .then => diff --git a/test.ts b/test.ts index f2d247f..ecbaf00 100644 --- a/test.ts +++ b/test.ts @@ -63,7 +63,11 @@ let group = new Bottleneck.Group({ maxConcurrent: 5, minTime: 1000, highWater: 10, - strategy: Bottleneck.strategy.LEAK + strategy: Bottleneck.strategy.LEAK, + datastore: "ioredis", + clearDatastore: true, + clientOptions: {}, + clusterNodes: [] }); group.on('created', (limiter, key) => { diff --git a/test/ioredis.js b/test/ioredis.js new file mode 100644 index 0000000..a2be982 --- /dev/null +++ b/test/ioredis.js @@ -0,0 +1,27 @@ +var makeTest = require('./context') +var Bottleneck = require('../lib/index.js') +var assert = require('assert') + +if (process.env.DATASTORE === 'ioredis') { + describe('IORedis-only', function () { + var c + + afterEach(function () { + c.limiter.disconnect(false) + }) + + it('Should connect in Redis Cluter mode', function () { + c = makeTest({ + maxConcurrent: 2, + clientOptions: {}, + clusterNodes: [{ + host: '127.0.0.1', + port: 6379 + }] + }) + + c.mustEqual(c.limiter.datastore, 'ioredis') + c.mustEqual(c.limiter._store.connection.client.nodes().length, 1) + }) + }) +} diff --git a/test/redis.js b/test/redis.js index 5368c0d..9d5bf7f 100644 --- a/test/redis.js +++ b/test/redis.js @@ -25,7 +25,7 @@ if (process.env.DATASTORE === 'redis' || process.env.DATASTORE === 'ioredis') { }) }) - it('Should pass clients', function () { + it('Should return clients', function () { c = makeTest({ maxConcurrent: 2 }) return c.limiter.ready() @@ -35,6 +35,18 @@ if (process.env.DATASTORE === 'redis' || process.env.DATASTORE === 'ioredis') { }) }) + it('Should return a promise when disconnecting', function () { + c = makeTest({ maxConcurrent: 2 }) + + return c.limiter.ready() + .then(function (clients) { + return c.limiter.disconnect() + }) + .then(function (limiter) { + assert(limiter instanceof Bottleneck) + }) + }) + it('Should not have a key TTL by default for standalone limiters', function () { c = makeTest()