@superfunctions/middleware 0.2.0 → 0.3.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/dist/rate-limit.d.ts +3 -0
- package/dist/rate-limit.js +5 -2
- package/dist/rate-limit.test.js +12 -0
- package/package.json +23 -10
package/dist/rate-limit.d.ts
CHANGED
|
@@ -27,11 +27,14 @@ export interface CheckLimitInput {
|
|
|
27
27
|
}
|
|
28
28
|
export interface CheckManyLimitsInput {
|
|
29
29
|
keys: string[];
|
|
30
|
+
/** Override the shared limit for each unprefixed key. */
|
|
31
|
+
limitsByKey?: Record<string, number>;
|
|
30
32
|
windowSeconds?: number;
|
|
31
33
|
limit?: number;
|
|
32
34
|
}
|
|
33
35
|
export interface RateLimitManyResult {
|
|
34
36
|
allowed: boolean;
|
|
37
|
+
blockedKeys?: string[];
|
|
35
38
|
remainingByKey: Map<string, number>;
|
|
36
39
|
resetAtByKey: Map<string, string>;
|
|
37
40
|
resetAt: string;
|
package/dist/rate-limit.js
CHANGED
|
@@ -289,6 +289,7 @@ export function createRateLimiter(config) {
|
|
|
289
289
|
const uniqueKeys = [...new Set(input.keys)];
|
|
290
290
|
const effectiveWindowMs = (input.windowSeconds !== undefined ? input.windowSeconds * 1000 : undefined) ?? windowMs;
|
|
291
291
|
const effectiveLimit = input.limit ?? maxRequests;
|
|
292
|
+
const limits = uniqueKeys.map((key) => input.limitsByKey && Object.prototype.hasOwnProperty.call(input.limitsByKey, key) ? input.limitsByKey[key] ?? effectiveLimit : effectiveLimit);
|
|
292
293
|
const namespacedKeys = uniqueKeys.map((key) => `${keyPrefix}${key}`);
|
|
293
294
|
if (uniqueKeys.length === 0) {
|
|
294
295
|
return {
|
|
@@ -301,11 +302,12 @@ export function createRateLimiter(config) {
|
|
|
301
302
|
}
|
|
302
303
|
return withKeyLocks(namespacedKeys, async () => {
|
|
303
304
|
const currentTime = now();
|
|
304
|
-
const preflight = await Promise.all(namespacedKeys.map((key) => evaluateConfiguredKey(key, currentTime, effectiveWindowMs,
|
|
305
|
+
const preflight = await Promise.all(namespacedKeys.map((key, index) => evaluateConfiguredKey(key, currentTime, effectiveWindowMs, limits[index], false)));
|
|
305
306
|
const blocked = preflight.filter((result) => !result.allowed);
|
|
306
307
|
if (blocked.length > 0) {
|
|
307
308
|
return {
|
|
308
309
|
allowed: false,
|
|
310
|
+
blockedKeys: uniqueKeys.filter((_key, index) => !preflight[index].allowed),
|
|
309
311
|
remainingByKey: new Map(uniqueKeys.map((key, index) => [key, preflight[index].remaining])),
|
|
310
312
|
resetAtByKey: new Map(uniqueKeys.map((key, index) => [key, preflight[index].resetAt])),
|
|
311
313
|
resetAt: new Date(Math.max(...blocked.map((result) => Date.parse(result.resetAt)))).toISOString(),
|
|
@@ -318,13 +320,14 @@ export function createRateLimiter(config) {
|
|
|
318
320
|
// Commit each key through the configured store. If a later commit
|
|
319
321
|
// becomes blocked or fails, retain earlier quota charges (fail
|
|
320
322
|
// closed) instead of restoring stale snapshots over concurrent work.
|
|
321
|
-
const result = await evaluateConfiguredKey(key, currentTime, effectiveWindowMs,
|
|
323
|
+
const result = await evaluateConfiguredKey(key, currentTime, effectiveWindowMs, limits[index], true);
|
|
322
324
|
committed.push(result);
|
|
323
325
|
if (!result.allowed) {
|
|
324
326
|
const actual = preflight.map((entry, entryIndex) => committed[entryIndex] ?? entry);
|
|
325
327
|
const blockedActual = actual.filter((entry) => !entry.allowed);
|
|
326
328
|
return {
|
|
327
329
|
allowed: false,
|
|
330
|
+
blockedKeys: uniqueKeys.filter((_key, index) => !actual[index].allowed),
|
|
328
331
|
remainingByKey: new Map(uniqueKeys.map((entry, entryIndex) => [entry, actual[entryIndex].remaining])),
|
|
329
332
|
resetAtByKey: new Map(uniqueKeys.map((entry, entryIndex) => [entry, actual[entryIndex].resetAt])),
|
|
330
333
|
resetAt: new Date(Math.max(...blockedActual.map((entry) => Date.parse(entry.resetAt)))).toISOString(),
|
package/dist/rate-limit.test.js
CHANGED
|
@@ -348,3 +348,15 @@ describe('rate-limit', () => {
|
|
|
348
348
|
});
|
|
349
349
|
});
|
|
350
350
|
});
|
|
351
|
+
it('uses only own limit overrides, including inherited property names', async () => {
|
|
352
|
+
const limiter = createRateLimiter({ windowMs: 60000, maxRequests: 2, algorithm: 'fixed-window' });
|
|
353
|
+
const keys = ['constructor', 'toString', '__proto__'];
|
|
354
|
+
const limitsByKey = Object.create({ constructor: 0 });
|
|
355
|
+
limitsByKey.toString = 1;
|
|
356
|
+
const first = await limiter.checkMany({ keys, limitsByKey });
|
|
357
|
+
expect(first.allowed).toBe(true);
|
|
358
|
+
expect(first.remainingByKey.get('constructor')).toBe(1);
|
|
359
|
+
expect(first.remainingByKey.get('toString')).toBe(0);
|
|
360
|
+
expect(first.remainingByKey.get('__proto__')).toBe(1);
|
|
361
|
+
expect((await limiter.checkMany({ keys, limitsByKey })).blockedKeys).toEqual(['toString']);
|
|
362
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superfunctions/middleware",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared middleware utilities for Superfunctions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,37 +15,50 @@
|
|
|
15
15
|
"default": "./dist/rate-limit.js"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
-
"files": [
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
19
21
|
"scripts": {
|
|
20
22
|
"build": "tsc",
|
|
21
|
-
"test": "vitest",
|
|
23
|
+
"test": "vitest run",
|
|
22
24
|
"test:watch": "vitest --watch",
|
|
23
25
|
"typecheck": "tsc --noEmit",
|
|
24
26
|
"clean": "rm -rf dist"
|
|
25
27
|
},
|
|
26
|
-
"keywords": [
|
|
28
|
+
"keywords": [
|
|
29
|
+
"middleware",
|
|
30
|
+
"rate-limit",
|
|
31
|
+
"superfunctions"
|
|
32
|
+
],
|
|
27
33
|
"author": "21n",
|
|
28
34
|
"license": "MIT",
|
|
29
|
-
"bugs": {
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/21nCo/super-functions/issues"
|
|
37
|
+
},
|
|
30
38
|
"repository": {
|
|
31
39
|
"type": "git",
|
|
32
40
|
"url": "git+https://github.com/21nCo/super-functions.git",
|
|
33
41
|
"directory": "packages/middleware"
|
|
34
42
|
},
|
|
35
|
-
"publishConfig": {
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
36
46
|
"peerDependencies": {
|
|
37
|
-
"@superfunctions/db": "0.2.0"
|
|
47
|
+
"@superfunctions/db": "^0.2.0 || ^0.3.0"
|
|
38
48
|
},
|
|
39
49
|
"peerDependenciesMeta": {
|
|
40
|
-
"@superfunctions/db": {
|
|
50
|
+
"@superfunctions/db": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
41
53
|
},
|
|
42
54
|
"dependencies": {
|
|
43
55
|
"@superfunctions/observability": "0.0.1"
|
|
44
56
|
},
|
|
45
57
|
"devDependencies": {
|
|
46
|
-
"@superfunctions/db": "
|
|
58
|
+
"@superfunctions/db": "0.3.0",
|
|
47
59
|
"@types/node": "^22.0.0",
|
|
48
60
|
"typescript": "^5.6.0",
|
|
49
61
|
"vitest": "^3.2.4"
|
|
50
|
-
}
|
|
62
|
+
},
|
|
63
|
+
"gitHead": "11f7e104d2d59f62ac94ab2dd06fc01144d0390b"
|
|
51
64
|
}
|