bottleneck
Version:
Distributed task scheduler and rate limiter
1,341 lines (1,160 loc) • 38.1 kB
JavaScript
/**
* This file contains the Bottleneck library (MIT), compiled to ES2017, and without Clustering support.
* https://github.com/SGrondin/bottleneck
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Bottleneck = factory());
}(this, (function () { 'use strict';
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getCjsExportFromNamespace (n) {
return n && n.default || n;
}
var 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;
};
var 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;
};
var parser = {
load: load,
overwrite: overwrite
};
var DLList;
DLList = class DLList {
constructor(_queues) {
this._queues = _queues;
this._first = null;
this._last = null;
this.length = 0;
}
push(value) {
var node, ref1;
this.length++;
if ((ref1 = this._queues) != null) {
ref1.incr();
}
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, ref2, value;
if (this._first == null) {
return void 0;
} else {
this.length--;
if ((ref1 = this._queues) != null) {
ref1.decr();
}
}
value = this._first.value;
this._first = (ref2 = this._first.next) != null ? ref2 : (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;
}
};
var DLList_1 = DLList;
var Events;
Events = class Events {
constructor(instance) {
this.instance = instance;
this._events = {};
if ((this.instance.on != null) || (this.instance.once != null) || (this.instance.removeAllListeners != null)) {
throw new Error("An Emitter already exists for this object");
}
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 = typeof listener.cb === "function" ? listener.cb(...args) : void 0;
return ret != null ? typeof ret.then === "function" ? ret.then(function() {}).catch((e) => {
return this.trigger("error", e);
}) : void 0 : void 0;
} catch (error) {
e = error;
{
return this.trigger("error", e);
}
}
});
}
};
var Events_1 = Events;
var DLList$1, Events$1, Queues;
DLList$1 = DLList_1;
Events$1 = Events_1;
Queues = class Queues {
constructor(num_priorities) {
var i;
this.Events = new Events$1(this);
this._length = 0;
this._lists = (function() {
var 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$1(this));
}
return results;
}).call(this);
}
incr() {
if (this._length++ === 0) {
return this.Events.trigger("leftzero");
}
}
decr() {
if (--this._length === 0) {
return this.Events.trigger("zero");
}
}
push(priority, job) {
return this._lists[priority].push(job);
}
queued(priority) {
if (priority != null) {
return this._lists[priority].length;
} else {
return this._length;
}
}
shiftAll(fn) {
return this._lists.forEach(function(list) {
return list.forEachShift(fn);
});
}
getFirst(arr = this._lists) {
var j, len, list;
for (j = 0, len = arr.length; j < len; j++) {
list = arr[j];
if (list.length > 0) {
return list;
}
}
return [];
}
shiftLastFrom(priority) {
return this.getFirst(this._lists.slice(priority).reverse()).shift();
}
};
var Queues_1 = Queues;
var BottleneckError;
BottleneckError = class BottleneckError extends Error {};
var BottleneckError_1 = BottleneckError;
var BottleneckError$1, LocalDatastore, parser$1;
parser$1 = parser;
BottleneckError$1 = BottleneckError_1;
LocalDatastore = class LocalDatastore {
constructor(instance, storeOptions, storeInstanceOptions) {
var base;
this.instance = instance;
this.storeOptions = storeOptions;
this.clientId = this.instance._randomIndex();
parser$1.load(storeInstanceOptions, storeInstanceOptions, this);
this._nextRequest = this._lastReservoirRefresh = Date.now();
this._running = 0;
this._done = 0;
this._unblockTime = 0;
this.ready = this.yieldLoop();
this.clients = {};
if (typeof (base = (this.heartbeat = setInterval(() => {
var now, reservoirRefreshActive;
now = Date.now();
reservoirRefreshActive = (this.storeOptions.reservoirRefreshInterval != null) && (this.storeOptions.reservoirRefreshAmount != null);
if (reservoirRefreshActive && now >= this._lastReservoirRefresh + this.storeOptions.reservoirRefreshInterval) {
this.storeOptions.reservoir = this.storeOptions.reservoirRefreshAmount;
this._lastReservoirRefresh = now;
return this.instance._drainAll(this.computeCapacity());
}
}, this.heartbeatInterval))).unref === "function") {
base.unref();
}
}
async __publish__(message) {
await this.yieldLoop();
return this.instance.Events.trigger("message", message.toString());
}
async __disconnect__(flush) {
await this.yieldLoop();
clearInterval(this.heartbeat);
return this.Promise.resolve();
}
yieldLoop(t = 0) {
return new this.Promise(function(resolve, reject) {
return setTimeout(resolve, t);
});
}
computePenalty() {
var ref;
return (ref = this.storeOptions.penalty) != null ? ref : (15 * this.storeOptions.minTime) || 5000;
}
async __updateSettings__(options) {
await this.yieldLoop();
parser$1.overwrite(options, options, this.storeOptions);
this.instance._drainAll(this.computeCapacity());
return true;
}
async __running__() {
await this.yieldLoop();
return this._running;
}
async __done__() {
await this.yieldLoop();
return this._done;
}
async __groupCheck__(time) {
await this.yieldLoop();
return (this._nextRequest + this.timeout) < time;
}
computeCapacity() {
var maxConcurrent, reservoir;
({maxConcurrent, reservoir} = this.storeOptions);
if ((maxConcurrent != null) && (reservoir != null)) {
return Math.min(maxConcurrent - this._running, reservoir);
} else if (maxConcurrent != null) {
return maxConcurrent - this._running;
} else if (reservoir != null) {
return reservoir;
} else {
return null;
}
}
conditionsCheck(weight) {
var capacity;
capacity = this.computeCapacity();
return (capacity == null) || weight <= capacity;
}
async __incrementReservoir__(incr) {
var reservoir;
await this.yieldLoop();
reservoir = this.storeOptions.reservoir += incr;
this.instance._drainAll(this.computeCapacity());
return reservoir;
}
async __currentReservoir__() {
await this.yieldLoop();
return this.storeOptions.reservoir;
}
isBlocked(now) {
return this._unblockTime >= now;
}
check(weight, now) {
return this.conditionsCheck(weight) && (this._nextRequest - now) <= 0;
}
async __check__(weight) {
var now;
await this.yieldLoop();
now = Date.now();
return this.check(weight, now);
}
async __register__(index, weight, expiration) {
var now, wait;
await this.yieldLoop();
now = Date.now();
if (this.conditionsCheck(weight)) {
this._running += weight;
if (this.storeOptions.reservoir != null) {
this.storeOptions.reservoir -= weight;
}
wait = Math.max(this._nextRequest - now, 0);
this._nextRequest = now + wait + this.storeOptions.minTime;
return {
success: true,
wait,
reservoir: this.storeOptions.reservoir
};
} else {
return {
success: false
};
}
}
strategyIsBlock() {
return this.storeOptions.strategy === 3;
}
async __submit__(queueLength, weight) {
var blocked, now, reachedHWM;
await this.yieldLoop();
if ((this.storeOptions.maxConcurrent != null) && weight > this.storeOptions.maxConcurrent) {
throw new BottleneckError$1(`Impossible to add a job having a weight of ${weight} to a limiter having a maxConcurrent setting of ${this.storeOptions.maxConcurrent}`);
}
now = Date.now();
reachedHWM = (this.storeOptions.highWater != null) && queueLength === this.storeOptions.highWater && !this.check(weight, now);
blocked = this.strategyIsBlock() && (reachedHWM || this.isBlocked(now));
if (blocked) {
this._unblockTime = now + this.computePenalty();
this._nextRequest = this._unblockTime + this.storeOptions.minTime;
this.instance._dropAllQueued();
}
return {
reachedHWM,
blocked,
strategy: this.storeOptions.strategy
};
}
async __free__(index, weight) {
await this.yieldLoop();
this._running -= weight;
this._done += weight;
this.instance._drainAll(this.computeCapacity());
return {
running: this._running
};
}
};
var LocalDatastore_1 = LocalDatastore;
var BottleneckError$2, States;
BottleneckError$2 = BottleneckError_1;
States = class States {
constructor(status1) {
this.status = status1;
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) {
var initial;
initial = 0;
this.jobs[id] = initial;
return this.counts[initial]++;
}
remove(id) {
var current;
current = this.jobs[id];
if (current != null) {
this.counts[current]--;
delete this.jobs[id];
}
return current != null;
}
jobStatus(id) {
var ref;
return (ref = this.status[this.jobs[id]]) != null ? ref : null;
}
statusJobs(status) {
var k, pos, ref, results, v;
if (status != null) {
pos = this.status.indexOf(status);
if (pos < 0) {
throw new BottleneckError$2(`status must be one of ${this.status.join(', ')}`);
}
ref = this.jobs;
results = [];
for (k in ref) {
v = ref[k];
if (v === pos) {
results.push(k);
}
}
return results;
} else {
return Object.keys(this.jobs);
}
}
statusCounts() {
return this.counts.reduce(((acc, v, i) => {
acc[this.status[i]] = v;
return acc;
}), {});
}
};
var States_1 = States;
var DLList$2, Sync,
splice = [].splice;
DLList$2 = DLList_1;
Sync = class Sync {
constructor(name, Promise) {
this.submit = this.submit.bind(this);
this.name = name;
this.Promise = Promise;
this._running = 0;
this._queue = new DLList$2();
}
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(...next.args, (...args) => {
this._running--;
this._tryToRun();
return typeof next.cb === "function" ? next.cb(...args) : void 0;
});
}
}
submit(task, ...args) {
var cb, ref;
ref = args, [...args] = ref, [cb] = splice.call(args, -1);
this._queue.push({task, args, cb});
return this._tryToRun();
}
schedule(task, ...args) {
var wrapped;
wrapped = function(...args) {
var cb, ref;
ref = args, [...args] = ref, [cb] = splice.call(args, -1);
return (task(...args)).then(function(...args) {
return cb(null, ...args);
}).catch(function(...args) {
return cb(...args);
});
};
return new this.Promise((resolve, reject) => {
return this.submit(wrapped, ...args, function(...args) {
return (args[0] != null ? reject : (args.shift(), resolve))(...args);
});
});
}
};
var Sync_1 = Sync;
var version = "2.14.1";
var version$1 = {
version: version
};
var version$2 = /*#__PURE__*/Object.freeze({
version: version,
default: version$1
});
var require$$2 = () => console.log('You must import the full version of Bottleneck in order to use this feature.');
var require$$3 = () => console.log('You must import the full version of Bottleneck in order to use this feature.');
var require$$4 = () => console.log('You must import the full version of Bottleneck in order to use this feature.');
var Events$2, Group, IORedisConnection$1, RedisConnection$1, Scripts$1, parser$2;
parser$2 = parser;
Events$2 = Events_1;
RedisConnection$1 = require$$2;
IORedisConnection$1 = require$$3;
Scripts$1 = require$$4;
Group = (function() {
class Group {
constructor(limiterOptions = {}) {
this.deleteKey = this.deleteKey.bind(this);
this.updateSettings = this.updateSettings.bind(this);
this.limiterOptions = limiterOptions;
parser$2.load(this.limiterOptions, this.defaults, this);
this.Events = new Events$2(this);
this.instances = {};
this.Bottleneck = Bottleneck_1;
this._startAutoCleanup();
this.sharedConnection = this.connection != null;
if (this.connection == null) {
if (this.limiterOptions.datastore === "redis") {
this.connection = new RedisConnection$1(Object.assign({}, this.limiterOptions, {Events: this.Events}));
} else if (this.limiterOptions.datastore === "ioredis") {
this.connection = new IORedisConnection$1(Object.assign({}, this.limiterOptions, {Events: 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: `${this.id}-${key}`,
timeout: this.timeout,
connection: this.connection
}));
this.Events.trigger("created", limiter, key);
return limiter;
})();
}
async deleteKey(key = "") {
var deleted, instance;
instance = this.instances[key];
if (this.connection) {
deleted = (await this.connection.__runCommand__(['del', ...Scripts$1.allKeys(`${this.id}-${key}`)]));
}
if (instance != null) {
delete this.instances[key];
await instance.disconnect();
}
return (instance != null) || deleted > 0;
}
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);
}
async clusterKeys() {
var cursor, end, found, i, k, keys, len, next, start;
if (this.connection == null) {
return this.Promise.resolve(this.keys());
}
keys = [];
cursor = null;
start = `b_${this.id}-`.length;
end = "_settings".length;
while (cursor !== 0) {
[next, found] = (await this.connection.__runCommand__(["scan", cursor != null ? cursor : 0, "match", `b_${this.id}-*_settings`, "count", 10000]));
cursor = ~~next;
for (i = 0, len = found.length; i < len; i++) {
k = found[i];
keys.push(k.slice(start, -end));
}
}
return keys;
}
_startAutoCleanup() {
var base;
clearInterval(this.interval);
return typeof (base = (this.interval = setInterval(async() => {
var e, k, ref, results, time, v;
time = Date.now();
ref = this.instances;
results = [];
for (k in ref) {
v = ref[k];
try {
if ((await 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$2.overwrite(options, this.defaults, this);
parser$2.overwrite(options, options, this.limiterOptions);
if (options.timeout != null) {
return this._startAutoCleanup();
}
}
disconnect(flush = true) {
var ref;
if (!this.sharedConnection) {
return (ref = this.connection) != null ? ref.disconnect(flush) : void 0;
}
}
}
Group.prototype.defaults = {
timeout: 1000 * 60 * 5,
connection: null,
Promise: Promise,
id: "group-key"
};
return Group;
}).call(commonjsGlobal);
var Group_1 = Group;
var Batcher, Events$3, parser$3;
parser$3 = parser;
Events$3 = Events_1;
Batcher = (function() {
class Batcher {
constructor(options = {}) {
this.options = options;
parser$3.load(this.options, this.defaults, this);
this.Events = new Events$3(this);
this._arr = [];
this._resetPromise();
this._lastFlush = Date.now();
}
_resetPromise() {
return this._promise = new this.Promise((res, rej) => {
return this._resolve = res;
});
}
_flush() {
clearTimeout(this._timeout);
this._lastFlush = Date.now();
this._resolve();
this.Events.trigger("batch", this._arr);
this._arr = [];
return this._resetPromise();
}
add(data) {
var ret;
this._arr.push(data);
ret = this._promise;
if (this._arr.length === this.maxSize) {
this._flush();
} else if ((this.maxTime != null) && this._arr.length === 1) {
this._timeout = setTimeout(() => {
return this._flush();
}, this.maxTime);
}
return ret;
}
}
Batcher.prototype.defaults = {
maxTime: null,
maxSize: null,
Promise: Promise
};
return Batcher;
}).call(commonjsGlobal);
var Batcher_1 = Batcher;
var require$$3$1 = () => console.log('You must import the full version of Bottleneck in order to use this feature.');
var require$$7 = getCjsExportFromNamespace(version$2);
var Bottleneck, DEFAULT_PRIORITY, Events$4, LocalDatastore$1, NUM_PRIORITIES, Queues$1, RedisDatastore$1, States$1, Sync$1, parser$4,
splice$1 = [].splice;
NUM_PRIORITIES = 10;
DEFAULT_PRIORITY = 5;
parser$4 = parser;
Queues$1 = Queues_1;
LocalDatastore$1 = LocalDatastore_1;
RedisDatastore$1 = require$$3$1;
Events$4 = Events_1;
States$1 = States_1;
Sync$1 = Sync_1;
Bottleneck = (function() {
class Bottleneck {
constructor(options = {}, ...invalid) {
var storeInstanceOptions, storeOptions;
this._drainOne = this._drainOne.bind(this);
this.submit = this.submit.bind(this);
this.schedule = this.schedule.bind(this);
this.updateSettings = this.updateSettings.bind(this);
this.incrementReservoir = this.incrementReservoir.bind(this);
this._validateOptions(options, invalid);
parser$4.load(options, this.instanceDefaults, this);
this._queues = new Queues$1(NUM_PRIORITIES);
this._scheduled = {};
this._states = new States$1(["RECEIVED", "QUEUED", "RUNNING", "EXECUTING"].concat(this.trackDoneStatus ? ["DONE"] : []));
this._limiter = null;
this.Events = new Events$4(this);
this._submitLock = new Sync$1("submit", this.Promise);
this._registerLock = new Sync$1("register", this.Promise);
storeOptions = parser$4.load(options, this.storeDefaults, {});
this._store = (function() {
if (this.datastore === "redis" || this.datastore === "ioredis" || (this.connection != null)) {
storeInstanceOptions = parser$4.load(options, this.redisStoreDefaults, {});
return new RedisDatastore$1(this, storeOptions, storeInstanceOptions);
} else if (this.datastore === "local") {
storeInstanceOptions = parser$4.load(options, this.localStoreDefaults, {});
return new LocalDatastore$1(this, storeOptions, storeInstanceOptions);
} else {
throw new Bottleneck.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`);
}
}).call(this);
this._queues.on("leftzero", () => {
var base;
return typeof (base = this._store.heartbeat).ref === "function" ? base.ref() : void 0;
});
this._queues.on("zero", () => {
var base;
return typeof (base = this._store.heartbeat).unref === "function" ? base.unref() : void 0;
});
}
_validateOptions(options, invalid) {
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.");
}
}
ready() {
return this._store.ready;
}
clients() {
return this._store.clients;
}
channel() {
return `b_${this.id}`;
}
channel_client() {
return `b_${this.id}_${this._store.clientId}`;
}
publish(message) {
return this._store.__publish__(message);
}
disconnect(flush = true) {
return this._store.__disconnect__(flush);
}
chain(_limiter) {
this._limiter = _limiter;
return this;
}
queued(priority) {
return this._queues.queued(priority);
}
empty() {
return this.queued() === 0 && this._submitLock.isEmpty();
}
running() {
return this._store.__running__();
}
done() {
return this._store.__done__();
}
jobStatus(id) {
return this._states.jobStatus(id);
}
jobs(status) {
return this._states.statusJobs(status);
}
counts() {
return this._states.statusCounts();
}
_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;
}
}
_randomIndex() {
return Math.random().toString(36).slice(2);
}
check(weight = 1) {
return this._store.__check__(weight);
}
_run(next, wait, index) {
var completed, done;
this.Events.trigger("debug", `Scheduling ${next.options.id}`, {
args: next.args,
options: next.options
});
done = false;
completed = async(...args) => {
var e, running;
if (!done) {
try {
done = true;
this._states.next(next.options.id); // DONE
clearTimeout(this._scheduled[index].expiration);
delete this._scheduled[index];
this.Events.trigger("debug", `Completed ${next.options.id}`, {
args: next.args,
options: next.options
});
this.Events.trigger("done", `Completed ${next.options.id}`, {
args: next.args,
options: next.options
});
({running} = (await this._store.__free__(index, next.options.weight)));
this.Events.trigger("debug", `Freed ${next.options.id}`, {
args: next.args,
options: next.options
});
if (running === 0 && this.empty()) {
this.Events.trigger("idle");
}
return typeof next.cb === "function" ? next.cb(...args) : void 0;
} catch (error) {
e = error;
return this.Events.trigger("error", e);
}
}
};
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(next.options, next.task, ...next.args, completed);
} else {
return next.task(...next.args, 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(capacity) {
return this._registerLock.schedule(() => {
var args, index, next, options, queue;
if (this.queued() === 0) {
return this.Promise.resolve(false);
}
queue = this._queues.getFirst();
({options, args} = next = queue.first());
if ((capacity != null) && options.weight > capacity) {
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;
this.Events.trigger("debug", `Drained ${options.id}`, {success, args, options});
if (success) {
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(capacity) {
return this._drainOne(capacity).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") {
if (this._states.remove(job.options.id)) {
if (this.rejectOnDrop) {
if (typeof job.cb === "function") {
job.cb(new Bottleneck.prototype.BottleneckError(message));
}
}
return this.Events.trigger("dropped", job);
}
}
_dropAllQueued(message) {
return this._queues.shiftAll((job) => {
return this._drop(job, message);
});
}
stop(options = {}) {
var done, waitForExecuting;
options = parser$4.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 this.Promise.resolve(false);
}, this._registerLock.schedule(() => {
return this._submitLock.schedule(() => {
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._dropAllQueued(options.dropErrorMessage);
return waitForExecuting(0);
});
})) : this.schedule({
priority: NUM_PRIORITIES - 1,
weight: 0
}, () => {
return waitForExecuting(1);
});
this.submit = (...args) => {
var cb, ref;
ref = args, [...args] = ref, [cb] = splice$1.call(args, -1);
return typeof cb === "function" ? cb(new Bottleneck.prototype.BottleneckError(options.enqueueErrorMessage)) : void 0;
};
this.stop = () => {
return this.Promise.reject(new Bottleneck.prototype.BottleneckError("stop() has already been called"));
};
return done;
}
submit(...args) {
var cb, job, options, ref, ref1, task;
if (typeof args[0] === "function") {
ref = args, [task, ...args] = ref, [cb] = splice$1.call(args, -1);
options = parser$4.load({}, this.jobDefaults, {});
} else {
ref1 = args, [options, task, ...args] = ref1, [cb] = splice$1.call(args, -1);
options = parser$4.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) {
if (typeof job.cb === "function") {
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(async() => {
var blocked, e, reachedHWM, shifted, strategy;
try {
({reachedHWM, blocked, strategy} = (await this._store.__submit__(this.queued(), options.weight)));
this.Events.trigger("debug", `Queued ${options.id}`, {args, options, reachedHWM, blocked});
} catch (error) {
e = error;
this._states.remove(options.id);
this.Events.trigger("debug", `Could not queue ${options.id}`, {
args,
options,
error: e
});
if (typeof job.cb === "function") {
job.cb(e);
}
return false;
}
if (blocked) {
this._drop(job);
return true;
} else if (reachedHWM) {
shifted = strategy === Bottleneck.prototype.strategy.LEAK ? this._queues.shiftLastFrom(options.priority) : strategy === Bottleneck.prototype.strategy.OVERFLOW_PRIORITY ? this._queues.shiftLastFrom(options.priority + 1) : strategy === Bottleneck.prototype.strategy.OVERFLOW ? job : void 0;
if (shifted != null) {
this._drop(shifted);
}
if ((shifted == null) || strategy === Bottleneck.prototype.strategy.OVERFLOW) {
if (shifted == null) {
this._drop(job);
}
return reachedHWM;
}
}
this._states.next(job.options.id); // QUEUED
this._queues.push(options.priority, job);
await this._drainAll();
return reachedHWM;
});
}
schedule(...args) {
var options, task, wrapped;
if (typeof args[0] === "function") {
[task, ...args] = args;
options = parser$4.load({}, this.jobDefaults, {});
} else {
[options, task, ...args] = args;
options = parser$4.load(options, this.jobDefaults);
}
wrapped = (...args) => {
var cb, ref, returned;
ref = args, [...args] = ref, [cb] = splice$1.call(args, -1);
returned = task(...args);
return (!(((returned != null ? returned.then : void 0) != null) && typeof returned.then === "function") ? this.Promise.resolve(returned) : returned).then(function(...args) {
return cb(null, ...args);
}).catch(function(...args) {
return cb(...args);
});
};
return new this.Promise((resolve, reject) => {
return this.submit(options, wrapped, ...args, function(...args) {
return (args[0] != null ? reject : (args.shift(), resolve))(...args);
}).catch((e) => {
return this.Events.trigger("error", e);
});
});
}
wrap(fn) {
var wrapped;
wrapped = (...args) => {
return this.schedule(fn, ...args);
};
wrapped.withOptions = (options, ...args) => {
return this.schedule(options, fn, ...args);
};
return wrapped;
}
async updateSettings(options = {}) {
await this._store.__updateSettings__(parser$4.overwrite(options, this.storeDefaults));
parser$4.overwrite(options, this.instanceDefaults, this);
return this;
}
currentReservoir() {
return this._store.__currentReservoir__();
}
incrementReservoir(incr = 0) {
return this._store.__incrementReservoir__(incr);
}
}
Bottleneck.default = Bottleneck;
Bottleneck.Events = Events$4;
Bottleneck.version = Bottleneck.prototype.version = require$$7.version;
Bottleneck.strategy = Bottleneck.prototype.strategy = {
LEAK: 1,
OVERFLOW: 2,
OVERFLOW_PRIORITY: 4,
BLOCK: 3
};
Bottleneck.BottleneckError = Bottleneck.prototype.BottleneckError = BottleneckError_1;
Bottleneck.Group = Bottleneck.prototype.Group = Group_1;
Bottleneck.RedisConnection = Bottleneck.prototype.RedisConnection = require$$2;
Bottleneck.IORedisConnection = Bottleneck.prototype.IORedisConnection = require$$3;
Bottleneck.Batcher = Bottleneck.prototype.Batcher = Batcher_1;
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,
reservoirRefreshInterval: null,
reservoirRefreshAmount: null
};
Bottleneck.prototype.localStoreDefaults = {
Promise: Promise,
timeout: null,
heartbeatInterval: 250
};
Bottleneck.prototype.redisStoreDefaults = {
Promise: Promise,
timeout: null,
heartbeatInterval: 5000,
clientOptions: {},
clusterNodes: null,
clearDatastore: false,
connection: null
};
Bottleneck.prototype.instanceDefaults = {
datastore: "local",
connection: null,
id: "<no-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(commonjsGlobal);
var Bottleneck_1 = Bottleneck;
var lib = Bottleneck_1;
return lib;
})));