rate-limiter-flexible 6.2.0 → 7.0.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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![node version][node-image]][node-url]
4
4
  [![deno version](https://img.shields.io/badge/deno-^1.5.3-lightgrey?logo=deno)](https://github.com/denoland/deno)
5
5
 
6
- [node-image]: https://img.shields.io/badge/node.js-%3E=_18.0-green.svg?style=flat-square
6
+ [node-image]: https://img.shields.io/badge/node.js-%3E=_20.0-green.svg?style=flat-square
7
7
  [node-url]: http://nodejs.org/download/
8
8
 
9
9
  <img src="img/rlflx-logo-small.png" width="50" alt="Logo"/>
@@ -12,7 +12,7 @@
12
12
 
13
13
  **rate-limiter-flexible** counts and limits the number of actions by key and protects from DDoS and brute force attacks at any scale.
14
14
 
15
- It works with _Redis_, _Valkey_, _Prisma_, _DynamoDB_, process _Memory_, _Cluster_ or _PM2_, _Memcached_, _MongoDB_, _MySQL_, _SQLite_, and _PostgreSQL_.
15
+ It works with _Valkey_, _Redis_, _Prisma_, _DynamoDB_, process _Memory_, _Cluster_ or _PM2_, _Memcached_, _MongoDB_, _MySQL_, _SQLite_, and _PostgreSQL_.
16
16
 
17
17
  Memory limiter also works in the browser.
18
18
 
@@ -24,7 +24,9 @@ Memory limiter also works in the browser.
24
24
 
25
25
  **Ready for growth.** It provides a unified API for all limiters. Whenever your application grows, it is ready. Prepare your limiters in minutes.
26
26
 
27
- **Friendly.** No matter which node package you prefer: `redis` or `ioredis`, `iovalkey`, `sequelize`/`typeorm` or `knex`, `memcached`, native driver or `mongoose`. It works with all of them.
27
+ **Friendly.** No matter which node package you prefer: [`valkey-glide`](https://www.npmjs.com/package/@valkey/valkey-glide) or [`iovalkey`](https://www.npmjs.com/package/iovalkey), `redis` or `ioredis`, `sequelize`/`typeorm` or `knex`, `memcached`, native driver or `mongoose`. It works with all of them.
28
+
29
+ **Safe for using with valkey cluster.** [`valkey-glide`](https://www.npmjs.com/package/@valkey/valkey-glide) implementation, [RateLimiterValkeyGlide](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterValkeyGlide), is being tested against `valkey` cluster to ensure the rate limiter is agnostic to the server type, it able to avoid race conditions in high traffic along with sharded cluster, and to ensure compatibility and high performance.
28
30
 
29
31
  **In-memory blocks.** Avoid extra requests to store with [inMemoryBlockOnConsumed](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#inmemoryblockonconsumed).
30
32
 
@@ -95,7 +97,7 @@ const headers = {
95
97
  "Retry-After": rateLimiterRes.msBeforeNext / 1000,
96
98
  "X-RateLimit-Limit": opts.points,
97
99
  "X-RateLimit-Remaining": rateLimiterRes.remainingPoints,
98
- "X-RateLimit-Reset": new Date(Date.now() + rateLimiterRes.msBeforeNext)
100
+ "X-RateLimit-Reset": Math.ceil((Date.now() + rateLimiterRes.msBeforeNext) / 1000)
99
101
  }
100
102
  ```
101
103
 
@@ -137,18 +139,18 @@ Some copy/paste examples on Wiki:
137
139
 
138
140
  * [Options](https://github.com/animir/node-rate-limiter-flexible/wiki/Options)
139
141
  * [API methods](https://github.com/animir/node-rate-limiter-flexible/wiki/API-methods)
142
+ * [Valkey](https://github.com/animir/node-rate-limiter-flexible/wiki/Valkey)
140
143
  * [Redis](https://github.com/animir/node-rate-limiter-flexible/wiki/Redis)
141
144
  * [Memory](https://github.com/animir/node-rate-limiter-flexible/wiki/Memory)
142
145
  * [DynamoDb](https://github.com/animir/node-rate-limiter-flexible/wiki/DynamoDB)
143
146
  * [Prisma](https://github.com/animir/node-rate-limiter-flexible/wiki/Prisma)
144
- * [Valkey](https://github.com/animir/node-rate-limiter-flexible/wiki/Valkey)
145
147
  * [BurstyRateLimiter](https://github.com/animir/node-rate-limiter-flexible/wiki/BurstyRateLimiter) Traffic burst support
146
148
  * [Mongo](https://github.com/animir/node-rate-limiter-flexible/wiki/Mongo) (with [sharding support](https://github.com/animir/node-rate-limiter-flexible/wiki/Mongo#mongodb-sharding-options))
147
149
  * [MySQL](https://github.com/animir/node-rate-limiter-flexible/wiki/MySQL) (support Sequelize and Knex)
148
150
  * [Postgres](https://github.com/animir/node-rate-limiter-flexible/wiki/PostgreSQL) (support Sequelize, TypeORM and Knex)
149
151
  * [SQLite](https://github.com/animir/node-rate-limiter-flexible/wiki/SQLite)
150
152
  * [RateLimiterCluster](https://github.com/animir/node-rate-limiter-flexible/wiki/Cluster) ([PM2 cluster docs read here](https://github.com/animir/node-rate-limiter-flexible/wiki/PM2-cluster))
151
- * [Memcache](https://github.com/animir/node-rate-limiter-flexible/wiki/Memcache)
153
+ * [Memcached](https://github.com/animir/node-rate-limiter-flexible/wiki/Memcache)
152
154
  * [RateLimiterUnion](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterUnion) Combine 2 or more limiters to act as single
153
155
  * [RLWrapperBlackAndWhite](https://github.com/animir/node-rate-limiter-flexible/wiki/Black-and-White-lists) Black and White lists
154
156
  * [RateLimiterQueue](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterQueue) Rate limiter with FIFO queue
@@ -177,7 +179,7 @@ See [releases](https://github.com/animir/node-rate-limiter-flexible/releases) fo
177
179
 
178
180
  `Required for store limiters`
179
181
 
180
- Must be `redis`, `ioredis`, `memcached`, `mongodb`, `pg`, `mysql2`, `mysql` or any other related pool or connection.
182
+ Must be `@valkey/valkey-glide`, `iovalkey`, `redis`, `ioredis`, `memcached`, `mongodb`, `pg`, `mysql2`, `mysql` or any other related pool or connection.
181
183
 
182
184
  ### Other options on Wiki:
183
185
  * [keyPrefix](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#keyprefix) Make keys unique among different limiters.
package/index.js CHANGED
@@ -2,7 +2,7 @@ const RateLimiterRedis = require('./lib/RateLimiterRedis');
2
2
  const RateLimiterMongo = require('./lib/RateLimiterMongo');
3
3
  const RateLimiterMySQL = require('./lib/RateLimiterMySQL');
4
4
  const RateLimiterPostgres = require('./lib/RateLimiterPostgres');
5
- const {RateLimiterClusterMaster, RateLimiterClusterMasterPM2, RateLimiterCluster} = require('./lib/RateLimiterCluster');
5
+ const { RateLimiterClusterMaster, RateLimiterClusterMasterPM2, RateLimiterCluster } = require('./lib/RateLimiterCluster');
6
6
  const RateLimiterMemory = require('./lib/RateLimiterMemory');
7
7
  const RateLimiterMemcache = require('./lib/RateLimiterMemcache');
8
8
  const RLWrapperBlackAndWhite = require('./lib/RLWrapperBlackAndWhite');
@@ -13,6 +13,7 @@ const RateLimiterRes = require('./lib/RateLimiterRes');
13
13
  const RateLimiterDynamo = require('./lib/RateLimiterDynamo');
14
14
  const RateLimiterPrisma = require('./lib/RateLimiterPrisma');
15
15
  const RateLimiterValkey = require('./lib/RateLimiterValkey');
16
+ const RateLimiterValkeyGlide = require('./lib/RateLimiterValkeyGlide');
16
17
  const RateLimiterSQLite = require('./lib/RateLimiterSQLite');
17
18
 
18
19
  module.exports = {
@@ -33,5 +34,6 @@ module.exports = {
33
34
  RateLimiterDynamo,
34
35
  RateLimiterPrisma,
35
36
  RateLimiterValkey,
37
+ RateLimiterValkeyGlide,
36
38
  RateLimiterSQLite,
37
39
  };
@@ -11,6 +11,8 @@ const {
11
11
  RateLimiterMySQL,
12
12
  RateLimiterPostgres,
13
13
  RateLimiterRedis,
14
+ RateLimiterValkey,
15
+ RateLimiterValkeyGlide,
14
16
  } = require('../index');
15
17
 
16
18
  function getDelayMs(count, delays, maxWait) {
@@ -119,6 +121,16 @@ ExpressBruteFlexible.prototype.getMiddleware = function (options) {
119
121
  this.blockLimiter = new RateLimiterPostgres(blockLimiterOptions);
120
122
  this.counterLimiter = new RateLimiterPostgres(counterLimiterOptions);
121
123
  break;
124
+ case 'valkey-glide':
125
+ this.freeLimiter = new RateLimiterValkeyGlide(freeLimiterOptions);
126
+ this.blockLimiter = new RateLimiterValkeyGlide(blockLimiterOptions);
127
+ this.counterLimiter = new RateLimiterValkeyGlide(counterLimiterOptions);
128
+ break;
129
+ case 'valkey':
130
+ this.freeLimiter = new RateLimiterValkey(freeLimiterOptions);
131
+ this.blockLimiter = new RateLimiterValkey(blockLimiterOptions);
132
+ this.counterLimiter = new RateLimiterValkey(counterLimiterOptions);
133
+ break;
122
134
  case 'redis':
123
135
  this.freeLimiter = new RateLimiterRedis(freeLimiterOptions);
124
136
  this.blockLimiter = new RateLimiterRedis(blockLimiterOptions);
@@ -217,8 +217,7 @@ class RateLimiterSQLite extends RateLimiterStoreAbstract {
217
217
  return res;
218
218
  }
219
219
 
220
- async _upsertTransactionSQLite3(upsertQuery, upsertParams) {
221
- const conn = await this._getConnection();
220
+ async _upsertTransactionSQLite3(conn, upsertQuery, upsertParams) {
222
221
  return await new Promise((resolve, reject) => {
223
222
  conn.serialize(() => {
224
223
  conn.run("SAVEPOINT rate_limiter_trx;", (err) => {
@@ -237,9 +236,7 @@ class RateLimiterSQLite extends RateLimiterStoreAbstract {
237
236
  });
238
237
  }
239
238
 
240
- async _upsertTransactionBetterSQLite3(upsertQuery, upsertParams) {
241
- const conn = await this._getConnection();
242
-
239
+ async _upsertTransactionBetterSQLite3(conn, upsertQuery, upsertParams) {
243
240
  return conn.transaction(() =>
244
241
  conn.prepare(upsertQuery).get(...upsertParams)
245
242
  )();
@@ -263,9 +260,14 @@ class RateLimiterSQLite extends RateLimiterStoreAbstract {
263
260
  try {
264
261
  switch (this._internalStoreType) {
265
262
  case "sqlite3":
266
- return this._upsertTransactionSQLite3(upsertQuery, upsertParams);
263
+ return this._upsertTransactionSQLite3(
264
+ conn,
265
+ upsertQuery,
266
+ upsertParams
267
+ );
267
268
  case "better-sqlite3":
268
269
  return this._upsertTransactionBetterSQLite3(
270
+ conn,
269
271
  upsertQuery,
270
272
  upsertParams
271
273
  );
@@ -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;
package/lib/constants.js CHANGED
@@ -9,6 +9,8 @@ const LIMITER_TYPES = {
9
9
  DYNAMO: 'dynamo',
10
10
  PRISMA: 'prisma',
11
11
  SQLITE: 'sqlite',
12
+ VALKEY: 'valkey',
13
+ VALKEY_GLIDE: 'valkey-glide',
12
14
  };
13
15
 
14
16
  const ERR_UNKNOWN_LIMITER_TYPE_MESSAGE = 'Unknown limiter type. Use one of LIMITER_TYPES constants.';
package/lib/index.d.ts CHANGED
@@ -417,3 +417,99 @@ interface IRateLimiterDynamoOptions extends IRateLimiterStoreOptions {
417
417
  export class RateLimiterDynamo extends RateLimiterStoreAbstract {
418
418
  constructor(opts: IRateLimiterDynamoOptions, cb?: ICallbackReady);
419
419
  }
420
+
421
+ /**
422
+ * Options for RateLimiterValkeyGlide
423
+ */
424
+ interface IRateLimiterValkeyGlideOptions extends IRateLimiterStoreOptions {
425
+ /**
426
+ * Valkey Glide client instance (GlideClient or GlideClusterClient)
427
+ */
428
+ storeClient: any; // GlideClient | GlideClusterClient;
429
+
430
+ /**
431
+ * Whether to reject requests if Valkey is not ready
432
+ * @default false
433
+ */
434
+ rejectIfValkeyNotReady?: boolean;
435
+
436
+ /**
437
+ * Custom Lua script for rate limiting logic.
438
+ * Must accept parameters:
439
+ * - KEYS[1]: The key being rate limited
440
+ * - ARGV[1]: Points to consume (as string, use tonumber() to convert)
441
+ * - ARGV[2]: Duration in seconds (as string, use tonumber() to convert)
442
+ *
443
+ * Must return an array with exactly two elements:
444
+ * - [0]: Consumed points (number)
445
+ * - [1]: TTL in milliseconds (number)
446
+ */
447
+ customFunction?: string;
448
+
449
+ /**
450
+ * Custom name for the function library, defaults to 'ratelimiter'.
451
+ * The name is used to identify the library of the Lua function.
452
+ * A custom name should be used only if you want to use different
453
+ * libraries for different rate limiters.
454
+ * @default 'ratelimiter'
455
+ */
456
+ customFunctionLibName?: string;
457
+ }
458
+
459
+ /**
460
+ * Rate limiter that uses Valkey Glide client for storage
461
+ */
462
+ export class RateLimiterValkeyGlide extends RateLimiterStoreAbstract {
463
+ /**
464
+ * Creates a new instance of RateLimiterValkeyGlide
465
+ *
466
+ * @param opts Configuration options
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * // Basic usage
471
+ * const rateLimiter = new RateLimiterValkeyGlide({
472
+ * storeClient: glideClient,
473
+ * points: 5,
474
+ * duration: 1
475
+ * });
476
+ *
477
+ * // With custom Lua function
478
+ * const customScript = `local key = KEYS[1]
479
+ * local pointsToConsume = tonumber(ARGV[1]) or 0
480
+ * local secDuration = tonumber(ARGV[2]) or 0
481
+ *
482
+ * -- Custom implementation
483
+ * -- ...
484
+ *
485
+ * -- Must return exactly two values: [consumed_points, ttl_in_ms]
486
+ * return {consumed, ttl}`;
487
+ *
488
+ * const rateLimiter = new RateLimiterValkeyGlide({
489
+ * storeClient: glideClient,
490
+ * points: 5,
491
+ * customFunction: customScript
492
+ * });
493
+ *
494
+ * // With insurance limiter
495
+ * const rateLimiter = new RateLimiterValkeyGlide({
496
+ * storeClient: primaryGlideClient,
497
+ * points: 5,
498
+ * duration: 2,
499
+ * insuranceLimiter: new RateLimiterMemory({
500
+ * points: 5,
501
+ * duration: 2
502
+ * })
503
+ * });
504
+ * ```
505
+ */
506
+ constructor(opts: IRateLimiterValkeyGlideOptions);
507
+
508
+ /**
509
+ * Close the rate limiter and release resources
510
+ * Note: The method won't close the Valkey client, as it may be shared with other instances.
511
+ *
512
+ * @returns Promise that resolves when the rate limiter is closed
513
+ */
514
+ close(): Promise<void>;
515
+ }
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "rate-limiter-flexible",
3
- "version": "6.2.0",
3
+ "version": "7.0.0",
4
4
  "description": "Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "dc:up": "docker-compose -f docker-compose.yml up -d",
8
8
  "dc:down": "docker-compose -f docker-compose.yml down",
9
+ "valkey-cluster:up": "docker-compose -f docker-compose.valkey-cluster.yml up -d",
10
+ "valkey-cluster:down": "docker-compose -f docker-compose.valkey-cluster.yml down -v",
11
+ "test:valkey-cluster": "VALKEY_CLUSTER_PORT=7001 mocha test/RateLimiterValkeyGlide.test.js -- -g 'RateLimiterValkeyGlide with cluster client'",
9
12
  "prisma:postgres": "prisma generate --schema=./test/RateLimiterPrisma/Postgres/schema.prisma && prisma db push --schema=./test/RateLimiterPrisma/Postgres/schema.prisma",
10
13
  "test": "npm run prisma:postgres && nyc --reporter=html --reporter=text mocha",
11
14
  "debug-test": "mocha --inspect-brk lib/**/**.test.js",
@@ -33,7 +36,12 @@
33
36
  "prisma",
34
37
  "koa",
35
38
  "express",
36
- "hapi"
39
+ "hapi",
40
+ "valkey",
41
+ "valkey-glide",
42
+ "GLIDE",
43
+ "cluster",
44
+ "memcached"
37
45
  ],
38
46
  "author": "animir <animirr@gmail.com>",
39
47
  "license": "ISC",
@@ -56,6 +64,7 @@
56
64
  "ioredis": "^5.3.2",
57
65
  "iovalkey": "^0.3.1",
58
66
  "istanbul": "^1.1.0-alpha.1",
67
+ "knex": "^3.1.0",
59
68
  "memcached-mock": "^0.1.0",
60
69
  "mocha": "^10.2.0",
61
70
  "nyc": "^15.1.0",
@@ -63,7 +72,8 @@
63
72
  "redis": "^4.6.8",
64
73
  "redis-mock": "^0.48.0",
65
74
  "sinon": "^17.0.1",
66
- "sqlite3": "^5.1.7"
75
+ "sqlite3": "^5.1.7",
76
+ "@valkey/valkey-glide": "^1.3.1"
67
77
  },
68
78
  "browser": {
69
79
  "cluster": false,
package/.editorconfig DELETED
@@ -1,13 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_size = 2
5
- indent_style = space
6
- end_of_line = lf
7
- charset = utf-8
8
- trim_trailing_whitespace = true
9
- insert_final_newline = true
10
-
11
- [*.md]
12
- insert_final_newline = false
13
- trim_trailing_whitespace = false