@depup/rate-limiter-flexible 9.1.1-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +25 -0
  3. package/changes.json +5 -0
  4. package/index.js +55 -0
  5. package/lib/BurstyRateLimiter.js +78 -0
  6. package/lib/ExpressBruteFlexible.js +359 -0
  7. package/lib/RLWrapperBlackAndWhite.js +195 -0
  8. package/lib/RLWrapperTimeouts.js +82 -0
  9. package/lib/RateLimiterAbstract.js +125 -0
  10. package/lib/RateLimiterCluster.js +367 -0
  11. package/lib/RateLimiterDrizzle.js +174 -0
  12. package/lib/RateLimiterDrizzleNonAtomic.js +175 -0
  13. package/lib/RateLimiterDynamo.js +401 -0
  14. package/lib/RateLimiterEtcd.js +63 -0
  15. package/lib/RateLimiterEtcdNonAtomic.js +80 -0
  16. package/lib/RateLimiterInsuredAbstract.js +112 -0
  17. package/lib/RateLimiterMemcache.js +150 -0
  18. package/lib/RateLimiterMemory.js +106 -0
  19. package/lib/RateLimiterMongo.js +261 -0
  20. package/lib/RateLimiterMySQL.js +400 -0
  21. package/lib/RateLimiterPostgres.js +351 -0
  22. package/lib/RateLimiterPrisma.js +127 -0
  23. package/lib/RateLimiterQueue.js +131 -0
  24. package/lib/RateLimiterRedis.js +209 -0
  25. package/lib/RateLimiterRedisNonAtomic.js +195 -0
  26. package/lib/RateLimiterRes.js +64 -0
  27. package/lib/RateLimiterSQLite.js +338 -0
  28. package/lib/RateLimiterStoreAbstract.js +349 -0
  29. package/lib/RateLimiterUnion.js +51 -0
  30. package/lib/RateLimiterValkey.js +117 -0
  31. package/lib/RateLimiterValkeyGlide.js +273 -0
  32. package/lib/component/BlockedKeys/BlockedKeys.js +75 -0
  33. package/lib/component/BlockedKeys/index.js +3 -0
  34. package/lib/component/MemoryStorage/MemoryStorage.js +83 -0
  35. package/lib/component/MemoryStorage/Record.js +40 -0
  36. package/lib/component/MemoryStorage/index.js +3 -0
  37. package/lib/component/RateLimiterEtcdTransactionFailedError.js +10 -0
  38. package/lib/component/RateLimiterQueueError.js +13 -0
  39. package/lib/component/RateLimiterSetupError.js +10 -0
  40. package/lib/constants.js +21 -0
  41. package/package.json +100 -0
  42. package/types.d.ts +581 -0
@@ -0,0 +1,51 @@
1
+ const RateLimiterAbstract = require('./RateLimiterAbstract');
2
+
3
+ module.exports = class RateLimiterUnion {
4
+ constructor(...limiters) {
5
+ if (limiters.length < 1) {
6
+ throw new Error('RateLimiterUnion: at least one limiter have to be passed');
7
+ }
8
+ limiters.forEach((limiter) => {
9
+ if (!(limiter instanceof RateLimiterAbstract)) {
10
+ throw new Error('RateLimiterUnion: all limiters have to be instance of RateLimiterAbstract');
11
+ }
12
+ });
13
+
14
+ this._limiters = limiters;
15
+ }
16
+
17
+ consume(key, points = 1) {
18
+ return new Promise((resolve, reject) => {
19
+ const promises = [];
20
+ this._limiters.forEach((limiter) => {
21
+ promises.push(limiter.consume(key, points).catch(rej => ({ rejected: true, rej })));
22
+ });
23
+
24
+ Promise.all(promises)
25
+ .then((res) => {
26
+ const resObj = {};
27
+ let rejected = false;
28
+
29
+ res.forEach((item) => {
30
+ if (item.rejected === true) {
31
+ rejected = true;
32
+ }
33
+ });
34
+
35
+ for (let i = 0; i < res.length; i++) {
36
+ if (rejected && res[i].rejected === true) {
37
+ resObj[this._limiters[i].keyPrefix] = res[i].rej;
38
+ } else if (!rejected) {
39
+ resObj[this._limiters[i].keyPrefix] = res[i];
40
+ }
41
+ }
42
+
43
+ if (rejected) {
44
+ reject(resObj);
45
+ } else {
46
+ resolve(resObj);
47
+ }
48
+ });
49
+ });
50
+ }
51
+ };
@@ -0,0 +1,117 @@
1
+ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
2
+ const RateLimiterRes = require('./RateLimiterRes');
3
+
4
+ const incrTtlLuaScript = `
5
+ server.call('set', KEYS[1], 0, 'EX', ARGV[2], 'NX')
6
+ local consumed = server.call('incrby', KEYS[1], ARGV[1])
7
+ local ttl = server.call('pttl', KEYS[1])
8
+ return {consumed, ttl}
9
+ `;
10
+
11
+ class RateLimiterValkey extends RateLimiterStoreAbstract {
12
+ /**
13
+ *
14
+ * @param {Object} opts
15
+ * Defaults {
16
+ * ... see other in RateLimiterStoreAbstract
17
+ *
18
+ * storeClient: ValkeyClient
19
+ * rejectIfValkeyNotReady: boolean = false - reject / invoke insuranceLimiter immediately when valkey connection is not "ready"
20
+ * }
21
+ */
22
+ constructor(opts) {
23
+ super(opts);
24
+ this.client = opts.storeClient;
25
+
26
+ this._rejectIfValkeyNotReady = !!opts.rejectIfValkeyNotReady;
27
+ this._incrTtlLuaScript = opts.customIncrTtlLuaScript || incrTtlLuaScript;
28
+
29
+ this.client.defineCommand('rlflxIncr', {
30
+ numberOfKeys: 1,
31
+ lua: this._incrTtlLuaScript,
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Prevent actual valkey call if valkey connection is not ready
37
+ * @return {boolean}
38
+ * @private
39
+ */
40
+ _isValkeyReady() {
41
+ if (!this._rejectIfValkeyNotReady) {
42
+ return true;
43
+ }
44
+
45
+ return this.client.status === 'ready';
46
+ }
47
+
48
+ _getRateLimiterRes(rlKey, changedPoints, result) {
49
+ let consumed;
50
+ let resTtlMs;
51
+
52
+ if (Array.isArray(result[0])) {
53
+ [[, consumed], [, resTtlMs]] = result;
54
+ } else {
55
+ [consumed, resTtlMs] = result;
56
+ }
57
+
58
+ const res = new RateLimiterRes();
59
+ res.consumedPoints = +consumed;
60
+ res.isFirstInDuration = res.consumedPoints === changedPoints;
61
+ res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
62
+ res.msBeforeNext = resTtlMs;
63
+
64
+ return res;
65
+ }
66
+
67
+ _upsert(rlKey, points, msDuration, forceExpire = false) {
68
+ if (!this._isValkeyReady()) {
69
+ throw new Error('Valkey connection is not ready');
70
+ }
71
+
72
+ const secDuration = Math.floor(msDuration / 1000);
73
+
74
+ if (forceExpire) {
75
+ const multi = this.client.multi();
76
+
77
+ if (secDuration > 0) {
78
+ multi.set(rlKey, points, 'EX', secDuration);
79
+ } else {
80
+ multi.set(rlKey, points);
81
+ }
82
+
83
+ return multi.pttl(rlKey).exec();
84
+ }
85
+
86
+ if (secDuration > 0) {
87
+ return this.client.rlflxIncr([rlKey, String(points), String(secDuration), String(this.points), String(this.duration)]);
88
+ }
89
+
90
+ return this.client.multi().incrby(rlKey, points).pttl(rlKey).exec();
91
+ }
92
+
93
+ _get(rlKey) {
94
+ if (!this._isValkeyReady()) {
95
+ throw new Error('Valkey connection is not ready');
96
+ }
97
+
98
+ return this.client
99
+ .multi()
100
+ .get(rlKey)
101
+ .pttl(rlKey)
102
+ .exec()
103
+ .then((result) => {
104
+ const [[, points]] = result;
105
+ if (points === null) return null;
106
+ return result;
107
+ });
108
+ }
109
+
110
+ _delete(rlKey) {
111
+ return this.client
112
+ .del(rlKey)
113
+ .then(result => result > 0);
114
+ }
115
+ }
116
+
117
+ module.exports = RateLimiterValkey;
@@ -0,0 +1,273 @@
1
+ /* eslint-disable no-unused-vars */
2
+ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
3
+ const RateLimiterRes = require('./RateLimiterRes');
4
+
5
+ /**
6
+ * @typedef {import('@valkey/valkey-glide').GlideClient} GlideClient
7
+ * @typedef {import('@valkey/valkey-glide').GlideClusterClient} GlideClusterClient
8
+ */
9
+
10
+ const DEFAULT_LIBRARY_NAME = 'ratelimiterflexible';
11
+
12
+ const DEFAULT_VALKEY_SCRIPT = `local key = KEYS[1]
13
+ local pointsToConsume = tonumber(ARGV[1])
14
+ if tonumber(ARGV[2]) > 0 then
15
+ server.call('set', key, "0", 'EX', ARGV[2], 'NX')
16
+ local consumed = server.call('incrby', key, pointsToConsume)
17
+ local pttl = server.call('pttl', key)
18
+ return {consumed, pttl}
19
+ end
20
+ local consumed = server.call('incrby', key, pointsToConsume)
21
+ local pttl = server.call('pttl', key)
22
+ return {consumed, pttl}`;
23
+
24
+ const GET_VALKEY_SCRIPT = `local key = KEYS[1]
25
+ local value = server.call('get', key)
26
+ if value == nil then
27
+ return value
28
+ end
29
+ local pttl = server.call('pttl', key)
30
+ return {tonumber(value), pttl}`;
31
+
32
+ class RateLimiterValkeyGlide extends RateLimiterStoreAbstract {
33
+ /**
34
+ * Constructor for RateLimiterValkeyGlide
35
+ *
36
+ * @param {Object} opts - Configuration options
37
+ * @param {GlideClient|GlideClusterClient} opts.storeClient - Valkey Glide client instance (required)
38
+ * @param {number} [opts.points=4] - Maximum number of points that can be consumed over duration
39
+ * @param {number} [opts.duration=1] - Duration in seconds before points are reset
40
+ * @param {number} [opts.blockDuration=0] - Duration in seconds that a key will be blocked for if consumed more than points
41
+ * @param {boolean} [opts.rejectIfValkeyNotReady=false] - Whether to reject requests if Valkey is not ready
42
+ * @param {boolean} [opts.execEvenly=false] - Delay actions to distribute them evenly over duration
43
+ * @param {number} [opts.execEvenlyMinDelayMs] - Minimum delay between actions when execEvenly is true
44
+ * @param {string} [opts.customFunction] - Custom Lua script for rate limiting logic
45
+ * @param {number} [opts.inMemoryBlockOnConsumed] - Points threshold for in-memory blocking
46
+ * @param {number} [opts.inMemoryBlockDuration] - Duration in seconds for in-memory blocking
47
+ * @param {string} [opts.customFunctionLibName] - Custom name for the function library, defaults to 'ratelimiter'.
48
+ * The name is used to identify the library of the lua function. An custom name should be used only if you
49
+ * you want to use different libraries for different rate limiters, otherwise it is not needed.
50
+ * @param {RateLimiterAbstract} [opts.insuranceLimiter] - Backup limiter to use when the primary client fails
51
+ *
52
+ * @example
53
+ * const rateLimiter = new RateLimiterValkeyGlide({
54
+ * storeClient: glideClient,
55
+ * points: 5,
56
+ * duration: 1
57
+ * });
58
+ *
59
+ * @example <caption>With custom Lua function</caption>
60
+ * const customScript = `local key = KEYS[1]
61
+ * local pointsToConsume = tonumber(ARGV[1]) or 0
62
+ * local secDuration = tonumber(ARGV[2]) or 0
63
+ *
64
+ * -- Custom implementation
65
+ * -- ...
66
+ *
67
+ * -- Must return exactly two values: [consumed_points, ttl_in_ms]
68
+ * return {consumed, ttl}`
69
+ *
70
+ * const rateLimiter = new RateLimiterValkeyGlide({
71
+ * storeClient: glideClient,
72
+ * points: 5,
73
+ * customFunction: customScript
74
+ * });
75
+ *
76
+ * @example <caption>With insurance limiter</caption>
77
+ * const rateLimiter = new RateLimiterValkeyGlide({
78
+ * storeClient: primaryGlideClient,
79
+ * points: 5,
80
+ * duration: 2,
81
+ * insuranceLimiter: new RateLimiterMemory({
82
+ * points: 5,
83
+ * duration: 2
84
+ * })
85
+ * });
86
+ *
87
+ * @description
88
+ * When providing a custom Lua script via `opts.customFunction`, it must:
89
+ *
90
+ * 1. Accept parameters:
91
+ * - KEYS[1]: The key being rate limited
92
+ * - ARGV[1]: Points to consume (as string, use tonumber() to convert)
93
+ * - ARGV[2]: Duration in seconds (as string, use tonumber() to convert)
94
+ *
95
+ * 2. Return an array with exactly two elements:
96
+ * - [0]: Consumed points (number)
97
+ * - [1]: TTL in milliseconds (number)
98
+ *
99
+ * 3. Handle scenarios:
100
+ * - New key creation: Initialize with expiry for fixed windows
101
+ * - Key updates: Increment existing counters
102
+ */
103
+ constructor(opts) {
104
+ super(opts);
105
+ this.client = opts.storeClient;
106
+ this._scriptLoaded = false;
107
+ this._getScriptLoaded = false;
108
+ this._rejectIfValkeyNotReady = !!opts.rejectIfValkeyNotReady;
109
+ this._luaScript = opts.customFunction || DEFAULT_VALKEY_SCRIPT;
110
+ this._libraryName = opts.customFunctionLibName || DEFAULT_LIBRARY_NAME;
111
+ }
112
+
113
+ /**
114
+ * Ensure scripts are loaded in the Valkey server
115
+ * @returns {Promise<boolean>} True if scripts are loaded
116
+ * @private
117
+ */
118
+ async _loadScripts() {
119
+ if (this._scriptLoaded && this._getScriptLoaded) {
120
+ return true;
121
+ }
122
+ if (!this.client) {
123
+ throw new Error('Valkey client is not set');
124
+ }
125
+ const promises = [];
126
+ if (!this._scriptLoaded) {
127
+ const script = Buffer.from(`#!lua name=${this._libraryName}
128
+ local function consume(KEYS, ARGV)
129
+ ${this._luaScript.trim()}
130
+ end
131
+ server.register_function('consume', consume)`);
132
+ promises.push(this.client.functionLoad(script, { replace: true }));
133
+ } else promises.push(Promise.resolve(this._libraryName));
134
+
135
+ if (!this._getScriptLoaded) {
136
+ const script = Buffer.from(`#!lua name=ratelimiter_get
137
+ local function getValue(KEYS, ARGV)
138
+ ${GET_VALKEY_SCRIPT.trim()}
139
+ end
140
+ server.register_function('getValue', getValue)`);
141
+ promises.push(this.client.functionLoad(script, { replace: true }));
142
+ } else promises.push(Promise.resolve('ratelimiter_get'));
143
+
144
+ const results = await Promise.all(promises);
145
+ this._scriptLoaded = results[0] === this._libraryName;
146
+ this._getScriptLoaded = results[1] === 'ratelimiter_get';
147
+
148
+ if ((!this._scriptLoaded || !this._getScriptLoaded)) {
149
+ throw new Error('Valkey connection is not ready, scripts not loaded');
150
+ }
151
+ return true;
152
+ }
153
+
154
+ /**
155
+ * Update or insert the rate limiter record
156
+ *
157
+ * @param {string} rlKey - The rate limiter key
158
+ * @param {number} pointsToConsume - Points to be consumed
159
+ * @param {number} msDuration - Duration in milliseconds
160
+ * @param {boolean} [forceExpire=false] - Whether to force expiration
161
+ * @param {Object} [options={}] - Additional options
162
+ * @returns {Promise<Array>} Array containing consumed points and TTL
163
+ * @private
164
+ */
165
+ async _upsert(rlKey, pointsToConsume, msDuration, forceExpire = false, options = {}) {
166
+ await this._loadScripts();
167
+ const secDuration = Math.floor(msDuration / 1000);
168
+ if (forceExpire) {
169
+ if (secDuration > 0) {
170
+ await this.client.set(
171
+ rlKey,
172
+ String(pointsToConsume),
173
+ { expiry: { type: 'EX', count: secDuration } },
174
+ );
175
+ return [pointsToConsume, secDuration * 1000];
176
+ }
177
+ await this.client.set(rlKey, String(pointsToConsume));
178
+ return [pointsToConsume, -1];
179
+ }
180
+ const result = await this.client.fcall(
181
+ 'consume',
182
+ [rlKey],
183
+ [String(pointsToConsume), String(secDuration)],
184
+ );
185
+ return result;
186
+ }
187
+
188
+ /**
189
+ * Get the rate limiter record
190
+ *
191
+ * @param {string} rlKey - The rate limiter key
192
+ * @param {Object} [options={}] - Additional options
193
+ * @returns {Promise<Array|null>} Array containing consumed points and TTL, or null if not found
194
+ * @private
195
+ */
196
+ async _get(rlKey, options = {}) {
197
+ await this._loadScripts();
198
+ const res = await this.client.fcall('getValue', [rlKey], []);
199
+ return res.length > 0 ? res : null;
200
+ }
201
+
202
+ /**
203
+ * Delete the rate limiter record
204
+ *
205
+ * @param {string} rlKey - The rate limiter key
206
+ * @param {Object} [options={}] - Additional options
207
+ * @returns {Promise<boolean>} True if successful, false otherwise
208
+ * @private
209
+ */
210
+ async _delete(rlKey, options = {}) {
211
+ const result = await this.client.del([rlKey]);
212
+ return result > 0;
213
+ }
214
+
215
+ /**
216
+ * Convert raw result to RateLimiterRes object
217
+ *
218
+ * @param {string} rlKey - The rate limiter key
219
+ * @param {number} changedPoints - Points changed in this operation
220
+ * @param {Array|null} result - Result from Valkey operation
221
+ * @returns {RateLimiterRes|null} RateLimiterRes object or null if result is null
222
+ * @private
223
+ */
224
+ _getRateLimiterRes(rlKey, changedPoints, result) {
225
+ if (result === null) {
226
+ return null;
227
+ }
228
+ const res = new RateLimiterRes();
229
+ const [consumedPointsStr, pttl] = result;
230
+ const consumedPoints = Number(consumedPointsStr);
231
+
232
+ // Handle consumed points
233
+ res.isFirstInDuration = consumedPoints === changedPoints;
234
+ res.consumedPoints = consumedPoints;
235
+ res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
236
+ res.msBeforeNext = pttl;
237
+ return res;
238
+ }
239
+
240
+ /**
241
+ * Close the rate limiter and release resources
242
+ * Note: The method won't going to close the Valkey client, as it may be shared with other instances.
243
+ * @returns {Promise<void>} Promise that resolves when the rate limiter is closed
244
+ */
245
+ async close() {
246
+ if (this._scriptLoaded) {
247
+ await this.client.functionDelete(this._libraryName);
248
+ this._scriptLoaded = false;
249
+ }
250
+ if (this._getScriptLoaded) {
251
+ await this.client.functionDelete('ratelimiter_get');
252
+ this._getScriptLoaded = false;
253
+ }
254
+ if (this.insuranceLimiter) {
255
+ try {
256
+ await this.insuranceLimiter.close();
257
+ } catch (e) {
258
+ // We can't assume that insuranceLimiter is a Valkey client or any
259
+ // other insuranceLimiter type which implement close method.
260
+ }
261
+ }
262
+ // Clear instance properties to let garbage collector free memory
263
+ this.client = null;
264
+ this._scriptLoaded = false;
265
+ this._getScriptLoaded = false;
266
+ this._rejectIfValkeyNotReady = false;
267
+ this._luaScript = null;
268
+ this._libraryName = null;
269
+ this.insuranceLimiter = null;
270
+ }
271
+ }
272
+
273
+ module.exports = RateLimiterValkeyGlide;
@@ -0,0 +1,75 @@
1
+ module.exports = class BlockedKeys {
2
+ constructor() {
3
+ this._keys = {}; // {'key': 1526279430331}
4
+ this._addedKeysAmount = 0;
5
+ }
6
+
7
+ collectExpired() {
8
+ const now = Date.now();
9
+
10
+ Object.keys(this._keys).forEach((key) => {
11
+ if (this._keys[key] <= now) {
12
+ delete this._keys[key];
13
+ }
14
+ });
15
+
16
+ this._addedKeysAmount = Object.keys(this._keys).length;
17
+ }
18
+
19
+ /**
20
+ * Add new blocked key
21
+ *
22
+ * @param key String
23
+ * @param sec Number
24
+ */
25
+ add(key, sec) {
26
+ this.addMs(key, sec * 1000);
27
+ }
28
+
29
+ /**
30
+ * Add new blocked key for ms
31
+ *
32
+ * @param key String
33
+ * @param ms Number
34
+ */
35
+ addMs(key, ms) {
36
+ this._keys[key] = Date.now() + ms;
37
+ this._addedKeysAmount++;
38
+ if (this._addedKeysAmount > 999) {
39
+ this.collectExpired();
40
+ }
41
+ }
42
+
43
+ /**
44
+ * 0 means not blocked
45
+ *
46
+ * @param key
47
+ * @returns {number}
48
+ */
49
+ msBeforeExpire(key) {
50
+ const expire = this._keys[key];
51
+
52
+ if (expire && expire >= Date.now()) {
53
+ this.collectExpired();
54
+ const now = Date.now();
55
+ return expire >= now ? expire - now : 0;
56
+ }
57
+
58
+ return 0;
59
+ }
60
+
61
+ /**
62
+ * If key is not given, delete all data in memory
63
+ *
64
+ * @param {string|undefined} key
65
+ */
66
+ delete(key) {
67
+ if (key) {
68
+ delete this._keys[key];
69
+ } else {
70
+ Object.keys(this._keys).forEach((key) => {
71
+ delete this._keys[key];
72
+ });
73
+ }
74
+ }
75
+ };
@@ -0,0 +1,3 @@
1
+ const BlockedKeys = require('./BlockedKeys');
2
+
3
+ module.exports = BlockedKeys;
@@ -0,0 +1,83 @@
1
+ const Record = require('./Record');
2
+ const RateLimiterRes = require('../../RateLimiterRes');
3
+
4
+ module.exports = class MemoryStorage {
5
+ constructor() {
6
+ /**
7
+ * @type {Object.<string, Record>}
8
+ * @private
9
+ */
10
+ this._storage = {};
11
+ }
12
+
13
+ incrby(key, value, durationSec) {
14
+ if (this._storage[key]) {
15
+ const msBeforeExpires = this._storage[key].expiresAt
16
+ ? this._storage[key].expiresAt.getTime() - new Date().getTime()
17
+ : -1;
18
+ if (!this._storage[key].expiresAt || msBeforeExpires > 0) {
19
+ // Change value
20
+ this._storage[key].value = this._storage[key].value + value;
21
+
22
+ return new RateLimiterRes(0, msBeforeExpires, this._storage[key].value, false);
23
+ }
24
+
25
+ return this.set(key, value, durationSec);
26
+ }
27
+ return this.set(key, value, durationSec);
28
+ }
29
+
30
+ set(key, value, durationSec) {
31
+ const durationMs = durationSec * 1000;
32
+
33
+ if (this._storage[key] && this._storage[key].timeoutId) {
34
+ clearTimeout(this._storage[key].timeoutId);
35
+ }
36
+
37
+ this._storage[key] = new Record(
38
+ value,
39
+ durationMs > 0 ? new Date(Date.now() + durationMs) : null
40
+ );
41
+ if (durationMs > 0) {
42
+ this._storage[key].timeoutId = setTimeout(() => {
43
+ delete this._storage[key];
44
+ }, durationMs);
45
+ if (this._storage[key].timeoutId.unref) {
46
+ this._storage[key].timeoutId.unref();
47
+ }
48
+ }
49
+
50
+ return new RateLimiterRes(0, durationMs === 0 ? -1 : durationMs, this._storage[key].value, true);
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @param key
56
+ * @returns {*}
57
+ */
58
+ get(key) {
59
+ if (this._storage[key]) {
60
+ const msBeforeExpires = this._storage[key].expiresAt
61
+ ? this._storage[key].expiresAt.getTime() - new Date().getTime()
62
+ : -1;
63
+ return new RateLimiterRes(0, msBeforeExpires, this._storage[key].value, false);
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param key
71
+ * @returns {boolean}
72
+ */
73
+ delete(key) {
74
+ if (this._storage[key]) {
75
+ if (this._storage[key].timeoutId) {
76
+ clearTimeout(this._storage[key].timeoutId);
77
+ }
78
+ delete this._storage[key];
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+ };
@@ -0,0 +1,40 @@
1
+ module.exports = class Record {
2
+ /**
3
+ *
4
+ * @param value int
5
+ * @param expiresAt Date|int
6
+ * @param timeoutId
7
+ */
8
+ constructor(value, expiresAt, timeoutId = null) {
9
+ this.value = value;
10
+ this.expiresAt = expiresAt;
11
+ this.timeoutId = timeoutId;
12
+ }
13
+
14
+ get value() {
15
+ return this._value;
16
+ }
17
+
18
+ set value(value) {
19
+ this._value = parseInt(value);
20
+ }
21
+
22
+ get expiresAt() {
23
+ return this._expiresAt;
24
+ }
25
+
26
+ set expiresAt(value) {
27
+ if (!(value instanceof Date) && Number.isInteger(value)) {
28
+ value = new Date(value);
29
+ }
30
+ this._expiresAt = value;
31
+ }
32
+
33
+ get timeoutId() {
34
+ return this._timeoutId;
35
+ }
36
+
37
+ set timeoutId(value) {
38
+ this._timeoutId = value;
39
+ }
40
+ };
@@ -0,0 +1,3 @@
1
+ const MemoryStorage = require('./MemoryStorage');
2
+
3
+ module.exports = MemoryStorage;
@@ -0,0 +1,10 @@
1
+ module.exports = class RateLimiterEtcdTransactionFailedError extends Error {
2
+ constructor(message) {
3
+ super();
4
+ if (Error.captureStackTrace) {
5
+ Error.captureStackTrace(this, this.constructor);
6
+ }
7
+ this.name = 'RateLimiterEtcdTransactionFailedError';
8
+ this.message = message;
9
+ }
10
+ };
@@ -0,0 +1,13 @@
1
+ module.exports = class RateLimiterQueueError extends Error {
2
+ constructor(message, extra) {
3
+ super();
4
+ if (Error.captureStackTrace) {
5
+ Error.captureStackTrace(this, this.constructor);
6
+ }
7
+ this.name = 'CustomError';
8
+ this.message = message;
9
+ if (extra) {
10
+ this.extra = extra;
11
+ }
12
+ }
13
+ };
@@ -0,0 +1,10 @@
1
+ module.exports = class RateLimiterSetupError extends Error {
2
+ constructor(message) {
3
+ super();
4
+ if (Error.captureStackTrace) {
5
+ Error.captureStackTrace(this, this.constructor);
6
+ }
7
+ this.name = 'RateLimiterSetupError';
8
+ this.message = message;
9
+ }
10
+ };