rate-limiter-flexible 11.0.0 → 11.0.2
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 +1 -1
- package/lib/RateLimiterDrizzle.js +68 -15
- package/lib/RateLimiterMemory.js +2 -0
- package/lib/RateLimiterStoreAbstract.js +3 -0
- package/package.json +22 -21
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Memory limiter also works in the browser.
|
|
|
32
32
|
|
|
33
33
|
**Deno compatible** See [this example](https://gist.github.com/animir/d06ca92931677f330d3f2d4c6c3108e4)
|
|
34
34
|
|
|
35
|
-
The
|
|
35
|
+
The Flexible Fixed Window algorithm starts counting from the moment a request is received, diversifying rate limit reset times across clients. Read more [here](https://github.com/animir/node-rate-limiter-flexible/wiki/Fixed-Window-rate-limiting-algorithm-explained)
|
|
36
36
|
|
|
37
37
|
## Installation
|
|
38
38
|
|
|
@@ -9,23 +9,76 @@ class RateLimiterDrizzleError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async function
|
|
12
|
+
async function loadDrizzleOperators() {
|
|
13
13
|
if (drizzleOperators) return drizzleOperators;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const requiredOperators = ['and', 'or', 'gt', 'lt', 'eq', 'isNull', 'sql'];
|
|
16
|
+
|
|
17
|
+
// Use dynamic import to prevent static analysis tools from detecting the import
|
|
18
|
+
function getPackageName() {
|
|
19
|
+
return ['drizzle', 'orm'].join('-');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const collectModuleLayers = (mod) => {
|
|
23
|
+
const layers = [];
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
let current = mod;
|
|
26
|
+
|
|
27
|
+
while (current && (typeof current === 'object' || typeof current === 'function') && !seen.has(current)) {
|
|
28
|
+
layers.push(current);
|
|
29
|
+
seen.add(current);
|
|
30
|
+
current = current.default;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return layers;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const importModule = async (specifier, { optional = false } = {}) => {
|
|
37
|
+
try {
|
|
38
|
+
return await import(/* @vite-ignore */ specifier);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (optional) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
throw new RateLimiterDrizzleError(
|
|
44
|
+
error.message || 'Failed to import drizzle-orm package.'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const mainModule = await importModule(`${getPackageName()}`);
|
|
50
|
+
const sqlModule = await importModule(`${getPackageName()}/sql`, { optional: true });
|
|
51
|
+
|
|
52
|
+
const mainCandidates = collectModuleLayers(mainModule);
|
|
53
|
+
const sqlCandidates = sqlModule ? collectModuleLayers(sqlModule) : [];
|
|
54
|
+
const resolvedOperators = {};
|
|
55
|
+
const missingOperators = [];
|
|
56
|
+
|
|
57
|
+
for (const operatorName of requiredOperators) {
|
|
58
|
+
const candidates = operatorName === 'sql'
|
|
59
|
+
? [...sqlCandidates, ...mainCandidates]
|
|
60
|
+
: mainCandidates;
|
|
61
|
+
|
|
62
|
+
const operator = candidates
|
|
63
|
+
.map((candidate) => candidate?.[operatorName])
|
|
64
|
+
.find((value) => typeof value === 'function');
|
|
65
|
+
|
|
66
|
+
if (!operator) {
|
|
67
|
+
missingOperators.push(operatorName);
|
|
68
|
+
continue;
|
|
19
69
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
70
|
+
|
|
71
|
+
resolvedOperators[operatorName] = operator;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (missingOperators.length > 0) {
|
|
25
75
|
throw new RateLimiterDrizzleError(
|
|
26
|
-
|
|
76
|
+
`Failed to load drizzle-orm operators: missing ${missingOperators.join(', ')}`
|
|
27
77
|
);
|
|
28
78
|
}
|
|
79
|
+
|
|
80
|
+
drizzleOperators = resolvedOperators;
|
|
81
|
+
return drizzleOperators;
|
|
29
82
|
}
|
|
30
83
|
|
|
31
84
|
const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
|
|
@@ -71,7 +124,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
|
71
124
|
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'))
|
|
72
125
|
}
|
|
73
126
|
|
|
74
|
-
const { eq, sql } = await
|
|
127
|
+
const { eq, sql } = await loadDrizzleOperators();
|
|
75
128
|
const now = new Date();
|
|
76
129
|
const newExpire = msDuration > 0 ? new Date(now.getTime() + msDuration) : null;
|
|
77
130
|
|
|
@@ -117,7 +170,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
|
117
170
|
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'))
|
|
118
171
|
}
|
|
119
172
|
|
|
120
|
-
const { and, or, gt, eq, isNull } = await
|
|
173
|
+
const { and, or, gt, eq, isNull } = await loadDrizzleOperators();
|
|
121
174
|
|
|
122
175
|
const [response] = await this.drizzleClient
|
|
123
176
|
.select()
|
|
@@ -139,7 +192,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
|
139
192
|
return Promise.reject(new RateLimiterDrizzleError('Drizzle client is not established'))
|
|
140
193
|
}
|
|
141
194
|
|
|
142
|
-
const { eq } = await
|
|
195
|
+
const { eq } = await loadDrizzleOperators();
|
|
143
196
|
|
|
144
197
|
const [result] = await this.drizzleClient
|
|
145
198
|
.delete(this.schema)
|
|
@@ -156,7 +209,7 @@ class RateLimiterDrizzle extends RateLimiterStoreAbstract {
|
|
|
156
209
|
|
|
157
210
|
this._clearExpiredTimeoutId = setTimeout(async () => {
|
|
158
211
|
try {
|
|
159
|
-
const { lt } = await
|
|
212
|
+
const { lt } = await loadDrizzleOperators();
|
|
160
213
|
await this.drizzleClient
|
|
161
214
|
.delete(this.schema)
|
|
162
215
|
.where(lt(this.schema.expire, new Date(Date.now() - EXPIRED_THRESHOLD_MS)));
|
package/lib/RateLimiterMemory.js
CHANGED
|
@@ -35,6 +35,8 @@ class RateLimiterMemory extends RateLimiterAbstract {
|
|
|
35
35
|
if (delay < this.execEvenlyMinDelayMs) {
|
|
36
36
|
delay = res.consumedPoints * this.execEvenlyMinDelayMs;
|
|
37
37
|
}
|
|
38
|
+
// Adjust msBeforeNext to reflect time already waited before resolving
|
|
39
|
+
res.msBeforeNext = Math.max(res.msBeforeNext - delay, 0);
|
|
38
40
|
|
|
39
41
|
setTimeout(resolve, delay, res);
|
|
40
42
|
} else {
|
|
@@ -85,6 +85,9 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterInsuredAbstra
|
|
|
85
85
|
delay = res.consumedPoints * this.execEvenlyMinDelayMs;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Adjust msBeforeNext to reflect time already waited before resolving
|
|
89
|
+
res.msBeforeNext = Math.max(res.msBeforeNext - delay, 0);
|
|
90
|
+
|
|
88
91
|
setTimeout(resolve, delay, res);
|
|
89
92
|
} else {
|
|
90
93
|
resolve(res);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rate-limiter-flexible",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.2",
|
|
4
4
|
"description": "Node.js atomic and non-atomic counters, rate limiting tools, protection from DoS and brute-force attacks at scale",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test:valkey-cluster": "VALKEY_CLUSTER_PORT=7001 mocha test/RateLimiterValkeyGlide.test.js -- -g 'RateLimiterValkeyGlide with cluster client'",
|
|
12
12
|
"prisma:postgres": "prisma generate --schema=./test/RateLimiterPrisma/Postgres/schema.prisma && prisma db push --schema=./test/RateLimiterPrisma/Postgres/schema.prisma",
|
|
13
13
|
"drizzle:postgres": "cd ./test/RateLimiterDrizzle/Postgres && drizzle-kit push",
|
|
14
|
-
"test": "npm
|
|
14
|
+
"test:drizzle:postgres:v1": "npm i --no-save --no-package-lock drizzle-orm@^1.0.0-beta.23 && mocha test/RateLimiterDrizzle/Postgres/RateLimiterDrizzlePostgres.test.js && ( [ \"$GITHUB_ACTIONS\" = \"true\" ] || npm i )",
|
|
15
|
+
"test": "npm run prisma:postgres && npm run drizzle:postgres && nyc --reporter=html --reporter=text mocha \"test/**/*.test.js\" && npm run test:drizzle:postgres:v1",
|
|
15
16
|
"debug-test": "mocha --inspect-brk lib/**/**.test.js",
|
|
16
17
|
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
|
17
18
|
"eslint": "eslint --quiet lib/**/**.js test/**/**.js",
|
|
@@ -53,32 +54,32 @@
|
|
|
53
54
|
"homepage": "https://github.com/animir/node-rate-limiter-flexible#readme",
|
|
54
55
|
"types": "./types.d.ts",
|
|
55
56
|
"devDependencies": {
|
|
56
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
57
|
-
"@prisma/client": "^5.
|
|
58
|
-
"@valkey/valkey-glide": "^1.3.
|
|
59
|
-
"better-sqlite3": "^11.
|
|
60
|
-
"chai": "^4.
|
|
61
|
-
"coveralls": "^3.
|
|
62
|
-
"drizzle-kit": "^0.31.
|
|
63
|
-
"drizzle-orm": "^0.
|
|
57
|
+
"@aws-sdk/client-dynamodb": "^3.1029.0",
|
|
58
|
+
"@prisma/client": "^5.22.0",
|
|
59
|
+
"@valkey/valkey-glide": "^1.3.4",
|
|
60
|
+
"better-sqlite3": "^11.10.0",
|
|
61
|
+
"chai": "^4.5.0",
|
|
62
|
+
"coveralls": "^3.1.1",
|
|
63
|
+
"drizzle-kit": "^0.31.10",
|
|
64
|
+
"drizzle-orm": "^0.45.2",
|
|
64
65
|
"eslint": "^4.19.1",
|
|
65
66
|
"eslint-config-airbnb-base": "^12.1.0",
|
|
66
|
-
"eslint-plugin-import": "^2.
|
|
67
|
+
"eslint-plugin-import": "^2.32.0",
|
|
67
68
|
"eslint-plugin-node": "^6.0.1",
|
|
68
|
-
"eslint-plugin-security": "^1.
|
|
69
|
+
"eslint-plugin-security": "^1.7.1",
|
|
69
70
|
"etcd3": "^1.1.2",
|
|
70
|
-
"ioredis": "^5.
|
|
71
|
-
"iovalkey": "^0.3.
|
|
71
|
+
"ioredis": "^5.10.1",
|
|
72
|
+
"iovalkey": "^0.3.3",
|
|
72
73
|
"istanbul": "^1.1.0-alpha.1",
|
|
73
|
-
"knex": "^3.
|
|
74
|
+
"knex": "^3.2.9",
|
|
74
75
|
"memcached-mock": "^0.1.0",
|
|
75
|
-
"mocha": "^10.2
|
|
76
|
+
"mocha": "^10.8.2",
|
|
76
77
|
"nyc": "^15.1.0",
|
|
77
|
-
"pg": "^8.
|
|
78
|
-
"prisma": "^5.
|
|
79
|
-
"redis": "^4.
|
|
80
|
-
"redis-mock": "^0.
|
|
81
|
-
"sinon": "^17.0.
|
|
78
|
+
"pg": "^8.20.0",
|
|
79
|
+
"prisma": "^5.22.0",
|
|
80
|
+
"redis": "^4.7.1",
|
|
81
|
+
"redis-mock": "^0.56.3",
|
|
82
|
+
"sinon": "^17.0.2",
|
|
82
83
|
"sqlite3": "^5.1.7"
|
|
83
84
|
},
|
|
84
85
|
"browser": {
|