@voltro/plugin-ratelimit 0.82.0 → 0.83.1
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/CHANGELOG.md +361 -0
- package/dist/index.d.ts +23 -8
- package/dist/index.js +112 -128
- package/dist/postgresStore-CDP61pxx.js +160 -0
- package/dist/postgresStore.d.ts +38 -8
- package/dist/postgresStore.js +2 -51
- package/dist/redisStore.d.ts +6 -6
- package/package.json +5 -4
- package/dist/window-lyQ6LXhj.js +0 -117
package/dist/postgresStore.js
CHANGED
|
@@ -1,51 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { SqlClient as n } from "@effect/sql";
|
|
4
|
-
//#region src/postgresStore.ts
|
|
5
|
-
var r = (r = {}) => {
|
|
6
|
-
let i = r.table ?? "_voltro_ratelimit", a = r.staleAfterMs ?? 36e5, o = null, s = (e) => t.runPromise(t.provideService(e, n.SqlClient, o)), c = (e, t, n) => ({
|
|
7
|
-
failOpenReason: n,
|
|
8
|
-
allowed: !0,
|
|
9
|
-
remaining: e.limit,
|
|
10
|
-
resetAtMs: t + e.windowMs,
|
|
11
|
-
retryAfterMs: 0
|
|
12
|
-
});
|
|
13
|
-
return {
|
|
14
|
-
consume: (r, a, l) => {
|
|
15
|
-
if (!o) return t.succeed(c(a, l, "unbound"));
|
|
16
|
-
let u = t.gen(function* () {
|
|
17
|
-
let o = yield* n.SqlClient;
|
|
18
|
-
return yield* o.withTransaction(t.gen(function* () {
|
|
19
|
-
yield* o`SELECT pg_advisory_xact_lock(hashtextextended(${r}, 0))`;
|
|
20
|
-
let t = (yield* o`
|
|
21
|
-
SELECT state FROM ${o(i)} WHERE key = ${r} FOR UPDATE`)[0]?.state, { state: n, decision: s } = e(t, a, l), c = JSON.stringify(n);
|
|
22
|
-
return yield* o`
|
|
23
|
-
INSERT INTO ${o(i)} (key, state, touched)
|
|
24
|
-
VALUES (${r}, ${c}::jsonb, ${l})
|
|
25
|
-
ON CONFLICT (key) DO UPDATE SET state = ${c}::jsonb, touched = ${l}`, s;
|
|
26
|
-
}));
|
|
27
|
-
});
|
|
28
|
-
return t.tryPromise(() => s(u)).pipe(t.catchAll(() => t.succeed(c(a, l, "store-error"))));
|
|
29
|
-
},
|
|
30
|
-
sweep: (e) => {
|
|
31
|
-
o && s(t.gen(function* () {
|
|
32
|
-
let t = yield* n.SqlClient;
|
|
33
|
-
yield* t`DELETE FROM ${t(i)} WHERE touched < ${e - a}`;
|
|
34
|
-
})).catch(() => {});
|
|
35
|
-
},
|
|
36
|
-
size: () => -1,
|
|
37
|
-
bindSql: async (e) => {
|
|
38
|
-
await t.runPromise(t.provideService(t.gen(function* () {
|
|
39
|
-
let e = yield* n.SqlClient;
|
|
40
|
-
yield* e`
|
|
41
|
-
CREATE TABLE IF NOT EXISTS ${e(i)} (
|
|
42
|
-
key TEXT PRIMARY KEY,
|
|
43
|
-
state JSONB NOT NULL,
|
|
44
|
-
touched BIGINT NOT NULL
|
|
45
|
-
)`;
|
|
46
|
-
}), n.SqlClient, e)), o = e;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
//#endregion
|
|
51
|
-
export { r as postgresStore };
|
|
1
|
+
import { n as e, r as t, t as n } from "./postgresStore-CDP61pxx.js";
|
|
2
|
+
export { n as RATELIMIT_TABLE, e as postgresStore, t as rateLimitTable };
|
package/dist/redisStore.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { DataStore } from '@voltro/database';
|
|
1
2
|
import { Effect } from 'effect';
|
|
2
3
|
import { RespClient } from '@voltro/kv/connection';
|
|
3
|
-
import { SqlClient } from '@effect/sql';
|
|
4
4
|
|
|
5
5
|
declare type Algorithm = 'fixed-window' | 'sliding-window' | 'token-bucket';
|
|
6
6
|
|
|
@@ -30,11 +30,11 @@ declare interface RateLimitStore {
|
|
|
30
30
|
readonly activate?: (env: NodeJS.ProcessEnv) => Promise<void>;
|
|
31
31
|
/** Optional teardown at plugin deactivate (close pools). */
|
|
32
32
|
readonly deactivate?: () => Promise<void>;
|
|
33
|
-
/** Optional bind of the
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
readonly
|
|
33
|
+
/** Optional bind of the app's `DataStore` (the plugin's `bindDataStore`).
|
|
34
|
+
* A store kept in the app's database reads and writes through it — its
|
|
35
|
+
* table is declared through `extendSchema`, never created here. Not
|
|
36
|
+
* present on memory / redis stores. */
|
|
37
|
+
readonly bind?: (store: DataStore) => Promise<void>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export declare const redisStore: (options?: RedisStoreOptions) => RateLimitStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-ratelimit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.1",
|
|
4
4
|
"description": "Rate-limiting plugin — per-endpoint, per-subject and per-tenant request limits via the RPC interceptors. Highly configurable rules (fixed-window / sliding-window / token-bucket), composite keying, static per-tenant overrides and a dynamic resolve() hook. Stores: memory (default, single-node), postgres + redis (multi-node, atomic).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -48,9 +48,10 @@
|
|
|
48
48
|
"node": ">=24.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@voltro/
|
|
52
|
-
"@voltro/
|
|
53
|
-
"@voltro/
|
|
51
|
+
"@voltro/database": "0.83.1",
|
|
52
|
+
"@voltro/kv": "0.83.1",
|
|
53
|
+
"@voltro/logger": "0.83.1",
|
|
54
|
+
"@voltro/protocol": "0.83.1"
|
|
54
55
|
},
|
|
55
56
|
"optionalDependencies": {
|
|
56
57
|
"@effect/sql": "^0.52.1"
|
package/dist/window-lyQ6LXhj.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
//#region src/window.ts
|
|
2
|
-
var e = {
|
|
3
|
-
ms: 1,
|
|
4
|
-
s: 1e3,
|
|
5
|
-
m: 6e4,
|
|
6
|
-
h: 36e5,
|
|
7
|
-
d: 864e5
|
|
8
|
-
}, t = (t) => {
|
|
9
|
-
if (typeof t == "number") {
|
|
10
|
-
if (!Number.isFinite(t) || t <= 0) throw Error(`@voltro/plugin-ratelimit: window must be a positive number, got ${t}`);
|
|
11
|
-
return t;
|
|
12
|
-
}
|
|
13
|
-
let n = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(t.trim());
|
|
14
|
-
if (!n) throw Error(`@voltro/plugin-ratelimit: invalid window "${t}" — use e.g. "1m", "10s", "500ms", "2h", "1d"`);
|
|
15
|
-
return Number(n[1]) * e[n[2]];
|
|
16
|
-
}, n = (e, t, n) => {
|
|
17
|
-
switch (t.algorithm) {
|
|
18
|
-
case "fixed-window": return r(e, t, n);
|
|
19
|
-
case "sliding-window": return i(e, t, n);
|
|
20
|
-
case "token-bucket": return a(e, t, n);
|
|
21
|
-
}
|
|
22
|
-
}, r = (e, t, n) => {
|
|
23
|
-
let r = e?.kind === "fixed" ? e.windowStart : n, i = e?.kind === "fixed" ? e.count : 0;
|
|
24
|
-
n - r >= t.windowMs && (r = n, i = 0);
|
|
25
|
-
let a = r + t.windowMs;
|
|
26
|
-
return i >= t.limit ? {
|
|
27
|
-
state: {
|
|
28
|
-
kind: "fixed",
|
|
29
|
-
windowStart: r,
|
|
30
|
-
count: i
|
|
31
|
-
},
|
|
32
|
-
decision: {
|
|
33
|
-
allowed: !1,
|
|
34
|
-
remaining: 0,
|
|
35
|
-
resetAtMs: a,
|
|
36
|
-
retryAfterMs: a - n
|
|
37
|
-
}
|
|
38
|
-
} : (i += 1, {
|
|
39
|
-
state: {
|
|
40
|
-
kind: "fixed",
|
|
41
|
-
windowStart: r,
|
|
42
|
-
count: i
|
|
43
|
-
},
|
|
44
|
-
decision: {
|
|
45
|
-
allowed: !0,
|
|
46
|
-
remaining: t.limit - i,
|
|
47
|
-
resetAtMs: a,
|
|
48
|
-
retryAfterMs: 0
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}, i = (e, t, n) => {
|
|
52
|
-
let r = t.windowMs, i = e?.kind === "sliding" ? e.currStart : n, a = e?.kind === "sliding" ? e.currCount : 0, o = e?.kind === "sliding" ? e.prevCount : 0, s = Math.floor((n - i) / r);
|
|
53
|
-
s >= 2 ? (o = 0, a = 0, i = n) : s === 1 && (o = a, a = 0, i += r);
|
|
54
|
-
let c = (r - (n - i)) / r, l = o * c + a, u = i + r;
|
|
55
|
-
return l >= t.limit ? {
|
|
56
|
-
state: {
|
|
57
|
-
kind: "sliding",
|
|
58
|
-
currStart: i,
|
|
59
|
-
currCount: a,
|
|
60
|
-
prevCount: o
|
|
61
|
-
},
|
|
62
|
-
decision: {
|
|
63
|
-
allowed: !1,
|
|
64
|
-
remaining: 0,
|
|
65
|
-
resetAtMs: u,
|
|
66
|
-
retryAfterMs: u - n
|
|
67
|
-
}
|
|
68
|
-
} : (a += 1, {
|
|
69
|
-
state: {
|
|
70
|
-
kind: "sliding",
|
|
71
|
-
currStart: i,
|
|
72
|
-
currCount: a,
|
|
73
|
-
prevCount: o
|
|
74
|
-
},
|
|
75
|
-
decision: {
|
|
76
|
-
allowed: !0,
|
|
77
|
-
remaining: Math.max(0, Math.floor(t.limit - l - 1)),
|
|
78
|
-
resetAtMs: u,
|
|
79
|
-
retryAfterMs: 0
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}, a = (e, t, n) => {
|
|
83
|
-
let r = t.burst, i = t.limit / t.windowMs, a = e?.kind === "token" ? e.tokens : r, o = e?.kind === "token" ? e.lastRefill : n;
|
|
84
|
-
if (a = Math.min(r, a + (n - o) * i), a >= 1) {
|
|
85
|
-
--a;
|
|
86
|
-
let e = n + Math.ceil((r - a) / i);
|
|
87
|
-
return {
|
|
88
|
-
state: {
|
|
89
|
-
kind: "token",
|
|
90
|
-
tokens: a,
|
|
91
|
-
lastRefill: n
|
|
92
|
-
},
|
|
93
|
-
decision: {
|
|
94
|
-
allowed: !0,
|
|
95
|
-
remaining: Math.floor(a),
|
|
96
|
-
resetAtMs: e,
|
|
97
|
-
retryAfterMs: 0
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
let s = Math.ceil((1 - a) / i);
|
|
102
|
-
return {
|
|
103
|
-
state: {
|
|
104
|
-
kind: "token",
|
|
105
|
-
tokens: a,
|
|
106
|
-
lastRefill: n
|
|
107
|
-
},
|
|
108
|
-
decision: {
|
|
109
|
-
allowed: !1,
|
|
110
|
-
remaining: 0,
|
|
111
|
-
resetAtMs: n + s,
|
|
112
|
-
retryAfterMs: s
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
//#endregion
|
|
117
|
-
export { t as n, n as t };
|