diff --git a/lib/db.js b/lib/db.js index e5ab7f7..42b8e35 100644 --- a/lib/db.js +++ b/lib/db.js @@ -1,3 +1,4 @@ +/// const ms = require('ms'); const fs = require('fs'); const _ = require('lodash'); @@ -37,7 +38,7 @@ class LimitDBRedis extends EventEmitter { /** * Creates an instance of LimitDB client for Redis. - * @param {params} params - The configuration for the database and client. + * @param {LimitDBParams} params - The configuration for the database and client. */ constructor(config) { super(); @@ -149,9 +150,9 @@ class LimitDBRedis extends EventEmitter { } /** - * @param {string} type - * @param {object} params - * @returns + * @param {Bucket & { overrides: Object?, overridesCache: Object?, overridesMatch: Object? }} type + * @param {GetParams | PutParams | TakeParams | TakeElevatedParams} params + * @returns {NormalizedType} */ bucketKeyConfig(type, params) { if (typeof params.configOverride === 'object') { @@ -194,9 +195,9 @@ class LimitDBRedis extends EventEmitter { /** * Take N elements from a bucket if available. * - * @param {takeParams} params - The params for take. - * @param {function(Error, takeResult)} callback. - * @param {function(key, bucketKeyConfig, count)} takeFunc + * @param {TakeParams} params - The params for take. + * @param {function(null, TakeResult) | function(Error)} callback + * @param {function(key: string, bucketKeyConfig: NormalizedType, count: number)} takeFunc */ _doTake(params, callback, takeFunc) { const valError = validateParams(params, this.buckets); @@ -255,8 +256,8 @@ class LimitDBRedis extends EventEmitter { /** * Take N elements from a bucket if available. * - * @param {takeParams} params - The params for take. - * @param {function(Error, takeResult)} callback. + * @param {TakeParams} params + * @param {function(null, TakeResult) | function(Error)} callback */ take(params, callback) { this._doTake(params, callback, (key, bucketKeyConfig, count) => { @@ -289,6 +290,12 @@ class LimitDBRedis extends EventEmitter { }) } + /** + * Take N elements from a bucket if available, use elevated limits if configured. + * + * @param {TakeElevatedParams} params + * @param {function(null, TakeElevatedResult) | function(Error)} callback + */ takeElevated(params, callback) { let erlParams; @@ -355,8 +362,8 @@ class LimitDBRedis extends EventEmitter { * Take N elements from a bucket if available otherwise wait for them. * The callback is called when the number of request tokens is available. * - * @param {waitParams} params - The params for take. - * @param {function(Error, waitResult)} callback. + * @param {WaitParams} params - The params for take. + * @param {function(null, WaitResult) | function(Error)} callback */ wait(params, callback) { this.take(params, (err, result) => { @@ -385,8 +392,8 @@ class LimitDBRedis extends EventEmitter { /** * Put N elements in the bucket. * - * @param {putParams} params - The params for take. - * @param {function(Error, putResult)} [callback]. + * @param {PutParams} params - The params for take. + * @param {function(null, PutResult) | function(Error)} [callback] */ put(params, callback) { callback = callback || _.noop; @@ -438,8 +445,8 @@ class LimitDBRedis extends EventEmitter { /** * Get elements in the bucket. * - * @param {getParams} params - The params for take. - * @param {function(Error, getResult)} [callback]. + * @param {GetParams} params - The params for take. + * @param {function(null, GetResult) | function(Error)} [callback] */ get(params, callback) { callback = callback || _.noop; @@ -508,69 +515,4 @@ class LimitDBRedis extends EventEmitter { } -module.exports = LimitDBRedis; - -/** - * And now some typedefs for you: - * - * @typedef {Object} type - * @property {integer} [per_interval] The number of tokens to add per interval. - * @property {integer} [interval] The length of the interval in milliseconds. - * @property {integer} [size] The maximum number of tokens in the bucket. - * @property {integer} [per_second] The number of tokens to add per second. Equivalent to "interval: 1000, per_interval: x". - * @property {integer} [per_minute] The number of tokens to add per minute. Equivalent to "interval: 60000, per_interval: x". - * @property {integer} [per_hour] The number of tokens to add per hour. Equivalent to "interval: 3600000, per_interval: x". - * @property {integer} [per_day] The number of tokens to add per day. Equivalent to "interval: 86400000, per_interval: x". - * - * @typedef {Object} params - * uri nodes buckets prefix - * @property {string} [params.uri] Address of Redis. - * @property {Object.} [params.nodes] Redis Cluster Configuration https://github.com/luin/ioredis#cluster". - * @property {Object.} [params.types] The buckets configuration. - * @property {string} [params.prefix] Prefix keys in Redis. - * @property {type} [params.configOverride] Bucket configuration override - * - * @typedef takeParams - * @property {string} type The name of the bucket type. - * @property {string} key The key of the bucket instance. - * @property {integer} [count=1] The number of tokens to take from the bucket. - * @property {type} configOverride Externally provided bucket configruation - * - * @typedef takeResult - * @property {boolean} conformant Returns true if there is enough capacity in the bucket and the tokens has been removed. - * @property {integer} remaining The number of tokens remaining in the bucket. - * @property {integer} reset A unix timestamp indicating when the bucket is going to be full. - * @property {integer} limit The size of the bucket. - * - * @typedef waitParams - * @property {string} type The name of the bucket type. - * @property {string} key The key of the bucket instance. - * @property {integer} [count=1] The number of tokens to wait for. - * @property {type} configOverride Externally provided bucket configruation - * - * @typedef waitResult - * @property {integer} remaining The number of tokens remaining in the bucket. - * @property {integer} reset A unix timestamp indicating when the bucket is going to be full. - * @property {integer} limit The size of the bucket. - * - * @typedef putParams - * @property {string} type The name of the bucket type. - * @property {string} key The key of the bucket instance. - * @property {integer} [count=SIZE] The number of tokens to put in the bucket. Defaults to the size of the bucket. - * @property {type} configOverride Externally provided bucket configruation - * - * @typedef putResult - * @property {integer} remaining The number of tokens remaining in the bucket. - * @property {integer} reset A unix timestamp indicating when the bucket is going to be full. - * @property {integer} limit The size of the bucket. - * - * @typedef getParams - * @property {string} type The name of the bucket type. - * @property {string} key The key of the bucket instance. - * @property {type} configOverride Externally provided bucket configruation - * - * @typedef getResult - * @property {integer} remaining The number of tokens remaining in the bucket. - * @property {integer} reset A unix timestamp indicating when the bucket is going to be full. - * @property {integer} limit The size of the bucket. -*/ +module.exports = LimitDBRedis; \ No newline at end of file diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..9f1b888 --- /dev/null +++ b/lib/types.js @@ -0,0 +1,155 @@ +// --- Public Types --- + +/** + * @typedef {Object} LimitDBParams + * uri nodes buckets prefix + * @property {string} [params.uri] Address of Redis. + * @property {Object.} [params.nodes] Redis Cluster Configuration https://github.com/luin/ioredis#cluster". + * @property {Object.} [params.types] The buckets configuration. + * @property {string} [params.prefix] Prefix keys in Redis. + * @property {Bucket} [params.configOverride] Bucket configuration override + */ + +/** +* @typedef {Object} Bucket +* @property {number} [per_interval] The number of tokens to add per interval. +* @property {number} [interval] The length of the interval in milliseconds. +* @property {number} [size] The maximum number of tokens in the bucket. +* @property {number} [per_second] The number of tokens to add per second. Equivalent to "interval: 1000, per_interval: x". +* @property {number} [per_minute] The number of tokens to add per minute. Equivalent to "interval: 60000, per_interval: x". +* @property {number} [per_hour] The number of tokens to add per hour. Equivalent to "interval: 3600000, per_interval: x". +* @property {number} [per_day] The number of tokens to add per day. Equivalent to "interval: 86400000, per_interval: x". +* @property {number} [unlimited] the maximum number of tokens in the bucket. equivalent to "size: x". +* @property {number} [skip_n_calls] the number of calls to skip. equivalent to "size: x". +* @property {ElevatedLimitParams} [elevated_limits] The elevated limit configuration. +*/ + +/** + * @typedef TakeParams + * @property {string} type The name of the bucket type. + * @property {string} key The key of the bucket instance. + * @property {number} [count=1] The number of tokens to take from the bucket. + * @property {Bucket} configOverride Externally provided bucket configuration +*/ + +/** + * @typedef TakeResult + * @property {boolean} conformant Returns true if there is enough capacity in the bucket and the tokens has been removed. + * @property {number} remaining The number of tokens remaining in the bucket. + * @property {number} reset A unix timestamp indicating when the bucket is going to be full. + * @property {number} limit The size of the bucket. + */ +/** + + * @typedef WaitParams + * @property {string} type The name of the bucket type. + * @property {string} key The key of the bucket instance. + * @property {number} [count=1] The number of tokens to wait for. + * @property {Bucket} configOverride Externally provided bucket configuration + */ + +/** + * @typedef WaitResult + * @property {number} remaining The number of tokens remaining in the bucket. + * @property {number} reset A unix timestamp indicating when the bucket is going to be full. + * @property {number} limit The size of the bucket. + */ + +/** + * @typedef PutParams + * @property {string} type The name of the bucket type. + * @property {string} key The key of the bucket instance. + * @property {number} [count=SIZE] The number of tokens to put in the bucket. Defaults to the size of the bucket. + * @property {Bucket} configOverride Externally provided bucket configruation + */ + +/** + * @typedef PutResult + * @property {number} remaining The number of tokens remaining in the bucket. + * @property {number} reset A unix timestamp indicating when the bucket is going to be full. + * @property {number} limit The size of the bucket. + */ + +/** + * @typedef GetParams + * @property {string} type The name of the bucket type. + * @property {string} key The key of the bucket instance. + * @property {Bucket} configOverride Externally provided bucket configuration + */ + +/** + * @typedef GetResult + * @property {number} remaining The number of tokens remaining in the bucket. + * @property {number} reset A unix timestamp indicating when the bucket is going to be full. + * @property {number} limit The size of the bucket. + */ + +/** + * @typedef {Object} TakeElevatedParams + * @property {string} type - The name of the bucket type. + * @property {string} key - The key of the bucket instance. + * @property {number} [count=1] - The number of tokens to take from the bucket. + * @property {ElevatedLimitParams} [elevated_limits] - (Optional) The elevated limit configuration. + * @property {Bucket} configOverride Externally provided bucket configuration + */ + +/** + * @typedef {Object} ElevatedLimitParams + * @property {string} erl_is_active_key - The key to check if the elevated limits are active. + * @property {string} erl_quota_key - The key to store the quota for the elevated limits. + * @property {number} erl_activation_period_seconds - The activation period for the elevated limits in seconds. + * // temporal options + * @property {number} [per_interval] The number of tokens to add per interval. + * @property {number} [interval] The length of the interval in milliseconds. + * @property {number} [size] The maximum number of tokens in the bucket. + * @property {number} [per_second] The number of tokens to add per second. Equivalent to "interval: 1000, per_interval: x". + * @property {number} [per_minute] The number of tokens to add per minute. Equivalent to "interval: 60000, per_interval: x". + * @property {number} [per_hour] The number of tokens to add per hour. Equivalent to "interval: 3600000, per_interval: x". + * @property {number} [per_day] The number of tokens to add per day. Equivalent to "interval: 86400000, per_interval: x". + * @property {number} [unlimited] The maximum number of tokens in the bucket. Equivalent to "size: x". + */ + +/** + * @typedef {Object} TakeElevatedResult + * @property {boolean} conformant - Returns true if there is enough capacity in the bucket and the tokens has been removed. + * @property {number} remaining - The number of tokens remaining in the bucket. + * @property {number} reset - A unix timestamp indicating when the bucket is going to be full. + * @property {number} limit - The size of the bucket. + * @property {boolean} delayed - Indicates if the operation was delayed. + * @property {Elevated_result} elevated_limits - The elevated limit result + */ + +/** + * @typedef {Object} Elevated_result + * @property {boolean} erl_configured_for_bucket - Indicates if the bucket is configured for elevated limits. + * @property {boolean} triggered - Indicates if the elevated limits were triggered. + * @property {boolean} activated - Indicates if the elevated limits were activated. + * @property {number} quota_remaining - The remaining quota for elevated limits. + * @property {number} quota_allocated - The allocated quota for elevated limits. + * @property {number} erl_activation_period_seconds - The activation period for elevated limits in seconds. + */ + +// --- Internal Types --- + +/** + * @typedef {Object} NormalizedType -- the internal representation of a bucket + * @property {number} [per_interval] The number of tokens to add per interval. + * @property {number} [interval] The length of the interval in milliseconds. + * @property {number} [size] The maximum number of tokens in the bucket. + * @property {number} [ttl] The time to live for the bucket in seconds. + * @property {number} [ms_per_interval] The number of milliseconds per interval. + * @property {number} [drip_interval] The interval for the drip in milliseconds. + * @property {number} [unlimited] the maximum number of tokens in the bucket. equivalent to "size: x". + * @property {number} [skip_n_calls] the number of calls to skip. equivalent to "size: x". + * @property {NormalizedType} [elevated_limits] The elevated limit configuration. + * @property {boolean} [erl_configured_for_bucket] Indicates if the bucket is configured for elevated limits. + */ + +/** + * @typedef {Object} ElevatedLimitConfiguration + * @property {string} erl_is_active_key - The key to check if the elevated limits are active. + * @property {string} erl_quota_key - The key to store the quota for the elevated limits. + * @property {number} erl_activation_period_seconds - The activation period for the elevated limits in seconds. + * @property {number} erl_quota - The quota for the elevated limits. + * @property {string} erl_quota_interval - The interval for the quota. + */ diff --git a/lib/utils.js b/lib/utils.js index 6a21b0c..a93f8dc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,4 @@ +/// const ms = require('ms'); const _ = require('lodash'); const LRU = require('lru-cache'); @@ -17,7 +18,12 @@ const ERL_QUOTA_INTERVALS = { }; const ERL_QUOTA_INTERVALS_SHORTCUTS = Object.keys(ERL_QUOTA_INTERVALS); -function normalizeTemporals(params) { +/** + * + * @param {Bucket} params + * @returns {NormalizedType} + */ +function parseIntervals(params) { const type = _.pick(params, [ 'per_interval', 'interval', @@ -43,19 +49,24 @@ function normalizeTemporals(params) { type.ms_per_interval = type.per_interval / type.interval; type.drip_interval = type.interval / type.per_interval; } - - if (params.elevated_limits) { - type.elevated_limits = normalizeElevatedTemporals(params.elevated_limits); - } - - return type; + return type } -function normalizeElevatedTemporals(params) { - let type = normalizeTemporals(params); +/** + * + * @param {Bucket} params + * @returns {NormalizedType} + */ +function normalizeTemporals(params) { + const type = parseIntervals(params); - if (typeof type.size !== 'undefined' && typeof type.per_interval !== 'undefined') { - type.erl_configured_for_bucket = true; + if (params.elevated_limits) { + const elevatedLimits = parseIntervals(params.elevated_limits); + const isErlDefined = !_.isUndefined(elevatedLimits.size) && !_.isUndefined(elevatedLimits.per_interval); + type.elevated_limits = { + ...elevatedLimits, + erl_configured_for_bucket: isErlDefined, + }; } return type; @@ -103,7 +114,7 @@ function normalizeType(params) { /** * Load the buckets configuration. * - * @param {Object.} bucketsConfig The buckets configuration. + * @param {Object.} bucketsConfig The buckets configuration. * @memberof LimitDB */ function buildBuckets(bucketsConfig) { @@ -132,6 +143,12 @@ function randomBetween(min, max) { return Math.random() * (max - min) + min; } +/** + * Extracts ERL configuration from the ERL parameters + * + * @param {ElevatedLimitParams} params The object to extract the ERL parameters from. + * @returns {ElevatedLimitConfiguration} The extracted ERL parameters. + */ function getERLParams(params) { const type = _.pick(params, [ 'erl_is_active_key', @@ -159,6 +176,13 @@ function endOfMonthTimestamp() { return Date.UTC(curDate.getUTCFullYear(), curDate.getUTCMonth() + 1, 1, 0, 0, 0, 0); } +/** + * Resolves the elevated parameters by providing default values for elevated_limits unless they are defined in the bucketKeyConfig. + * + * @param {ElevatedLimitParams} erlParams - The ERL parameters to resolve. + * @param {NormalizedType} bucketKeyConfig - The configuration of the bucket key. + * @returns {NormalizedType & ElevatedLimitConfiguration} The resolved ERL parameters. + */ function resolveElevatedParams(erlParams, bucketKeyConfig) { // provide default values for elevated_limits unless the bucketKeyConfig has them return { diff --git a/lib/validation.js b/lib/validation.js index b834e29..dad5679 100644 --- a/lib/validation.js +++ b/lib/validation.js @@ -58,6 +58,12 @@ function validateOverride(configOverride) { } } +/** + * Validates the elevated limits parameters. + * + * @param {ElevatedLimitConfiguration} params The parameters to validate. + * @returns {LimitdRedisValidationError | void} The error, if any. + */ function validateERLParams(params) { if (!params) { return new LimitdRedisValidationError('elevated_limits object is required for elevated limits', { code: 107 });