UNPKG

bottleneck

Version:

Distributed task scheduler and rate limiter

1,507 lines (1,287 loc) 57.7 kB
(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<t.length;i++)o(t[i]);return o}return r})()({1:[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 _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"); }); }; } // Generated by CoffeeScript 2.2.4 (function () { var Bottleneck, DEFAULT_PRIORITY, DLList, Events, Local, NUM_PRIORITIES, RedisStorage, States, Sync, packagejson, parser, splice = [].splice; NUM_PRIORITIES = 10; DEFAULT_PRIORITY = 5; parser = require("./parser"); Local = require("./Local"); RedisStorage = require("./RedisStorage"); Events = require("./Events"); States = require("./States"); DLList = require("./DLList"); Sync = require("./Sync"); packagejson = require("../package.json"); Bottleneck = function () { class Bottleneck { constructor(options = {}, ...invalid) { var sDefaults; this.ready = this.ready.bind(this); this.clients = this.clients.bind(this); this.disconnect = this.disconnect.bind(this); this.chain = this.chain.bind(this); this.queued = this.queued.bind(this); this.running = this.running.bind(this); this.check = this.check.bind(this); this._drainOne = this._drainOne.bind(this); this.submit = this.submit.bind(this); this.schedule = this.schedule.bind(this); this.wrap = this.wrap.bind(this); this.updateSettings = this.updateSettings.bind(this); this.currentReservoir = this.currentReservoir.bind(this); this.incrementReservoir = this.incrementReservoir.bind(this); if (!(options != null && typeof options === "object" && invalid.length === 0)) { throw new Bottleneck.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1."); } parser.load(options, this.instanceDefaults, this); this._queues = this._makeQueues(); this._scheduled = {}; this._states = new States(["RECEIVED", "QUEUED", "RUNNING", "EXECUTING"].concat(this.trackDoneStatus ? ["DONE"] : [])); this._limiter = null; this.Events = new Events(this); this._submitLock = new Sync("submit"); this._registerLock = new Sync("register"); sDefaults = parser.load(options, this.storeDefaults, {}); this._store = function () { if (this.datastore === "local") { return new Local(parser.load(options, this.storeInstanceDefaults, sDefaults)); } else if (this.datastore === "redis") { return new RedisStorage(this, sDefaults, parser.load(options, this.storeInstanceDefaults, {})); } else { throw new Bottleneck.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`); } }.call(this); } ready() { return this._store.ready; } clients() { return this._store.clients; } disconnect(flush = true) { var _this = this; return _asyncToGenerator(function* () { return yield _this._store.disconnect(flush); })(); } chain(_limiter) { this._limiter = _limiter; return this; } queued(priority) { if (priority != null) { return this._queues[priority].length; } else { return this._queues.reduce(function (a, b) { return a + b.length; }, 0); } } empty() { return this.queued() === 0 && this._submitLock.isEmpty(); } running() { var _this2 = this; return _asyncToGenerator(function* () { return yield _this2._store.__running__(); })(); } jobStatus(id) { return this._states.jobStatus(id); } counts() { return this._states.statusCounts(); } _makeQueues() { var i, j, ref, results; results = []; for (i = j = 1, ref = NUM_PRIORITIES; 1 <= ref ? j <= ref : j >= 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; j < len; i = ++j) { x = arr[i]; if (fn(x)) { return x; } } }()) != null ? ref : []; } _getFirst(arr) { return this._find(arr, function (x) { return x.length > 0; }); } _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); // DONE clearTimeout(_this4._scheduled[index].expiration); delete _this4._scheduled[index]; _this4.Events.trigger("debug", [`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); // RUNNING 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); // EXECUTING 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, options }]); index = this._randomIndex(); return this._store.__register__(index, options.weight, options.expiration).then(({ success, wait, reservoir }) => { var empty, next; this.Events.trigger("debug", [`Drained ${options.id}`, { success, args, 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) { this._states.remove(job.options.id); if (this.rejectOnDrop) { job.cb.apply({}, [new Bottleneck.prototype.BottleneckError("This job has been dropped by Bottleneck")]); } return this.Events.trigger("dropped", [job]); } submit(...args) { var _this5 = this; var cb, job, options, ref, ref1, task; if (typeof args[0] === "function") { var _ref3, _ref4, _splice$call, _splice$call2; ref = args, (_ref3 = ref, _ref4 = _toArray(_ref3), task = _ref4[0], args = _ref4.slice(1), _ref3), (_splice$call = splice.call(args, -1), _splice$call2 = _slicedToArray(_splice$call, 1), cb = _splice$call2[0], _splice$call); options = this.jobDefaults; } else { var _ref5, _ref6, _splice$call3, _splice$call4; ref1 = args, (_ref5 = ref1, _ref6 = _toArray(_ref5), options = _ref6[0], task = _ref6[1], args = _ref6.slice(2), _ref5), (_splice$call3 = splice.call(args, -1), _splice$call4 = _slicedToArray(_splice$call3, 1), cb = _splice$call4[0], _splice$call3); options = parser.load(options, this.jobDefaults); } job = { options, task, args, 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) { job.cb(new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${options.id})`)); return false; } this._states.start(options.id); // RECEIVED this.Events.trigger("debug", [`Queueing ${options.id}`, { args, options }]); return this._submitLock.schedule(_asyncToGenerator(function* () { var blocked, e, reachedHWM, shifted, strategy; try { var _ref8 = yield _this5._store.__submit__(_this5.queued(), options.weight); reachedHWM = _ref8.reachedHWM; blocked = _ref8.blocked; strategy = _ref8.strategy; _this5.Events.trigger("debug", [`Queued ${options.id}`, { args, options, reachedHWM, blocked }]); } catch (error) { e = error; _this5._states.remove(options.id); _this5.Events.trigger("debug", [`Could not queue ${options.id}`, { args, options, error: e }]); job.cb(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); // QUEUED _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 = 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 _ref9, _ref10, _splice$call5, _splice$call6; var cb, ref, returned; ref = args, (_ref9 = ref, _ref10 = _toArray(_ref9), args = _ref10.slice(0), _ref9), (_splice$call5 = splice.call(args, -1), _splice$call6 = _slicedToArray(_splice$call5, 1), cb = _splice$call6[0], _splice$call5); 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) { return (...args) => { return this.schedule.apply({}, Array.prototype.concat(fn, args)); }; } 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: "<no-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, _groupTimeout: null }; Bottleneck.prototype.instanceDefaults = { datastore: "local", id: "<no-id>", rejectOnDrop: true, trackDoneStatus: false, Promise: Promise }; return Bottleneck; }.call(this); module.exports = Bottleneck; }).call(undefined); },{"../package.json":13,"./BottleneckError":2,"./DLList":3,"./Events":4,"./Group":5,"./Local":6,"./RedisStorage":7,"./States":8,"./Sync":9,"./parser":12}],2:[function(require,module,exports){ "use strict"; // Generated by CoffeeScript 2.2.4 (function () { var BottleneckError; BottleneckError = class BottleneckError extends Error {}; module.exports = BottleneckError; }).call(undefined); },{}],3:[function(require,module,exports){ "use strict"; // Generated by CoffeeScript 2.2.4 (function () { var DLList; DLList = class DLList { constructor() { this._first = null; this._last = null; this.length = 0; } push(value) { var node; this.length++; node = { 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; } }; module.exports = DLList; }).call(undefined); },{}],4:[function(require,module,exports){ "use strict"; // Generated by CoffeeScript 2.2.4 (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, 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"); }); }; } // Generated by CoffeeScript 2.2.4 (function () { var Events, Group, parser; parser = require("./parser"); Events = require("./Events"); Group = function () { class Group { constructor(limiterOptions = {}, groupOptions = {}) { 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(groupOptions, this.defaults, this); this.Events = new Events(this); this.instances = {}; this.Bottleneck = require("./Bottleneck"); this._startAutoCleanup(); } 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}`, _groupTimeout: this.timeout })); 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); if (options.timeout != null) { return this._startAutoCleanup(); } } }; Group.prototype.defaults = { timeout: 1000 * 60 * 5 }; return Group; }.call(this); module.exports = Group; }).call(undefined); },{"./Bottleneck":1,"./Events":4,"./parser":12}],6:[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"); }); }; } // Generated by CoffeeScript 2.2.4 (function () { var BottleneckError, DLList, Local, parser; parser = require("./parser"); DLList = require("./DLList"); BottleneckError = require("./BottleneckError"); Local = class Local { 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 || 5000; } __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._groupTimeout < time; })(); } conditionsCheck(weight) { return (this.maxConcurrent == null || this._running + weight <= this.maxConcurrent) && (this.reservoir == null || this.reservoir - weight >= 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, 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, 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 = Local; }).call(undefined); },{"./BottleneckError":2,"./DLList":3,"./parser":12}],7:[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"); }); }; } // Generated by CoffeeScript 2.2.4 (function () { var BottleneckError, DLList, RedisStorage, libraries, lua, parser, scriptTemplates; parser = require("./parser"); DLList = require("./DLList"); BottleneckError = require("./BottleneckError"); 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"] }; scriptTemplates = function scriptTemplates(id) { return { init: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["refresh_expiration"], code: lua["init.lua"] }, update_settings: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["validate_keys", "refresh_expiration"], code: lua["update_settings.lua"] }, running: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["validate_keys", "refresh_running"], code: lua["running.lua"] }, group_check: { keys: [`b_${id}_settings`], libs: [], code: lua["group_check.lua"] }, check: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["validate_keys", "refresh_running", "conditions_check"], code: lua["check.lua"] }, submit: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["validate_keys", "refresh_running", "conditions_check", "refresh_expiration"], code: lua["submit.lua"] }, register: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["validate_keys", "refresh_running", "conditions_check", "refresh_expiration"], code: lua["register.lua"] }, free: { keys: [`b_${id}_settings`, `b_${id}_running`, `b_${id}_executing`], libs: ["validate_keys", "refresh_running"], code: lua["free.lua"] }, current_reservoir: { keys: [`b_${id}_settings`], libs: ["validate_keys"], code: lua["current_reservoir.lua"] }, increment_reservoir: { keys: [`b_${id}_settings`], libs: ["validate_keys"], code: lua["increment_reservoir.lua"] } }; }; RedisStorage = class RedisStorage { constructor(instance, initSettings, options) { var redis; this.loadAll = this.loadAll.bind(this); this.instance = instance; this.initSettings = initSettings; redis = eval("require")("redis"); // Obfuscated or else Webpack/Angular will try to inline the optional redis module this.originalId = this.instance.id; this.scripts = scriptTemplates(this.originalId); parser.load(options, options, this); this.client = redis.createClient(this.clientOptions); this.subClient = redis.createClient(this.clientOptions); this.shas = {}; this.clients = { client: this.client, subscriber: this.subClient }; this.isReady = false; this.ready = new this.Promise((resolve, reject) => { var count, done, errorListener; errorListener = function errorListener(e) { 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.instance.Events.trigger("error", [e]); }); }); return resolve(); } }; this.client.on("error", errorListener); this.client.on("ready", function () { return done(); }); this.subClient.on("error", errorListener); return this.subClient.on("ready", () => { this.subClient.on("subscribe", function () { return done(); }); return this.subClient.subscribe(`b_${this.originalId}`); }); }).then(this.loadAll).then(() => { var args; this.subClient.on("message", (channel, 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); } }); args = this.prepareInitSettings(options.clearDatastore); this.isReady = true; return this.runScript("init", args); }).then(results => { return this.clients; }); } disconnect(flush) { this.client.end(flush); this.subClient.end(flush); return this; } loadScript(name) { return new this.Promise((resolve, reject) => { var payload; payload = this.scripts[name].libs.map(function (lib) { return libraries[lib]; }).join("\n") + this.scripts[name].code; 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]); }); }); } loadAll() { var k, v; return this.Promise.all(function () { var ref, results1; ref = this.scripts; results1 = []; for (k in ref) { v = ref[k]; results1.push(this.loadScript(k)); } return results1; }.call(this)); } 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._groupTimeout })); args.unshift(clear ? 1 : 0); return args; } runScript(name, args) { var script; 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 { script = this.scripts[name]; return new this.Promise((resolve, reject) => { var arr; arr = [this.shas[name], script.keys.length].concat(script.keys, args, function (err, replies) { if (err != null) { return reject(err); } return resolve(replies); }); this.instance.Events.trigger("debug", [`Calling Redis script: ${name}.lua`, args]); return this.client.evalsha.bind(this.client).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); } }); } } 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, 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 }; } 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 = RedisStorage; }).call(undefined); },{"./BottleneckError":2,"./DLList":3,"./lua.json":11,"./parser":12}],8:[function(require,module,exports){ "use strict"; // Generated by CoffeeScript 2.2.4 (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 < this.status.length) { this.counts[current]--; this.counts[next]++; return this.jobs[id]++; } else if (current != null) { this.counts[current]--; return delete this.jobs[id]; } } start(id, initial = 0) { this.jobs[id] = initial; return this.counts[initial]++; } remove(id) { var current; current = this.jobs[id]; if (current != null) { this.counts[current]--; return delete this.jobs[id]; } } jobStatus(id) { var ref; return (ref = this.status[this.jobs[id]]) != null ? ref : null; } statusCounts() { return this.counts.reduce((acc, v, i) => { acc[this.status[i]] = v; return acc; }, {}); } }; module.exports = States; }).call(undefined); },{"./BottleneckError":2}],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 _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } // Generated by CoffeeScript 2.2.4 (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, args, 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}],10:[function(require,module,exports){ "use strict"; // Generated by CoffeeScript 2.2.4 (function () { module.exports = require("./Bottleneck"); }).call(undefined); },{"./Bottleneck":1}],11:[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\nloc