@fuzdev/fuz_app 0.141.0 → 0.143.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 (159) hide show
  1. package/dist/actions/CLAUDE.md +10 -5
  2. package/dist/actions/connection_closer.d.ts +19 -2
  3. package/dist/actions/connection_closer.d.ts.map +1 -1
  4. package/dist/actions/connection_closer.js +25 -2
  5. package/dist/actions/perform_action.d.ts +10 -0
  6. package/dist/actions/perform_action.d.ts.map +1 -1
  7. package/dist/actions/perform_action.js +23 -9
  8. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  9. package/dist/actions/register_ws_endpoint.js +5 -2
  10. package/dist/auth/CLAUDE.md +33 -13
  11. package/dist/auth/account_actions.d.ts.map +1 -1
  12. package/dist/auth/account_actions.js +12 -1
  13. package/dist/auth/account_route_schema.d.ts.map +1 -1
  14. package/dist/auth/account_route_schema.js +8 -0
  15. package/dist/auth/account_routes.d.ts +7 -2
  16. package/dist/auth/account_routes.d.ts.map +1 -1
  17. package/dist/auth/account_routes.js +240 -187
  18. package/dist/auth/admin_action_specs.d.ts +6 -1
  19. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  20. package/dist/auth/admin_action_specs.js +6 -1
  21. package/dist/auth/admin_actions.d.ts.map +1 -1
  22. package/dist/auth/admin_actions.js +14 -2
  23. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  24. package/dist/auth/bootstrap_routes.js +85 -76
  25. package/dist/auth/cell_action_specs.d.ts +7 -3
  26. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  27. package/dist/auth/cell_action_specs.js +9 -5
  28. package/dist/auth/cell_actions.d.ts +3 -3
  29. package/dist/auth/cell_actions.d.ts.map +1 -1
  30. package/dist/auth/cell_actions.js +67 -38
  31. package/dist/auth/cell_authorize.d.ts +69 -8
  32. package/dist/auth/cell_authorize.d.ts.map +1 -1
  33. package/dist/auth/cell_authorize.js +89 -9
  34. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  35. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  36. package/dist/auth/cell_field_action_specs.js +7 -7
  37. package/dist/auth/cell_field_actions.d.ts +7 -5
  38. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  39. package/dist/auth/cell_field_actions.js +24 -22
  40. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  41. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  42. package/dist/auth/cell_grant_action_specs.js +6 -0
  43. package/dist/auth/cell_grant_actions.d.ts +3 -1
  44. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  45. package/dist/auth/cell_grant_actions.js +16 -2
  46. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  47. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  48. package/dist/auth/cell_item_action_specs.js +7 -7
  49. package/dist/auth/cell_item_actions.d.ts +6 -5
  50. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  51. package/dist/auth/cell_item_actions.js +23 -22
  52. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  53. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  54. package/dist/auth/cell_relation_list_limit.js +27 -0
  55. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  56. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  57. package/dist/auth/cell_relation_visibility.js +6 -5
  58. package/dist/auth/cell_steward.d.ts +45 -0
  59. package/dist/auth/cell_steward.d.ts.map +1 -0
  60. package/dist/auth/cell_steward.js +53 -0
  61. package/dist/auth/request_context.d.ts +4 -0
  62. package/dist/auth/request_context.d.ts.map +1 -1
  63. package/dist/auth/request_context.js +6 -1
  64. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  65. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  66. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  67. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_actions.js +23 -16
  69. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  70. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_queries.js +16 -8
  72. package/dist/auth/signup_routes.d.ts +4 -3
  73. package/dist/auth/signup_routes.d.ts.map +1 -1
  74. package/dist/auth/signup_routes.js +157 -134
  75. package/dist/auth/token_scope.d.ts +7 -1
  76. package/dist/auth/token_scope.d.ts.map +1 -1
  77. package/dist/auth/token_scope.js +8 -2
  78. package/dist/db/CLAUDE.md +32 -19
  79. package/dist/db/cell_ddl.d.ts +1 -2
  80. package/dist/db/cell_ddl.d.ts.map +1 -1
  81. package/dist/db/cell_ddl.js +1 -2
  82. package/dist/db/cell_field_queries.d.ts +20 -18
  83. package/dist/db/cell_field_queries.d.ts.map +1 -1
  84. package/dist/db/cell_field_queries.js +28 -22
  85. package/dist/db/cell_item_queries.d.ts +20 -14
  86. package/dist/db/cell_item_queries.d.ts.map +1 -1
  87. package/dist/db/cell_item_queries.js +28 -18
  88. package/dist/db/cell_moderation.d.ts +67 -0
  89. package/dist/db/cell_moderation.d.ts.map +1 -0
  90. package/dist/db/cell_moderation.js +66 -0
  91. package/dist/db/cell_queries.d.ts +44 -14
  92. package/dist/db/cell_queries.d.ts.map +1 -1
  93. package/dist/db/cell_queries.js +68 -45
  94. package/dist/db/cell_view_sql.d.ts +102 -0
  95. package/dist/db/cell_view_sql.d.ts.map +1 -0
  96. package/dist/db/cell_view_sql.js +112 -0
  97. package/dist/db/db_pg.d.ts +1 -1
  98. package/dist/db/db_pg.js +1 -1
  99. package/dist/http/CLAUDE.md +1 -1
  100. package/dist/http/auth_shape.d.ts +5 -2
  101. package/dist/http/auth_shape.d.ts.map +1 -1
  102. package/dist/http/auth_shape.js +5 -2
  103. package/dist/http/ip_canonical.d.ts +56 -5
  104. package/dist/http/ip_canonical.d.ts.map +1 -1
  105. package/dist/http/ip_canonical.js +147 -35
  106. package/dist/primitive_schemas.d.ts +10 -1
  107. package/dist/primitive_schemas.d.ts.map +1 -1
  108. package/dist/primitive_schemas.js +11 -2
  109. package/dist/rate_limiter.d.ts +179 -12
  110. package/dist/rate_limiter.d.ts.map +1 -1
  111. package/dist/rate_limiter.js +352 -42
  112. package/dist/server/serve_fact_route.d.ts +8 -4
  113. package/dist/server/serve_fact_route.d.ts.map +1 -1
  114. package/dist/server/serve_fact_route.js +15 -6
  115. package/dist/testing/CLAUDE.md +16 -7
  116. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  117. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  118. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  119. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  121. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/cell_relations.js +145 -2
  123. package/dist/testing/cross_backend/expected_schema.json +0 -1
  124. package/dist/testing/cross_backend/full_spine_mount.js +2 -1
  125. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  126. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/login_security.js +154 -0
  128. package/dist/testing/cross_backend/origin_conformance.d.ts +1 -1
  129. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  131. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  132. package/dist/testing/cross_backend/query_shape.js +24 -2
  133. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  134. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  135. package/dist/testing/cross_backend/spine_surface_constants.d.ts +13 -21
  136. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/spine_surface_constants.js +13 -21
  138. package/dist/testing/cross_backend/testing_origin_check.d.ts +128 -0
  139. package/dist/testing/cross_backend/testing_origin_check.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_origin_check.js +87 -0
  141. package/dist/testing/cross_backend/testing_reset_actions.d.ts +7 -119
  142. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  143. package/dist/testing/cross_backend/testing_reset_actions.js +8 -92
  144. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  145. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  146. package/dist/testing/schema_introspect.js +1 -1
  147. package/dist/testing/schema_parity.d.ts +4 -3
  148. package/dist/testing/schema_parity.d.ts.map +1 -1
  149. package/dist/testing/schema_parity.js +4 -3
  150. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  151. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  152. package/dist/testing/schema_ready_fixture.js +2 -4
  153. package/dist/ui/auth_state.svelte.d.ts +10 -0
  154. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  155. package/dist/ui/auth_state.svelte.js +22 -4
  156. package/package.json +1 -1
  157. package/dist/db/cell_history_ddl.d.ts +0 -38
  158. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  159. package/dist/db/cell_history_ddl.js +0 -61
@@ -18,7 +18,8 @@
18
18
  * strings (`'unknown'`, `'attacker:controlled'`, `'::1\n'`) pass
19
19
  * through unchanged so downstream strict validators can still
20
20
  * reject them.
21
- * 2. Parses via Hono's `convertIPv*ToBinary` family.
21
+ * 2. Parses with `parse_ipv4_bits` / `parse_ipv6_bits` — the grammar of
22
+ * Rust's `IpAddr::from_str`, which the spine parses with.
22
23
  * 3. Re-emits the canonical RFC 5952 string (lowercase hex,
23
24
  * longest-zero-run compressed, IPv4-mapped emitted in the dotted
24
25
  * form mandated by RFC 5952 §5).
@@ -27,13 +28,102 @@
27
28
  * canonicalization because the dotted form is the only form the
28
29
  * strip can recognize symmetrically.
29
30
  *
30
- * Mirrors `zzz_server::proxy::normalize_ip` (landed 2026-05-16) which
31
- * uses the same parse-then-canonicalize-then-strip ordering for the
32
- * same rate-limit-key-poisoning surface.
31
+ * Mirrors the Rust spine's `fuz_http::normalize_ip`, which uses the same
32
+ * parse-then-canonicalize-then-strip ordering for the same
33
+ * rate-limit-key-poisoning surface.
34
+ *
35
+ * `rate_limit_ip_key` is the bucket key an IP-keyed rate limiter uses: an
36
+ * IPv6 address collapses to its `/64`, the smallest block a host is commonly
37
+ * assigned, so rotating through one's own subnet buys no fresh budget. Only
38
+ * the limiter call sites apply it — the client IP stays the full address
39
+ * everywhere else (audit rows, token `last_used_ip`, `REMOTE_ADDR`).
40
+ *
41
+ * The module imports nothing: the action dispatcher keys its IP limiter
42
+ * through it, so it must not pull the optional `hono` peer.
33
43
  *
34
44
  * @module
35
45
  */
36
- import { convertIPv6ToBinary, distinctRemoteAddr } from 'hono/utils/ipaddr';
46
+ /** A dotted-quad IPv4 address: four decimal octets, 0–255, no leading zeros. */
47
+ const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/;
48
+ /** One IPv6 group: one to four hex digits. */
49
+ const IPV6_GROUP_REGEX = /^[0-9a-fA-F]{1,4}$/;
50
+ /**
51
+ * Parse a dotted-quad IPv4 address to its 32-bit value.
52
+ *
53
+ * Four decimal octets, each 0–255 with no leading zeros (`01.2.3.4` is
54
+ * refused, as Rust's `Ipv4Addr::from_str` refuses it).
55
+ *
56
+ * @returns the address as an unsigned 32-bit number, or `null` when `ip` isn't one
57
+ */
58
+ export const parse_ipv4_bits = (ip) => {
59
+ if (!IPV4_REGEX.test(ip))
60
+ return null;
61
+ let bits = 0;
62
+ for (const octet of ip.split('.'))
63
+ bits = bits * 256 + Number(octet);
64
+ return bits;
65
+ };
66
+ /**
67
+ * Parse the colon-separated groups on one side of a `::` (or the whole
68
+ * address when there is none), `null` on a malformed one. Only the last group
69
+ * of the address may be a dotted-quad IPv4 tail, which fills two groups.
70
+ */
71
+ const parse_ipv6_groups = (part, allow_ipv4_tail) => {
72
+ if (part === '')
73
+ return [];
74
+ const pieces = part.split(':');
75
+ const groups = [];
76
+ for (let i = 0; i < pieces.length; i++) {
77
+ const piece = pieces[i];
78
+ if (allow_ipv4_tail && i === pieces.length - 1 && piece.includes('.')) {
79
+ const ipv4 = parse_ipv4_bits(piece);
80
+ if (ipv4 === null)
81
+ return null;
82
+ groups.push(Math.floor(ipv4 / 0x10000), ipv4 % 0x10000);
83
+ }
84
+ else if (IPV6_GROUP_REGEX.test(piece)) {
85
+ groups.push(parseInt(piece, 16));
86
+ }
87
+ else {
88
+ return null;
89
+ }
90
+ }
91
+ return groups;
92
+ };
93
+ /**
94
+ * Parse an IPv6 address to its 128-bit value.
95
+ *
96
+ * The grammar of Rust's `Ipv6Addr::from_str`: eight groups of one to four
97
+ * hex digits (either case), or fewer around a single `::` standing for one or
98
+ * more zero groups, the last two groups optionally written as a dotted-quad
99
+ * IPv4 address. No zone (`%eth0`) — callers strip it first.
100
+ *
101
+ * @returns the address as a `bigint` in `[0, 2^128)`, or `null` when `ip` isn't one
102
+ */
103
+ export const parse_ipv6_bits = (ip) => {
104
+ const halves = ip.split('::');
105
+ let groups;
106
+ if (halves.length === 1) {
107
+ const all = parse_ipv6_groups(ip, true);
108
+ if (all === null || all.length !== 8)
109
+ return null;
110
+ groups = all;
111
+ }
112
+ else if (halves.length === 2) {
113
+ const head = parse_ipv6_groups(halves[0], false);
114
+ const tail = parse_ipv6_groups(halves[1], true);
115
+ if (head === null || tail === null || head.length + tail.length > 7)
116
+ return null;
117
+ groups = [...head, ...new Array(8 - head.length - tail.length).fill(0), ...tail];
118
+ }
119
+ else {
120
+ return null;
121
+ }
122
+ let bits = 0n;
123
+ for (const group of groups)
124
+ bits = (bits << 16n) | BigInt(group);
125
+ return bits;
126
+ };
37
127
  /**
38
128
  * Allowed character set for a bare IP literal.
39
129
  *
@@ -80,39 +170,27 @@ export const IP_LITERAL_CHARS = /^[0-9a-fA-F.:]+$/;
80
170
  export const canonicalize_ip = (ip) => {
81
171
  const lowered = ip.toLowerCase();
82
172
  // Strict char-set filter — reject brackets, whitespace, control bytes,
83
- // letters g-z before invoking the parser. Hono's `convertIPv6ToBinary`
84
- // silently accepts `'::1\n'` and similar; canonicalizing those would
85
- // erase the malformed form so downstream `validate_ip_strict` could no
86
- // longer reject it. Pass-through preserves the original string.
173
+ // letters g-z, and `%` (so a zone) before invoking the parser, so a
174
+ // malformed form passes through for downstream `validate_ip_strict` to
175
+ // reject
87
176
  if (!IP_LITERAL_CHARS.test(lowered))
88
177
  return lowered;
89
- const family = distinctRemoteAddr(lowered);
90
- if (family === 'IPv4') {
91
- // IPv4's dotted-decimal form is already canonical — no transform
92
- // needed. Malformed forms (`999.999.999.999`) still pass through
93
- // here; downstream `validate_ip_strict` rejects them via its own
94
- // round-trip parse.
178
+ // IPv4's dotted-decimal form is already canonical — no transform needed.
179
+ // Malformed forms (`999.999.999.999`) fail both parsers and pass through.
180
+ if (parse_ipv4_bits(lowered) !== null)
95
181
  return lowered;
182
+ const bits = parse_ipv6_bits(lowered);
183
+ if (bits === null)
184
+ return lowered;
185
+ const canonical = ipv6_bigint_to_canonical(bits);
186
+ // Strip `::ffff:` only when the canonical form is dotted IPv4-mapped
187
+ // (`::ffff:X.X.X.X`). Pure IPv6 values that happen to start with
188
+ // `::ffff:` (e.g. `::ffff:1` → `0:0:0:0:0:0:ffff:1`, where group[5] is 0
189
+ // not 0xffff) emit without the dot and are preserved.
190
+ if (canonical.startsWith('::ffff:') && canonical.substring(7).includes('.')) {
191
+ return canonical.substring(7);
96
192
  }
97
- if (family === 'IPv6') {
98
- try {
99
- const bits = convertIPv6ToBinary(lowered);
100
- const canonical = ipv6_bigint_to_canonical(bits);
101
- // Strip `::ffff:` only when the canonical form is dotted
102
- // IPv4-mapped (`::ffff:X.X.X.X`). Pure IPv6 values that happen
103
- // to start with `::ffff:` (e.g. `::ffff:1` → `0:0:0:0:0:0:ffff:1`,
104
- // where group[5] is 0 not 0xffff) emit without the dot and
105
- // are preserved.
106
- if (canonical.startsWith('::ffff:') && canonical.substring(7).includes('.')) {
107
- return canonical.substring(7);
108
- }
109
- return canonical;
110
- }
111
- catch {
112
- return lowered;
113
- }
114
- }
115
- return lowered;
193
+ return canonical;
116
194
  };
117
195
  /**
118
196
  * Convert a 128-bit IPv6 binary value into its RFC 5952 canonical string form.
@@ -126,7 +204,7 @@ export const canonicalize_ip = (ip) => {
126
204
  *
127
205
  * Pure helper exported for the test suite to exercise the
128
206
  * canonicalization invariants directly without a full
129
- * `convertIPv6ToBinary` round-trip.
207
+ * `parse_ipv6_bits` round-trip.
130
208
  *
131
209
  * @param bits - the 128-bit IPv6 value as `bigint`. Must satisfy `0n <= bits < 2n ** 128n`;
132
210
  * throws `RangeError` otherwise. Silent truncation would mask caller bugs since the
@@ -193,3 +271,37 @@ export const ipv6_bigint_to_canonical = (bits) => {
193
271
  .join(':');
194
272
  return before + '::' + after;
195
273
  };
274
+ /** The high 64 bits of an IPv6 address — its `/64` network prefix. */
275
+ const IPV6_PREFIX_64_MASK = ((1n << 64n) - 1n) << 64n;
276
+ /**
277
+ * The bucket key an IP-keyed rate limiter uses for client IP `ip`.
278
+ *
279
+ * An IPv6 address keys its `/64`: the low 64 bits masked off, rendered in
280
+ * RFC 5952 canonical form with a `/64` suffix (`2001:db8:1:2::/64`; the
281
+ * all-zero prefix is `::/64`). A zone suffix (`%eth0`) is dropped first. An
282
+ * IPv4 address — an IPv4-mapped IPv6 one included — keys its canonical form,
283
+ * with no suffix. Anything that doesn't parse (`'unknown'` among them) is
284
+ * returned unchanged. Twin of the Rust spine's `fuz_http::rate_limit_ip_key`.
285
+ *
286
+ * For limiter keys only: audit rows and every other record of the client
287
+ * keep the full address.
288
+ *
289
+ * @example
290
+ * rate_limit_ip_key('2001:db8:1:2::1') // → '2001:db8:1:2::/64'
291
+ * rate_limit_ip_key('2001:db8:1:2:8000::') // → '2001:db8:1:2::/64'
292
+ * rate_limit_ip_key('fe80::1%eth0') // → 'fe80::/64'
293
+ * rate_limit_ip_key('203.0.113.7') // → '203.0.113.7'
294
+ * rate_limit_ip_key('unknown') // → 'unknown'
295
+ */
296
+ export const rate_limit_ip_key = (ip) => {
297
+ const zone_at = ip.indexOf('%');
298
+ const canonical = canonicalize_ip(zone_at === -1 ? ip : ip.slice(0, zone_at));
299
+ if (!IP_LITERAL_CHARS.test(canonical))
300
+ return ip;
301
+ if (parse_ipv4_bits(canonical) !== null)
302
+ return canonical;
303
+ const bits = parse_ipv6_bits(canonical);
304
+ if (bits === null)
305
+ return ip;
306
+ return `${ipv6_bigint_to_canonical(bits & IPV6_PREFIX_64_MASK)}/64`;
307
+ };
@@ -117,7 +117,16 @@ export declare const Email: z.ZodString;
117
117
  export type Email = z.infer<typeof Email>;
118
118
  /** Returns a new random (v4) `Uuid`. */
119
119
  export declare const create_uuid: () => Uuid;
120
- /** Branded UUID schema. */
120
+ /**
121
+ * Branded UUID schema. Accepts either case and outputs lowercase — the form
122
+ * Postgres renders a `uuid` in and every id the server mints — so a parsed id
123
+ * compares equal to the same id read back from the database. The Rust spine
124
+ * canonicalizes the same way.
125
+ *
126
+ * `.toLowerCase()` rather than `.transform()`: an overwrite keeps the output
127
+ * type and stays invisible to `z.toJSONSchema`, where a transform would leave
128
+ * no input schema to render.
129
+ */
121
130
  export declare const Uuid: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
122
131
  export type Uuid = z.infer<typeof Uuid>;
123
132
  /** `Uuid` that defaults to a fresh `create_uuid()` when the input is `undefined`. */
@@ -1 +1 @@
1
- {"version":3,"file":"primitive_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/primitive_schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAIlF,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,cAAc,GAAI,2BAA2B,oBAAoB,KAAG,CAAC,CAAC,SA8ClF,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,6GAA6G;AAC7G,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,gNAAgN;AAChN,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,wDAEc,CAAC;AACpC,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,wDAEqE,CAAC;AACnG,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAqBpC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,KAAK,aAYhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAE1C,wCAAwC;AACxC,eAAO,MAAM,WAAW,QAAO,IAAmC,CAAC;AAEnE,2BAA2B;AAC3B,eAAO,MAAM,IAAI,8CAAyB,CAAC;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,qFAAqF;AACrF,eAAO,MAAM,eAAe,4DAA4B,CAAC;AACzD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,wCAAwC;AACxC,eAAO,MAAM,QAAQ,yDAAqC,CAAC;AAC3D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,QAAO,QAAgD,CAAC;AAErF,sFAAsF;AACtF,eAAO,MAAM,WAAW,uEAAqC,CAAC;AAC9D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,uFAAuF;AACvF,eAAO,MAAM,UAAU,aAEuD,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,EAAE,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAEA,CAAC"}
1
+ {"version":3,"file":"primitive_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/primitive_schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAIlF,gEAAgE;AAChE,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,cAAc,GAAI,2BAA2B,oBAAoB,KAAG,CAAC,CAAC,SA8ClF,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,6GAA6G;AAC7G,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,gNAAgN;AAChN,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,wDAEc,CAAC;AACpC,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,wDAEqE,CAAC;AACnG,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAqBpC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,KAAK,aAYhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAE1C,wCAAwC;AACxC,eAAO,MAAM,WAAW,QAAO,IAAmC,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI,8CAAuC,CAAC;AACzD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,qFAAqF;AACrF,eAAO,MAAM,eAAe,4DAA4B,CAAC;AACzD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,wCAAwC;AACxC,eAAO,MAAM,QAAQ,yDAAqC,CAAC;AAC3D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,QAAO,QAAgD,CAAC;AAErF,sFAAsF;AACtF,eAAO,MAAM,WAAW,uEAAqC,CAAC;AAC9D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,uFAAuF;AACvF,eAAO,MAAM,UAAU,aAEuD,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,EAAE,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAEA,CAAC"}
@@ -181,8 +181,17 @@ export const Email = z
181
181
  });
182
182
  /** Returns a new random (v4) `Uuid`. */
183
183
  export const create_uuid = () => crypto.randomUUID();
184
- /** Branded UUID schema. */
185
- export const Uuid = z.uuid().brand('Uuid');
184
+ /**
185
+ * Branded UUID schema. Accepts either case and outputs lowercase — the form
186
+ * Postgres renders a `uuid` in and every id the server mints — so a parsed id
187
+ * compares equal to the same id read back from the database. The Rust spine
188
+ * canonicalizes the same way.
189
+ *
190
+ * `.toLowerCase()` rather than `.transform()`: an overwrite keeps the output
191
+ * type and stays invisible to `z.toJSONSchema`, where a transform would leave
192
+ * no input schema to render.
193
+ */
194
+ export const Uuid = z.uuid().toLowerCase().brand('Uuid');
186
195
  /** `Uuid` that defaults to a fresh `create_uuid()` when the input is `undefined`. */
187
196
  export const UuidWithDefault = Uuid.default(create_uuid);
188
197
  /** Branded ISO 8601 datetime schema. */
@@ -4,6 +4,23 @@
4
4
  * Tracks failed attempts per key (typically IP address) using a sliding
5
5
  * time window. No external dependencies — state resets on server restart.
6
6
  *
7
+ * **Reserve, then settle.** A handler that checks the limiter, awaits work,
8
+ * and records afterwards lets every request of a concurrent burst pass the
9
+ * check before any of them records — a burst of any size gets through.
10
+ * `RateLimiter.try_reserve` closes that: it admits a request and counts it in
11
+ * flight in one synchronous step, and the attempt counts against the cap
12
+ * until its `RateLimitReservation` is settled — `record` keeps it as an
13
+ * attempt, `release` gives it back. Every handler whose outcome decides
14
+ * whether the attempt counts reserves, and releases in a `finally` so an
15
+ * early return or a throw never leaks an in-flight slot; `check` + `record`
16
+ * stays for callers that record in the same synchronous step they check.
17
+ *
18
+ * **Attempts count while in flight, whatever their outcome** — so a burst of
19
+ * *correct* concurrent attempts from one key is capped too. The password
20
+ * surfaces therefore reserve with `RateLimiter.reserve`, which waits up to
21
+ * `RESERVATION_WAIT_MAX_MS` for a slot when the cap is reached only by
22
+ * attempts in flight, and refuses at once when it is reached by recorded ones.
23
+ *
7
24
  * @module
8
25
  */
9
26
  import type { Context } from 'hono';
@@ -19,11 +36,19 @@ export declare const DEFAULT_RATE_LIMITER_MAX_KEYS = 100000;
19
36
  * Configuration for a rate limiter instance.
20
37
  */
21
38
  export interface RateLimiterOptions {
22
- /** Maximum allowed attempts within the window. */
39
+ /**
40
+ * Maximum allowed attempts within the window, a safe integer ≥ 0. `0`
41
+ * refuses every attempt — `check`, `try_reserve`, and `reserve` answer at
42
+ * once with the window's length as `retry_after`, tracking nothing.
43
+ */
23
44
  max_attempts: number;
24
- /** Sliding window duration in milliseconds. */
45
+ /** Sliding window duration in milliseconds, a safe integer ≥ 0. */
25
46
  window_ms: number;
26
- /** Interval for pruning stale entries (0 disables the timer). */
47
+ /**
48
+ * Interval for pruning stale entries, an integer from `0` to
49
+ * `PERIODIC_INTERVAL_MS_MAX` (`0` disables the timer) — the largest delay
50
+ * a timer honours; past it `setInterval` fires every millisecond.
51
+ */
27
52
  cleanup_interval_ms: number;
28
53
  /**
29
54
  * Maximum tracked keys. When exceeded, the least-recently-used key is
@@ -39,6 +64,8 @@ export interface RateLimiterOptions {
39
64
  * can, however, drop out of the table and reset their budget — set
40
65
  * `max_keys` high enough to fit the expected legitimate key set and
41
66
  * this stays theoretical.
67
+ *
68
+ * A number must be a positive safe integer.
42
69
  */
43
70
  max_keys?: number | null;
44
71
  }
@@ -112,21 +139,78 @@ export declare const RATE_LIMITERS_DISABLED_BANNER = "!!! RATE LIMITERS DISABLED
112
139
  * `RateLimiterMode::limiter`.
113
140
  */
114
141
  export declare const announce_rate_limiters_disabled: () => void;
142
+ /**
143
+ * `retry_after` reported while a key is at its cap only because of attempts
144
+ * still in flight. Those settle within a request's lifetime, so the earliest
145
+ * moment worth retrying is the next second, not the oldest recorded attempt's
146
+ * expiry. Mirrors the Rust spine's `IN_FLIGHT_RETRY_AFTER_SECONDS`.
147
+ */
148
+ export declare const IN_FLIGHT_RETRY_AFTER_SECONDS = 1;
149
+ /**
150
+ * How long a password-surface reservation (`RateLimiter.reserve`) waits for a
151
+ * slot when the key is at its cap only because of attempts still in flight,
152
+ * before answering `429`. Mirrors the Rust spine's `RESERVATION_WAIT_MAX`.
153
+ *
154
+ * Counting attempts while they are in flight caps *successful* concurrent
155
+ * attempts from one key too — a class behind one NAT logging in together.
156
+ * Those attempts settle within a request's lifetime, so a short wait turns
157
+ * most of those refusals into a late success, while a key at its cap on
158
+ * *recorded* attempts is still refused at once.
159
+ */
160
+ export declare const RESERVATION_WAIT_MAX_MS = 5000;
115
161
  /**
116
162
  * Result of a rate limit check or record operation.
117
163
  */
118
164
  export interface RateLimitResult {
119
165
  /** Whether the request is allowed. */
120
166
  allowed: boolean;
121
- /** Remaining attempts before blocking. */
167
+ /** Remaining attempts before blocking, counting attempts still in flight. */
122
168
  remaining: number;
123
169
  /** Seconds until the oldest active attempt expires (0 if allowed). */
124
170
  retry_after: number;
125
171
  }
172
+ /**
173
+ * An attempt admitted by `RateLimiter.try_reserve` or `RateLimiter.reserve`,
174
+ * counted against the key's cap until settled.
175
+ *
176
+ * Settle it exactly once: `record` keeps it as an attempt (a failure, on the
177
+ * failure-counting surfaces), `release` gives it back. Both are idempotent and
178
+ * the first call wins, so a handler calls `record` where the outcome is known
179
+ * and `release` in a `finally`. When the key's entry was evicted or reset
180
+ * meanwhile, `record` still records (into the key's current entry) and
181
+ * `release` is a no-op. A reservation made against no limiter
182
+ * (`try_reserve_optional` / `reserve_optional` with `null`) is inert.
183
+ */
184
+ export interface RateLimitReservation {
185
+ /**
186
+ * Settle as a recorded attempt.
187
+ *
188
+ * @param now - current timestamp in ms (defaults to `Date.now()`)
189
+ */
190
+ record: (now?: number) => void;
191
+ /** Settle without recording an attempt. */
192
+ release: () => void;
193
+ }
194
+ /** Result of `RateLimiter.try_reserve` and `RateLimiter.reserve`. */
195
+ export type RateLimitReserveResult = {
196
+ ok: true;
197
+ reservation: RateLimitReservation;
198
+ } | {
199
+ ok: false;
200
+ retry_after: number;
201
+ };
202
+ /** Options for `RateLimiter.reserve`. */
203
+ export interface RateLimitReserveOptions {
204
+ /** How long to wait for a slot. Default `RESERVATION_WAIT_MAX_MS`. */
205
+ max_wait_ms?: number;
206
+ /** Stops the wait (a refusal, `retry_after: IN_FLIGHT_RETRY_AFTER_SECONDS`) — pass the request's signal. */
207
+ signal?: AbortSignal;
208
+ }
126
209
  /**
127
210
  * In-memory sliding window rate limiter.
128
211
  *
129
- * Stores an array of timestamps per key. On `check`/`record`, timestamps
212
+ * Stores an array of timestamps per key, plus a count of attempts reserved
213
+ * (`try_reserve`) and not yet settled. On `check`/`record`, timestamps
130
214
  * outside the window are pruned. `retry_after` reports seconds until the
131
215
  * oldest active timestamp expires.
132
216
  *
@@ -144,15 +228,32 @@ export interface RateLimitResult {
144
228
  export declare class RateLimiter {
145
229
  #private;
146
230
  readonly options: RateLimiterOptions;
231
+ /**
232
+ * @param options - the limiter's bounds
233
+ * @throws {RangeError} when `max_attempts`, `window_ms`, or `cleanup_interval_ms` is not a safe integer ≥ 0, `cleanup_interval_ms` exceeds `PERIODIC_INTERVAL_MS_MAX`, or `max_keys` is neither `null` nor a positive safe integer
234
+ */
147
235
  constructor(options: RateLimiterOptions);
148
236
  /** Number of tracked keys. */
149
237
  get size(): number;
150
238
  /**
151
- * Check whether `key` is allowed without recording an attempt.
239
+ * Keys holding a parked-waiter slot right now (see `reserve`): each slot is
240
+ * removed with its key's last waiter. For tests pinning that a waiter
241
+ * leaves nothing behind; the twin of the Rust spine's test-only
242
+ * `waiter_keys`.
243
+ *
244
+ * @internal
245
+ */
246
+ get waiter_keys(): number;
247
+ /**
248
+ * Check whether `key` is allowed without recording an attempt, counting
249
+ * recorded attempts and reserved ones still in flight.
152
250
  *
153
251
  * Prunes timestamps that fell outside the window as a side effect (and
154
- * removes the key entirely when none remain), so the backing map stays
155
- * bounded even under read-only traffic.
252
+ * removes the key entirely when none remain and none are in flight), so
253
+ * the backing map stays bounded even under read-only traffic.
254
+ *
255
+ * A check followed by an awaited operation and then a `record` lets a
256
+ * concurrent burst past the cap; reserve with `try_reserve` instead.
156
257
  *
157
258
  * @param key - rate limit key (e.g. IP address)
158
259
  * @param now - current timestamp in ms (defaults to `Date.now()`)
@@ -167,6 +268,44 @@ export declare class RateLimiter {
167
268
  * @mutates internal map - appends `now` to the timestamp list for `key` (after pruning expired entries)
168
269
  */
169
270
  record(key: string, now?: number): RateLimitResult;
271
+ /**
272
+ * Admit an attempt for `key` and count it in flight, in one synchronous
273
+ * step.
274
+ *
275
+ * The attempt counts against the cap — for this request and every
276
+ * concurrent one — until the returned reservation is settled: recorded as
277
+ * an attempt, or released. So no burst of concurrent requests gets more
278
+ * than `max_attempts` past the limiter between them. Release in a
279
+ * `finally`: nothing settles a reservation for you, so one left unsettled
280
+ * holds its slot for the life of the process.
281
+ *
282
+ * @param key - rate limit key (e.g. IP address)
283
+ * @param now - current timestamp in ms (defaults to `Date.now()`)
284
+ * @returns the reservation, or `{ok: false, retry_after}` when recorded plus in-flight attempts already reach the cap (nothing is reserved then)
285
+ * @mutates internal map - increments `key`'s in-flight count, creating its entry when absent
286
+ */
287
+ try_reserve(key: string, now?: number): RateLimitReserveResult;
288
+ /**
289
+ * `try_reserve`, waiting up to `max_wait_ms` for a slot when the key is at
290
+ * its cap only because of attempts still in flight.
291
+ *
292
+ * Each settle (and record and reset) on the key wakes its waiters to
293
+ * re-decide — that key's only, so a settle costs nothing for waiters parked
294
+ * elsewhere: a freed slot is reserved; recorded attempts reaching the cap
295
+ * refuse at once with their real `retry_after`; reaching `max_wait_ms` (or
296
+ * `signal` aborting) refuses with `IN_FLIGHT_RETRY_AFTER_SECONDS`. At most
297
+ * `max_attempts` reservations wait on one key — one more is refused at
298
+ * once — so a flood can't park requests without bound. A waiter that
299
+ * leaves, by any path, leaves the key's waiter count as it found it. A
300
+ * waiter holds no place in a queue: each wake, every waiter re-decides, so
301
+ * one can lose every race for a freed slot until `max_wait_ms` refuses it.
302
+ *
303
+ * @param key - rate limit key (e.g. IP address)
304
+ * @param options - the wait bound and an abort signal
305
+ * @returns the reservation, or `{ok: false, retry_after}` as described (nothing is reserved then)
306
+ * @mutates internal map - increments `key`'s in-flight count on admission; counts the call in `key`'s waiters while it waits
307
+ */
308
+ reserve(key: string, options?: RateLimitReserveOptions): Promise<RateLimitReserveResult>;
170
309
  /**
171
310
  * Clear all attempts for `key`.
172
311
  *
@@ -181,8 +320,9 @@ export declare class RateLimiter {
181
320
  * otherwise an attacker interleaves their own logins (or, under open signup,
182
321
  * throwaway account creations needing no credential at all) with guesses
183
322
  * against arbitrary victims and sprays indefinitely from one address.
184
- * Nothing records on the IP bucket on success on any path, so with no reset
185
- * it is a pure monotone-within-window failure counter that only time clears.
323
+ * The only success that records on an IP bucket is an open signup (charged
324
+ * like a failure); no success clears one, so with no reset each is a
325
+ * monotone-within-window counter that only time clears.
186
326
  *
187
327
  * Because they are monotone, the IP buckets are **one instance per auth
188
328
  * surface** (`login_ip_rate_limiter`, `signup_ip_rate_limiter`,
@@ -192,11 +332,15 @@ export declare class RateLimiter {
192
332
  * four routes at once — a cost that used to be masked by the reset this
193
333
  * doc forbids.
194
334
  *
195
- * @mutates internal map - removes the entry for `key`
335
+ * Attempts still in flight on `key` (reserved by concurrent requests) are
336
+ * kept: a reset forgives what was recorded, not what is still running.
337
+ *
338
+ * @mutates internal map - removes the entry for `key`, or clears its timestamps while attempts are in flight
196
339
  */
197
340
  reset(key: string): void;
198
341
  /**
199
- * Remove entries whose timestamps are all outside the window.
342
+ * Remove entries whose timestamps are all outside the window and that
343
+ * have no attempt in flight.
200
344
  *
201
345
  * @param now - current timestamp in ms (defaults to `Date.now()`)
202
346
  * @mutates internal map - prunes expired timestamps and deletes empty keys
@@ -209,6 +353,29 @@ export declare class RateLimiter {
209
353
  */
210
354
  dispose(): void;
211
355
  }
356
+ /**
357
+ * `RateLimiter.try_reserve` against a limiter that may be absent.
358
+ *
359
+ * An inert reservation when `limiter` is `null` (disabled) or `key` is `null`
360
+ * (no resolvable key — e.g. no client IP), so a handler reserves and settles
361
+ * the same way whether or not the surface is limited. Twin of the Rust
362
+ * spine's `try_reserve_optional`.
363
+ *
364
+ * @param limiter - the limiter, or `null` when disabled
365
+ * @param key - rate limit key, or `null` to skip
366
+ * @param now - current timestamp in ms (defaults to `Date.now()`)
367
+ */
368
+ export declare const try_reserve_optional: (limiter: RateLimiter | null, key: string | null, now?: number) => RateLimitReserveResult;
369
+ /**
370
+ * `RateLimiter.reserve` against a limiter that may be absent — an inert
371
+ * reservation when `limiter` or `key` is `null`. Twin of the Rust spine's
372
+ * `reserve_optional`.
373
+ *
374
+ * @param limiter - the limiter, or `null` when disabled
375
+ * @param key - rate limit key, or `null` to skip
376
+ * @param options - the wait bound and an abort signal
377
+ */
378
+ export declare const reserve_optional: (limiter: RateLimiter | null, key: string | null, options?: RateLimitReserveOptions) => Promise<RateLimitReserveResult>;
212
379
  /**
213
380
  * Create a `RateLimiter` with sensible defaults for per-IP login protection.
214
381
  *
@@ -1 +1 @@
1
- {"version":3,"file":"rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/rate_limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,EAAE,kBAKzC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,gCAAgC,EAAE,kBAK9C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,kBAK1C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,EAAE,kBAK/C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,sBAAsB,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,6BAA6B,iGACgD,CAAC;AAI3F;;;;;;;;;;GAUG;AACH,eAAO,MAAM,+BAA+B,QAAO,IAIlD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,WAAW;;IACvB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;gBAOzB,OAAO,EAAE,kBAAkB;IAcvC,8BAA8B;IAC9B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAcD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,eAAe;IA2B7D;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,eAAe;IA0B9D;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIxB;;;;;OAKG;IACH,OAAO,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI;IAgBvC;;;;OAIG;IACH,OAAO,IAAI,IAAI;CAMf;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,OAAO,CAAC,kBAAkB,CAAC,KAAG,WAE3E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,GAAG,OAAO,EAAE,aAAa,MAAM,KAAG,QAI7E,CAAC"}
1
+ {"version":3,"file":"rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/rate_limiter.ts"],"names":[],"mappings":"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"}