rate-limiter-flexible 7.3.2 → 8.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 +2 -2
- package/index.js +6 -0
- package/lib/RateLimiterDrizzleNonAtomic.js +175 -0
- package/lib/index.d.ts +19 -0
- package/package.json +1 -1
- package/lib/component/index.d.ts +0 -9
package/README.md
CHANGED
|
@@ -131,9 +131,9 @@ Some copy/paste examples on Wiki:
|
|
|
131
131
|
* [Options](https://github.com/animir/node-rate-limiter-flexible/wiki/Options)
|
|
132
132
|
* [API methods](https://github.com/animir/node-rate-limiter-flexible/wiki/API-methods)
|
|
133
133
|
|
|
134
|
-
* [Drizzle](https://github.com/animir/node-rate-limiter-flexible/wiki/Drizzle)
|
|
134
|
+
* [Drizzle](https://github.com/animir/node-rate-limiter-flexible/wiki/Drizzle) Atomic and non-atomic counters.
|
|
135
135
|
* [DynamoDb](https://github.com/animir/node-rate-limiter-flexible/wiki/DynamoDB)
|
|
136
|
-
* [Etcd](https://github.com/animir/node-rate-limiter-flexible/wiki/Etcd)
|
|
136
|
+
* [Etcd](https://github.com/animir/node-rate-limiter-flexible/wiki/Etcd) Atomic and non-atomic counters.
|
|
137
137
|
* [Memcached](https://github.com/animir/node-rate-limiter-flexible/wiki/Memcache)
|
|
138
138
|
* [Memory](https://github.com/animir/node-rate-limiter-flexible/wiki/Memory)
|
|
139
139
|
* [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))
|
package/index.js
CHANGED
|
@@ -13,11 +13,14 @@ const RateLimiterRes = require('./lib/RateLimiterRes');
|
|
|
13
13
|
const RateLimiterDynamo = require('./lib/RateLimiterDynamo');
|
|
14
14
|
const RateLimiterPrisma = require('./lib/RateLimiterPrisma');
|
|
15
15
|
const RateLimiterDrizzle = require('./lib/RateLimiterDrizzle');
|
|
16
|
+
const RateLimiterDrizzleNonAtomic = require('./lib/RateLimiterDrizzleNonAtomic');
|
|
16
17
|
const RateLimiterValkey = require('./lib/RateLimiterValkey');
|
|
17
18
|
const RateLimiterValkeyGlide = require('./lib/RateLimiterValkeyGlide');
|
|
18
19
|
const RateLimiterSQLite = require('./lib/RateLimiterSQLite');
|
|
19
20
|
const RateLimiterEtcd = require('./lib/RateLimiterEtcd');
|
|
20
21
|
const RateLimiterEtcdNonAtomic = require('./lib/RateLimiterEtcdNonAtomic');
|
|
22
|
+
const RateLimiterQueueError = require('./lib/component/RateLimiterQueueError');
|
|
23
|
+
const RateLimiterEtcdTransactionFailedError = require('./lib/component/RateLimiterEtcdTransactionFailedError');
|
|
21
24
|
|
|
22
25
|
module.exports = {
|
|
23
26
|
RateLimiterRedis,
|
|
@@ -41,5 +44,8 @@ module.exports = {
|
|
|
41
44
|
RateLimiterSQLite,
|
|
42
45
|
RateLimiterEtcd,
|
|
43
46
|
RateLimiterDrizzle,
|
|
47
|
+
RateLimiterDrizzleNonAtomic,
|
|
44
48
|
RateLimiterEtcdNonAtomic,
|
|
49
|
+
RateLimiterQueueError,
|
|
50
|
+
RateLimiterEtcdTransactionFailedError,
|
|
45
51
|
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
let drizzleOperators = null;
|
|
2
|
+
const CLEANUP_INTERVAL_MS = 300000; // 5 minutes
|
|
3
|
+
const EXPIRED_THRESHOLD_MS = 3600000; // 1 hour
|
|
4
|
+
|
|
5
|
+
class RateLimiterDrizzleError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'RateLimiterDrizzleError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getDrizzleOperators() {
|
|
13
|
+
if (drizzleOperators) return drizzleOperators;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Use dynamic import to prevent static analysis tools from detecting the import
|
|
17
|
+
function getPackageName() {
|
|
18
|
+
return ['drizzle', 'orm'].join('-');
|
|
19
|
+
}
|
|
20
|
+
const drizzleOrm = await import(`${getPackageName()}`);
|
|
21
|
+
const { and, or, gt, lt, eq, isNull, sql } = drizzleOrm.default || drizzleOrm;
|
|
22
|
+
drizzleOperators = { and, or, gt, lt, eq, isNull, sql };
|
|
23
|
+
return drizzleOperators;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new RateLimiterDrizzleError(
|
|
26
|
+
'drizzle-orm is not installed. Please install drizzle-orm to use RateLimiterDrizzleNonAtomic.'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
|
|
32
|
+
const RateLimiterRes = require('./RateLimiterRes');
|
|
33
|
+
|
|
34
|
+
class RateLimiterDrizzleNonAtomic extends RateLimiterStoreAbstract {
|
|
35
|
+
constructor(opts) {
|
|
36
|
+
super(opts);
|
|
37
|
+
|
|
38
|
+
if (!opts?.schema) {
|
|
39
|
+
throw new RateLimiterDrizzleError('Drizzle schema is required');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!opts?.storeClient) {
|
|
43
|
+
throw new RateLimiterDrizzleError('Drizzle client is required');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.schema = opts.schema;
|
|
47
|
+
this.drizzleClient = opts.storeClient;
|
|
48
|
+
this.clearExpiredByTimeout = opts.clearExpiredByTimeout ?? true;
|
|
49
|
+
|
|
50
|
+
if (this.clearExpiredByTimeout) {
|
|
51
|
+
this._clearExpiredHourAgo();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_getRateLimiterRes(rlKey, changedPoints, result) {
|
|
56
|
+
const res = new RateLimiterRes();
|
|
57
|
+
|
|
58
|
+
let doc = result;
|
|
59
|
+
res.isFirstInDuration = doc.points === changedPoints;
|
|
60
|
+
res.consumedPoints = doc.points;
|
|
61
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
|
|
62
|
+
res.msBeforeNext = doc.expire !== null
|
|
63
|
+
? Math.max(new Date(doc.expire).getTime() - Date.now(), 0)
|
|
64
|
+
: -1;
|
|
65
|
+
|
|
66
|
+
return res;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async _upsert(key, points, msDuration, forceExpire = false) {
|
|
70
|
+
if (!this.drizzleClient) {
|
|
71
|
+
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { eq } = await getDrizzleOperators();
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const newExpire = msDuration > 0 ? new Date(now.getTime() + msDuration) : null;
|
|
77
|
+
|
|
78
|
+
const [existingRecord] = await this.drizzleClient
|
|
79
|
+
.select()
|
|
80
|
+
.from(this.schema)
|
|
81
|
+
.where(eq(this.schema.key, key))
|
|
82
|
+
.limit(1);
|
|
83
|
+
|
|
84
|
+
const shouldUpdateExpire =
|
|
85
|
+
forceExpire ||
|
|
86
|
+
!existingRecord ||
|
|
87
|
+
!existingRecord.expire ||
|
|
88
|
+
existingRecord.expire <= now ||
|
|
89
|
+
newExpire === null;
|
|
90
|
+
|
|
91
|
+
let newPoints;
|
|
92
|
+
if (existingRecord && !shouldUpdateExpire) {
|
|
93
|
+
newPoints = existingRecord.points + points;
|
|
94
|
+
} else {
|
|
95
|
+
newPoints = points;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const [data] = await this.drizzleClient
|
|
99
|
+
.insert(this.schema)
|
|
100
|
+
.values({
|
|
101
|
+
key,
|
|
102
|
+
points: newPoints,
|
|
103
|
+
expire: newExpire,
|
|
104
|
+
})
|
|
105
|
+
.onConflictDoUpdate({
|
|
106
|
+
target: this.schema.key,
|
|
107
|
+
set: {
|
|
108
|
+
points: newPoints,
|
|
109
|
+
...(shouldUpdateExpire && { expire: newExpire }),
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
.returning();
|
|
113
|
+
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async _get(rlKey) {
|
|
118
|
+
if (!this.drizzleClient) {
|
|
119
|
+
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { and, or, gt, eq, isNull } = await getDrizzleOperators();
|
|
123
|
+
|
|
124
|
+
const [response] = await this.drizzleClient
|
|
125
|
+
.select()
|
|
126
|
+
.from(this.schema)
|
|
127
|
+
.where(
|
|
128
|
+
and(
|
|
129
|
+
eq(this.schema.key, rlKey),
|
|
130
|
+
or(gt(this.schema.expire, new Date()), isNull(this.schema.expire))
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
.limit(1);
|
|
134
|
+
|
|
135
|
+
return response || null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async _delete(rlKey) {
|
|
139
|
+
if (!this.drizzleClient) {
|
|
140
|
+
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const { eq } = await getDrizzleOperators();
|
|
144
|
+
|
|
145
|
+
const [result] = await this.drizzleClient
|
|
146
|
+
.delete(this.schema)
|
|
147
|
+
.where(eq(this.schema.key, rlKey))
|
|
148
|
+
.returning({ key: this.schema.key });
|
|
149
|
+
|
|
150
|
+
return !!(result && result.key);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_clearExpiredHourAgo() {
|
|
154
|
+
if (this._clearExpiredTimeoutId) {
|
|
155
|
+
clearTimeout(this._clearExpiredTimeoutId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this._clearExpiredTimeoutId = setTimeout(async () => {
|
|
159
|
+
try {
|
|
160
|
+
const { lt } = await getDrizzleOperators();
|
|
161
|
+
await this.drizzleClient
|
|
162
|
+
.delete(this.schema)
|
|
163
|
+
.where(lt(this.schema.expire, new Date(Date.now() - EXPIRED_THRESHOLD_MS)));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.warn('Failed to clear expired records:', error);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this._clearExpiredHourAgo();
|
|
169
|
+
}, CLEANUP_INTERVAL_MS);
|
|
170
|
+
|
|
171
|
+
this._clearExpiredTimeoutId.unref();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = RateLimiterDrizzleNonAtomic;
|
package/lib/index.d.ts
CHANGED
|
@@ -377,6 +377,10 @@ export class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
|
377
377
|
constructor(opts: IRateLimiterStoreNoAutoExpiryOptionsAndSchema, cb?: ICallbackReady);
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
export class RateLimiterDrizzleNonAtomic extends RateLimiterStoreAbstract {
|
|
381
|
+
constructor(opts: IRateLimiterStoreNoAutoExpiryOptionsAndSchema, cb?: ICallbackReady);
|
|
382
|
+
}
|
|
383
|
+
|
|
380
384
|
export class RateLimiterMemcache extends RateLimiterStoreAbstract { }
|
|
381
385
|
|
|
382
386
|
export class RateLimiterUnion {
|
|
@@ -542,3 +546,18 @@ export class RateLimiterEtcd extends RateLimiterEtcdNonAtomic {
|
|
|
542
546
|
export class RateLimiterEtcdNonAtomic extends RateLimiterStoreAbstract {
|
|
543
547
|
constructor(opts: IRateLimiterStoreOptions);
|
|
544
548
|
}
|
|
549
|
+
|
|
550
|
+
export class RateLimiterQueueError extends Error {
|
|
551
|
+
constructor(message?: string, extra?: string);
|
|
552
|
+
|
|
553
|
+
readonly name: string;
|
|
554
|
+
readonly message: string;
|
|
555
|
+
readonly extra: string;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export class RateLimiterEtcdTransactionFailedError extends Error {
|
|
559
|
+
constructor(message?: string);
|
|
560
|
+
|
|
561
|
+
readonly name: string;
|
|
562
|
+
readonly message: string;
|
|
563
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rate-limiter-flexible",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.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": {
|