@fuzdev/fuz_app 0.64.0 → 0.66.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 (223) hide show
  1. package/dist/actions/CLAUDE.md +510 -946
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +1 -1
  4. package/dist/actions/action_event_data.d.ts +1 -1
  5. package/dist/actions/broadcast_api.d.ts +1 -1
  6. package/dist/actions/broadcast_api.js +1 -1
  7. package/dist/actions/cancel.d.ts +2 -2
  8. package/dist/actions/cancel.js +3 -3
  9. package/dist/actions/connection_closer.d.ts +1 -4
  10. package/dist/actions/connection_closer.d.ts.map +1 -1
  11. package/dist/actions/connection_closer.js +1 -4
  12. package/dist/actions/register_action_ws.d.ts +2 -2
  13. package/dist/actions/register_ws_endpoint.d.ts +1 -1
  14. package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
  15. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  16. package/dist/actions/transports_ws_auth_guard.js +1 -2
  17. package/dist/auth/CLAUDE.md +570 -1871
  18. package/dist/auth/account_schema.d.ts +1 -1
  19. package/dist/auth/account_schema.d.ts.map +1 -1
  20. package/dist/auth/api_token_queries.js +1 -1
  21. package/dist/auth/audit_log_ddl.d.ts +1 -1
  22. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  23. package/dist/auth/audit_log_ddl.js +1 -1
  24. package/dist/auth/audit_log_schema.js +2 -2
  25. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  26. package/dist/auth/bootstrap_account.js +1 -5
  27. package/dist/auth/bootstrap_routes.d.ts +7 -1
  28. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  29. package/dist/auth/bootstrap_routes.js +15 -11
  30. package/dist/auth/daemon_token_middleware.d.ts +15 -5
  31. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  32. package/dist/auth/daemon_token_middleware.js +24 -15
  33. package/dist/auth/invite_queries.d.ts +17 -7
  34. package/dist/auth/invite_queries.d.ts.map +1 -1
  35. package/dist/auth/invite_queries.js +19 -8
  36. package/dist/auth/keyring.d.ts +6 -6
  37. package/dist/auth/keyring.js +8 -8
  38. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  39. package/dist/auth/role_grant_offer_actions.js +4 -2
  40. package/dist/auth/signup_routes.d.ts +47 -1
  41. package/dist/auth/signup_routes.d.ts.map +1 -1
  42. package/dist/auth/signup_routes.js +103 -52
  43. package/dist/db/create_db.d.ts.map +1 -1
  44. package/dist/db/create_db.js +13 -0
  45. package/dist/dev/setup.d.ts +2 -2
  46. package/dist/dev/setup.js +3 -3
  47. package/dist/env/resolve.d.ts +44 -7
  48. package/dist/env/resolve.d.ts.map +1 -1
  49. package/dist/env/resolve.js +94 -27
  50. package/dist/http/CLAUDE.md +243 -522
  51. package/dist/http/error_schemas.d.ts +0 -4
  52. package/dist/http/error_schemas.d.ts.map +1 -1
  53. package/dist/http/error_schemas.js +0 -4
  54. package/dist/http/ip_canonical.d.ts +5 -4
  55. package/dist/http/ip_canonical.d.ts.map +1 -1
  56. package/dist/http/ip_canonical.js +8 -4
  57. package/dist/http/jsonrpc.d.ts +23 -7
  58. package/dist/http/jsonrpc.d.ts.map +1 -1
  59. package/dist/http/jsonrpc.js +19 -3
  60. package/dist/http/origin.d.ts +1 -1
  61. package/dist/http/origin.js +1 -1
  62. package/dist/http/surface.d.ts +9 -2
  63. package/dist/http/surface.d.ts.map +1 -1
  64. package/dist/runtime/mock.d.ts +1 -1
  65. package/dist/runtime/mock.js +2 -2
  66. package/dist/server/app_server.d.ts +41 -10
  67. package/dist/server/app_server.d.ts.map +1 -1
  68. package/dist/server/app_server.js +10 -4
  69. package/dist/server/env.d.ts +7 -7
  70. package/dist/server/env.d.ts.map +1 -1
  71. package/dist/server/env.js +14 -14
  72. package/dist/server/static.d.ts +4 -4
  73. package/dist/server/static.js +7 -7
  74. package/dist/testing/CLAUDE.md +740 -418
  75. package/dist/testing/admin_integration.d.ts +18 -23
  76. package/dist/testing/admin_integration.d.ts.map +1 -1
  77. package/dist/testing/admin_integration.js +230 -216
  78. package/dist/testing/app_server.d.ts +141 -39
  79. package/dist/testing/app_server.d.ts.map +1 -1
  80. package/dist/testing/app_server.js +157 -44
  81. package/dist/testing/audit_completeness.d.ts +25 -22
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +198 -159
  84. package/dist/testing/bootstrap_success.d.ts +28 -0
  85. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  86. package/dist/testing/bootstrap_success.js +144 -0
  87. package/dist/testing/cross_backend/backend_config.d.ts +113 -0
  88. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -0
  89. package/dist/testing/cross_backend/backend_config.js +1 -0
  90. package/dist/testing/cross_backend/bench/bench_report.d.ts +46 -0
  91. package/dist/testing/cross_backend/bench/bench_report.d.ts.map +1 -0
  92. package/dist/testing/cross_backend/bench/bench_report.js +83 -0
  93. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +44 -0
  94. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts.map +1 -0
  95. package/dist/testing/cross_backend/bench/run_cross_impl_bench.js +38 -0
  96. package/dist/testing/cross_backend/bench/scenario.d.ts +57 -0
  97. package/dist/testing/cross_backend/bench/scenario.d.ts.map +1 -0
  98. package/dist/testing/cross_backend/bench/scenario.js +28 -0
  99. package/dist/testing/cross_backend/bootstrap_backend.d.ts +41 -0
  100. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -0
  101. package/dist/testing/cross_backend/bootstrap_backend.js +34 -0
  102. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +24 -0
  103. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -0
  104. package/dist/testing/cross_backend/build_test_backend_paths.js +33 -0
  105. package/dist/testing/cross_backend/capabilities.d.ts +65 -0
  106. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/capabilities.js +47 -0
  108. package/dist/testing/cross_backend/default_backend_configs.d.ts +122 -0
  109. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -0
  110. package/dist/testing/cross_backend/default_backend_configs.js +111 -0
  111. package/dist/testing/cross_backend/default_secrets.d.ts +40 -0
  112. package/dist/testing/cross_backend/default_secrets.d.ts.map +1 -0
  113. package/dist/testing/cross_backend/default_secrets.js +39 -0
  114. package/dist/testing/cross_backend/default_spine_surface.d.ts +64 -0
  115. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -0
  116. package/dist/testing/cross_backend/default_spine_surface.js +121 -0
  117. package/dist/testing/cross_backend/setup.d.ts +451 -0
  118. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  119. package/dist/testing/cross_backend/setup.js +581 -0
  120. package/dist/testing/cross_backend/spawn_backend.d.ts +58 -0
  121. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -0
  122. package/dist/testing/cross_backend/spawn_backend.js +229 -0
  123. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts +66 -0
  124. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts.map +1 -0
  125. package/dist/testing/cross_backend/spine_stub_backend_config.js +49 -0
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +37 -0
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/sse_round_trip.js +137 -0
  129. package/dist/testing/cross_backend/standard.d.ts +96 -0
  130. package/dist/testing/cross_backend/standard.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/standard.js +49 -0
  132. package/dist/testing/cross_backend/testing_reset_actions.d.ts +171 -0
  133. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/testing_reset_actions.js +213 -0
  135. package/dist/testing/cross_backend/testing_server_bun.d.ts +5 -0
  136. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -0
  137. package/dist/testing/cross_backend/testing_server_bun.js +59 -0
  138. package/dist/testing/cross_backend/testing_server_core.d.ts +140 -0
  139. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_server_core.js +68 -0
  141. package/dist/testing/cross_backend/testing_server_deno.d.ts +5 -0
  142. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/testing_server_deno.js +37 -0
  144. package/dist/testing/cross_backend/testing_server_node.d.ts +5 -0
  145. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/testing_server_node.js +50 -0
  147. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +72 -0
  148. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -0
  149. package/dist/testing/cross_backend/ts_spine_backend_config.js +112 -0
  150. package/dist/testing/cross_backend/ws_round_trip.d.ts +35 -0
  151. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -0
  152. package/dist/testing/cross_backend/ws_round_trip.js +113 -0
  153. package/dist/testing/data_exposure.d.ts +11 -14
  154. package/dist/testing/data_exposure.d.ts.map +1 -1
  155. package/dist/testing/data_exposure.js +123 -146
  156. package/dist/testing/db_entities.d.ts +22 -1
  157. package/dist/testing/db_entities.d.ts.map +1 -1
  158. package/dist/testing/db_entities.js +24 -1
  159. package/dist/testing/integration.d.ts +56 -21
  160. package/dist/testing/integration.d.ts.map +1 -1
  161. package/dist/testing/integration.js +294 -319
  162. package/dist/testing/integration_helpers.d.ts +16 -6
  163. package/dist/testing/integration_helpers.d.ts.map +1 -1
  164. package/dist/testing/integration_helpers.js +7 -7
  165. package/dist/testing/mock_fs.d.ts.map +1 -1
  166. package/dist/testing/mock_fs.js +0 -2
  167. package/dist/testing/rate_limiting.d.ts.map +1 -1
  168. package/dist/testing/rate_limiting.js +9 -0
  169. package/dist/testing/role_grant_helpers.d.ts +31 -0
  170. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  171. package/dist/testing/role_grant_helpers.js +46 -0
  172. package/dist/testing/round_trip.d.ts +20 -16
  173. package/dist/testing/round_trip.d.ts.map +1 -1
  174. package/dist/testing/round_trip.js +61 -86
  175. package/dist/testing/rpc_helpers.d.ts +10 -4
  176. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  177. package/dist/testing/rpc_helpers.js +1 -1
  178. package/dist/testing/rpc_round_trip.d.ts +24 -21
  179. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  180. package/dist/testing/rpc_round_trip.js +87 -104
  181. package/dist/testing/schema_introspect.d.ts +106 -0
  182. package/dist/testing/schema_introspect.d.ts.map +1 -0
  183. package/dist/testing/schema_introspect.js +123 -0
  184. package/dist/testing/schema_parity.d.ts +144 -0
  185. package/dist/testing/schema_parity.d.ts.map +1 -0
  186. package/dist/testing/schema_parity.js +233 -0
  187. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  188. package/dist/testing/sse_round_trip.js +1 -68
  189. package/dist/testing/standard.d.ts +56 -25
  190. package/dist/testing/standard.d.ts.map +1 -1
  191. package/dist/testing/standard.js +62 -5
  192. package/dist/testing/stubs.d.ts +21 -6
  193. package/dist/testing/stubs.d.ts.map +1 -1
  194. package/dist/testing/stubs.js +33 -23
  195. package/dist/testing/testing_rate_limiter.d.ts +59 -0
  196. package/dist/testing/testing_rate_limiter.d.ts.map +1 -0
  197. package/dist/testing/testing_rate_limiter.js +74 -0
  198. package/dist/testing/transports/bootstrap.d.ts +52 -0
  199. package/dist/testing/transports/bootstrap.d.ts.map +1 -0
  200. package/dist/testing/transports/bootstrap.js +70 -0
  201. package/dist/testing/transports/fetch_transport.d.ts +81 -0
  202. package/dist/testing/transports/fetch_transport.d.ts.map +1 -0
  203. package/dist/testing/transports/fetch_transport.js +74 -0
  204. package/dist/testing/transports/sse_frame_reader.d.ts +41 -0
  205. package/dist/testing/transports/sse_frame_reader.d.ts.map +1 -0
  206. package/dist/testing/transports/sse_frame_reader.js +84 -0
  207. package/dist/testing/transports/sse_transport.d.ts +54 -0
  208. package/dist/testing/transports/sse_transport.d.ts.map +1 -0
  209. package/dist/testing/transports/sse_transport.js +51 -0
  210. package/dist/testing/transports/ws_client.d.ts +108 -0
  211. package/dist/testing/transports/ws_client.d.ts.map +1 -0
  212. package/dist/testing/transports/ws_client.js +56 -0
  213. package/dist/testing/transports/ws_transport.d.ts +43 -0
  214. package/dist/testing/transports/ws_transport.d.ts.map +1 -0
  215. package/dist/testing/transports/ws_transport.js +169 -0
  216. package/dist/testing/ws_round_trip.d.ts +21 -103
  217. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  218. package/dist/testing/ws_round_trip.js +42 -40
  219. package/dist/ui/CLAUDE.md +5 -3
  220. package/dist/ui/MenuLink.svelte +16 -16
  221. package/dist/ui/MenuLink.svelte.d.ts +13 -4
  222. package/dist/ui/MenuLink.svelte.d.ts.map +1 -1
  223. package/package.json +10 -4
@@ -17,7 +17,6 @@ import { create_app_surface_spec, } from '../http/surface.js';
17
17
  import { AUDIT_LOG_SSE_MAX_PER_SCOPE } from '../realtime/sse_auth_guard.js';
18
18
  import { SubscriberRegistry } from '../realtime/subscriber_registry.js';
19
19
  import { BaseServerEnv } from '../server/env.js';
20
- /* eslint-disable @typescript-eslint/require-await */
21
20
  /**
22
21
  * Create a Proxy that throws descriptive errors on any property access or method call.
23
22
  *
@@ -102,10 +101,10 @@ const stub_db = create_noop_stub('stub_db');
102
101
  * `create_test_app` already does this on the test backend.
103
102
  */
104
103
  export const create_test_audit_emitter = () => ({
105
- emit: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
106
- emit_role_grant_target: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
107
- emit_pool: async () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
108
- notify: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
104
+ emit: () => { },
105
+ emit_role_grant_target: () => { },
106
+ emit_pool: async () => { },
107
+ notify: () => { },
109
108
  on_event_chain: Object.freeze([]),
110
109
  });
111
110
  /**
@@ -123,9 +122,9 @@ export const create_stub_audit_sse = () => {
123
122
  max_per_scope: AUDIT_LOG_SSE_MAX_PER_SCOPE,
124
123
  });
125
124
  return {
126
- subscribe: () => () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
125
+ subscribe: () => () => { },
127
126
  log: new Logger('test:audit_sse', { level: 'off' }),
128
- on_audit_event: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
127
+ on_audit_event: () => { },
129
128
  registry,
130
129
  };
131
130
  };
@@ -146,7 +145,7 @@ export const stub_app_deps = {
146
145
  export const create_stub_app_deps = () => ({
147
146
  stat: async () => null,
148
147
  read_text_file: async () => '',
149
- delete_file: async (_path) => { }, // eslint-disable-line @typescript-eslint/no-empty-function
148
+ delete_file: async (_path) => { },
150
149
  keyring: create_noop_stub('keyring'),
151
150
  password: create_noop_stub('password'),
152
151
  db: stub_db,
@@ -193,7 +192,7 @@ export const create_stub_app_server_context = (session_options) => {
193
192
  db_type: 'pglite-memory',
194
193
  db_name: 'test',
195
194
  migration_results: [],
196
- close: async () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
195
+ close: async () => { },
197
196
  },
198
197
  bootstrap_status: { available: false, token_path: null },
199
198
  session_options,
@@ -207,7 +206,14 @@ export const create_stub_app_server_context = (session_options) => {
207
206
  };
208
207
  };
209
208
  /**
210
- * Create an `AppSurfaceSpec` for attack surface testing.
209
+ * Create an `AppSurfaceSpec` for the standard testing suites.
210
+ *
211
+ * Used by both in-process and cross-process tests as the schema source —
212
+ * the cross-process-ness lives in the transport + per-test fixture, not
213
+ * here. The on-disk `*_attack_surface.json` snapshot is observability
214
+ * (gen-time drift detection via `assert_surface_matches_snapshot`); the
215
+ * suites consume the spec object this function returns, not the JSON
216
+ * file.
211
217
  *
212
218
  * Mirrors `create_app_server`'s route assembly: consumer routes +
213
219
  * factory-managed bootstrap routes + surface generation. If
@@ -215,18 +221,11 @@ export const create_stub_app_server_context = (session_options) => {
215
221
  * to stay in sync (single source of truth for all consumers).
216
222
  *
217
223
  * @param options - surface spec options
218
- * @returns the surface spec for snapshot and adversarial testing
224
+ * @returns the surface spec for the standard suites
219
225
  */
220
226
  export const create_test_app_surface_spec = (options) => {
221
227
  const ctx = create_stub_app_server_context(options.session_options);
222
228
  const consumer_routes = options.create_route_specs(ctx);
223
- // Mirror create_app_server's factory-managed route assembly
224
- const bootstrap_routes = create_bootstrap_route_specs(ctx.deps, {
225
- session_options: options.session_options,
226
- bootstrap_status: { available: false, token_path: null },
227
- ip_rate_limiter: null,
228
- });
229
- const prefix = options.bootstrap_route_prefix ?? '/api/account';
230
229
  // Auto-mount rpc endpoints (mirrors create_app_server) so consumer
231
230
  // `create_route_specs` does not need to call `create_rpc_endpoint`.
232
231
  const resolved_rpc_endpoints = typeof options.rpc_endpoints === 'function'
@@ -240,11 +239,22 @@ export const create_test_app_surface_spec = (options) => {
240
239
  // Resolve ws endpoints (mirrors create_app_server). Surface-only —
241
240
  // no `register_ws_endpoint` call here, so no `upgradeWebSocket` needed.
242
241
  const resolved_ws_endpoints = typeof options.ws_endpoints === 'function' ? options.ws_endpoints(ctx) : options.ws_endpoints;
243
- const route_specs = [
244
- ...consumer_routes,
245
- ...rpc_route_specs,
246
- ...prefix_route_specs(prefix, bootstrap_routes),
247
- ];
242
+ // Bootstrap routes mirror `create_app_server`: mounted for `surface_only`
243
+ // and `live` modes; omitted for `disabled` / undefined. Surface generation
244
+ // uses an `available: false` placeholder regardless of mode — the handler
245
+ // short-circuits to 403 ALREADY_BOOTSTRAPPED, which is what surface tests
246
+ // assert on. Live token_path is passed through for shape symmetry only.
247
+ const bootstrap_route_specs = options.bootstrap && options.bootstrap.mode !== 'disabled'
248
+ ? prefix_route_specs(options.bootstrap.route_prefix ?? '/api/account', create_bootstrap_route_specs(ctx.deps, {
249
+ session_options: options.session_options,
250
+ bootstrap_status: {
251
+ available: false,
252
+ token_path: options.bootstrap.mode === 'live' ? options.bootstrap.token_path : null,
253
+ },
254
+ ip_rate_limiter: null,
255
+ }))
256
+ : [];
257
+ const route_specs = [...consumer_routes, ...rpc_route_specs, ...bootstrap_route_specs];
248
258
  let middleware_specs = create_stub_api_middleware();
249
259
  if (options.transform_middleware) {
250
260
  middleware_specs = options.transform_middleware(middleware_specs);
@@ -0,0 +1,59 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Test-only `RateLimiter` subclass with bucket tracking + reset_all.
4
+ *
5
+ * Production code never instantiates this; test binaries swap it in for
6
+ * production `RateLimiter` so `_testing_reset` can clear every bucket
7
+ * between cases. Mirrors the `TestingArgon2idHasher` pattern from the
8
+ * Rust spine — same call surface as the production class, plus test-only
9
+ * knobs (`reset_all`, `tracked_keys`).
10
+ *
11
+ * Constructor + every overridden method preserve production semantics
12
+ * by delegating to `super.*` after tracking; `reset_all` walks the
13
+ * tracked-keys set and calls `super.reset` per entry. Tests that depend
14
+ * on burst behavior get identical results between production and test
15
+ * instantiations.
16
+ *
17
+ * Usage in a test binary:
18
+ *
19
+ * ```ts
20
+ * import {TestingRateLimiter} from '@fuzdev/fuz_app/testing/testing_rate_limiter.js';
21
+ *
22
+ * const limiter = new TestingRateLimiter(default_login_ip_rate_limit);
23
+ * await create_app_server({backend, ip_rate_limiter: limiter, ...});
24
+ *
25
+ * // Inside the `_testing_reset` handler's `reset_state`:
26
+ * limiter.reset_all();
27
+ * ```
28
+ *
29
+ * @module
30
+ */
31
+ import { RateLimiter, type RateLimitResult } from '../rate_limiter.js';
32
+ /**
33
+ * `RateLimiter` plus bucket tracking. Every `check`/`record` call adds
34
+ * its key to `#seen_keys`; `reset` removes it; `reset_all` clears every
35
+ * tracked bucket. Drop-in replacement anywhere a `RateLimiter` is
36
+ * expected — the type is nominally compatible via subclassing.
37
+ */
38
+ export declare class TestingRateLimiter extends RateLimiter {
39
+ #private;
40
+ check(key: string, now?: number): RateLimitResult;
41
+ record(key: string, now?: number): RateLimitResult;
42
+ reset(key: string): void;
43
+ /**
44
+ * Clear every bucket this limiter has been asked about. Idempotent;
45
+ * safe to call before any check/record activity. Designed to be invoked
46
+ * from a `_testing_reset` handler's `reset_state` callback so the test
47
+ * binary's rate-limit buckets don't leak across test cases.
48
+ */
49
+ reset_all(): void;
50
+ /**
51
+ * Snapshot of every bucket key this limiter has observed via
52
+ * `check`/`record`. Doesn't reflect post-cleanup pruning — keys that
53
+ * `cleanup()` removed remain in `tracked_keys` until `reset`/`reset_all`
54
+ * runs (or the limiter is disposed). Useful for assertions like
55
+ * "limiter saw exactly N IPs" in tests.
56
+ */
57
+ get tracked_keys(): ReadonlySet<string>;
58
+ }
59
+ //# sourceMappingURL=testing_rate_limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing_rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/testing_rate_limiter.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAC,WAAW,EAAE,KAAK,eAAe,EAAC,MAAM,oBAAoB,CAAC;AAErE;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;;IAGzC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAKjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAKlD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjC;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,WAAW,CAAC,MAAM,CAAC,CAEtC;CACD"}
@@ -0,0 +1,74 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Test-only `RateLimiter` subclass with bucket tracking + reset_all.
4
+ *
5
+ * Production code never instantiates this; test binaries swap it in for
6
+ * production `RateLimiter` so `_testing_reset` can clear every bucket
7
+ * between cases. Mirrors the `TestingArgon2idHasher` pattern from the
8
+ * Rust spine — same call surface as the production class, plus test-only
9
+ * knobs (`reset_all`, `tracked_keys`).
10
+ *
11
+ * Constructor + every overridden method preserve production semantics
12
+ * by delegating to `super.*` after tracking; `reset_all` walks the
13
+ * tracked-keys set and calls `super.reset` per entry. Tests that depend
14
+ * on burst behavior get identical results between production and test
15
+ * instantiations.
16
+ *
17
+ * Usage in a test binary:
18
+ *
19
+ * ```ts
20
+ * import {TestingRateLimiter} from '@fuzdev/fuz_app/testing/testing_rate_limiter.js';
21
+ *
22
+ * const limiter = new TestingRateLimiter(default_login_ip_rate_limit);
23
+ * await create_app_server({backend, ip_rate_limiter: limiter, ...});
24
+ *
25
+ * // Inside the `_testing_reset` handler's `reset_state`:
26
+ * limiter.reset_all();
27
+ * ```
28
+ *
29
+ * @module
30
+ */
31
+ import { RateLimiter } from '../rate_limiter.js';
32
+ /**
33
+ * `RateLimiter` plus bucket tracking. Every `check`/`record` call adds
34
+ * its key to `#seen_keys`; `reset` removes it; `reset_all` clears every
35
+ * tracked bucket. Drop-in replacement anywhere a `RateLimiter` is
36
+ * expected — the type is nominally compatible via subclassing.
37
+ */
38
+ export class TestingRateLimiter extends RateLimiter {
39
+ #seen_keys = new Set();
40
+ check(key, now) {
41
+ this.#seen_keys.add(key);
42
+ return super.check(key, now);
43
+ }
44
+ record(key, now) {
45
+ this.#seen_keys.add(key);
46
+ return super.record(key, now);
47
+ }
48
+ reset(key) {
49
+ this.#seen_keys.delete(key);
50
+ super.reset(key);
51
+ }
52
+ /**
53
+ * Clear every bucket this limiter has been asked about. Idempotent;
54
+ * safe to call before any check/record activity. Designed to be invoked
55
+ * from a `_testing_reset` handler's `reset_state` callback so the test
56
+ * binary's rate-limit buckets don't leak across test cases.
57
+ */
58
+ reset_all() {
59
+ for (const key of this.#seen_keys) {
60
+ super.reset(key);
61
+ }
62
+ this.#seen_keys.clear();
63
+ }
64
+ /**
65
+ * Snapshot of every bucket key this limiter has observed via
66
+ * `check`/`record`. Doesn't reflect post-cleanup pruning — keys that
67
+ * `cleanup()` removed remain in `tracked_keys` until `reset`/`reset_all`
68
+ * runs (or the limiter is disposed). Useful for assertions like
69
+ * "limiter saw exactly N IPs" in tests.
70
+ */
71
+ get tracked_keys() {
72
+ return this.#seen_keys;
73
+ }
74
+ }
@@ -0,0 +1,52 @@
1
+ import '../assert_dev_env.js';
2
+ import { Uuid } from '@fuzdev/fuz_util/id.js';
3
+ import type { BackendConfig } from '../cross_backend/backend_config.js';
4
+ import type { FetchTransport } from './fetch_transport.js';
5
+ /** Input for `bootstrap()`. */
6
+ export interface BootstrapOptions {
7
+ /**
8
+ * The cookie-threading HTTP transport pointed at the binary. After
9
+ * `bootstrap()` resolves, the transport carries the keeper session
10
+ * cookie — every later call against it is authenticated as keeper.
11
+ */
12
+ readonly transport: FetchTransport;
13
+ /**
14
+ * Backend config — used for `bootstrap_path` plus the
15
+ * `bootstrap.username` / `bootstrap.password` / `bootstrap.token`
16
+ * credentials. The runner already wrote `bootstrap.token` to
17
+ * `bootstrap.token_path` before spawning, so the binary picks the
18
+ * token up at startup.
19
+ */
20
+ readonly config: BackendConfig;
21
+ }
22
+ /** The keeper credentials captured from `POST /api/account/bootstrap`. */
23
+ export interface BootstrapResult {
24
+ /**
25
+ * Same transport that came in, now carrying the keeper session
26
+ * cookie in its jar. Returned for call-site clarity (callers don't
27
+ * have to remember the mutation happens in place).
28
+ */
29
+ readonly transport: FetchTransport;
30
+ /** Account JSON returned by `POST /bootstrap`. */
31
+ readonly account: {
32
+ readonly id: Uuid;
33
+ readonly username: string;
34
+ };
35
+ /** Actor JSON returned by `POST /bootstrap`. */
36
+ readonly actor: {
37
+ readonly id: Uuid;
38
+ };
39
+ /** Raw `Set-Cookie` values for threading into a WS transport. */
40
+ readonly cookies: ReadonlyArray<string>;
41
+ }
42
+ /**
43
+ * Fire `POST {config.bootstrap_path}` and capture the keeper session.
44
+ *
45
+ * @throws Error when the binary refuses bootstrap (non-2xx response) or
46
+ * the body fails to parse as the expected `{account, actor}` envelope.
47
+ * The error carries the status + raw body so a mistyped token /
48
+ * username collision / boot-time DB drift surfaces with enough
49
+ * context to debug.
50
+ */
51
+ export declare const bootstrap: (options: BootstrapOptions) => Promise<BootstrapResult>;
52
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/transports/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,oCAAoC,CAAC;AACtE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAezD,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAC/B;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACjE,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,eAAe,CA4BlF,CAAC"}
@@ -0,0 +1,70 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Stateless cross-process bootstrap.
4
+ *
5
+ * POSTs `{bootstrap_path}` against the running test binary with the
6
+ * preconfigured token + username + password, parses the
7
+ * `BootstrapOutput` envelope, captures the session `Set-Cookie` onto
8
+ * the supplied `FetchTransport` (which carries it on every subsequent
9
+ * call), and returns the keeper credentials.
10
+ *
11
+ * Fires exactly once per backend lifetime — `spawn_backend` calls it
12
+ * inside vitest's `globalSetup`. Per-test fixtures re-use the captured
13
+ * keeper credentials; fresh per-test accounts come from
14
+ * `fixture.create_account()` (signup+login through production RPC),
15
+ * not re-bootstrap. The hybrid reset model in `default_cross_process_setup`
16
+ * depends on this — re-bootstrap would race the bootstrap lock and
17
+ * in-memory caches.
18
+ *
19
+ * @module
20
+ */
21
+ import { z } from 'zod';
22
+ import { Uuid } from '@fuzdev/fuz_util/id.js';
23
+ /**
24
+ * The `BootstrapOutput` envelope shape the cross-process bootstrap call
25
+ * cares about. Looser than the full `BootstrapOutput` Zod schema in
26
+ * `auth/bootstrap_account.ts` — that schema is the canonical wire shape
27
+ * on the server side, and this one is a structural subset the runner
28
+ * uses to extract the keeper identity. Kept local so cross-process
29
+ * testing doesn't pull the full auth-domain schema into its dep graph.
30
+ */
31
+ const BootstrapResponse = z.object({
32
+ account: z.object({ id: Uuid, username: z.string() }),
33
+ actor: z.object({ id: Uuid }),
34
+ });
35
+ /**
36
+ * Fire `POST {config.bootstrap_path}` and capture the keeper session.
37
+ *
38
+ * @throws Error when the binary refuses bootstrap (non-2xx response) or
39
+ * the body fails to parse as the expected `{account, actor}` envelope.
40
+ * The error carries the status + raw body so a mistyped token /
41
+ * username collision / boot-time DB drift surfaces with enough
42
+ * context to debug.
43
+ */
44
+ export const bootstrap = async (options) => {
45
+ const { transport, config } = options;
46
+ const response = await transport(config.bootstrap_path, {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({
50
+ token: config.bootstrap.token,
51
+ username: config.bootstrap.username,
52
+ password: config.bootstrap.password,
53
+ }),
54
+ });
55
+ if (!response.ok) {
56
+ const body = await response.text().catch(() => '<unreadable>');
57
+ throw new Error(`bootstrap(${config.name}) failed: status=${response.status} body=${body}`);
58
+ }
59
+ const raw = await response.json();
60
+ const parsed = BootstrapResponse.safeParse(raw);
61
+ if (!parsed.success) {
62
+ throw new Error(`bootstrap(${config.name}) returned unexpected body: ${JSON.stringify(raw)} (${parsed.error.message})`);
63
+ }
64
+ return {
65
+ transport,
66
+ account: parsed.data.account,
67
+ actor: parsed.data.actor,
68
+ cookies: transport.cookies(),
69
+ };
70
+ };
@@ -0,0 +1,81 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cookie-threading HTTP transport for cross-process tests.
4
+ *
5
+ * Wraps the global `fetch` against a base URL, carries cookies across
6
+ * requests in a `Map`-backed jar so the session cookie set on bootstrap
7
+ * is re-sent on every subsequent call. Satisfies the `RpcTestTransport`
8
+ * shape `Hono.request` already does — so every suite body that takes
9
+ * `transport: RpcTestTransport` works against a cross-process binary
10
+ * unchanged.
11
+ *
12
+ * The `Origin` header is threaded onto every request because the
13
+ * backend's allowlist (`{ZZZ,ZAP,FUZ}_ALLOWED_ORIGINS`) rejects mutations
14
+ * without an `Origin`. Cross-process tests run with
15
+ * `ALLOWED_ORIGINS=http://localhost:*`, so defaulting `origin` to the
16
+ * configured `base_url` is safe.
17
+ *
18
+ * The cookie jar is intentionally simple — it does not honour `Domain`,
19
+ * `Path`, `Expires`, or `SameSite` attributes. Cross-process tests
20
+ * always hit a single host:port, so name-keyed last-write-wins matches
21
+ * the behaviour real browsers exhibit against the same surface.
22
+ *
23
+ * @module
24
+ */
25
+ import type { RpcTestTransport } from '../rpc_helpers.js';
26
+ /** Construction options for `create_fetch_transport`. */
27
+ export interface FetchTransportOptions {
28
+ /** Base URL the binary is reachable at — e.g. `http://localhost:8788`. */
29
+ readonly base_url: string;
30
+ /**
31
+ * Initial cookie values to seed the jar. Pass the `Set-Cookie` values
32
+ * captured from a prior `bootstrap()` call to keep the keeper session
33
+ * across a transport-recreation boundary. Each entry is a full
34
+ * `Set-Cookie` value (the same string `Headers.getSetCookie()` returns).
35
+ */
36
+ readonly initial_cookies?: ReadonlyArray<string>;
37
+ /**
38
+ * Origin header threaded onto every request when the caller hasn't set
39
+ * one. Defaults to `base_url`; backends running with
40
+ * `ALLOWED_ORIGINS=http://localhost:*` accept `http://localhost:<port>`
41
+ * matching the spawned binary.
42
+ *
43
+ * Pass `null` to disable the default — useful for bearer-only probes
44
+ * that must not look like browser-context requests (the auth middleware
45
+ * silently discards bearer credentials when `Origin`/`Referer` is
46
+ * present). Callers can still set `Origin` per-call via `init.headers`.
47
+ */
48
+ readonly origin?: string | null;
49
+ }
50
+ /**
51
+ * The transport shape: callable as `RpcTestTransport` plus a `cookies()`
52
+ * accessor that returns the current jar state. The accessor exists so
53
+ * `ws_transport` can thread the session cookie onto the WS upgrade
54
+ * without an HTTP round trip.
55
+ */
56
+ export interface FetchTransport extends RpcTestTransport {
57
+ /**
58
+ * Snapshot of every cookie currently in the jar, formatted as full
59
+ * `Set-Cookie` values (`name=value`). Used by `ws_transport` to
60
+ * compose the `Cookie` header on the upgrade request.
61
+ */
62
+ readonly cookies: () => ReadonlyArray<string>;
63
+ }
64
+ /**
65
+ * Build a cookie-threading transport pinned to `options.base_url`. The
66
+ * returned function carries a private `Map<name, cookie-head>` jar that
67
+ * updates on every response's `Set-Cookie` and re-sends on every
68
+ * subsequent request.
69
+ *
70
+ * Request rewriting:
71
+ *
72
+ * - Absolute URLs (`http://other.example/...`) pass through verbatim —
73
+ * handy for cross-origin negative tests that target a deliberately
74
+ * different host.
75
+ * - Relative URLs are resolved against `base_url`.
76
+ * - `Origin` is set to `options.origin ?? base_url` unless the caller
77
+ * already provided one.
78
+ * - `Cookie` is set from the jar unless the caller already provided one.
79
+ */
80
+ export declare const create_fetch_transport: (options: FetchTransportOptions) => FetchTransport;
81
+ //# sourceMappingURL=fetch_transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch_transport.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/transports/fetch_transport.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AAExD,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,qBAAqB,KAAG,cAyCvE,CAAC"}
@@ -0,0 +1,74 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Parse the `name=value` head of a `Set-Cookie` value. Returns `null`
4
+ * for malformed inputs (missing `=`, empty name). Drops every attribute
5
+ * after the first `;` — the jar is name-keyed and the lifetime
6
+ * attributes (`Expires`, `Max-Age`, `Path`, `Domain`, `SameSite`)
7
+ * don't affect cross-process test plumbing.
8
+ */
9
+ const parse_set_cookie = (value) => {
10
+ const head = value.split(';', 1)[0].trim();
11
+ const eq = head.indexOf('=');
12
+ if (eq <= 0)
13
+ return null;
14
+ const name = head.slice(0, eq).trim();
15
+ if (!name)
16
+ return null;
17
+ return { name, cookie: head };
18
+ };
19
+ /**
20
+ * Build a cookie-threading transport pinned to `options.base_url`. The
21
+ * returned function carries a private `Map<name, cookie-head>` jar that
22
+ * updates on every response's `Set-Cookie` and re-sends on every
23
+ * subsequent request.
24
+ *
25
+ * Request rewriting:
26
+ *
27
+ * - Absolute URLs (`http://other.example/...`) pass through verbatim —
28
+ * handy for cross-origin negative tests that target a deliberately
29
+ * different host.
30
+ * - Relative URLs are resolved against `base_url`.
31
+ * - `Origin` is set to `options.origin ?? base_url` unless the caller
32
+ * already provided one.
33
+ * - `Cookie` is set from the jar unless the caller already provided one.
34
+ */
35
+ export const create_fetch_transport = (options) => {
36
+ const { base_url, initial_cookies, origin } = options;
37
+ const jar = new Map();
38
+ if (initial_cookies) {
39
+ for (const raw of initial_cookies) {
40
+ const parsed = parse_set_cookie(raw);
41
+ if (parsed)
42
+ jar.set(parsed.name, parsed.cookie);
43
+ }
44
+ }
45
+ const default_origin = origin === null ? null : (origin ?? base_url);
46
+ const cookies = () => Array.from(jar.values());
47
+ const transport = (async (url, init) => {
48
+ const target = /^https?:\/\//i.test(url) ? url : `${base_url}${url}`;
49
+ const headers = new Headers(init.headers);
50
+ if (default_origin !== null && !headers.has('Origin')) {
51
+ headers.set('Origin', default_origin);
52
+ }
53
+ if (!headers.has('Cookie') && jar.size > 0) {
54
+ headers.set('Cookie', Array.from(jar.values()).join('; '));
55
+ }
56
+ const response = await fetch(target, { ...init, headers });
57
+ // `Headers.getSetCookie()` returns each `Set-Cookie` value as a
58
+ // separate string — the only way to read multiple cookies set in
59
+ // one response (Headers.get() collapses to a single comma-joined
60
+ // string). Node 19.7+ ships it; vitest's runtime supports it.
61
+ const set_cookies = response.headers.getSetCookie();
62
+ for (const raw of set_cookies) {
63
+ const parsed = parse_set_cookie(raw);
64
+ if (parsed)
65
+ jar.set(parsed.name, parsed.cookie);
66
+ }
67
+ return response;
68
+ });
69
+ // Attach the cookies() accessor onto the callable. Using a property
70
+ // definition keeps it non-enumerable-ish and avoids polluting `.bind`
71
+ // / `.call` reflection on the function.
72
+ Object.defineProperty(transport, 'cookies', { value: cookies, enumerable: false });
73
+ return transport;
74
+ };
@@ -0,0 +1,41 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * SSE frame reader over a `ReadableStreamDefaultReader<Uint8Array>`.
4
+ *
5
+ * Transport-agnostic core shared by the in-process SSE route suite
6
+ * (`testing/sse_round_trip.ts`, reading a Hono `Response.body`) and the
7
+ * cross-process `transports/sse_transport.ts` (reading a streaming `fetch`
8
+ * body): `\n\n`-delimited framing, a per-read timeout (so vitest can't hang
9
+ * on a stalled stream), and `wait_for_close` for server-initiated close
10
+ * detection (the auth-guard revocation seam).
11
+ *
12
+ * @module
13
+ */
14
+ /** Default per-read / wait-for-close timeout. */
15
+ export declare const SSE_FRAME_READ_TIMEOUT_MS = 2000;
16
+ /** Frame-level reader returned by `create_sse_frame_reader`. */
17
+ export interface SseFrameReader {
18
+ /**
19
+ * Read one complete SSE frame (up to the next `\n\n`), without the
20
+ * trailing terminator. Throws if the per-read timeout elapses or the
21
+ * stream ends before a frame arrives.
22
+ */
23
+ read_frame: (timeout_ms?: number) => Promise<string>;
24
+ /**
25
+ * Drain until the server closes the stream. Resolves `true` if the
26
+ * stream closes within `timeout_ms`, `false` on timeout.
27
+ */
28
+ wait_for_close: (timeout_ms?: number) => Promise<boolean>;
29
+ /** Cancel the underlying reader. Safe to call when already closed. */
30
+ cancel: () => Promise<void>;
31
+ }
32
+ /**
33
+ * Wrap a byte-stream reader in `\n\n`-delimited SSE frame parsing.
34
+ *
35
+ * Preserves bytes past a frame terminator in an internal buffer for the next
36
+ * `read_frame`. `read_frame` and `wait_for_close` both race each underlying
37
+ * read against `timeout_ms` so a misbehaving stream surfaces as a failure
38
+ * rather than a vitest hang.
39
+ */
40
+ export declare const create_sse_frame_reader: (reader: ReadableStreamDefaultReader<Uint8Array>, default_timeout_ms?: number) => SseFrameReader;
41
+ //# sourceMappingURL=sse_frame_reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse_frame_reader.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/transports/sse_frame_reader.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;GAWG;AAEH,iDAAiD;AACjD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,UAAU,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD;;;OAGG;IACH,cAAc,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,sEAAsE;IACtE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GACnC,QAAQ,2BAA2B,CAAC,UAAU,CAAC,EAC/C,2BAA8C,KAC5C,cA8DF,CAAC"}