@fuzdev/fuz_app 0.124.0 → 0.126.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 (90) hide show
  1. package/dist/actions/CLAUDE.md +58 -21
  2. package/dist/actions/action_rpc.d.ts +14 -11
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +5 -4
  5. package/dist/actions/connection_closer.d.ts +13 -3
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +7 -5
  8. package/dist/actions/register_action_ws.d.ts +18 -13
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +9 -7
  11. package/dist/actions/register_ws_endpoint.d.ts +6 -3
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +6 -3
  14. package/dist/actions/transports.d.ts +9 -0
  15. package/dist/actions/transports.d.ts.map +1 -1
  16. package/dist/actions/transports.js +9 -0
  17. package/dist/actions/transports_ws_backend.d.ts +16 -1
  18. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_backend.js +26 -6
  20. package/dist/actions/ws_endpoint_spec.d.ts +24 -4
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  22. package/dist/actions/ws_endpoint_spec.js +15 -1
  23. package/dist/auth/middleware.d.ts +29 -1
  24. package/dist/auth/middleware.d.ts.map +1 -1
  25. package/dist/auth/middleware.js +35 -1
  26. package/dist/http/surface.d.ts +3 -1
  27. package/dist/http/surface.d.ts.map +1 -1
  28. package/dist/http/surface.js +15 -1
  29. package/dist/rate_limiter.d.ts +25 -3
  30. package/dist/rate_limiter.d.ts.map +1 -1
  31. package/dist/rate_limiter.js +4 -3
  32. package/dist/realtime/sse_auth_guard.d.ts +2 -1
  33. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  34. package/dist/realtime/sse_auth_guard.js +4 -2
  35. package/dist/realtime/subscriber_registry.d.ts +12 -0
  36. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  37. package/dist/realtime/subscriber_registry.js +18 -2
  38. package/dist/server/app_server.d.ts +127 -51
  39. package/dist/server/app_server.d.ts.map +1 -1
  40. package/dist/server/app_server.js +107 -60
  41. package/dist/server/app_server_context.d.ts +17 -9
  42. package/dist/server/app_server_context.d.ts.map +1 -1
  43. package/dist/server/static.d.ts +1 -1
  44. package/dist/testing/CLAUDE.md +77 -19
  45. package/dist/testing/app_server.d.ts +6 -3
  46. package/dist/testing/app_server.d.ts.map +1 -1
  47. package/dist/testing/app_server.js +11 -12
  48. package/dist/testing/connection_closer_helpers.d.ts +10 -4
  49. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  50. package/dist/testing/connection_closer_helpers.js +4 -0
  51. package/dist/testing/cross_backend/capabilities.d.ts +3 -3
  52. package/dist/testing/cross_backend/default_backend_configs.d.ts +31 -0
  53. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  54. package/dist/testing/cross_backend/default_backend_configs.js +31 -0
  55. package/dist/testing/cross_backend/full_spine_mount.d.ts +35 -11
  56. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/full_spine_mount.js +26 -9
  58. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +15 -0
  59. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  60. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +3 -2
  61. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/testing_backdoor.js +2 -1
  63. package/dist/testing/cross_backend/testing_reset_actions.d.ts +6 -3
  64. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/testing_reset_actions.js +6 -3
  66. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/testing_server_bun.js +11 -7
  68. package/dist/testing/cross_backend/testing_server_core.d.ts +68 -37
  69. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
  70. package/dist/testing/cross_backend/testing_server_core.js +20 -12
  71. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
  72. package/dist/testing/cross_backend/testing_server_deno.js +5 -4
  73. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
  74. package/dist/testing/cross_backend/testing_server_node.js +20 -7
  75. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +17 -1
  76. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  77. package/dist/testing/cross_backend/ts_spine_backend_config.js +11 -6
  78. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +26 -0
  79. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -0
  80. package/dist/testing/cross_backend/ws_action_rate_limit.js +261 -0
  81. package/dist/testing/stubs.d.ts +14 -4
  82. package/dist/testing/stubs.d.ts.map +1 -1
  83. package/dist/testing/stubs.js +31 -13
  84. package/dist/testing/transports/ws_transport.d.ts +7 -0
  85. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  86. package/dist/testing/transports/ws_transport.js +1 -1
  87. package/dist/testing/ws_round_trip.d.ts +36 -6
  88. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  89. package/dist/testing/ws_round_trip.js +28 -8
  90. package/package.json +15 -5
@@ -51,6 +51,21 @@ export interface SpineStubBackendConfigOptions {
51
51
  * TS spine binary wires the equivalent set unconditionally.
52
52
  */
53
53
  readonly trusted_proxies?: string;
54
+ /**
55
+ * Enable the action rate limiters (`FUZ_ACTION_RATE_LIMIT_ENABLED=true`) —
56
+ * one per-IP and one per-account limiter shared by the RPC and WS
57
+ * endpoints. Off by default, like `enable_login_rate_limit`; set only for
58
+ * the dedicated security cross project, which drives them through
59
+ * `ws_action_rate_limit.ts`. Mirrors `TsSpineBackendConfigOptions.enable_action_rate_limit`.
60
+ */
61
+ readonly enable_action_rate_limit?: boolean;
62
+ /**
63
+ * Replace both action limiters' `max_attempts`
64
+ * (`FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS`) so a suite reaches the cap in a few
65
+ * calls. Requires `enable_action_rate_limit` — the binary refuses to boot
66
+ * otherwise. Mirrors `TsSpineBackendConfigOptions.action_rate_limit_max_attempts`.
67
+ */
68
+ readonly action_rate_limit_max_attempts?: number;
54
69
  }
55
70
  /**
56
71
  * Build the `BackendConfig` for `testing_spine_stub`. Resolves the binary
@@ -1 +1 @@
1
- {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAqBnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAkDF,CAAC"}
1
+ {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUzD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAqBnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAmDF,CAAC"}
@@ -39,7 +39,7 @@ import '../assert_dev_env.js';
39
39
  import { fileURLToPath } from 'node:url';
40
40
  import { build_test_backend_paths } from './build_test_backend_paths.js';
41
41
  import { SPINE_EXPECTED_SCHEMA_URL } from './spine_surface_constants.js';
42
- import { LOGIN_RATE_LIMIT_ENABLED_ENV, make_default_rust_backend_config, rust_default_capabilities } from './default_backend_configs.js';
42
+ import { LOGIN_RATE_LIMIT_ENABLED_ENV, action_rate_limit_env, make_default_rust_backend_config, rust_default_capabilities } from './default_backend_configs.js';
43
43
  /** Env var naming the prebuilt `testing_spine_stub` binary. Required when `binary_path` is omitted. */
44
44
  export const RUST_SPINE_STUB_BIN_ENV = 'FUZ_TESTING_RUST_SPINE_STUB_BIN';
45
45
  /**
@@ -127,7 +127,8 @@ export const rust_spine_stub_backend_config = (options = {}) => {
127
127
  // limiters + trust the loopback proxy so the limiter keys on the
128
128
  // resolved `X-Forwarded-For` IP. The stub reads both directly.
129
129
  ...(enable_login_rate_limit ? { [LOGIN_RATE_LIMIT_ENABLED_ENV]: 'true' } : {}),
130
- ...(trusted_proxies !== undefined ? { FUZ_TRUSTED_PROXIES: trusted_proxies } : {})
130
+ ...(trusted_proxies !== undefined ? { FUZ_TRUSTED_PROXIES: trusted_proxies } : {}),
131
+ ...action_rate_limit_env(options)
131
132
  }
132
133
  });
133
134
  };
@@ -1 +1 @@
1
- {"version":3,"file":"testing_backdoor.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_backdoor.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4D9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAqExE,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CAAC;AAEvE,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAiCF,CAAC"}
1
+ {"version":3,"file":"testing_backdoor.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_backdoor.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAqExE,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CAAC;AAEvE,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAiCF,CAAC"}
@@ -12,7 +12,8 @@ import '../assert_dev_env.js';
12
12
  * info-leak of the set were the gate to break). Their only structural fence
13
13
  * is the **daemon-token** credential gate on
14
14
  * each spec's `auth` axis. A test binary live-mounts them on its RPC
15
- * endpoint but keeps them off the declared surface — so the spec-derived
15
+ * endpoint (and, on fuz_app's spine binary and the Rust stub, its WS
16
+ * endpoint) but keeps them off the declared surface — so the spec-derived
16
17
  * `describe_rpc_attack_surface_tests` never enumerates them, and nothing
17
18
  * else fires them with a non-daemon credential to prove the gate holds
18
19
  * end-to-end. This suite does, against each impl's real auth resolution.
@@ -163,9 +163,12 @@ export declare const testing_reset_action_spec: {
163
163
  * asserting on audit rows.
164
164
  *
165
165
  * On the TS spine the barrier is **satisfied by construction**: the test
166
- * binary runs `await_pending_effects: true`, so every mutation's fire-and-
167
- * forget audit emits are awaited before its response returns — by the time
168
- * a later drain call runs, prior emits are already durable. The action still
166
+ * binary runs `await_pending_effects: true`, so every **HTTP** mutation's
167
+ * fire-and-forget audit emits are awaited before its response returns — by
168
+ * the time a later drain call runs, prior emits are already durable. A
169
+ * WS-dispatched mutation replies before its effects flush, so on TS this
170
+ * action is not a barrier for WS-originated emits; issue audit-asserting
171
+ * mutations over HTTP. The action still
169
172
  * exists so the cross-backend test body calls the same method on every
170
173
  * backend; the Rust spine (whose audit writes are detached tokio tasks)
171
174
  * does the real await in `AuditEmitter::drain_inflight`.
@@ -1 +1 @@
1
- {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACmB,CAAC;AAE3E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaF,CAAC;AAyB/C,eAAO,MAAM,qCAAqC,GACjD,SAAS,aAAa,CAAC,SAAS,CAAC,KAC/B,SAMF,CAAC;AAEF,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAsIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACmB,CAAC;AAE3E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaF,CAAC;AAyB/C,eAAO,MAAM,qCAAqC,GACjD,SAAS,aAAa,CAAC,SAAS,CAAC,KAC/B,SAMF,CAAC;AAEF,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAsIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
@@ -169,9 +169,12 @@ export const testing_reset_action_spec = {
169
169
  * asserting on audit rows.
170
170
  *
171
171
  * On the TS spine the barrier is **satisfied by construction**: the test
172
- * binary runs `await_pending_effects: true`, so every mutation's fire-and-
173
- * forget audit emits are awaited before its response returns — by the time
174
- * a later drain call runs, prior emits are already durable. The action still
172
+ * binary runs `await_pending_effects: true`, so every **HTTP** mutation's
173
+ * fire-and-forget audit emits are awaited before its response returns — by
174
+ * the time a later drain call runs, prior emits are already durable. A
175
+ * WS-dispatched mutation replies before its effects flush, so on TS this
176
+ * action is not a barrier for WS-originated emits; issue audit-asserting
177
+ * mutations over HTTP. The action still
175
178
  * exists so the cross-backend test body calls the same method on every
176
179
  * backend; the Rust spine (whose audit writes are detached tokio tasks)
177
180
  * does the real await in `AuditEmitter::drain_inflight`.
@@ -1 +1 @@
1
- {"version":3,"file":"testing_server_bun.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_bun.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAelF,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,QAAO,oBA0D5C,CAAC"}
1
+ {"version":3,"file":"testing_server_bun.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_bun.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAelF,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,QAAO,oBA8D5C,CAAC"}
@@ -2,16 +2,16 @@ import '../assert_dev_env.js';
2
2
  /**
3
3
  * Bun runtime adapter for spawnable cross-process test server binaries.
4
4
  *
5
- * Binds `Bun.serve` and `hono/bun`'s module-level `upgradeWebSocket` +
5
+ * Binds `Bun.serve` and `@hono/bun`'s module-level `upgradeWebSocket` +
6
6
  * `websocket` handler. The shared `testing/cross_backend/testing_server_core.ts` owns the rest.
7
7
  * Third sibling to `testing/cross_backend/testing_server_node.ts` / `testing/cross_backend/testing_server_deno.ts` —
8
8
  * together the three isolate the JS-runtime axis (Node V8 / Deno V8 / Bun
9
9
  * JSC) on identical TS surfaces, and the Rust spine binary covers the
10
10
  * cross-language axis.
11
11
  *
12
- * Needs **no extra deps**: `hono/bun` ships with the `hono` peer dep and
13
- * `Bun.serve` is built in (unlike Node, which pulls `@hono/node-server` +
14
- * `@hono/node-ws`). `RuntimeDeps` reuse `create_node_runtime` — Bun
12
+ * Needs the optional `@hono/bun` peer dep (the Bun counterpart to Node's
13
+ * `@hono/node-server` + `@hono/node-ws`); `Bun.serve` is built in.
14
+ * `RuntimeDeps` reuse `create_node_runtime` — Bun
15
15
  * implements the `node:fs` / `node:process` surface `RuntimeDeps` +
16
16
  * `cli/daemon` touch.
17
17
  *
@@ -22,7 +22,7 @@ import '../assert_dev_env.js';
22
22
  * @module
23
23
  */
24
24
  import process from 'node:process';
25
- import { getConnInfo, upgradeWebSocket, websocket } from 'hono/bun';
25
+ import { getConnInfo, upgradeWebSocket, websocket } from '@hono/bun';
26
26
  import { create_node_runtime } from '../../runtime/node.js';
27
27
  /** Build the Bun {@link TestingServerAdapter}. */
28
28
  export const create_bun_testing_adapter = () => ({
@@ -32,7 +32,11 @@ export const create_bun_testing_adapter = () => ({
32
32
  // Bun's WS upgrade is module-level and stateless (like Deno) — no
33
33
  // post-serve attach. The `websocket` handler is threaded into `serve`
34
34
  // below, where `Bun.serve` wants it.
35
- prepare_websocket: () => ({ upgrade_websocket: upgradeWebSocket }),
35
+ // TODO: `Bun.serve({websocket: {maxPayloadLength}})` (default 16 MiB) could
36
+ // carry the endpoint's `max_message_bytes` as a frame cap, like Node's
37
+ // `maxPayload`; until then the per-message check in `register_ws_endpoint`
38
+ // closes an oversized message with `WS_CLOSE_MESSAGE_TOO_BIG` (1009).
39
+ prepare_websocket: () => ({ create_upgrade_websocket: () => upgradeWebSocket }),
36
40
  serve: ({ fetch, port, hostname }) => {
37
41
  const server = Bun.serve({
38
42
  fetch: fetch,
@@ -44,7 +48,7 @@ export const create_bun_testing_adapter = () => ({
44
48
  });
45
49
  const handle = {
46
50
  // Bun bug (1.3.14): after a *server-initiated* WebSocket close
47
- // (`ServerWebSocket.close()` / `hono/bun`'s `WSContext.close()`),
51
+ // (`ServerWebSocket.close()` / `@hono/bun`'s `WSContext.close()`),
48
52
  // `server.stop()` never resolves — Bun doesn't decrement its
49
53
  // active-connection count for a server-closed socket, so the stop
50
54
  // waits forever for a connection it already closed. The trigger is
@@ -7,15 +7,20 @@ import '../assert_dev_env.js';
7
7
  * the `cross_backend/*` suites (and the cross-impl bench) can drive it the
8
8
  * same way they drive the Rust spine. This module owns the runtime-neutral
9
9
  * orchestration — stale-daemon check, daemon-info write, serve, post-serve
10
- * WS attach, graceful drain shutdown — and delegates the runtime-boundary
10
+ * WS attach, graceful shutdown — and delegates the runtime-boundary
11
11
  * primitives (HTTP serve, WS upgrade construction, signals, pid, exit) to a
12
- * {@link TestingServerAdapter}. The two shipped adapters are
13
- * `testing/cross_backend/testing_server_node.ts` (`@hono/node-server` + `@hono/node-ws`) and
14
- * `testing/cross_backend/testing_server_deno.ts` (`Deno.serve` + `hono/deno`).
12
+ * {@link TestingServerAdapter}. The shipped adapters are
13
+ * `testing/cross_backend/testing_server_node.ts` (`@hono/node-server` + `@hono/node-ws`),
14
+ * `testing/cross_backend/testing_server_deno.ts` (`Deno.serve` + `@hono/deno`), and
15
+ * `testing/cross_backend/testing_server_bun.ts` (`Bun.serve` + `@hono/bun`).
15
16
  *
16
- * The app itself — routes, RPC, DB, `_testing_reset`, optional WS mount —
17
- * is the caller's {@link StartTestingServerOptions.build_app} seam, so this
18
- * core stays domain-free. fuz_app's own `testing_spine_server` passes a
17
+ * The app itself — routes, RPC, DB, `_testing_reset`, WS endpoints — is the
18
+ * caller's {@link StartTestingServerOptions.build_app} seam, so this core
19
+ * stays domain-free. WebSocket endpoints mount through `create_app_server`'s
20
+ * `ws_endpoints`: `build_app` asks its context to prepare the runtime's
21
+ * upgrade helper and passes the resulting factory as
22
+ * `create_upgrade_websocket`; the core attaches it to the server once
23
+ * `serve` returns (Node's `injectWebSocket`). fuz_app's own `testing_spine_server` passes a
19
24
  * no-domain build; consumers (zzz, fuz_forge) pass their domain build.
20
25
  *
21
26
  * **NEVER ships in a release.** This module lives under `cross_backend/` and
@@ -33,32 +38,52 @@ import type { RuntimeDeps } from '../../runtime/deps.js';
33
38
  /**
34
39
  * Adapter-built handle to a bound HTTP server.
35
40
  *
36
- * `shutdown` stops accepting new connections and drains in-flight ones.
37
- * `native` is an adapter-specific server reference — used by Node's
38
- * `@hono/node-ws` `injectWebSocket(server)` post-serve hook; Deno leaves it
39
- * unset.
41
+ * `shutdown` stops accepting new connections at once and settles when the
42
+ * in-flight ones have drained — the core starts it, closes the app (which
43
+ * ends live WebSockets), then awaits it. `native` is an adapter-specific
44
+ * server reference — used by Node's `@hono/node-ws` `injectWebSocket(server)`
45
+ * post-serve hook.
40
46
  */
41
47
  export interface ServeHandle {
42
48
  shutdown: () => Promise<void>;
43
49
  /** Adapter-specific server ref for post-serve hooks. Type-erased at the seam. */
44
50
  native?: unknown;
45
51
  }
52
+ /** Options for {@link TestingServerAdapter.prepare_websocket}. */
53
+ export interface PrepareWebsocketOptions {
54
+ /**
55
+ * Largest inbound WebSocket message, in bytes — pass the largest
56
+ * `WsEndpointSpec.max_message_bytes` of the mounted endpoints; a smaller
57
+ * value refuses messages the endpoint would accept. Node's `ws` server takes
58
+ * it as its frame cap (`maxPayload`), so an oversized message is refused
59
+ * before it is buffered whole; Deno and Bun ignore it, leaving the
60
+ * per-message check `register_ws_endpoint` makes (both close with
61
+ * `WS_CLOSE_MESSAGE_TOO_BIG`). The default sits far below `ws`'s own
62
+ * 100 MiB, so a build raising an endpoint's cap must pass it here too.
63
+ *
64
+ * @default DEFAULT_WS_MAX_MESSAGE_BYTES
65
+ */
66
+ max_message_bytes?: number;
67
+ }
46
68
  /**
47
69
  * Result of an adapter's WS preparation step.
48
70
  *
49
- * `upgrade_websocket` is the Hono `UpgradeWebSocket` closure the caller's
50
- * WS mount uses to register the endpoint. `attach_to_server` runs after
51
- * `serve()` returns a {@link ServeHandle} — Node uses it for
52
- * `injectWebSocket(server)`; Deno leaves it undefined.
71
+ * `create_upgrade_websocket` is the factory a build passes to
72
+ * `create_app_server` as `AppServerOptions.create_upgrade_websocket`.
73
+ * `attach_to_server` runs after `serve()` returns a {@link ServeHandle} —
74
+ * Node uses it for `injectWebSocket(server)`, and it does nothing when the
75
+ * factory was never called (no `ws_endpoints`); Deno and Bun leave it
76
+ * undefined.
53
77
  */
54
78
  export interface PreparedWebsocket {
55
- upgrade_websocket: UpgradeWebSocket;
79
+ create_upgrade_websocket: (app: Hono) => UpgradeWebSocket;
56
80
  attach_to_server?: (handle: ServeHandle) => void;
57
81
  }
58
82
  /**
59
83
  * Runtime adapter contract for the test-binary entry. Each adapter
60
- * (`testing/cross_backend/testing_server_node.ts`, `testing/cross_backend/testing_server_deno.ts`) implements this and
61
- * hands the shape to {@link start_testing_server}.
84
+ * (`testing/cross_backend/testing_server_node.ts`, `testing/cross_backend/testing_server_deno.ts`,
85
+ * `testing/cross_backend/testing_server_bun.ts`) implements this and hands the shape to
86
+ * {@link start_testing_server}.
62
87
  */
63
88
  export interface TestingServerAdapter {
64
89
  /** Human-readable runtime label for log output (e.g. `"Node"`, `"Deno"`). */
@@ -67,8 +92,8 @@ export interface TestingServerAdapter {
67
92
  runtime: RuntimeDeps;
68
93
  /** Extract the raw TCP connection IP from a Hono context. */
69
94
  get_connection_ip: (c: Context) => string | undefined;
70
- /** Build the WS upgrade closure after the caller's `build_app` returns the app. */
71
- prepare_websocket: (app: Hono) => PreparedWebsocket;
95
+ /** Prepare the runtime's WS upgrade helper and its post-serve attach step. */
96
+ prepare_websocket: (options?: PrepareWebsocketOptions) => PreparedWebsocket;
72
97
  /** Bind `app.fetch` to `port` on `hostname`; return a {@link ServeHandle}. */
73
98
  serve: (options: {
74
99
  fetch: Hono['fetch'];
@@ -82,22 +107,29 @@ export interface TestingServerAdapter {
82
107
  /** Forceful exit on graceful-shutdown completion or fatal error. */
83
108
  exit: (code: number) => never;
84
109
  }
85
- /**
86
- * The assembled app a {@link StartTestingServerOptions.build_app} seam
87
- * returns.
88
- *
89
- * `mount_websocket` is invoked by the core after the app exists and the
90
- * adapter prepared the WS upgrade closure — the closure mounts the WS
91
- * endpoint(s) (e.g. via `register_ws_endpoint`) and wires any
92
- * audit-revocation guards. Omit it for an HTTP-only binary.
93
- */
110
+ /** What the core hands a {@link StartTestingServerOptions.build_app} seam. */
111
+ export interface BuildTestingAppContext {
112
+ /**
113
+ * Prepare the runtime adapter's WS upgrade helper and return the factory
114
+ * to pass to `create_app_server` as `create_upgrade_websocket`. The core
115
+ * runs the adapter's attach step once the server is bound, so a build
116
+ * that mounts `ws_endpoints` needs nothing else. An HTTP-only build
117
+ * doesn't call it. Call it at most once per build: the Node adapter
118
+ * attaches one upgrade listener per preparation, and two would race on
119
+ * the shared socket.
120
+ */
121
+ prepare_websocket: (options?: PrepareWebsocketOptions) => (app: Hono) => UpgradeWebSocket;
122
+ }
123
+ /** The assembled app a {@link StartTestingServerOptions.build_app} seam returns. */
94
124
  export interface BuiltTestingApp {
95
- /** The assembled Hono app (HTTP routes + RPC already mounted). */
125
+ /** The assembled Hono app (HTTP routes, RPC, and WS endpoints already mounted). */
96
126
  app: Hono;
97
- /** Tear down backend(s) + DB + any rotation on graceful shutdown. */
127
+ /**
128
+ * Tear down backend(s) + DB + any rotation on graceful shutdown. Called
129
+ * after the listener stops accepting connections and before its drain is
130
+ * awaited, so it must end live WebSockets (`AppServer.close` does).
131
+ */
98
132
  close: () => Promise<void>;
99
- /** Mount WS endpoint(s) given the runtime-prepared upgrade closure. */
100
- mount_websocket?: (upgrade_websocket: UpgradeWebSocket) => void;
101
133
  }
102
134
  /** Options for {@link start_testing_server}. */
103
135
  export interface StartTestingServerOptions {
@@ -120,10 +152,9 @@ export interface StartTestingServerOptions {
120
152
  /**
121
153
  * Build the app. Closes over the entry's runtime + connection-IP getter
122
154
  * + password deps + resolved config — so this core never touches the
123
- * domain. Returns the assembled app, a `close` teardown, and an optional
124
- * `mount_websocket` hook.
155
+ * domain. Returns the assembled app and a `close` teardown.
125
156
  */
126
- build_app: () => Promise<BuiltTestingApp>;
157
+ build_app: (context: BuildTestingAppContext) => Promise<BuiltTestingApp>;
127
158
  /** Optional logger; defaults to a `[daemon_name]`-namespaced `Logger`. */
128
159
  log?: LoggerType;
129
160
  }
@@ -141,7 +172,7 @@ export declare const is_loopback_host: (host: string) => boolean;
141
172
  * Boot a test-mode server using the supplied runtime adapter.
142
173
  *
143
174
  * Mirrors a production `start_server` at the surface level — stale-daemon
144
- * check, daemon-info write, bind, graceful drain — but the app is the
175
+ * check, daemon-info write, bind, graceful shutdown — but the app is the
145
176
  * caller's no-domain (or domain) {@link StartTestingServerOptions.build_app}
146
177
  * and the runtime boundary is the {@link TestingServerAdapter}. Refuses any
147
178
  * non-loopback bind host (the test binary must stay on loopback — see
@@ -1 +1 @@
1
- {"version":3,"file":"testing_server_core.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_core.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAU,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAG5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,mFAAmF;IACnF,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,iBAAiB,CAAC;IACpD,8EAA8E;IAC9E,KAAK,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,WAAW,CAAC;IAC1F,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,yBAAyB,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAClE,oEAAoE;IACpE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,kEAAkE;IAClE,GAAG,EAAE,IAAI,CAAC;IACV,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,uEAAuE;IACvE,eAAe,CAAC,EAAE,CAAC,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChE;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,0EAA0E;IAC1E,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,OAG/C,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAU,SAAS,yBAAyB,KAAG,OAAO,CAAC,IAAI,CA4D3F,CAAC"}
1
+ {"version":3,"file":"testing_server_core.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_core.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ export const is_loopback_host = (host) => {
18
18
  * Boot a test-mode server using the supplied runtime adapter.
19
19
  *
20
20
  * Mirrors a production `start_server` at the surface level — stale-daemon
21
- * check, daemon-info write, bind, graceful drain — but the app is the
21
+ * check, daemon-info write, bind, graceful shutdown — but the app is the
22
22
  * caller's no-domain (or domain) {@link StartTestingServerOptions.build_app}
23
23
  * and the runtime boundary is the {@link TestingServerAdapter}. Refuses any
24
24
  * non-loopback bind host (the test binary must stay on loopback — see
@@ -42,12 +42,14 @@ export const start_testing_server = async (options) => {
42
42
  log.warn(`stale daemon.json (pid ${stale.pid} not running), replacing`);
43
43
  }
44
44
  }
45
- const built = await build_app();
46
- let ws;
47
- if (built.mount_websocket) {
48
- ws = adapter.prepare_websocket(built.app);
49
- built.mount_websocket(ws.upgrade_websocket);
50
- }
45
+ const prepared_websockets = [];
46
+ const built = await build_app({
47
+ prepare_websocket: (ws_options) => {
48
+ const prepared = adapter.prepare_websocket(ws_options);
49
+ prepared_websockets.push(prepared);
50
+ return prepared.create_upgrade_websocket;
51
+ }
52
+ });
51
53
  await write_daemon_info(runtime, daemon_name, {
52
54
  version: 1,
53
55
  pid: adapter.pid,
@@ -57,7 +59,8 @@ export const start_testing_server = async (options) => {
57
59
  });
58
60
  log.info(`Listening on http://${host}:${port} (${adapter.runtime_label}, test mode)`);
59
61
  const server = adapter.serve({ fetch: built.app.fetch, port, hostname: host });
60
- ws?.attach_to_server?.(server);
62
+ for (const prepared of prepared_websockets)
63
+ prepared.attach_to_server?.(server);
61
64
  let shutting_down = false;
62
65
  const shutdown = async () => {
63
66
  if (shutting_down)
@@ -65,11 +68,16 @@ export const start_testing_server = async (options) => {
65
68
  shutting_down = true;
66
69
  log.info('shutting down...');
67
70
  try {
68
- // Drain HTTP first (stop accepting + let in-flight requests finish)
69
- // before tearing down the backend, so a request still draining
70
- // never hits a closed DB.
71
- await server.shutdown();
71
+ // Stop accepting first so no new request reaches a closed DB, but
72
+ // don't await the drain yet: live WebSockets hold it open until
73
+ // `built.close()` ends them (`AppServer.close`), so awaiting it
74
+ // first would wait on sockets nothing is closing. A request already
75
+ // in flight when the signal lands may still see the backend close
76
+ // under it — acceptable for a test binary, where shutdown follows
77
+ // the suite.
78
+ const drained = server.shutdown();
72
79
  await built.close();
80
+ await drained;
73
81
  }
74
82
  catch (error) {
75
83
  log.error('shutdown error:', error);
@@ -1 +1 @@
1
- {"version":3,"file":"testing_server_deno.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_deno.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqB9B,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAelF,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,QAAO,oBAkB7C,CAAC"}
1
+ {"version":3,"file":"testing_server_deno.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_deno.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsB9B,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAelF,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,QAAO,oBAkB7C,CAAC"}
@@ -2,8 +2,9 @@ import '../assert_dev_env.js';
2
2
  /**
3
3
  * Deno runtime adapter for spawnable cross-process test server binaries.
4
4
  *
5
- * Binds `Deno.serve` and `hono/deno`'s module-level `upgradeWebSocket`. The
6
- * shared `testing/cross_backend/testing_server_core.ts` owns the rest. Counterpart to
5
+ * Binds `Deno.serve` and `@hono/deno`'s module-level `upgradeWebSocket` (the
6
+ * optional `@hono/deno` peer dep). The shared
7
+ * `testing/cross_backend/testing_server_core.ts` owns the rest. Counterpart to
7
8
  * `testing/cross_backend/testing_server_node.ts` — together they isolate the JS-runtime axis (Deno
8
9
  * vs Node V8) on identical TS surfaces, and the Rust spine binary covers the
9
10
  * cross-language axis.
@@ -14,7 +15,7 @@ import '../assert_dev_env.js';
14
15
  *
15
16
  * @module
16
17
  */
17
- import { upgradeWebSocket } from 'hono/deno';
18
+ import { upgradeWebSocket } from '@hono/deno';
18
19
  import { create_deno_runtime } from '../../runtime/deno.js';
19
20
  /** Build the Deno {@link TestingServerAdapter}. */
20
21
  export const create_deno_testing_adapter = () => ({
@@ -22,7 +23,7 @@ export const create_deno_testing_adapter = () => ({
22
23
  runtime: create_deno_runtime([]),
23
24
  get_connection_ip: (c) => c.env?.remoteAddr?.hostname,
24
25
  // Deno's WS upgrade is module-level and stateless — no post-serve attach.
25
- prepare_websocket: () => ({ upgrade_websocket: upgradeWebSocket }),
26
+ prepare_websocket: () => ({ create_upgrade_websocket: () => upgradeWebSocket }),
26
27
  serve: ({ fetch, port, hostname }) => {
27
28
  const server = Deno.serve({ port, hostname }, fetch);
28
29
  const handle = { shutdown: () => server.shutdown(), native: server };
@@ -1 +1 @@
1
- {"version":3,"file":"testing_server_node.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_node.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAUlF,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,QAAO,oBAsB7C,CAAC"}
1
+ {"version":3,"file":"testing_server_node.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_node.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2B9B,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAUlF,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,QAAO,oBA+B7C,CAAC"}
@@ -3,9 +3,12 @@ import '../assert_dev_env.js';
3
3
  * Node runtime adapter for spawnable cross-process test server binaries.
4
4
  *
5
5
  * Binds `@hono/node-server`'s `serve()` and `@hono/node-ws`'s two-phase
6
- * `createNodeWebSocket(app)` / `injectWebSocket(server)`. The shared
7
- * `testing/cross_backend/testing_server_core.ts` owns the rest. A test binary builds this adapter
8
- * and hands it to `start_testing_server` alongside its `build_app` seam.
6
+ * `createNodeWebSocket({app})` / `injectWebSocket(server)`: the factory
7
+ * `prepare_websocket` returns builds the first half from the app
8
+ * `create_app_server` assembles, and the core runs the second once `serve()`
9
+ * returns. The shared `testing/cross_backend/testing_server_core.ts` owns
10
+ * the rest. A test binary builds this adapter and hands it to
11
+ * `start_testing_server` alongside its `build_app` seam.
9
12
  *
10
13
  * `@hono/node-server` + `@hono/node-ws` are **optional** peer deps (same
11
14
  * posture as `ws`) — only test binaries import them; production bundles
@@ -17,6 +20,7 @@ import process from 'node:process';
17
20
  import { serve } from '@hono/node-server';
18
21
  import { getConnInfo } from '@hono/node-server/conninfo';
19
22
  import { createNodeWebSocket } from '@hono/node-ws';
23
+ import { DEFAULT_WS_MAX_MESSAGE_BYTES } from '../../actions/transports.js';
20
24
  import { create_node_runtime } from '../../runtime/node.js';
21
25
  const node_serve_handle = (server) => ({
22
26
  shutdown: () => new Promise((resolve, reject) => {
@@ -29,14 +33,23 @@ export const create_node_testing_adapter = () => ({
29
33
  runtime_label: 'Node',
30
34
  runtime: create_node_runtime(),
31
35
  get_connection_ip: (c) => getConnInfo(c).remote.address,
32
- prepare_websocket: (app) => {
33
- const { upgradeWebSocket, injectWebSocket } = createNodeWebSocket({ app });
36
+ prepare_websocket: (options) => {
37
+ const max_payload = options?.max_message_bytes ?? DEFAULT_WS_MAX_MESSAGE_BYTES;
38
+ let node_ws;
34
39
  return {
35
- upgrade_websocket: upgradeWebSocket,
40
+ create_upgrade_websocket: (app) => {
41
+ node_ws = createNodeWebSocket({ app });
42
+ // `@hono/node-ws` builds its `ws` server with no options and takes
43
+ // none, so its frame cap would stay `ws`'s 100 MiB default. `ws` reads
44
+ // `options.maxPayload` per upgrade, so setting it here — before any
45
+ // connection — caps every socket at the endpoint's message limit.
46
+ node_ws.wss.options.maxPayload = max_payload;
47
+ return node_ws.upgradeWebSocket;
48
+ },
36
49
  attach_to_server: (handle) => {
37
50
  // `handle.native` is the `ServerType` from `serve()` —
38
51
  // type-erased at the {@link ServeHandle} seam, so it downcasts here.
39
- injectWebSocket(handle.native);
52
+ node_ws?.injectWebSocket(handle.native);
40
53
  }
41
54
  };
42
55
  },
@@ -47,6 +47,21 @@ export interface TsSpineBackendConfigOptions {
47
47
  * client IP. Mirrors `SpineStubBackendConfigOptions.enable_login_rate_limit`.
48
48
  */
49
49
  readonly enable_login_rate_limit?: boolean;
50
+ /**
51
+ * Enable the action rate limiters (`FUZ_ACTION_RATE_LIMIT_ENABLED=true`) —
52
+ * one per-IP and one per-account limiter shared by the RPC and WS
53
+ * endpoints. Off by default, like `enable_login_rate_limit`; set only for
54
+ * the dedicated security cross project, which drives them through
55
+ * `ws_action_rate_limit.ts`. Mirrors `SpineStubBackendConfigOptions.enable_action_rate_limit`.
56
+ */
57
+ readonly enable_action_rate_limit?: boolean;
58
+ /**
59
+ * Replace both action limiters' `max_attempts`
60
+ * (`FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS`) so a suite reaches the cap in a few
61
+ * calls. Requires `enable_action_rate_limit` — the binary refuses to boot
62
+ * otherwise. Mirrors `SpineStubBackendConfigOptions.action_rate_limit_max_attempts`.
63
+ */
64
+ readonly action_rate_limit_max_attempts?: number;
50
65
  }
51
66
  /**
52
67
  * `BackendConfig` for the Node TS spine binary — spawned via `gro run`
@@ -57,7 +72,8 @@ export declare const ts_spine_node_backend_config: (options?: TsSpineBackendConf
57
72
  * `BackendConfig` for the Bun TS spine binary — spawned via `bun run`. Bun
58
73
  * resolves the entry's relative `.js`→`.ts` source specifiers natively (no
59
74
  * flag needed — unlike Deno's `--sloppy-imports`, and like Gro's loader on
60
- * the Node path), and `Bun.serve` + `hono/bun` need no extra deps.
75
+ * the Node path). `Bun.serve` is built in; the WS upgrade comes from the
76
+ * optional `@hono/bun` peer dep.
61
77
  */
62
78
  export declare const ts_spine_bun_backend_config: (options?: TsSpineBackendConfigOptions) => BackendConfig;
63
79
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,8GAA8G;AAC9G,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AA0C3D,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAC3C;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAmBF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAmBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAgCF,CAAC"}
1
+ {"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUzD,8GAA8G;AAC9G,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AA2C3D,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAoBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAoBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAiCF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { build_test_backend_paths } from './build_test_backend_paths.js';
3
- import { LOGIN_RATE_LIMIT_ENABLED_ENV, make_default_ts_backend_config, ts_default_capabilities } from './default_backend_configs.js';
3
+ import { LOGIN_RATE_LIMIT_ENABLED_ENV, action_rate_limit_env, make_default_ts_backend_config, ts_default_capabilities } from './default_backend_configs.js';
4
4
  import { SPINE_SSE_PATH } from './spine_surface_constants.js';
5
5
  /** Env var naming the backend root dir; `{dir}/run/daemon_token` must match `bootstrap.daemon_token_path`. */
6
6
  export const TS_SPINE_DIR_ENV = 'FUZ_TESTING_TS_SPINE_DIR';
@@ -9,7 +9,8 @@ export const TS_SPINE_DIR_ENV = 'FUZ_TESTING_TS_SPINE_DIR';
9
9
  * (the binary wires `audit_log_sse`), `ready` (the binary live-mounts the
10
10
  * `/ready` deploy gate in `build_spine_app`), `peer_request` (the backend
11
11
  * WS transport's `request_connection` path drives server→client `peer/ping`),
12
- * `ws_account_actions` (the binary mounts the account actions on `/api/ws`),
12
+ * `ws_account_actions` (the binary mounts its full action set on `/api/ws`,
13
+ * the account actions among them),
13
14
  * and `cell_gated_create` (the binary's `full_spine_mount` mounts the
14
15
  * `test_cell_gated_create_authorize` policy).
15
16
  */
@@ -78,7 +79,8 @@ export const ts_spine_node_backend_config = (options = {}) => {
78
79
  paths,
79
80
  extra_env: {
80
81
  [TS_SPINE_DIR_ENV]: paths.root,
81
- ...login_rate_limit_env(enable_login_rate_limit)
82
+ ...login_rate_limit_env(enable_login_rate_limit),
83
+ ...action_rate_limit_env(options)
82
84
  },
83
85
  capabilities: ts_spine_capabilities
84
86
  }),
@@ -89,7 +91,8 @@ export const ts_spine_node_backend_config = (options = {}) => {
89
91
  * `BackendConfig` for the Bun TS spine binary — spawned via `bun run`. Bun
90
92
  * resolves the entry's relative `.js`→`.ts` source specifiers natively (no
91
93
  * flag needed — unlike Deno's `--sloppy-imports`, and like Gro's loader on
92
- * the Node path), and `Bun.serve` + `hono/bun` need no extra deps.
94
+ * the Node path). `Bun.serve` is built in; the WS upgrade comes from the
95
+ * optional `@hono/bun` peer dep.
93
96
  */
94
97
  export const ts_spine_bun_backend_config = (options = {}) => {
95
98
  const { port = TS_SPINE_BUN_DEFAULT_PORT, database_url, enable_login_rate_limit } = options;
@@ -104,7 +107,8 @@ export const ts_spine_bun_backend_config = (options = {}) => {
104
107
  paths,
105
108
  extra_env: {
106
109
  [TS_SPINE_DIR_ENV]: paths.root,
107
- ...login_rate_limit_env(enable_login_rate_limit)
110
+ ...login_rate_limit_env(enable_login_rate_limit),
111
+ ...action_rate_limit_env(options)
108
112
  },
109
113
  capabilities: ts_spine_bun_capabilities
110
114
  }),
@@ -148,7 +152,8 @@ export const ts_spine_deno_backend_config = (options = {}) => {
148
152
  paths,
149
153
  extra_env: {
150
154
  [TS_SPINE_DIR_ENV]: paths.root,
151
- ...login_rate_limit_env(enable_login_rate_limit)
155
+ ...login_rate_limit_env(enable_login_rate_limit),
156
+ ...action_rate_limit_env(options)
152
157
  },
153
158
  capabilities: ts_spine_capabilities
154
159
  }),