rate-limiter-flexible 2.1.5 → 2.1.10

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
@@ -161,6 +161,7 @@ See [releases](https://github.com/animir/node-rate-limiter-flexible/releases) fo
161
161
  * [storeType](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#storetype) Have to be set to `knex`, if you use it.
162
162
  * [dbName](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#dbname) Where to store points.
163
163
  * [tableName](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#tablename) Table/collection.
164
+ * [tableCreated](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#tablecreated) Is table already created in MySQL or PostgreSQL.
164
165
  * [clearExpiredByTimeout](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#clearexpiredbytimeout) For MySQL and PostgreSQL.
165
166
 
166
167
  Smooth out traffic picks:
@@ -215,10 +216,11 @@ Make sure you've launched `npm run eslint` before creating PR, all errors have t
215
216
  You can try to run `npm run eslint-fix` to fix some issues.
216
217
 
217
218
  Any new limiter with storage have to be extended from `RateLimiterStoreAbstract`.
218
- It has to implement at least 4 methods:
219
+ It has to implement 4 methods:
219
220
  * `_getRateLimiterRes` parses raw data from store to `RateLimiterRes` object.
220
- * `_upsert` inserts or updates limits data by key and returns raw data.
221
- * `_get` returns raw data by key.
221
+ * `_upsert` must be atomic. it inserts or updates value by key and returns raw data. it must support `forceExpire` mode
222
+ to overwrite key expiration time.
223
+ * `_get` returns raw data by key or `null` if there is no key.
222
224
  * `_delete` deletes all key related data and returns `true` on deleted, `false` if key is not found.
223
225
 
224
226
  All other methods depends on store. See `RateLimiterRedis` or `RateLimiterPostgres` for example.
@@ -1,22 +1,47 @@
1
1
  const RateLimiterRes = require("./RateLimiterRes");
2
2
 
3
+ /**
4
+ * Bursty rate limiter exposes only msBeforeNext time and doesn't expose points from bursty limiter by default
5
+ * @type {BurstyRateLimiter}
6
+ */
3
7
  module.exports = class BurstyRateLimiter {
4
8
  constructor(rateLimiter, burstLimiter) {
5
9
  this._rateLimiter = rateLimiter;
6
10
  this._burstLimiter = burstLimiter
7
11
  }
8
12
 
13
+ /**
14
+ * Merge rate limiter response objects. Responses can be null
15
+ *
16
+ * @param {RateLimiterRes} [rlRes] Rate limiter response
17
+ * @param {RateLimiterRes} [blRes] Bursty limiter response
18
+ */
19
+ _combineRes(rlRes, blRes) {
20
+ return new RateLimiterRes(
21
+ rlRes.remainingPoints,
22
+ Math.min(rlRes.msBeforeNext, blRes.msBeforeNext),
23
+ rlRes.consumedPoints,
24
+ rlRes.isFirstInDuration
25
+ )
26
+ }
27
+
28
+ /**
29
+ * @param key
30
+ * @param pointsToConsume
31
+ * @param options
32
+ * @returns {Promise<any>}
33
+ */
9
34
  consume(key, pointsToConsume = 1, options = {}) {
10
35
  return this._rateLimiter.consume(key, pointsToConsume, options)
11
36
  .catch((rlRej) => {
12
37
  if (rlRej instanceof RateLimiterRes) {
13
38
  return this._burstLimiter.consume(key, pointsToConsume, options)
14
- .then(() => {
15
- return Promise.resolve(rlRej)
39
+ .then((blRes) => {
40
+ return Promise.resolve(this._combineRes(rlRej, blRes))
16
41
  })
17
42
  .catch((blRej) => {
18
43
  if (blRej instanceof RateLimiterRes) {
19
- return Promise.reject(rlRej)
44
+ return Promise.reject(this._combineRes(rlRej, blRej))
20
45
  } else {
21
46
  return Promise.reject(blRej)
22
47
  }
@@ -27,4 +52,23 @@ module.exports = class BurstyRateLimiter {
27
52
  }
28
53
  })
29
54
  }
55
+
56
+ /**
57
+ * It doesn't expose available points from burstLimiter
58
+ *
59
+ * @param key
60
+ * @returns {Promise<RateLimiterRes>}
61
+ */
62
+ get(key) {
63
+ return Promise.all([
64
+ this._rateLimiter.get(key),
65
+ this._burstLimiter.get(key),
66
+ ]).then(([rlRes, blRes]) => {
67
+ return this._combineRes(rlRes, blRes);
68
+ });
69
+ }
70
+
71
+ get points() {
72
+ return this._rateLimiter.points;
73
+ }
30
74
  };
@@ -28,24 +28,33 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
28
28
 
29
29
  this.clearExpiredByTimeout = opts.clearExpiredByTimeout;
30
30
 
31
- this._tableCreated = false;
32
- this._createDbAndTable()
33
- .then(() => {
34
- this._tableCreated = true;
35
- if (this.clearExpiredByTimeout) {
36
- this._clearExpiredHourAgo();
37
- }
38
- if (typeof cb === 'function') {
39
- cb();
40
- }
41
- })
42
- .catch((err) => {
43
- if (typeof cb === 'function') {
44
- cb(err);
45
- } else {
46
- throw err;
47
- }
48
- });
31
+ this.tableCreated = opts.tableCreated;
32
+ if (!this.tableCreated) {
33
+ this._createDbAndTable()
34
+ .then(() => {
35
+ this.tableCreated = true;
36
+ if (this.clearExpiredByTimeout) {
37
+ this._clearExpiredHourAgo();
38
+ }
39
+ if (typeof cb === 'function') {
40
+ cb();
41
+ }
42
+ })
43
+ .catch((err) => {
44
+ if (typeof cb === 'function') {
45
+ cb(err);
46
+ } else {
47
+ throw err;
48
+ }
49
+ });
50
+ } else {
51
+ if (this.clearExpiredByTimeout) {
52
+ this._clearExpiredHourAgo();
53
+ }
54
+ if (typeof cb === 'function') {
55
+ cb();
56
+ }
57
+ }
49
58
  }
50
59
 
51
60
  clearExpired(expire) {
@@ -189,6 +198,14 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
189
198
  this._tableName = typeof value === 'undefined' ? this.keyPrefix : value;
190
199
  }
191
200
 
201
+ get tableCreated() {
202
+ return this._tableCreated
203
+ }
204
+
205
+ set tableCreated(value) {
206
+ this._tableCreated = typeof value === 'undefined' ? false : !!value;
207
+ }
208
+
192
209
  get clearExpiredByTimeout() {
193
210
  return this._clearExpiredByTimeout;
194
211
  }
@@ -277,7 +294,7 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
277
294
  }
278
295
 
279
296
  _upsert(key, points, msDuration, forceExpire = false) {
280
- if (!this._tableCreated) {
297
+ if (!this.tableCreated) {
281
298
  return Promise.reject(Error('Table is not created yet'));
282
299
  }
283
300
 
@@ -301,7 +318,7 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
301
318
  }
302
319
 
303
320
  _get(rlKey) {
304
- if (!this._tableCreated) {
321
+ if (!this.tableCreated) {
305
322
  return Promise.reject(Error('Table is not created yet'));
306
323
  }
307
324
 
@@ -331,7 +348,7 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
331
348
  }
332
349
 
333
350
  _delete(rlKey) {
334
- if (!this._tableCreated) {
351
+ if (!this.tableCreated) {
335
352
  return Promise.reject(Error('Table is not created yet'));
336
353
  }
337
354
 
@@ -26,24 +26,30 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
26
26
 
27
27
  this.clearExpiredByTimeout = opts.clearExpiredByTimeout;
28
28
 
29
- this._tableCreated = false;
30
- this._createTable()
31
- .then(() => {
32
- this._tableCreated = true;
33
- if (this.clearExpiredByTimeout) {
34
- this._clearExpiredHourAgo();
35
- }
36
- if (typeof cb === 'function') {
37
- cb();
38
- }
39
- })
40
- .catch((err) => {
41
- if (typeof cb === 'function') {
42
- cb(err);
43
- } else {
44
- throw err;
45
- }
46
- });
29
+ this.tableCreated = opts.tableCreated;
30
+ if (!this.tableCreated) {
31
+ this._createTable()
32
+ .then(() => {
33
+ this.tableCreated = true;
34
+ if (this.clearExpiredByTimeout) {
35
+ this._clearExpiredHourAgo();
36
+ }
37
+ if (typeof cb === 'function') {
38
+ cb();
39
+ }
40
+ })
41
+ .catch((err) => {
42
+ if (typeof cb === 'function') {
43
+ cb(err);
44
+ } else {
45
+ throw err;
46
+ }
47
+ });
48
+ } else {
49
+ if (typeof cb === 'function') {
50
+ cb();
51
+ }
52
+ }
47
53
  }
48
54
 
49
55
  clearExpired(expire) {
@@ -152,17 +158,23 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
152
158
  }
153
159
 
154
160
  set clientType(value) {
161
+ const constructorName = this.client.constructor.name;
162
+
155
163
  if (typeof value === 'undefined') {
156
- if (this.client.constructor.name === 'Client') {
164
+ if (constructorName === 'Client') {
157
165
  value = 'client';
158
- } else if (this.client.constructor.name === 'Pool') {
166
+ } else if (
167
+ constructorName === 'Pool' ||
168
+ constructorName === 'BoundPool'
169
+ ) {
159
170
  value = 'pool';
160
- } else if (this.client.constructor.name === 'Sequelize') {
171
+ } else if (constructorName === 'Sequelize') {
161
172
  value = 'sequelize';
162
173
  } else {
163
174
  throw new Error('storeType is not defined');
164
175
  }
165
176
  }
177
+
166
178
  this._clientType = value.toLowerCase();
167
179
  }
168
180
 
@@ -174,6 +186,14 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
174
186
  this._tableName = typeof value === 'undefined' ? this.keyPrefix : value;
175
187
  }
176
188
 
189
+ get tableCreated() {
190
+ return this._tableCreated
191
+ }
192
+
193
+ set tableCreated(value) {
194
+ this._tableCreated = typeof value === 'undefined' ? false : !!value;
195
+ }
196
+
177
197
  get clearExpiredByTimeout() {
178
198
  return this._clearExpiredByTimeout;
179
199
  }
@@ -220,7 +240,7 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
220
240
  }
221
241
 
222
242
  _upsert(key, points, msDuration, forceExpire = false) {
223
- if (!this._tableCreated) {
243
+ if (!this.tableCreated) {
224
244
  return Promise.reject(Error('Table is not created yet'));
225
245
  }
226
246
 
@@ -248,7 +268,7 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
248
268
  }
249
269
 
250
270
  _get(rlKey) {
251
- if (!this._tableCreated) {
271
+ if (!this.tableCreated) {
252
272
  return Promise.reject(Error('Table is not created yet'));
253
273
  }
254
274
 
@@ -272,7 +292,7 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
272
292
  }
273
293
 
274
294
  _delete(rlKey) {
275
- if (!this._tableCreated) {
295
+ if (!this.tableCreated) {
276
296
  return Promise.reject(Error('Table is not created yet'));
277
297
  }
278
298
 
@@ -42,7 +42,9 @@ module.exports = class MemoryStorage {
42
42
  this._storage[key].timeoutId = setTimeout(() => {
43
43
  delete this._storage[key];
44
44
  }, durationMs);
45
- this._storage[key].timeoutId.unref();
45
+ if (this._storage[key].timeoutId.unref) {
46
+ this._storage[key].timeoutId.unref();
47
+ }
46
48
  }
47
49
 
48
50
  return new RateLimiterRes(0, durationMs === 0 ? -1 : durationMs, this._storage[key].value, true);
package/lib/index.d.ts CHANGED
@@ -67,6 +67,7 @@ interface IRateLimiterStoreOptions extends IRateLimiterOptions{
67
67
  insuranceLimiter?: RateLimiterAbstract;
68
68
  dbName?: string;
69
69
  tableName?: string;
70
+ tableCreated?: boolean;
70
71
  }
71
72
 
72
73
  interface IRateLimiterMongoOptions extends IRateLimiterStoreOptions{
@@ -157,7 +158,7 @@ interface IRateLimiterQueueOpts {
157
158
  }
158
159
 
159
160
  export class RateLimiterQueue {
160
- constructor(limiterFlexible: RateLimiterAbstract, opts?: IRateLimiterQueueOpts);
161
+ constructor(limiterFlexible: RateLimiterAbstract | BurstyRateLimiter, opts?: IRateLimiterQueueOpts);
161
162
 
162
163
  getTokensRemaining(key?: string | number): Promise<RateLimiterRes>;
163
164
 
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "rate-limiter-flexible",
3
- "version": "2.1.5",
3
+ "version": "2.1.10",
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
- "test": "./node_modules/istanbul/lib/cli.js cover ./node_modules/.bin/_mocha --recursive",
7
+ "test": "istanbul -v cover -- _mocha --recursive",
8
8
  "debug-test": "mocha --inspect-brk lib/**/**.test.js",
9
- "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls",
10
- "eslint": "node_modules/eslint/bin/eslint.js --quiet lib/**/**.js test/**/**.js",
11
- "eslint-fix": "node_modules/eslint/bin/eslint.js --fix lib/**/**.js test/**/**.js"
9
+ "coveralls": "cat ./coverage/lcov.info | coveralls",
10
+ "eslint": "eslint --quiet lib/**/**.js test/**/**.js",
11
+ "eslint-fix": "eslint --fix lib/**/**.js test/**/**.js"
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",