diff --git a/bin/bundle.js b/bin/bundle.js index c081bc4..e5f916c 100644 --- a/bin/bundle.js +++ b/bin/bundle.js @@ -31669,337 +31669,337 @@ exports.not = filename => !exports.is(filename); exports.default = module.exports; },{}],251:[function(require,module,exports){ - -exports.RateLimiter = require('./lib/rateLimiter'); -exports.TokenBucket = require('./lib/tokenBucket'); + +exports.RateLimiter = require('./lib/rateLimiter'); +exports.TokenBucket = require('./lib/tokenBucket'); },{"./lib/rateLimiter":253,"./lib/tokenBucket":254}],252:[function(require,module,exports){ (function (process){(function (){ -var getMilliseconds = function() { - if (typeof process !== 'undefined' && process.hrtime) { - var hrtime = process.hrtime(); - var seconds = hrtime[0]; - var nanoseconds = hrtime[1]; - - return seconds * 1e3 + Math.floor(nanoseconds / 1e6); - } - - return new Date().getTime(); -} - -module.exports = getMilliseconds; +var getMilliseconds = function() { + if (typeof process !== 'undefined' && process.hrtime) { + var hrtime = process.hrtime(); + var seconds = hrtime[0]; + var nanoseconds = hrtime[1]; + + return seconds * 1e3 + Math.floor(nanoseconds / 1e6); + } + + return new Date().getTime(); +} + +module.exports = getMilliseconds; }).call(this)}).call(this,require('_process')) },{"_process":341}],253:[function(require,module,exports){ (function (process){(function (){ -var TokenBucket = require('./tokenBucket'); -var getMilliseconds = require('./clock'); - -/** - * A generic rate limiter. Underneath the hood, this uses a token bucket plus - * an additional check to limit how many tokens we can remove each interval. - * @author John Hurliman - * - * @param {Number} tokensPerInterval Maximum number of tokens that can be - * removed at any given moment and over the course of one interval. - * @param {String|Number} interval The interval length in milliseconds, or as - * one of the following strings: 'second', 'minute', 'hour', day'. - * @param {Boolean} fireImmediately Optional. Whether or not the callback - * will fire immediately when rate limiting is in effect (default is false). - */ -var RateLimiter = function(tokensPerInterval, interval, fireImmediately) { - this.tokenBucket = new TokenBucket(tokensPerInterval, tokensPerInterval, - interval, null); - - // Fill the token bucket to start - this.tokenBucket.content = tokensPerInterval; - - this.curIntervalStart = getMilliseconds(); - this.tokensThisInterval = 0; - this.fireImmediately = fireImmediately; -}; - -RateLimiter.prototype = { - tokenBucket: null, - curIntervalStart: 0, - tokensThisInterval: 0, - fireImmediately: false, - - /** - * Remove the requested number of tokens and fire the given callback. If the - * rate limiter contains enough tokens and we haven't spent too many tokens - * in this interval already, this will happen immediately. Otherwise, the - * removal and callback will happen when enough tokens become available. - * @param {Number} count The number of tokens to remove. - * @param {Function} callback(err, remainingTokens) - * @returns {Boolean} True if the callback was fired immediately, otherwise - * false. - */ - removeTokens: function(count, callback) { - // Make sure the request isn't for more than we can handle - if (count > this.tokenBucket.bucketSize) { - process.nextTick(callback.bind(null, 'Requested tokens ' + count + - ' exceeds maximum tokens per interval ' + this.tokenBucket.bucketSize, - null)); - return false; - } - - var self = this; - var now = getMilliseconds(); - - // Advance the current interval and reset the current interval token count - // if needed - if (now < this.curIntervalStart - || now - this.curIntervalStart >= this.tokenBucket.interval) { - this.curIntervalStart = now; - this.tokensThisInterval = 0; - } - - // If we don't have enough tokens left in this interval, wait until the - // next interval - if (count > this.tokenBucket.tokensPerInterval - this.tokensThisInterval) { - if (this.fireImmediately) { - process.nextTick(callback.bind(null, null, -1)); - } else { - var waitInterval = Math.ceil( - this.curIntervalStart + this.tokenBucket.interval - now); - - setTimeout(function() { - self.tokenBucket.removeTokens(count, afterTokensRemoved); - }, waitInterval); - } - return false; - } - - // Remove the requested number of tokens from the token bucket - return this.tokenBucket.removeTokens(count, afterTokensRemoved); - - function afterTokensRemoved(err, tokensRemaining) { - if (err) return callback(err, null); - - self.tokensThisInterval += count; - callback(null, tokensRemaining); - } - }, - - /** - * Attempt to remove the requested number of tokens and return immediately. - * If the bucket (and any parent buckets) contains enough tokens and we - * haven't spent too many tokens in this interval already, this will return - * true. Otherwise, false is returned. - * @param {Number} count The number of tokens to remove. - * @param {Boolean} True if the tokens were successfully removed, otherwise - * false. - */ - tryRemoveTokens: function(count) { - // Make sure the request isn't for more than we can handle - if (count > this.tokenBucket.bucketSize) - return false; - - var now = getMilliseconds(); - - // Advance the current interval and reset the current interval token count - // if needed - if (now < this.curIntervalStart - || now - this.curIntervalStart >= this.tokenBucket.interval) { - this.curIntervalStart = now; - this.tokensThisInterval = 0; - } - - // If we don't have enough tokens left in this interval, return false - if (count > this.tokenBucket.tokensPerInterval - this.tokensThisInterval) - return false; - - // Try to remove the requested number of tokens from the token bucket - var removed = this.tokenBucket.tryRemoveTokens(count); - if (removed) { - this.tokensThisInterval += count; - } - return removed; - }, - - /** - * Returns the number of tokens remaining in the TokenBucket. - * @returns {Number} The number of tokens remaining. - */ - getTokensRemaining: function () { - this.tokenBucket.drip(); - return this.tokenBucket.content; - } -}; - -module.exports = RateLimiter; +var TokenBucket = require('./tokenBucket'); +var getMilliseconds = require('./clock'); + +/** + * A generic rate limiter. Underneath the hood, this uses a token bucket plus + * an additional check to limit how many tokens we can remove each interval. + * @author John Hurliman + * + * @param {Number} tokensPerInterval Maximum number of tokens that can be + * removed at any given moment and over the course of one interval. + * @param {String|Number} interval The interval length in milliseconds, or as + * one of the following strings: 'second', 'minute', 'hour', day'. + * @param {Boolean} fireImmediately Optional. Whether or not the callback + * will fire immediately when rate limiting is in effect (default is false). + */ +var RateLimiter = function(tokensPerInterval, interval, fireImmediately) { + this.tokenBucket = new TokenBucket(tokensPerInterval, tokensPerInterval, + interval, null); + + // Fill the token bucket to start + this.tokenBucket.content = tokensPerInterval; + + this.curIntervalStart = getMilliseconds(); + this.tokensThisInterval = 0; + this.fireImmediately = fireImmediately; +}; + +RateLimiter.prototype = { + tokenBucket: null, + curIntervalStart: 0, + tokensThisInterval: 0, + fireImmediately: false, + + /** + * Remove the requested number of tokens and fire the given callback. If the + * rate limiter contains enough tokens and we haven't spent too many tokens + * in this interval already, this will happen immediately. Otherwise, the + * removal and callback will happen when enough tokens become available. + * @param {Number} count The number of tokens to remove. + * @param {Function} callback(err, remainingTokens) + * @returns {Boolean} True if the callback was fired immediately, otherwise + * false. + */ + removeTokens: function(count, callback) { + // Make sure the request isn't for more than we can handle + if (count > this.tokenBucket.bucketSize) { + process.nextTick(callback.bind(null, 'Requested tokens ' + count + + ' exceeds maximum tokens per interval ' + this.tokenBucket.bucketSize, + null)); + return false; + } + + var self = this; + var now = getMilliseconds(); + + // Advance the current interval and reset the current interval token count + // if needed + if (now < this.curIntervalStart + || now - this.curIntervalStart >= this.tokenBucket.interval) { + this.curIntervalStart = now; + this.tokensThisInterval = 0; + } + + // If we don't have enough tokens left in this interval, wait until the + // next interval + if (count > this.tokenBucket.tokensPerInterval - this.tokensThisInterval) { + if (this.fireImmediately) { + process.nextTick(callback.bind(null, null, -1)); + } else { + var waitInterval = Math.ceil( + this.curIntervalStart + this.tokenBucket.interval - now); + + setTimeout(function() { + self.tokenBucket.removeTokens(count, afterTokensRemoved); + }, waitInterval); + } + return false; + } + + // Remove the requested number of tokens from the token bucket + return this.tokenBucket.removeTokens(count, afterTokensRemoved); + + function afterTokensRemoved(err, tokensRemaining) { + if (err) return callback(err, null); + + self.tokensThisInterval += count; + callback(null, tokensRemaining); + } + }, + + /** + * Attempt to remove the requested number of tokens and return immediately. + * If the bucket (and any parent buckets) contains enough tokens and we + * haven't spent too many tokens in this interval already, this will return + * true. Otherwise, false is returned. + * @param {Number} count The number of tokens to remove. + * @param {Boolean} True if the tokens were successfully removed, otherwise + * false. + */ + tryRemoveTokens: function(count) { + // Make sure the request isn't for more than we can handle + if (count > this.tokenBucket.bucketSize) + return false; + + var now = getMilliseconds(); + + // Advance the current interval and reset the current interval token count + // if needed + if (now < this.curIntervalStart + || now - this.curIntervalStart >= this.tokenBucket.interval) { + this.curIntervalStart = now; + this.tokensThisInterval = 0; + } + + // If we don't have enough tokens left in this interval, return false + if (count > this.tokenBucket.tokensPerInterval - this.tokensThisInterval) + return false; + + // Try to remove the requested number of tokens from the token bucket + var removed = this.tokenBucket.tryRemoveTokens(count); + if (removed) { + this.tokensThisInterval += count; + } + return removed; + }, + + /** + * Returns the number of tokens remaining in the TokenBucket. + * @returns {Number} The number of tokens remaining. + */ + getTokensRemaining: function () { + this.tokenBucket.drip(); + return this.tokenBucket.content; + } +}; + +module.exports = RateLimiter; }).call(this)}).call(this,require('_process')) },{"./clock":252,"./tokenBucket":254,"_process":341}],254:[function(require,module,exports){ (function (process){(function (){ - -/** - * A hierarchical token bucket for rate limiting. See - * http://en.wikipedia.org/wiki/Token_bucket for more information. - * @author John Hurliman - * - * @param {Number} bucketSize Maximum number of tokens to hold in the bucket. - * Also known as the burst rate. - * @param {Number} tokensPerInterval Number of tokens to drip into the bucket - * over the course of one interval. - * @param {String|Number} interval The interval length in milliseconds, or as - * one of the following strings: 'second', 'minute', 'hour', day'. - * @param {TokenBucket} parentBucket Optional. A token bucket that will act as - * the parent of this bucket. - */ -var TokenBucket = function(bucketSize, tokensPerInterval, interval, parentBucket) { - this.bucketSize = bucketSize; - this.tokensPerInterval = tokensPerInterval; - - if (typeof interval === 'string') { - switch (interval) { - case 'sec': case 'second': - this.interval = 1000; break; - case 'min': case 'minute': - this.interval = 1000 * 60; break; - case 'hr': case 'hour': - this.interval = 1000 * 60 * 60; break; - case 'day': - this.interval = 1000 * 60 * 60 * 24; break; - default: - throw new Error('Invaid interval ' + interval); - } - } else { - this.interval = interval; - } - - this.parentBucket = parentBucket; - this.content = 0; - this.lastDrip = +new Date(); -}; - -TokenBucket.prototype = { - bucketSize: 1, - tokensPerInterval: 1, - interval: 1000, - parentBucket: null, - content: 0, - lastDrip: 0, - - /** - * Remove the requested number of tokens and fire the given callback. If the - * bucket (and any parent buckets) contains enough tokens this will happen - * immediately. Otherwise, the removal and callback will happen when enough - * tokens become available. - * @param {Number} count The number of tokens to remove. - * @param {Function} callback(err, remainingTokens) - * @returns {Boolean} True if the callback was fired immediately, otherwise - * false. - */ - removeTokens: function(count, callback) { - var self = this; - - // Is this an infinite size bucket? - if (!this.bucketSize) { - process.nextTick(callback.bind(null, null, count, Number.POSITIVE_INFINITY)); - return true; - } - - // Make sure the bucket can hold the requested number of tokens - if (count > this.bucketSize) { - process.nextTick(callback.bind(null, 'Requested tokens ' + count + ' exceeds bucket size ' + - this.bucketSize, null)); - return false; - } - - // Drip new tokens into this bucket - this.drip(); - - // If we don't have enough tokens in this bucket, come back later - if (count > this.content) - return comeBackLater(); - - if (this.parentBucket) { - // Remove the requested from the parent bucket first - return this.parentBucket.removeTokens(count, function(err, remainingTokens) { - if (err) return callback(err, null); - - // Check that we still have enough tokens in this bucket - if (count > self.content) - return comeBackLater(); - - // Tokens were removed from the parent bucket, now remove them from - // this bucket and fire the callback. Note that we look at the current - // bucket and parent bucket's remaining tokens and return the smaller - // of the two values - self.content -= count; - callback(null, Math.min(remainingTokens, self.content)); - }); - } else { - // Remove the requested tokens from this bucket and fire the callback - this.content -= count; - process.nextTick(callback.bind(null, null, this.content)); - return true; - } - - function comeBackLater() { - // How long do we need to wait to make up the difference in tokens? - var waitInterval = Math.ceil( - (count - self.content) * (self.interval / self.tokensPerInterval)); - setTimeout(function() { self.removeTokens(count, callback); }, waitInterval); - return false; - } - }, - - /** - * Attempt to remove the requested number of tokens and return immediately. - * If the bucket (and any parent buckets) contains enough tokens this will - * return true, otherwise false is returned. - * @param {Number} count The number of tokens to remove. - * @param {Boolean} True if the tokens were successfully removed, otherwise - * false. - */ - tryRemoveTokens: function(count) { - // Is this an infinite size bucket? - if (!this.bucketSize) - return true; - - // Make sure the bucket can hold the requested number of tokens - if (count > this.bucketSize) - return false; - - // Drip new tokens into this bucket - this.drip(); - - // If we don't have enough tokens in this bucket, return false - if (count > this.content) - return false; - - // Try to remove the requested tokens from the parent bucket - if (this.parentBucket && !this.parentBucket.tryRemoveTokens(count)) - return false; - - // Remove the requested tokens from this bucket and return - this.content -= count; - return true; - }, - - /** - * Add any new tokens to the bucket since the last drip. - * @returns {Boolean} True if new tokens were added, otherwise false. - */ - drip: function() { - if (!this.tokensPerInterval) { - this.content = this.bucketSize; - return; - } - - var now = +new Date(); - var deltaMS = Math.max(now - this.lastDrip, 0); - this.lastDrip = now; - - var dripAmount = deltaMS * (this.tokensPerInterval / this.interval); - this.content = Math.min(this.content + dripAmount, this.bucketSize); - } -}; - -module.exports = TokenBucket; + +/** + * A hierarchical token bucket for rate limiting. See + * http://en.wikipedia.org/wiki/Token_bucket for more information. + * @author John Hurliman + * + * @param {Number} bucketSize Maximum number of tokens to hold in the bucket. + * Also known as the burst rate. + * @param {Number} tokensPerInterval Number of tokens to drip into the bucket + * over the course of one interval. + * @param {String|Number} interval The interval length in milliseconds, or as + * one of the following strings: 'second', 'minute', 'hour', day'. + * @param {TokenBucket} parentBucket Optional. A token bucket that will act as + * the parent of this bucket. + */ +var TokenBucket = function(bucketSize, tokensPerInterval, interval, parentBucket) { + this.bucketSize = bucketSize; + this.tokensPerInterval = tokensPerInterval; + + if (typeof interval === 'string') { + switch (interval) { + case 'sec': case 'second': + this.interval = 1000; break; + case 'min': case 'minute': + this.interval = 1000 * 60; break; + case 'hr': case 'hour': + this.interval = 1000 * 60 * 60; break; + case 'day': + this.interval = 1000 * 60 * 60 * 24; break; + default: + throw new Error('Invaid interval ' + interval); + } + } else { + this.interval = interval; + } + + this.parentBucket = parentBucket; + this.content = 0; + this.lastDrip = +new Date(); +}; + +TokenBucket.prototype = { + bucketSize: 1, + tokensPerInterval: 1, + interval: 1000, + parentBucket: null, + content: 0, + lastDrip: 0, + + /** + * Remove the requested number of tokens and fire the given callback. If the + * bucket (and any parent buckets) contains enough tokens this will happen + * immediately. Otherwise, the removal and callback will happen when enough + * tokens become available. + * @param {Number} count The number of tokens to remove. + * @param {Function} callback(err, remainingTokens) + * @returns {Boolean} True if the callback was fired immediately, otherwise + * false. + */ + removeTokens: function(count, callback) { + var self = this; + + // Is this an infinite size bucket? + if (!this.bucketSize) { + process.nextTick(callback.bind(null, null, count, Number.POSITIVE_INFINITY)); + return true; + } + + // Make sure the bucket can hold the requested number of tokens + if (count > this.bucketSize) { + process.nextTick(callback.bind(null, 'Requested tokens ' + count + ' exceeds bucket size ' + + this.bucketSize, null)); + return false; + } + + // Drip new tokens into this bucket + this.drip(); + + // If we don't have enough tokens in this bucket, come back later + if (count > this.content) + return comeBackLater(); + + if (this.parentBucket) { + // Remove the requested from the parent bucket first + return this.parentBucket.removeTokens(count, function(err, remainingTokens) { + if (err) return callback(err, null); + + // Check that we still have enough tokens in this bucket + if (count > self.content) + return comeBackLater(); + + // Tokens were removed from the parent bucket, now remove them from + // this bucket and fire the callback. Note that we look at the current + // bucket and parent bucket's remaining tokens and return the smaller + // of the two values + self.content -= count; + callback(null, Math.min(remainingTokens, self.content)); + }); + } else { + // Remove the requested tokens from this bucket and fire the callback + this.content -= count; + process.nextTick(callback.bind(null, null, this.content)); + return true; + } + + function comeBackLater() { + // How long do we need to wait to make up the difference in tokens? + var waitInterval = Math.ceil( + (count - self.content) * (self.interval / self.tokensPerInterval)); + setTimeout(function() { self.removeTokens(count, callback); }, waitInterval); + return false; + } + }, + + /** + * Attempt to remove the requested number of tokens and return immediately. + * If the bucket (and any parent buckets) contains enough tokens this will + * return true, otherwise false is returned. + * @param {Number} count The number of tokens to remove. + * @param {Boolean} True if the tokens were successfully removed, otherwise + * false. + */ + tryRemoveTokens: function(count) { + // Is this an infinite size bucket? + if (!this.bucketSize) + return true; + + // Make sure the bucket can hold the requested number of tokens + if (count > this.bucketSize) + return false; + + // Drip new tokens into this bucket + this.drip(); + + // If we don't have enough tokens in this bucket, return false + if (count > this.content) + return false; + + // Try to remove the requested tokens from the parent bucket + if (this.parentBucket && !this.parentBucket.tryRemoveTokens(count)) + return false; + + // Remove the requested tokens from this bucket and return + this.content -= count; + return true; + }, + + /** + * Add any new tokens to the bucket since the last drip. + * @returns {Boolean} True if new tokens were added, otherwise false. + */ + drip: function() { + if (!this.tokensPerInterval) { + this.content = this.bucketSize; + return; + } + + var now = +new Date(); + var deltaMS = Math.max(now - this.lastDrip, 0); + this.lastDrip = now; + + var dripAmount = deltaMS * (this.tokensPerInterval / this.interval); + this.content = Math.min(this.content + dripAmount, this.bucketSize); + } +}; + +module.exports = TokenBucket; }).call(this)}).call(this,require('_process')) },{"_process":341}],255:[function(require,module,exports){