@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
@@ -5,8 +5,8 @@
5
5
  * specs + handlers; the harness constructs real `WSContext` instances
6
6
  * backed by test-owned `send`/`close` pairs, fakes the authenticated
7
7
  * Hono context (`request_context`, credential type, session id, api
8
- * token id), and exposes a `connect()` factory returning a
9
- * `MockWsClient` per connection.
8
+ * token id), and exposes a `connect()` factory returning a `WsClient`
9
+ * per connection.
10
10
  *
11
11
  * Three layers are exported:
12
12
  *
@@ -14,19 +14,19 @@
14
14
  * `create_stub_upgrade`, `MinimalActionEnvironment`,
15
15
  * `dispatch_ws_message`) — used by fuz_app's own dispatcher tests
16
16
  * and by consumers wiring tight one-off tests.
17
- * - **Harness** (`create_ws_test_harness`, `MockWsClient`,
18
- * `keeper_identity`) — the high-level driver. Give it specs +
19
- * handlers, get back `{transport, connect()}`. `connect()` is async
20
- * and resolves after `on_socket_open` completes, so broadcasts sent
21
- * immediately after `await harness.connect()` reach the client.
22
- * - **Round-trip helpers** — `is_notification` / `is_notification_with`
23
- * / `is_response_for` predicates, JSON-RPC wire-frame types
24
- * (`JsonrpcNotificationFrame`, `JsonrpcSuccessResponseFrame`,
25
- * `JsonrpcErrorResponseFrame`distinct from the runtime Zod types
26
- * in `http/jsonrpc.ts` so tests can narrow `params` / `result`),
27
- * and `build_broadcast_api` for wiring a typed broadcast API against
28
- * the harness's transport. Used by consumer round-trip test suites
29
- * to replace ~100 lines of verbatim-identical glue.
17
+ * - **Harness** (`create_ws_test_harness`, `keeper_identity`) — the
18
+ * high-level driver. Give it specs + handlers, get back
19
+ * `{transport, connect()}`. `connect()` is async and resolves after
20
+ * `on_socket_open` completes, so broadcasts sent immediately after
21
+ * `await harness.connect()` reach the client. Returns a `WsClient`
22
+ * (shared interfacesee `transports/ws_client.ts`); the same
23
+ * interface is implemented by `transports/ws_transport.ts` for
24
+ * cross-process tests.
25
+ * - **Broadcast wiring** `build_broadcast_api` for wiring a typed
26
+ * broadcast API against the harness's transport. Wire-frame types
27
+ * + predicates (`is_notification`, `is_response_for`,
28
+ * `JsonrpcNotificationFrame`, ...) live in `transports/ws_client.ts`
29
+ * so both in-process and cross-process drivers reference one source.
30
30
  *
31
31
  * Hono's wire upgrade is skipped — the Node test runtime has no
32
32
  * `@hono/node-ws` adapter — but the full dispatch path is exercised
@@ -47,7 +47,7 @@ import { type RegisterActionWsOptions } from '../actions/register_action_ws.js';
47
47
  import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
48
48
  import { type RequestContext } from '../auth/request_context.js';
49
49
  import { type CredentialType } from '../hono_context.js';
50
- import { JSONRPC_VERSION } from '../http/jsonrpc.js';
50
+ import { type WsClient } from './transports/ws_client.js';
51
51
  /**
52
52
  * A `WSContext` paired with capture arrays. Use `sends` to assert on
53
53
  * outgoing frames; use `closes` to assert on revocation / close.
@@ -127,92 +127,6 @@ export interface WsConnectIdentity {
127
127
  /** Roles to grant via active role_grants. Pass `[ROLE_KEEPER]` for keeper actions. */
128
128
  roles?: Array<string>;
129
129
  }
130
- /** A mock WS client: send requests, inspect/await incoming messages. */
131
- export interface MockWsClient {
132
- /**
133
- * Send a JSON-RPC message (request or notification) to the server.
134
- *
135
- * @throws Error if called after `close()` resolves — the harness rejects
136
- * sends on a closed socket so post-close test bugs surface immediately.
137
- */
138
- send: (message: unknown) => Promise<void>;
139
- /**
140
- * Send a JSON-RPC request and await its response. Resolves with the
141
- * `result`; throws with a useful message (code, text, and any `data`
142
- * payload) on an error frame — without this, asserting on
143
- * `result.foo` for a failed request throws
144
- * `Cannot read property 'foo' of undefined`, which hides the real
145
- * cause. Use `send` + `wait_for(is_response_for(id))` directly when
146
- * you need to assert on the error frame itself.
147
- *
148
- * @throws Error if the server returns a JSON-RPC error frame for `id`,
149
- * or if `wait_for` times out before a matching response arrives.
150
- */
151
- request: <R = unknown>(id: number | string, method: string, params: unknown, timeout_ms?: number) => Promise<R>;
152
- /**
153
- * Close the connection, firing `onClose`. Returns a promise that
154
- * resolves once `on_socket_close` (and the transport's own cleanup)
155
- * have completed — tests that assert on post-close state should await.
156
- */
157
- close: (code?: number, reason?: string) => Promise<void>;
158
- /** Every message the server has sent, in arrival order. */
159
- readonly messages: ReadonlyArray<unknown>;
160
- /**
161
- * Wait until a message satisfies `predicate`. Matches are checked
162
- * against already-received messages first, then new arrivals until
163
- * the timeout (defaults to 1000ms).
164
- *
165
- * When `predicate` is a type guard (e.g. `is_notification_with<P>`),
166
- * the result is narrowed automatically and callers don't need to
167
- * spell `<JsonrpcNotificationFrame<P>>` on the call site.
168
- *
169
- * @throws Error if `timeout_ms` elapses before a matching message
170
- * arrives — the pending waiter is dropped from the internal list so
171
- * later messages don't keep iterating it.
172
- */
173
- wait_for: {
174
- <T>(predicate: (msg: unknown) => msg is T, timeout_ms?: number): Promise<T>;
175
- <T = unknown>(predicate: (msg: unknown) => boolean, timeout_ms?: number): Promise<T>;
176
- };
177
- }
178
- export interface JsonrpcNotificationFrame<P = unknown> {
179
- jsonrpc: typeof JSONRPC_VERSION;
180
- method: string;
181
- params: P;
182
- }
183
- export interface JsonrpcSuccessResponseFrame<R = unknown> {
184
- jsonrpc: typeof JSONRPC_VERSION;
185
- id: number | string;
186
- result: R;
187
- }
188
- export interface JsonrpcErrorResponseFrame<D = unknown> {
189
- jsonrpc: typeof JSONRPC_VERSION;
190
- id: number | string;
191
- error: {
192
- code: number;
193
- message: string;
194
- data?: D;
195
- };
196
- }
197
- /** Predicate matching a JSON-RPC notification with the given method name. */
198
- export declare const is_notification: (method: string) => (msg: unknown) => boolean;
199
- /**
200
- * Type-guard combinator: match a notification whose typed `params` satisfies
201
- * `match`. Collapses the common test pattern of casting `msg` to
202
- * `JsonrpcNotificationFrame<P>` in every predicate body.
203
- *
204
- * ```ts
205
- * const match_roster_for = (id: Uuid) =>
206
- * is_notification_with<RosterChangedParams>(
207
- * WORLD_METHODS.roster_changed,
208
- * (params) => params.character_id === id && !params.removed,
209
- * );
210
- * const roster = await client.wait_for(match_roster_for(char_id));
211
- * ```
212
- */
213
- export declare const is_notification_with: <P>(method: string, match: (params: P) => boolean) => (msg: unknown) => msg is JsonrpcNotificationFrame<P>;
214
- /** Predicate matching a JSON-RPC response frame (success or error) for the given request id. */
215
- export declare const is_response_for: (id: number | string) => (msg: unknown) => boolean;
216
130
  /** Options for `create_ws_test_harness`. */
217
131
  export interface CreateWsTestHarnessOptions {
218
132
  /**
@@ -246,8 +160,12 @@ export interface WsTestHarness {
246
160
  * immediately after the `await` reach the connection. Earlier
247
161
  * revisions returned synchronously and required a `settle_open()`
248
162
  * microtask drain — no longer necessary.
163
+ *
164
+ * Returns the shared `WsClient` interface — same surface the
165
+ * cross-process driver in `transports/ws_transport.ts` implements,
166
+ * so assertion helpers and suite bodies work against either impl.
249
167
  */
250
- connect: (identity?: WsConnectIdentity) => Promise<MockWsClient>;
168
+ connect: (identity?: WsConnectIdentity) => Promise<WsClient>;
251
169
  }
252
170
  /**
253
171
  * Create a WebSocket test harness for the given specs + handlers.
@@ -1 +1 @@
1
- {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,8 @@
5
5
  * specs + handlers; the harness constructs real `WSContext` instances
6
6
  * backed by test-owned `send`/`close` pairs, fakes the authenticated
7
7
  * Hono context (`request_context`, credential type, session id, api
8
- * token id), and exposes a `connect()` factory returning a
9
- * `MockWsClient` per connection.
8
+ * token id), and exposes a `connect()` factory returning a `WsClient`
9
+ * per connection.
10
10
  *
11
11
  * Three layers are exported:
12
12
  *
@@ -14,19 +14,19 @@
14
14
  * `create_stub_upgrade`, `MinimalActionEnvironment`,
15
15
  * `dispatch_ws_message`) — used by fuz_app's own dispatcher tests
16
16
  * and by consumers wiring tight one-off tests.
17
- * - **Harness** (`create_ws_test_harness`, `MockWsClient`,
18
- * `keeper_identity`) — the high-level driver. Give it specs +
19
- * handlers, get back `{transport, connect()}`. `connect()` is async
20
- * and resolves after `on_socket_open` completes, so broadcasts sent
21
- * immediately after `await harness.connect()` reach the client.
22
- * - **Round-trip helpers** — `is_notification` / `is_notification_with`
23
- * / `is_response_for` predicates, JSON-RPC wire-frame types
24
- * (`JsonrpcNotificationFrame`, `JsonrpcSuccessResponseFrame`,
25
- * `JsonrpcErrorResponseFrame`distinct from the runtime Zod types
26
- * in `http/jsonrpc.ts` so tests can narrow `params` / `result`),
27
- * and `build_broadcast_api` for wiring a typed broadcast API against
28
- * the harness's transport. Used by consumer round-trip test suites
29
- * to replace ~100 lines of verbatim-identical glue.
17
+ * - **Harness** (`create_ws_test_harness`, `keeper_identity`) — the
18
+ * high-level driver. Give it specs + handlers, get back
19
+ * `{transport, connect()}`. `connect()` is async and resolves after
20
+ * `on_socket_open` completes, so broadcasts sent immediately after
21
+ * `await harness.connect()` reach the client. Returns a `WsClient`
22
+ * (shared interfacesee `transports/ws_client.ts`); the same
23
+ * interface is implemented by `transports/ws_transport.ts` for
24
+ * cross-process tests.
25
+ * - **Broadcast wiring** `build_broadcast_api` for wiring a typed
26
+ * broadcast API against the harness's transport. Wire-frame types
27
+ * + predicates (`is_notification`, `is_response_for`,
28
+ * `JsonrpcNotificationFrame`, ...) live in `transports/ws_client.ts`
29
+ * so both in-process and cross-process drivers reference one source.
30
30
  *
31
31
  * Hono's wire upgrade is skipped — the Node test runtime has no
32
32
  * `@hono/node-ws` adapter — but the full dispatch path is exercised
@@ -47,9 +47,9 @@ import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
47
47
  import { REQUEST_CONTEXT_KEY } from '../auth/request_context.js';
48
48
  import { ROLE_KEEPER } from '../auth/role_schema.js';
49
49
  import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY, } from '../hono_context.js';
50
- import { JSONRPC_VERSION } from '../http/jsonrpc.js';
51
- import { create_jsonrpc_request, is_jsonrpc_error_response, is_jsonrpc_notification, is_jsonrpc_response, } from '../http/jsonrpc_helpers.js';
50
+ import { create_jsonrpc_request } from '../http/jsonrpc_helpers.js';
52
51
  import { create_test_account, create_test_actor, create_test_role_grant } from './entities.js';
52
+ import { WS_CLIENT_DEFAULT_TIMEOUT_MS, is_response_for, } from './transports/ws_client.js';
53
53
  /**
54
54
  * Build a real `WSContext` backed by in-memory `send`/`close` capture.
55
55
  * Parsing of outgoing frames is left to the caller — `sends` holds the
@@ -138,28 +138,6 @@ export const dispatch_ws_message = async (on_message, event, ws) => {
138
138
  if (result instanceof Promise)
139
139
  await result;
140
140
  };
141
- /** Predicate matching a JSON-RPC notification with the given method name. */
142
- export const is_notification = (method) => (msg) => is_jsonrpc_notification(msg) && msg.method === method;
143
- /**
144
- * Type-guard combinator: match a notification whose typed `params` satisfies
145
- * `match`. Collapses the common test pattern of casting `msg` to
146
- * `JsonrpcNotificationFrame<P>` in every predicate body.
147
- *
148
- * ```ts
149
- * const match_roster_for = (id: Uuid) =>
150
- * is_notification_with<RosterChangedParams>(
151
- * WORLD_METHODS.roster_changed,
152
- * (params) => params.character_id === id && !params.removed,
153
- * );
154
- * const roster = await client.wait_for(match_roster_for(char_id));
155
- * ```
156
- */
157
- export const is_notification_with = (method, match) => (msg) => is_jsonrpc_notification(msg) &&
158
- msg.method === method &&
159
- match(msg.params);
160
- /** Predicate matching a JSON-RPC response frame (success or error) for the given request id. */
161
- export const is_response_for = (id) => (msg) => (is_jsonrpc_response(msg) || is_jsonrpc_error_response(msg)) && msg.id === id;
162
- const DEFAULT_TIMEOUT_MS = 1000;
163
141
  /**
164
142
  * Build a `RequestContext` with a fresh UUID account/actor and role_grants
165
143
  * for the supplied roles. Used by the high-level harness so callers can
@@ -266,6 +244,11 @@ export const create_ws_test_harness = (options) => {
266
244
  };
267
245
  const received = [];
268
246
  const waiters = [];
247
+ // Resolvers awaiting a server-initiated close — fired by the
248
+ // `WSContext.close` callback below so `wait_for_close` resolves
249
+ // whether the dispatcher (e.g. an auth-guard revocation) or the
250
+ // test closed the socket.
251
+ const close_waiters = [];
269
252
  let is_closed = false;
270
253
  // Captured in `ws.close` below; `client.close(...)` returns it so
271
254
  // tests can await async `on_socket_close` cleanup.
@@ -291,6 +274,8 @@ export const create_ws_test_harness = (options) => {
291
274
  if (is_closed)
292
275
  return;
293
276
  is_closed = true;
277
+ for (const resolve of close_waiters.splice(0))
278
+ resolve();
294
279
  const close_event = new Event('close');
295
280
  Object.defineProperties(close_event, {
296
281
  code: { value: code ?? 1000, writable: false },
@@ -315,7 +300,7 @@ export const create_ws_test_harness = (options) => {
315
300
  // onOpen is typed as `void` by Hono but `register_action_ws`
316
301
  // returns a promise when `on_socket_open` does async bootstrap.
317
302
  await events.onOpen?.(new Event('open'), ws);
318
- const wait_for_impl = (predicate, timeout_ms = DEFAULT_TIMEOUT_MS) => {
303
+ const wait_for_impl = (predicate, timeout_ms = WS_CLIENT_DEFAULT_TIMEOUT_MS) => {
319
304
  for (const msg of received) {
320
305
  if (predicate(msg))
321
306
  return Promise.resolve(msg);
@@ -371,6 +356,23 @@ export const create_ws_test_harness = (options) => {
371
356
  await close_pending;
372
357
  },
373
358
  wait_for: wait_for_impl,
359
+ wait_for_close: (timeout_ms = WS_CLIENT_DEFAULT_TIMEOUT_MS) => {
360
+ if (is_closed)
361
+ return Promise.resolve(true);
362
+ return new Promise((resolve) => {
363
+ const on_close = () => {
364
+ clearTimeout(timer);
365
+ resolve(true);
366
+ };
367
+ const timer = setTimeout(() => {
368
+ const i = close_waiters.indexOf(on_close);
369
+ if (i >= 0)
370
+ close_waiters.splice(i, 1);
371
+ resolve(false);
372
+ }, timeout_ms);
373
+ close_waiters.push(on_close);
374
+ });
375
+ },
374
376
  };
375
377
  };
376
378
  return { transport, connect };
package/dist/ui/CLAUDE.md CHANGED
@@ -89,9 +89,11 @@ pattern rather than reintroducing prop-drilling.
89
89
  `SidebarState` if `sidebar_state` prop is not supplied).
90
90
  - `ColumnLayout.svelte` — fixed `aside` column + fluid `children`
91
91
  column; `column_width = '280px'`.
92
- - `MenuLink.svelte` — SvelteKit `<a>` with `selected`/`highlighted`
93
- derived from `page.url.pathname`. Takes `path` (resolved via
94
- `resolve` from `$app/paths`).
92
+ - `MenuLink.svelte` — SvelteKit `<a>` with `selected` derived from
93
+ `page.url.pathname` (fires for exact match + descendant pages).
94
+ Exact matches additionally get `aria-current="page"`. Takes `path`
95
+ (resolved via `resolve` from `$app/paths`). The `.highlighted` name is
96
+ intentionally free for orthogonal emphasis (badges, recent activity).
95
97
  - `sidebar_state.svelte.ts` — `SidebarState` (with `activate()` cleanup
96
98
  pattern, optional reactive `enabled` getter), `sidebar_state_context`.
97
99
 
@@ -1,8 +1,19 @@
1
1
  <script lang="ts">
2
2
  /**
3
3
  * SvelteKit-aware navigation link. Resolves `path` via `resolve` from
4
- * `$app/paths`, then derives `selected` (exact match) and `highlighted`
5
- * (current path is below `path`) from `page.url.pathname`.
4
+ * `$app/paths`, then derives `selected` from `page.url.pathname`
5
+ * fires for both the exact page (`/admin/repos`) and any descendant
6
+ * page (`/admin/repos/foo/log`). Exact matches additionally carry
7
+ * `aria-current="page"` so CSS / assistive tech can distinguish
8
+ * "this exact page" from "current section" when needed.
9
+ *
10
+ * The earlier `highlighted` flag (which fired only on the prefix-only
11
+ * case) was dropped: in every practical use it sat next to `.selected`
12
+ * with a near-identical visual treatment and just made callers compute
13
+ * two booleans for what reads to users as one "you are here" state.
14
+ * The `highlighted` *name* is now free for orthogonal emphasis (recent
15
+ * activity, unread badges, search matches) — that's how `fuz_ui`'s
16
+ * `DocsPageLinks` already uses it.
6
17
  *
7
18
  * @module
8
19
  */
@@ -15,31 +26,20 @@
15
26
 
16
27
  const {
17
28
  path,
18
- highlighted,
19
29
  children,
20
30
  ...rest
21
31
  }: OmitStrict<SvelteHTMLElements['a'], 'href' | 'children'> & {
22
32
  /** Route path passed to `resolve` from `$app/paths` to compute `href`. */
23
33
  path: string;
24
- /** Override the auto-derived `highlighted` flag (defaults to "current path is below `href`"). */
25
- highlighted?: boolean;
26
34
  children?: Snippet;
27
35
  } = $props();
28
36
 
29
37
  const href = $derived(resolve(path as any));
30
- const selected = $derived(href === page.url.pathname);
31
- const is_highlighted = $derived(
32
- highlighted ?? (page.url.pathname.startsWith(href + '/') && !selected),
33
- );
38
+ const is_exact = $derived(href === page.url.pathname);
39
+ const selected = $derived(is_exact || page.url.pathname.startsWith(href + '/'));
34
40
  </script>
35
41
 
36
42
  <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
37
- <a {href} class:selected class:highlighted={is_highlighted} {...rest}>
43
+ <a {href} class:selected aria-current={is_exact ? 'page' : undefined} {...rest}>
38
44
  {@render children?.()}
39
45
  </a>
40
-
41
- <style>
42
- .highlighted {
43
- background-color: var(--fg_10);
44
- }
45
- </style>
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * SvelteKit-aware navigation link. Resolves `path` via `resolve` from
3
- * `$app/paths`, then derives `selected` (exact match) and `highlighted`
4
- * (current path is below `path`) from `page.url.pathname`.
3
+ * `$app/paths`, then derives `selected` from `page.url.pathname`
4
+ * fires for both the exact page (`/admin/repos`) and any descendant
5
+ * page (`/admin/repos/foo/log`). Exact matches additionally carry
6
+ * `aria-current="page"` so CSS / assistive tech can distinguish
7
+ * "this exact page" from "current section" when needed.
8
+ *
9
+ * The earlier `highlighted` flag (which fired only on the prefix-only
10
+ * case) was dropped: in every practical use it sat next to `.selected`
11
+ * with a near-identical visual treatment and just made callers compute
12
+ * two booleans for what reads to users as one "you are here" state.
13
+ * The `highlighted` *name* is now free for orthogonal emphasis (recent
14
+ * activity, unread badges, search matches) — that's how `fuz_ui`'s
15
+ * `DocsPageLinks` already uses it.
5
16
  *
6
17
  * @module
7
18
  */
@@ -11,8 +22,6 @@ import type { OmitStrict } from '@fuzdev/fuz_util/types.js';
11
22
  type $$ComponentProps = OmitStrict<SvelteHTMLElements['a'], 'href' | 'children'> & {
12
23
  /** Route path passed to `resolve` from `$app/paths` to compute `href`. */
13
24
  path: string;
14
- /** Override the auto-derived `highlighted` flag (defaults to "current path is below `href`"). */
15
- highlighted?: boolean;
16
25
  children?: Snippet;
17
26
  };
18
27
  declare const MenuLink: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1 +1 @@
1
- {"version":3,"file":"MenuLink.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/MenuLink.svelte"],"names":[],"mappings":"AAGA;;;;;;OAMI;AACJ,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2BAA2B,CAAC;AAEzD,KAAK,gBAAgB,GAAI,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG;IACnF,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAiCH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"MenuLink.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/MenuLink.svelte"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;OAiBI;AACJ,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2BAA2B,CAAC;AAEzD,KAAK,gBAAgB,GAAI,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG;IACnF,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA4BH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.64.0",
3
+ "version": "0.66.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -30,6 +30,7 @@
30
30
  "hono": ">=4",
31
31
  "pg": ">=8",
32
32
  "svelte": "^5",
33
+ "ws": ">=8",
33
34
  "zod": ">=4"
34
35
  },
35
36
  "peerDependenciesMeta": {
@@ -38,6 +39,9 @@
38
39
  },
39
40
  "pg": {
40
41
  "optional": true
42
+ },
43
+ "ws": {
44
+ "optional": true
41
45
  }
42
46
  },
43
47
  "devDependencies": {
@@ -46,11 +50,11 @@
46
50
  "@fuzdev/fuz_code": "^0.45.1",
47
51
  "@fuzdev/fuz_css": "^0.59.0",
48
52
  "@fuzdev/fuz_ui": "^0.192.0",
49
- "@fuzdev/fuz_util": "^0.59.0",
53
+ "@fuzdev/fuz_util": "^0.60.1",
50
54
  "@fuzdev/gro": "^0.199.0",
51
55
  "@jridgewell/trace-mapping": "^0.3.31",
52
56
  "@node-rs/argon2": "^2.0.2",
53
- "@ryanatkn/eslint-config": "^0.11.0",
57
+ "@ryanatkn/eslint-config": "^0.12.1",
54
58
  "@sveltejs/acorn-typescript": "^1.0.9",
55
59
  "@sveltejs/adapter-static": "^3.0.10",
56
60
  "@sveltejs/kit": "^2.55.0",
@@ -58,11 +62,12 @@
58
62
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
59
63
  "@types/estree": "^1.0.8",
60
64
  "@types/pg": "^8.18.0",
65
+ "@types/ws": "^8.18.1",
61
66
  "@webref/css": "^8.2.0",
62
67
  "eslint": "^9.39.4",
63
68
  "eslint-plugin-svelte": "^3.15.1",
64
69
  "esm-env": "^1.2.2",
65
- "hono": "^4.12.7",
70
+ "hono": "^4.12.21",
66
71
  "jsdom": "^28.1.0",
67
72
  "magic-string": "^0.30.21",
68
73
  "pg": "^8.20.0",
@@ -76,6 +81,7 @@
76
81
  "typescript-eslint": "^8.57.0",
77
82
  "vite": "^7.3.1",
78
83
  "vitest": "^4.0.18",
84
+ "ws": "^8.20.1",
79
85
  "zimmerframe": "^1.1.4",
80
86
  "zod": "^4.3.6"
81
87
  },