@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,338 @@
1
+ const RateLimiterStoreAbstract = require("./RateLimiterStoreAbstract");
2
+ const RateLimiterRes = require("./RateLimiterRes");
3
+
4
+ class RateLimiterSQLite extends RateLimiterStoreAbstract {
5
+ /**
6
+ * Internal store type used to determine the SQLite client in use.
7
+ * It can be one of the following:
8
+ * - `"sqlite3".
9
+ * - `"better-sqlite3".
10
+ *
11
+ * @type {("sqlite3" | "better-sqlite3" | null)}
12
+ * @private
13
+ */
14
+ _internalStoreType = null;
15
+
16
+ /**
17
+ * @callback callback
18
+ * @param {Object} err
19
+ *
20
+ * @param {Object} opts
21
+ * @param {callback} cb
22
+ * Defaults {
23
+ * ... see other in RateLimiterStoreAbstract
24
+ * storeClient: sqliteClient, // SQLite database instance (sqlite3, better-sqlite3, or knex instance)
25
+ * storeType: 'sqlite3' | 'better-sqlite3' | 'knex', // Optional, defaults to 'sqlite3'
26
+ * tableName: 'string',
27
+ * tableCreated: boolean,
28
+ * clearExpiredByTimeout: boolean,
29
+ * }
30
+ */
31
+ constructor(opts, cb = null) {
32
+ super(opts);
33
+
34
+ this.client = opts.storeClient;
35
+ this.storeType = opts.storeType || "sqlite3";
36
+ this.tableName = opts.tableName;
37
+ this.tableCreated = opts.tableCreated || false;
38
+ this.clearExpiredByTimeout = opts.clearExpiredByTimeout;
39
+
40
+ this._validateStoreTypes(cb);
41
+ this._validateStoreClient(cb);
42
+ this._setInternalStoreType(cb);
43
+ this._validateTableName(cb);
44
+
45
+ if (!this.tableCreated) {
46
+ this._createDbAndTable()
47
+ .then(() => {
48
+ this.tableCreated = true;
49
+ if (this.clearExpiredByTimeout) this._clearExpiredHourAgo();
50
+ if (typeof cb === "function") cb();
51
+ })
52
+ .catch((err) => {
53
+ if (typeof cb === "function") cb(err);
54
+ else throw err;
55
+ });
56
+ } else {
57
+ if (this.clearExpiredByTimeout) this._clearExpiredHourAgo();
58
+ if (typeof cb === "function") cb();
59
+ }
60
+ }
61
+ _validateStoreTypes(cb) {
62
+ const validStoreTypes = ["sqlite3", "better-sqlite3", "knex"];
63
+ if (!validStoreTypes.includes(this.storeType)) {
64
+ const err = new Error(
65
+ `storeType must be one of: ${validStoreTypes.join(", ")}`
66
+ );
67
+ if (typeof cb === "function") return cb(err);
68
+ throw err;
69
+ }
70
+ }
71
+ _validateStoreClient(cb) {
72
+ if (this.storeType === "sqlite3") {
73
+ if (typeof this.client.run !== "function") {
74
+ const err = new Error(
75
+ "storeClient must be an instance of sqlite3.Database when storeType is 'sqlite3' or no storeType was provided"
76
+ );
77
+ if (typeof cb === "function") return cb(err);
78
+ throw err;
79
+ }
80
+ } else if (this.storeType === "better-sqlite3") {
81
+ if (
82
+ typeof this.client.prepare !== "function" ||
83
+ typeof this.client.run !== "undefined"
84
+ ) {
85
+ const err = new Error(
86
+ "storeClient must be an instance of better-sqlite3.Database when storeType is 'better-sqlite3'"
87
+ );
88
+ if (typeof cb === "function") return cb(err);
89
+ throw err;
90
+ }
91
+ } else if (this.storeType === "knex") {
92
+ if (typeof this.client.raw !== "function") {
93
+ const err = new Error(
94
+ "storeClient must be an instance of Knex when storeType is 'knex'"
95
+ );
96
+ if (typeof cb === "function") return cb(err);
97
+ throw err;
98
+ }
99
+ }
100
+ }
101
+ _setInternalStoreType(cb) {
102
+ if (this.storeType === "knex") {
103
+ const knexClientType = this.client.client.config.client;
104
+ if (knexClientType === "sqlite3") {
105
+ this._internalStoreType = "sqlite3";
106
+ } else if (knexClientType === "better-sqlite3") {
107
+ this._internalStoreType = "better-sqlite3";
108
+ } else {
109
+ const err = new Error(
110
+ "Knex must be configured with 'sqlite3' or 'better-sqlite3' for RateLimiterSQLite"
111
+ );
112
+ if (typeof cb === "function") return cb(err);
113
+ throw err;
114
+ }
115
+ } else {
116
+ this._internalStoreType = this.storeType;
117
+ }
118
+ }
119
+ _validateTableName(cb) {
120
+ if (!/^[A-Za-z0-9_]*$/.test(this.tableName)) {
121
+ const err = new Error("Table name must contain only letters and numbers");
122
+ if (typeof cb === "function") return cb(err);
123
+ throw err;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Acquires the database connection based on the storeType.
129
+ * @returns {Promise<Object>} The database client or connection
130
+ */
131
+ async _getConnection() {
132
+ if (this.storeType === "knex") {
133
+ return this.client.client.acquireConnection(); // Acquire raw connection from knex pool
134
+ }
135
+ return this.client; // For sqlite3 and better-sqlite3, return the client directly
136
+ }
137
+
138
+ /**
139
+ * Releases the database connection if necessary.
140
+ * @param {Object} conn The database client or connection
141
+ */
142
+ _releaseConnection(conn) {
143
+ if (this.storeType === "knex") {
144
+ this.client.client.releaseConnection(conn);
145
+ }
146
+ // No release needed for direct sqlite3 or better-sqlite3 clients
147
+ }
148
+
149
+ async _createDbAndTable() {
150
+ const conn = await this._getConnection();
151
+ try {
152
+ switch (this._internalStoreType) {
153
+ case "sqlite3":
154
+ await new Promise((resolve, reject) => {
155
+ conn.run(this._getCreateTableSQL(), (err) =>
156
+ err ? reject(err) : resolve()
157
+ );
158
+ });
159
+ break;
160
+ case "better-sqlite3":
161
+ conn.prepare(this._getCreateTableSQL()).run();
162
+ break;
163
+ default:
164
+ throw new Error("Unsupported internalStoreType");
165
+ }
166
+ } finally {
167
+ this._releaseConnection(conn);
168
+ }
169
+ }
170
+
171
+ _getCreateTableSQL() {
172
+ return `CREATE TABLE IF NOT EXISTS ${this.tableName} (
173
+ key TEXT PRIMARY KEY,
174
+ points INTEGER NOT NULL DEFAULT 0,
175
+ expire INTEGER
176
+ )`;
177
+ }
178
+
179
+ _clearExpiredHourAgo() {
180
+ if (this._clearExpiredTimeoutId) clearTimeout(this._clearExpiredTimeoutId);
181
+ this._clearExpiredTimeoutId = setTimeout(() => {
182
+ this.clearExpired(Date.now() - 3600000) // 1 hour ago
183
+ .then(() => this._clearExpiredHourAgo());
184
+ }, 300000); // Every 5 minutes
185
+ this._clearExpiredTimeoutId.unref();
186
+ }
187
+
188
+ async clearExpired(nowMs) {
189
+ const sql = `DELETE FROM ${this.tableName} WHERE expire < ?`;
190
+ const conn = await this._getConnection();
191
+ try {
192
+ switch (this._internalStoreType) {
193
+ case "sqlite3":
194
+ await new Promise((resolve, reject) => {
195
+ conn.run(sql, [nowMs], (err) => (err ? reject(err) : resolve()));
196
+ });
197
+ break;
198
+ case "better-sqlite3":
199
+ conn.prepare(sql).run(nowMs);
200
+ break;
201
+ default:
202
+ throw new Error("Unsupported internalStoreType");
203
+ }
204
+ } finally {
205
+ this._releaseConnection(conn);
206
+ }
207
+ }
208
+
209
+ _getRateLimiterRes(rlKey, changedPoints, result) {
210
+ const res = new RateLimiterRes();
211
+ res.isFirstInDuration = changedPoints === result.points;
212
+ res.consumedPoints = res.isFirstInDuration ? changedPoints : result.points;
213
+ res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
214
+ res.msBeforeNext = result.expire
215
+ ? Math.max(result.expire - Date.now(), 0)
216
+ : -1;
217
+ return res;
218
+ }
219
+
220
+ async _upsertTransactionSQLite3(conn, upsertQuery, upsertParams) {
221
+ return await new Promise((resolve, reject) => {
222
+ conn.serialize(() => {
223
+ conn.run("SAVEPOINT rate_limiter_trx;", (err) => {
224
+ if (err) return reject(err);
225
+ conn.get(upsertQuery, upsertParams, (err, row) => {
226
+ if (err) {
227
+ conn.run("ROLLBACK TO SAVEPOINT rate_limiter_trx;", () =>
228
+ reject(err)
229
+ );
230
+ return;
231
+ }
232
+ conn.run("RELEASE SAVEPOINT rate_limiter_trx;", () => resolve(row));
233
+ });
234
+ });
235
+ });
236
+ });
237
+ }
238
+
239
+ async _upsertTransactionBetterSQLite3(conn, upsertQuery, upsertParams) {
240
+ return conn.transaction(() =>
241
+ conn.prepare(upsertQuery).get(...upsertParams)
242
+ )();
243
+ }
244
+ async _upsertTransaction(rlKey, points, msDuration, forceExpire) {
245
+ const dateNow = Date.now();
246
+ const newExpire = msDuration > 0 ? dateNow + msDuration : null;
247
+ const upsertQuery = forceExpire
248
+ ? `INSERT OR REPLACE INTO ${this.tableName} (key, points, expire) VALUES (?, ?, ?) RETURNING points, expire`
249
+ : `INSERT INTO ${this.tableName} (key, points, expire)
250
+ VALUES (?, ?, ?)
251
+ ON CONFLICT(key) DO UPDATE SET
252
+ points = CASE WHEN expire IS NULL OR expire > ? THEN points + excluded.points ELSE excluded.points END,
253
+ expire = CASE WHEN expire IS NULL OR expire > ? THEN expire ELSE excluded.expire END
254
+ RETURNING points, expire`;
255
+ const upsertParams = forceExpire
256
+ ? [rlKey, points, newExpire]
257
+ : [rlKey, points, newExpire, dateNow, dateNow];
258
+
259
+ const conn = await this._getConnection();
260
+ try {
261
+ switch (this._internalStoreType) {
262
+ case "sqlite3":
263
+ return this._upsertTransactionSQLite3(
264
+ conn,
265
+ upsertQuery,
266
+ upsertParams
267
+ );
268
+ case "better-sqlite3":
269
+ return this._upsertTransactionBetterSQLite3(
270
+ conn,
271
+ upsertQuery,
272
+ upsertParams
273
+ );
274
+ default:
275
+ throw new Error("Unsupported internalStoreType");
276
+ }
277
+ } finally {
278
+ this._releaseConnection(conn);
279
+ }
280
+ }
281
+
282
+ _upsert(rlKey, points, msDuration, forceExpire = false) {
283
+ if (!this.tableCreated) {
284
+ return Promise.reject(new Error("Table is not created yet"));
285
+ }
286
+ return this._upsertTransaction(rlKey, points, msDuration, forceExpire);
287
+ }
288
+
289
+ async _get(rlKey) {
290
+ const sql = `SELECT points, expire FROM ${this.tableName} WHERE key = ? AND (expire > ? OR expire IS NULL)`;
291
+ const now = Date.now();
292
+ const conn = await this._getConnection();
293
+ try {
294
+ switch (this._internalStoreType) {
295
+ case "sqlite3":
296
+ return await new Promise((resolve, reject) => {
297
+ conn.get(sql, [rlKey, now], (err, row) =>
298
+ err ? reject(err) : resolve(row || null)
299
+ );
300
+ });
301
+ case "better-sqlite3":
302
+ return conn.prepare(sql).get(rlKey, now) || null;
303
+ default:
304
+ throw new Error("Unsupported internalStoreType");
305
+ }
306
+ } finally {
307
+ this._releaseConnection(conn);
308
+ }
309
+ }
310
+
311
+ async _delete(rlKey) {
312
+ if (!this.tableCreated) {
313
+ return Promise.reject(new Error("Table is not created yet"));
314
+ }
315
+ const sql = `DELETE FROM ${this.tableName} WHERE key = ?`;
316
+ const conn = await this._getConnection();
317
+ try {
318
+ switch (this._internalStoreType) {
319
+ case "sqlite3":
320
+ return await new Promise((resolve, reject) => {
321
+ conn.run(sql, [rlKey], function (err) {
322
+ if (err) reject(err);
323
+ else resolve(this.changes > 0);
324
+ });
325
+ });
326
+ case "better-sqlite3":
327
+ const result = conn.prepare(sql).run(rlKey);
328
+ return result.changes > 0;
329
+ default:
330
+ throw new Error("Unsupported internalStoreType");
331
+ }
332
+ } finally {
333
+ this._releaseConnection(conn);
334
+ }
335
+ }
336
+ }
337
+
338
+ module.exports = RateLimiterSQLite;
@@ -0,0 +1,349 @@
1
+ const RateLimiterAbstract = require('./RateLimiterAbstract');
2
+ const BlockedKeys = require('./component/BlockedKeys');
3
+ const RateLimiterRes = require('./RateLimiterRes');
4
+ const RateLimiterInsuredAbstract = require('./RateLimiterInsuredAbstract');
5
+
6
+ module.exports = class RateLimiterStoreAbstract extends RateLimiterInsuredAbstract {
7
+ /**
8
+ *
9
+ * @param opts Object Defaults {
10
+ * ... see other in RateLimiterAbstract
11
+ *
12
+ * inMemoryBlockOnConsumed: 40, // Number of points when key is blocked
13
+ * inMemoryBlockDuration: 10, // Block duration in seconds
14
+ * insuranceLimiter: RateLimiterAbstract
15
+ * }
16
+ */
17
+ constructor(opts = {}) {
18
+ super(opts);
19
+
20
+ this.inMemoryBlockOnConsumed = opts.inMemoryBlockOnConsumed;
21
+ this.inMemoryBlockDuration = opts.inMemoryBlockDuration;
22
+ this._inMemoryBlockedKeys = new BlockedKeys();
23
+ }
24
+
25
+ get client() {
26
+ return this._client;
27
+ }
28
+
29
+ set client(value) {
30
+ if (typeof value === 'undefined') {
31
+ throw new Error('storeClient is not set');
32
+ }
33
+ this._client = value;
34
+ }
35
+
36
+ /**
37
+ * Have to be launched after consume
38
+ * It blocks key and execute evenly depending on result from store
39
+ *
40
+ * It uses _getRateLimiterRes function to prepare RateLimiterRes from store result
41
+ *
42
+ * @param resolve
43
+ * @param reject
44
+ * @param rlKey
45
+ * @param changedPoints
46
+ * @param storeResult
47
+ * @param {Object} options
48
+ * @private
49
+ */
50
+ _afterConsume(resolve, reject, rlKey, changedPoints, storeResult, options = {}) {
51
+ const res = this._getRateLimiterRes(rlKey, changedPoints, storeResult);
52
+
53
+ if (this.inMemoryBlockOnConsumed > 0 && !(this.inMemoryBlockDuration > 0)
54
+ && res.consumedPoints >= this.inMemoryBlockOnConsumed
55
+ ) {
56
+ this._inMemoryBlockedKeys.addMs(rlKey, res.msBeforeNext);
57
+ if (res.consumedPoints > this.points) {
58
+ return reject(res);
59
+ } else {
60
+ return resolve(res)
61
+ }
62
+ } else if (res.consumedPoints > this.points) {
63
+ let blockPromise = Promise.resolve();
64
+ // Block only first time when consumed more than points
65
+ if (this.blockDuration > 0 && res.consumedPoints <= (this.points + changedPoints)) {
66
+ res.msBeforeNext = this.msBlockDuration;
67
+ blockPromise = this._block(rlKey, res.consumedPoints, this.msBlockDuration, options);
68
+ }
69
+
70
+ if (this.inMemoryBlockOnConsumed > 0 && res.consumedPoints >= this.inMemoryBlockOnConsumed) {
71
+ // Block key for this.inMemoryBlockDuration seconds
72
+ this._inMemoryBlockedKeys.add(rlKey, this.inMemoryBlockDuration);
73
+ res.msBeforeNext = this.msInMemoryBlockDuration;
74
+ }
75
+
76
+ blockPromise
77
+ .then(() => {
78
+ reject(res);
79
+ })
80
+ .catch((err) => {
81
+ reject(err);
82
+ });
83
+ } else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
84
+ let delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
85
+ if (delay < this.execEvenlyMinDelayMs) {
86
+ delay = res.consumedPoints * this.execEvenlyMinDelayMs;
87
+ }
88
+
89
+ setTimeout(resolve, delay, res);
90
+ } else {
91
+ resolve(res);
92
+ }
93
+ }
94
+
95
+ getInMemoryBlockMsBeforeExpire(rlKey) {
96
+ if (this.inMemoryBlockOnConsumed > 0) {
97
+ return this._inMemoryBlockedKeys.msBeforeExpire(rlKey);
98
+ }
99
+
100
+ return 0;
101
+ }
102
+
103
+ get inMemoryBlockOnConsumed() {
104
+ return this._inMemoryBlockOnConsumed;
105
+ }
106
+
107
+ set inMemoryBlockOnConsumed(value) {
108
+ this._inMemoryBlockOnConsumed = value ? parseInt(value) : 0;
109
+ if (this.inMemoryBlockOnConsumed > 0 && this.points > this.inMemoryBlockOnConsumed) {
110
+ throw new Error('inMemoryBlockOnConsumed option must be greater or equal "points" option');
111
+ }
112
+ }
113
+
114
+ get inMemoryBlockDuration() {
115
+ return this._inMemoryBlockDuration;
116
+ }
117
+
118
+ set inMemoryBlockDuration(value) {
119
+ this._inMemoryBlockDuration = value ? parseInt(value) : 0;
120
+ if (this.inMemoryBlockDuration > 0 && this.inMemoryBlockOnConsumed === 0) {
121
+ throw new Error('inMemoryBlockOnConsumed option must be set up');
122
+ }
123
+ }
124
+
125
+ get msInMemoryBlockDuration() {
126
+ return this._inMemoryBlockDuration * 1000;
127
+ }
128
+
129
+ /**
130
+ * Block any key for secDuration seconds
131
+ *
132
+ * @param key
133
+ * @param secDuration
134
+ * @param {Object} options
135
+ *
136
+ * @return Promise<RateLimiterRes>
137
+ */
138
+ block(key, secDuration, options = {}) {
139
+ const msDuration = secDuration * 1000;
140
+ return this._block(this.getKey(key), this.points + 1, msDuration, options);
141
+ }
142
+
143
+ /**
144
+ * Set points by key for any duration
145
+ *
146
+ * @param key
147
+ * @param points
148
+ * @param secDuration
149
+ * @param {Object} options
150
+ *
151
+ * @return Promise<RateLimiterRes>
152
+ */
153
+ set(key, points, secDuration, options = {}) {
154
+ const msDuration = (secDuration >= 0 ? secDuration : this.duration) * 1000;
155
+ return this._block(this.getKey(key), points, msDuration, options);
156
+ }
157
+
158
+ /**
159
+ *
160
+ * @param key
161
+ * @param pointsToConsume
162
+ * @param {Object} options
163
+ * @returns Promise<RateLimiterRes>
164
+ */
165
+ _consume(key, pointsToConsume = 1, options = {}) {
166
+ return new Promise((resolve, reject) => {
167
+ const rlKey = this.getKey(key);
168
+
169
+ const inMemoryBlockMsBeforeExpire = this.getInMemoryBlockMsBeforeExpire(rlKey);
170
+ if (inMemoryBlockMsBeforeExpire > 0) {
171
+ return reject(new RateLimiterRes(0, inMemoryBlockMsBeforeExpire));
172
+ }
173
+
174
+ this._upsert(rlKey, pointsToConsume, this._getKeySecDuration(options) * 1000, false, options)
175
+ .then((res) => {
176
+ this._afterConsume(resolve, reject, rlKey, pointsToConsume, res);
177
+ })
178
+ .catch((err) => reject(err));
179
+ });
180
+ }
181
+
182
+ /**
183
+ *
184
+ * @param key
185
+ * @param points
186
+ * @param {Object} options
187
+ * @returns Promise<RateLimiterRes>
188
+ */
189
+ _penalty(key, points = 1, options = {}) {
190
+ const rlKey = this.getKey(key);
191
+ return new Promise((resolve, reject) => {
192
+ this._upsert(rlKey, points, this._getKeySecDuration(options) * 1000, false, options)
193
+ .then((res) => {
194
+ resolve(this._getRateLimiterRes(rlKey, points, res));
195
+ })
196
+ .catch((res) => reject(res));
197
+ });
198
+ }
199
+
200
+ /**
201
+ *
202
+ * @param key
203
+ * @param points
204
+ * @param {Object} options
205
+ * @returns Promise<RateLimiterRes>
206
+ */
207
+ _reward(key, points = 1, options = {}) {
208
+ const rlKey = this.getKey(key);
209
+ return new Promise((resolve, reject) => {
210
+ this._upsert(rlKey, -points, this._getKeySecDuration(options) * 1000, false, options)
211
+ .then((res) => {
212
+ resolve(this._getRateLimiterRes(rlKey, -points, res));
213
+ })
214
+ .catch((res) => reject(res));
215
+ });
216
+ }
217
+
218
+ /**
219
+ *
220
+ * @param key
221
+ * @param {Object} options
222
+ * @returns Promise<RateLimiterRes>|null
223
+ */
224
+ get(key, options = {}) {
225
+ const rlKey = this.getKey(key);
226
+ return new Promise((resolve, reject) => {
227
+ this._get(rlKey, options)
228
+ .then((res) => {
229
+ if (res === null || typeof res === 'undefined') {
230
+ resolve(null);
231
+ } else {
232
+ resolve(this._getRateLimiterRes(rlKey, 0, res));
233
+ }
234
+ })
235
+ .catch((err) => {
236
+ this._handleError(err, 'get', resolve, reject, [key, options]);
237
+ });
238
+ });
239
+ }
240
+
241
+ /**
242
+ *
243
+ * @param key
244
+ * @param {Object} options
245
+ * @returns Promise<boolean>
246
+ */
247
+ delete(key, options = {}) {
248
+ const rlKey = this.getKey(key);
249
+ return new Promise((resolve, reject) => {
250
+ this._delete(rlKey, options)
251
+ .then((res) => {
252
+ this._inMemoryBlockedKeys.delete(rlKey);
253
+ resolve(res);
254
+ })
255
+ .catch((err) => {
256
+ this._handleError(err, 'delete', resolve, reject, [key, options]);
257
+ });
258
+ });
259
+ }
260
+
261
+ /**
262
+ * Cleanup keys no-matter expired or not.
263
+ */
264
+ deleteInMemoryBlockedAll() {
265
+ this._inMemoryBlockedKeys.delete();
266
+ }
267
+
268
+ /**
269
+ * Get RateLimiterRes object filled depending on storeResult, which specific for exact store
270
+ *
271
+ * @param rlKey
272
+ * @param changedPoints
273
+ * @param storeResult
274
+ * @private
275
+ */
276
+ _getRateLimiterRes(rlKey, changedPoints, storeResult) { // eslint-disable-line no-unused-vars
277
+ throw new Error("You have to implement the method '_getRateLimiterRes'!");
278
+ }
279
+
280
+ /**
281
+ * Block key for this.msBlockDuration milliseconds
282
+ * Usually, it just prolongs lifetime of key
283
+ *
284
+ * @param rlKey
285
+ * @param initPoints
286
+ * @param msDuration
287
+ * @param {Object} options
288
+ *
289
+ * @return Promise<any>
290
+ */
291
+ _block(rlKey, initPoints, msDuration, options = {}) {
292
+ return new Promise((resolve, reject) => {
293
+ this._upsert(rlKey, initPoints, msDuration, true, options)
294
+ .then(() => {
295
+ resolve(new RateLimiterRes(0, msDuration > 0 ? msDuration : -1, initPoints));
296
+ })
297
+ .catch((err) => {
298
+ this._handleError(err, 'block', resolve, reject, [this.parseKey(rlKey), msDuration / 1000, options]);
299
+ });
300
+ });
301
+ }
302
+
303
+ /**
304
+ * Have to be implemented in every limiter
305
+ * Resolve with raw result from Store OR null if rlKey is not set
306
+ * or Reject with error
307
+ *
308
+ * @param rlKey
309
+ * @param {Object} options
310
+ * @private
311
+ *
312
+ * @return Promise<any>
313
+ */
314
+ _get(rlKey, options = {}) { // eslint-disable-line no-unused-vars
315
+ throw new Error("You have to implement the method '_get'!");
316
+ }
317
+
318
+ /**
319
+ * Have to be implemented
320
+ * Resolve with true OR false if rlKey doesn't exist
321
+ * or Reject with error
322
+ *
323
+ * @param rlKey
324
+ * @param {Object} options
325
+ * @private
326
+ *
327
+ * @return Promise<any>
328
+ */
329
+ _delete(rlKey, options = {}) { // eslint-disable-line no-unused-vars
330
+ throw new Error("You have to implement the method '_delete'!");
331
+ }
332
+
333
+ /**
334
+ * Have to be implemented
335
+ * Resolve with object used for {@link _getRateLimiterRes} to generate {@link RateLimiterRes}
336
+ *
337
+ * @param {string} rlKey
338
+ * @param {number} points
339
+ * @param {number} msDuration
340
+ * @param {boolean} forceExpire
341
+ * @param {Object} options
342
+ * @abstract
343
+ *
344
+ * @return Promise<Object>
345
+ */
346
+ _upsert(rlKey, points, msDuration, forceExpire = false, options = {}) {
347
+ throw new Error("You have to implement the method '_upsert'!");
348
+ }
349
+ };