@voltro/plugin-ratelimit 0.32.0 → 0.34.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 (3) hide show
  1. package/CHANGELOG.md +2006 -0
  2. package/dist/index.js +62 -55
  3. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -2,9 +2,9 @@ import { RateLimited as e } from "./errors.js";
2
2
  import { n as t, t as n } from "./window-lyQ6LXhj.js";
3
3
  import { redisStore as r } from "./redisStore.js";
4
4
  import { Effect as i } from "effect";
5
- import { definePlugin as a } from "@voltro/protocol";
5
+ import { definePlugin as a, pluginInstanceName as o } from "@voltro/protocol";
6
6
  //#region src/store.ts
7
- var o = (e = {}) => {
7
+ var s = (e = {}) => {
8
8
  let t = e.maxKeys ?? 1e5, r = e.staleAfterMs ?? 36e5, a = /* @__PURE__ */ new Map(), o = (e) => {
9
9
  if (a.size <= t) return;
10
10
  let n = a.size - t, r = 0;
@@ -27,15 +27,15 @@ var o = (e = {}) => {
27
27
  },
28
28
  size: () => a.size
29
29
  };
30
- }, s = (e, t) => ({
30
+ }, c = (e, t) => ({
31
31
  allowed: !0,
32
32
  remaining: e.limit,
33
33
  resetAtMs: t + e.windowMs,
34
34
  retryAfterMs: 0
35
- }), c = (e = {}) => {
35
+ }), l = (e = {}) => {
36
36
  let t = null;
37
37
  return {
38
- consume: (e, n, r) => t ? t.consume(e, n, r) : i.succeed(s(n, r)),
38
+ consume: (e, n, r) => t ? t.consume(e, n, r) : i.succeed(c(n, r)),
39
39
  sweep: (e) => t?.sweep(e),
40
40
  size: () => t ? t.size() : -1,
41
41
  bindSql: async (n) => {
@@ -43,7 +43,11 @@ var o = (e = {}) => {
43
43
  await i.bindSql(n), t = i;
44
44
  }
45
45
  };
46
- }, l = (e, t) => typeof e == "function" ? e(t) : e, u = (e, t) => e === void 0 ? !0 : typeof e == "string" ? e === t : Array.isArray(e) ? e.includes(t) : e.test(t), d = (e, t) => e === void 0 ? !0 : Array.isArray(e) ? e.includes(t) : e === t, f = (e, t) => {
46
+ }, u = (e, t) => typeof e == "function" ? e(t) : e, d = /* @__PURE__ */ new WeakMap(), f = (e) => {
47
+ if (!e.global && !e.sticky) return e;
48
+ let t = d.get(e);
49
+ return t === void 0 && (t = new RegExp(e.source, e.flags.replace(/[gy]/g, "")), d.set(e, t)), t;
50
+ }, p = (e, t) => e === void 0 ? !0 : typeof e == "string" ? e === t : Array.isArray(e) ? e.includes(t) : f(e).test(t), m = (e, t) => e === void 0 ? !0 : Array.isArray(e) ? e.includes(t) : e === t, h = (e, t) => {
47
51
  let n = t === null ? void 0 : e.tenants?.[t];
48
52
  return {
49
53
  limit: n?.limit ?? e.limit,
@@ -54,27 +58,27 @@ var o = (e = {}) => {
54
58
  scope: e.scope ?? "tag",
55
59
  disabled: n?.disabled ?? !1
56
60
  };
57
- }, p = (e, t, n) => {
61
+ }, g = (e, t, n) => {
58
62
  for (let t = 0; t < e.length; t++) {
59
63
  let r = e[t];
60
- if (u(r.match, n.tag) && d(r.kind, n.kind)) return {
61
- effective: f(r, n.tenantId),
64
+ if (p(r.match, n.tag) && m(r.kind, n.kind)) return {
65
+ effective: h(r, n.tenantId),
62
66
  ruleId: `r${t}`
63
67
  };
64
68
  }
65
- if (t && d(t.kind, n.kind)) return {
66
- effective: f(t, n.tenantId),
69
+ if (t && m(t.kind, n.kind)) return {
70
+ effective: h(t, n.tenantId),
67
71
  ruleId: "default"
68
72
  };
69
- }, m = (e, n) => {
70
- let r = l(e.limit, n);
73
+ }, _ = (e, n) => {
74
+ let r = u(e.limit, n);
71
75
  return {
72
76
  limit: r,
73
- windowMs: t(l(e.window, n)),
77
+ windowMs: t(u(e.window, n)),
74
78
  algorithm: e.algorithm ?? "sliding-window",
75
- burst: e.burst === void 0 ? r : l(e.burst, n)
79
+ burst: e.burst === void 0 ? r : u(e.burst, n)
76
80
  };
77
- }, h = (e) => {
81
+ }, v = (e) => {
78
82
  let n = e.limit;
79
83
  return {
80
84
  limit: n,
@@ -82,12 +86,12 @@ var o = (e = {}) => {
82
86
  algorithm: e.algorithm ?? "sliding-window",
83
87
  burst: e.burst ?? n
84
88
  };
85
- }, g = (e) => ({
89
+ }, y = (e) => ({
86
90
  limit: e.limit,
87
91
  window: e.windowMs,
88
92
  algorithm: e.algorithm,
89
93
  burst: e.burst
90
- }), _ = (e, t) => {
94
+ }), b = (e, t) => {
91
95
  if (typeof e == "function") return e(t);
92
96
  switch (e) {
93
97
  case "subject": return `s:${t.tenantId ?? "∅"}:${t.subject.id ?? "anon"}`;
@@ -95,20 +99,20 @@ var o = (e = {}) => {
95
99
  case "apiKey": return t.subject.type === "apiKey" ? `k:${t.subject.id}` : `s:${t.tenantId ?? "∅"}:${t.subject.id ?? "anon"}`;
96
100
  case "global": return "g";
97
101
  }
98
- }, v = (e, t) => {
102
+ }, x = (e, t) => {
99
103
  let { effective: n, ruleId: r } = e, i = n.scope === "rule" ? `rule#${r}` : `tag:${t.tag}`, a = n.by;
100
- return `${i}|${Array.isArray(a) ? a.map((e) => _(e, t)).join("|") : _(a, t)}`;
101
- }, y = (e) => ({
104
+ return `${i}|${Array.isArray(a) ? a.map((e) => b(e, t)).join("|") : b(a, t)}`;
105
+ }, S = (e) => ({
102
106
  tag: e.tag,
103
107
  kind: e.kind,
104
108
  subject: e.subject,
105
109
  tenantId: e.subject.tenantId ?? null,
106
110
  traceId: e.traceId
107
- }), b = (n = {}) => {
108
- let s = n.rules ?? [], l = n.store === void 0 || n.store === "memory" ? o() : n.store === "postgres" ? c() : n.store === "redis" ? r() : n.store, u = (e) => {
111
+ }), C = (n = {}) => {
112
+ let c = n.rules ?? [], u = n.store === void 0 || n.store === "memory" ? s() : n.store === "postgres" ? l() : n.store === "redis" ? r() : n.store, d = (e) => {
109
113
  let t = n.enabled;
110
114
  return t === void 0 ? !0 : typeof t == "function" ? t(e) : t;
111
- }, d = (e, t, r, i) => {
115
+ }, f = (e, t, r, i) => {
112
116
  if (!n.onLimited) return;
113
117
  let a = {
114
118
  tag: e.tag,
@@ -122,27 +126,27 @@ var o = (e = {}) => {
122
126
  try {
123
127
  n.onLimited(a);
124
128
  } catch {}
125
- }, f = (e, t) => {
129
+ }, p = (e, t) => {
126
130
  let r = n.resolve;
127
131
  return r ? i.suspend(() => {
128
132
  let n = r(e, t);
129
- return (i.isEffect(n) ? n : n instanceof Promise ? i.promise(() => n) : i.succeed(n)).pipe(i.map((e) => e === "exempt" ? "exempt" : h(e === void 0 ? t : e)));
130
- }) : i.succeed(h(t));
131
- }, _ = (t, r) => i.suspend(() => {
132
- let a = y(r);
133
- if (!u(a)) return t;
134
- let o = p(s, n.default, a);
133
+ return (i.isEffect(n) ? n : n instanceof Promise ? i.promise(() => n) : i.succeed(n)).pipe(i.map((e) => e === "exempt" ? "exempt" : v(e === void 0 ? t : e)));
134
+ }) : i.succeed(v(t));
135
+ }, m = (t, r) => i.suspend(() => {
136
+ let a = S(r);
137
+ if (!d(a)) return t;
138
+ let o = g(c, n.default, a);
135
139
  if (!o || o.effective.disabled) return t;
136
- let c = g(m(o.effective, a));
137
- return f(a, c).pipe(i.flatMap((n) => {
140
+ let s = y(_(o.effective, a));
141
+ return p(a, s).pipe(i.flatMap((n) => {
138
142
  if (n === "exempt") return t;
139
- let r = v(o, a);
140
- return l.consume(r, n, Date.now()).pipe(i.catchAllDefect((e) => i.as(i.logWarning(`[ratelimit] store error — failing open for ${a.tag}: ${String(e)}`), {
143
+ let r = x(o, a);
144
+ return u.consume(r, n, Date.now()).pipe(i.catchAllDefect((e) => i.as(i.logWarning(`[ratelimit] store error — failing open for ${a.tag}: ${String(e)}`), {
141
145
  allowed: !0,
142
146
  remaining: n.limit,
143
147
  resetAtMs: Date.now(),
144
148
  retryAfterMs: 0
145
- })), i.flatMap((o) => o.allowed ? t : i.zipRight(i.sync(() => d(a, r, n, o)), i.fail(new e({
149
+ })), i.flatMap((o) => o.allowed ? t : i.zipRight(i.sync(() => f(a, r, n, o)), i.fail(new e({
146
150
  tag: a.tag,
147
151
  limit: n.limit,
148
152
  windowMs: n.windowMs,
@@ -150,34 +154,37 @@ var o = (e = {}) => {
150
154
  resetAtMs: o.resetAtMs
151
155
  })))));
152
156
  }));
153
- }), b = n.http ? async (e, r) => {
157
+ }), h = n.http ? async (e, r) => {
154
158
  let a = n.http, o = {
155
159
  limit: a.limit,
156
160
  windowMs: t(a.window),
157
161
  algorithm: a.algorithm ?? "fixed-window",
158
162
  burst: a.limit
159
- }, s = r.remoteAddr ?? "unknown", c = Date.now(), u = await i.runPromise(l.consume(`http:ip:${s}`, o, c)), d = String(Math.max(0, Math.ceil((u.resetAtMs - c) / 1e3)));
160
- return u.allowed ? e() : {
163
+ }, s = r.remoteAddr ?? "unknown", c = Date.now(), l = await i.runPromise(u.consume(`http:ip:${s}`, o, c)), d = String(Math.max(0, Math.ceil((l.resetAtMs - c) / 1e3)));
164
+ return l.allowed ? e() : {
161
165
  status: 429,
162
166
  body: "Too Many Requests",
163
167
  headers: {
164
168
  "content-type": "text/plain; charset=utf-8",
165
- "retry-after": String(Math.max(1, Math.ceil(u.retryAfterMs / 1e3))),
169
+ "retry-after": String(Math.max(1, Math.ceil(l.retryAfterMs / 1e3))),
166
170
  "ratelimit-limit": String(o.limit),
167
- "ratelimit-remaining": String(Math.max(0, u.remaining)),
171
+ "ratelimit-remaining": String(Math.max(0, l.remaining)),
168
172
  "ratelimit-reset": d
169
173
  }
170
174
  };
171
- } : void 0, x = [
175
+ } : void 0, b = [
172
176
  "rpc:intercept:mutation",
173
177
  "rpc:intercept:query",
174
178
  "rpc:intercept:action",
175
179
  ...n.http ? ["http:intercept"] : []
176
- ], S, C;
180
+ ], C, w;
177
181
  return a({
178
- name: n.name ? `@voltro/plugin-ratelimit#${n.name}` : "@voltro/plugin-ratelimit",
182
+ name: o({
183
+ base: "@voltro/plugin-ratelimit",
184
+ instance: n.name
185
+ }),
179
186
  description: "Per-endpoint / per-subject / per-tenant rate limiting via the rpc interceptors. Configurable rules, three algorithms, static + dynamic per-tenant overrides.",
180
- permissions: x,
187
+ permissions: b,
181
188
  declaredEnv: [
182
189
  {
183
190
  name: "CACHE_REDIS_URL",
@@ -210,7 +217,7 @@ var o = (e = {}) => {
210
217
  description: "Fallback Upstash REST token for the http redis driver."
211
218
  }
212
219
  ],
213
- ...b ? { onHttpRequest: b } : {},
220
+ ...h ? { onHttpRequest: h } : {},
214
221
  errorSchemas: [{
215
222
  schema: e,
216
223
  import: {
@@ -218,26 +225,26 @@ var o = (e = {}) => {
218
225
  name: "RateLimited"
219
226
  }
220
227
  }],
221
- interceptMutation: _,
222
- interceptQuery: _,
223
- interceptAction: _,
228
+ interceptMutation: m,
229
+ interceptQuery: m,
230
+ interceptAction: m,
224
231
  bindDataStore: (e, t) => {
225
232
  let n = t?.sql;
226
- n && l.bindSql && l.bindSql(n).catch((e) => {
227
- C = e;
233
+ n && u.bindSql && u.bindSql(n).catch((e) => {
234
+ w = e;
228
235
  });
229
236
  },
230
237
  onActivate: (e) => i.gen(function* () {
231
- l.activate && (yield* i.promise(() => l.activate(e.env))), S = setInterval(() => l.sweep(Date.now()), 6e4), S.unref?.(), C !== void 0 && e.logger.warn("rate-limit: SQL store bind failed — limiter runs fail-open", { error: C instanceof Error ? C.message : String(C) }), e.logger.info("rate-limit active", {
232
- rules: s.length,
238
+ u.activate && (yield* i.promise(() => u.activate(e.env))), C = setInterval(() => u.sweep(Date.now()), 6e4), C.unref?.(), w !== void 0 && e.logger.warn("rate-limit: SQL store bind failed — limiter runs fail-open", { error: w instanceof Error ? w.message : String(w) }), e.logger.info("rate-limit active", {
239
+ rules: c.length,
233
240
  hasDefault: n.default !== void 0,
234
241
  store: typeof n.store == "string" ? n.store : n.store === void 0 ? "memory" : "custom"
235
242
  });
236
243
  }),
237
244
  onDeactivate: () => i.gen(function* () {
238
- S && clearInterval(S), l.deactivate && (yield* i.promise(() => l.deactivate()));
245
+ C && clearInterval(C), u.deactivate && (yield* i.promise(() => u.deactivate()));
239
246
  })
240
247
  });
241
248
  };
242
249
  //#endregion
243
- export { e as RateLimited, n as consumeBucket, o as memoryStore, t as parseWindow, b as rateLimitPlugin, r as redisStore };
250
+ export { e as RateLimited, n as consumeBucket, s as memoryStore, t as parseWindow, C as rateLimitPlugin, r as redisStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-ratelimit",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
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",
@@ -37,7 +37,8 @@
37
37
  "types": "./dist/errors.d.ts",
38
38
  "import": "./dist/errors.js",
39
39
  "default": "./dist/errors.js"
40
- }
40
+ },
41
+ "./package.json": "./package.json"
41
42
  },
42
43
  "main": "./dist/index.js",
43
44
  "module": "./dist/index.js",
@@ -47,8 +48,8 @@
47
48
  "node": ">=24.0.0"
48
49
  },
49
50
  "dependencies": {
50
- "@voltro/kv": "0.32.0",
51
- "@voltro/protocol": "0.32.0"
51
+ "@voltro/kv": "0.34.0",
52
+ "@voltro/protocol": "0.34.0"
52
53
  },
53
54
  "optionalDependencies": {
54
55
  "@effect/sql": "^0.52.0"