@fuzdev/fuz_app 0.62.0 → 0.64.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 (136) hide show
  1. package/dist/actions/CLAUDE.md +139 -24
  2. package/dist/actions/action_rpc.d.ts +10 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +1 -1
  5. package/dist/actions/action_spec.d.ts +1 -1
  6. package/dist/actions/action_spec.js +1 -1
  7. package/dist/actions/connection_closer.d.ts +68 -0
  8. package/dist/actions/connection_closer.d.ts.map +1 -0
  9. package/dist/actions/connection_closer.js +41 -0
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +1 -0
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +23 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +5 -5
  17. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  18. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_auth_guard.js +23 -7
  20. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  22. package/dist/actions/ws_endpoint_spec.js +13 -0
  23. package/dist/auth/CLAUDE.md +124 -39
  24. package/dist/auth/account_action_specs.d.ts +7 -1
  25. package/dist/auth/account_action_specs.d.ts.map +1 -1
  26. package/dist/auth/account_action_specs.js +11 -4
  27. package/dist/auth/account_actions.d.ts +13 -0
  28. package/dist/auth/account_actions.d.ts.map +1 -1
  29. package/dist/auth/account_actions.js +40 -5
  30. package/dist/auth/account_routes.d.ts +12 -2
  31. package/dist/auth/account_routes.d.ts.map +1 -1
  32. package/dist/auth/account_routes.js +63 -12
  33. package/dist/auth/account_schema.d.ts +5 -5
  34. package/dist/auth/account_schema.js +2 -2
  35. package/dist/auth/actor_lookup_actions.d.ts +1 -1
  36. package/dist/auth/actor_lookup_actions.js +1 -1
  37. package/dist/auth/actor_lookup_queries.d.ts +1 -1
  38. package/dist/auth/actor_lookup_queries.js +1 -1
  39. package/dist/auth/actor_search_action_specs.d.ts +1 -1
  40. package/dist/auth/actor_search_action_specs.js +1 -1
  41. package/dist/auth/actor_search_actions.d.ts +1 -1
  42. package/dist/auth/actor_search_actions.js +1 -1
  43. package/dist/auth/actor_search_queries.d.ts +1 -1
  44. package/dist/auth/actor_search_queries.js +1 -1
  45. package/dist/auth/admin_action_specs.d.ts +8 -8
  46. package/dist/auth/admin_actions.d.ts +11 -0
  47. package/dist/auth/admin_actions.d.ts.map +1 -1
  48. package/dist/auth/admin_actions.js +25 -0
  49. package/dist/auth/all_action_spec_registries.d.ts +2 -2
  50. package/dist/auth/all_action_spec_registries.js +2 -2
  51. package/dist/auth/audit_emitter.d.ts +56 -12
  52. package/dist/auth/audit_emitter.d.ts.map +1 -1
  53. package/dist/auth/audit_emitter.js +38 -12
  54. package/dist/auth/audit_log_routes.d.ts +1 -1
  55. package/dist/auth/audit_log_routes.js +1 -1
  56. package/dist/auth/audit_log_schema.d.ts +30 -3
  57. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  58. package/dist/auth/audit_log_schema.js +21 -3
  59. package/dist/auth/bootstrap_routes.d.ts +1 -1
  60. package/dist/auth/invite_schema.d.ts +2 -2
  61. package/dist/auth/request_context.d.ts +1 -1
  62. package/dist/auth/signup_routes.d.ts +1 -1
  63. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  64. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  65. package/dist/auth/standard_rpc_actions.js +1 -0
  66. package/dist/env/update_env_variable.js +1 -1
  67. package/dist/http/CLAUDE.md +42 -26
  68. package/dist/http/ip_canonical.d.ts +99 -0
  69. package/dist/http/ip_canonical.d.ts.map +1 -0
  70. package/dist/http/ip_canonical.js +191 -0
  71. package/dist/http/origin.d.ts +13 -5
  72. package/dist/http/origin.d.ts.map +1 -1
  73. package/dist/http/origin.js +13 -31
  74. package/dist/http/pending_effects.d.ts +1 -1
  75. package/dist/http/pending_effects.js +1 -1
  76. package/dist/http/proxy.d.ts +13 -5
  77. package/dist/http/proxy.d.ts.map +1 -1
  78. package/dist/http/proxy.js +15 -23
  79. package/dist/http/surface.d.ts +50 -0
  80. package/dist/http/surface.d.ts.map +1 -1
  81. package/dist/http/surface.js +27 -1
  82. package/dist/primitive_schemas.d.ts +20 -4
  83. package/dist/primitive_schemas.d.ts.map +1 -1
  84. package/dist/primitive_schemas.js +25 -4
  85. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  86. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  87. package/dist/realtime/sse_auth_guard.js +15 -3
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +60 -0
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +95 -2
  94. package/dist/server/startup.d.ts.map +1 -1
  95. package/dist/server/startup.js +12 -0
  96. package/dist/testing/CLAUDE.md +91 -71
  97. package/dist/testing/admin_integration.d.ts.map +1 -1
  98. package/dist/testing/admin_integration.js +4 -5
  99. package/dist/testing/adversarial_headers.d.ts +6 -0
  100. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  101. package/dist/testing/adversarial_headers.js +13 -5
  102. package/dist/testing/app_server.d.ts +33 -32
  103. package/dist/testing/app_server.d.ts.map +1 -1
  104. package/dist/testing/app_server.js +4 -13
  105. package/dist/testing/attack_surface.d.ts +8 -7
  106. package/dist/testing/attack_surface.d.ts.map +1 -1
  107. package/dist/testing/attack_surface.js +12 -8
  108. package/dist/testing/audit_completeness.d.ts.map +1 -1
  109. package/dist/testing/audit_completeness.js +20 -6
  110. package/dist/testing/audit_drift_guard.d.ts +116 -0
  111. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  112. package/dist/testing/audit_drift_guard.js +134 -0
  113. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  114. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  115. package/dist/testing/connection_closer_helpers.js +48 -0
  116. package/dist/testing/integration.d.ts.map +1 -1
  117. package/dist/testing/integration.js +7 -9
  118. package/dist/testing/rate_limiting.js +4 -4
  119. package/dist/testing/rpc_helpers.d.ts +2 -1
  120. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  121. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  122. package/dist/testing/rpc_round_trip.js +6 -8
  123. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  124. package/dist/testing/sse_round_trip.js +12 -6
  125. package/dist/testing/stubs.d.ts +11 -0
  126. package/dist/testing/stubs.d.ts.map +1 -1
  127. package/dist/testing/stubs.js +4 -0
  128. package/dist/testing/surface_invariants.d.ts +66 -1
  129. package/dist/testing/surface_invariants.d.ts.map +1 -1
  130. package/dist/testing/surface_invariants.js +103 -1
  131. package/dist/ui/CLAUDE.md +13 -18
  132. package/dist/ui/SurfaceExplorer.svelte +161 -2
  133. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  134. package/dist/ui/keyed_async_slot.svelte.d.ts +1 -1
  135. package/dist/ui/keyed_async_slot.svelte.js +1 -1
  136. package/package.json +1 -1
@@ -19,6 +19,7 @@ import './assert_dev_env.js';
19
19
  import { assert } from 'vitest';
20
20
  import { middleware_applies } from '../http/schema_helpers.js';
21
21
  import { filter_protected_routes, filter_role_routes, filter_keeper_routes, filter_routes_with_input, filter_routes_with_params, filter_routes_with_query, filter_public_routes, format_route_key, } from '../http/surface_query.js';
22
+ import { PROTOCOL_ACTION_METHODS } from '../actions/action_codegen.js';
22
23
  // --- Structural invariants ---
23
24
  /**
24
25
  * Every protected route has 401 in `error_schemas`.
@@ -374,6 +375,83 @@ export const audit_error_schema_tightness = (surface) => {
374
375
  }
375
376
  return entries;
376
377
  };
378
+ // --- RPC / WS structural invariants ---
379
+ /**
380
+ * Every RPC method on every endpoint has a non-empty `description`.
381
+ *
382
+ * Parallel of `assert_descriptions_present` over `surface.rpc_endpoints`.
383
+ * Empty descriptions on RPC methods leak through `library.json` codegen and
384
+ * consumer-facing docs without the route-level check catching them.
385
+ */
386
+ export const assert_rpc_method_descriptions_present = (surface) => {
387
+ for (const ep of surface.rpc_endpoints) {
388
+ for (const method of ep.methods) {
389
+ assert.ok(method.description.length > 0, `RPC method '${method.name}' on endpoint '${ep.path}' has empty description`);
390
+ }
391
+ }
392
+ };
393
+ /**
394
+ * Every WS method on every endpoint has a non-empty `description`.
395
+ *
396
+ * Parallel of `assert_descriptions_present` over `surface.ws_endpoints`.
397
+ * Same rationale as `assert_rpc_method_descriptions_present` — codegen +
398
+ * surface explorer consume the description, blank values surface as `''`.
399
+ */
400
+ export const assert_ws_method_descriptions_present = (surface) => {
401
+ for (const ep of surface.ws_endpoints) {
402
+ for (const method of ep.methods) {
403
+ assert.ok(method.description.length > 0, `WS method '${method.name}' on endpoint '${ep.path}' has empty description`);
404
+ }
405
+ }
406
+ };
407
+ /**
408
+ * Every WS endpoint's `methods` includes every protocol action method
409
+ * (`heartbeat`, `cancel`).
410
+ *
411
+ * Consumers register WS endpoints by spreading `protocol_actions` from
412
+ * `actions/protocol.ts` before their own actions:
413
+ *
414
+ * ```ts
415
+ * ws_endpoints: [{path: '/api/ws', actions: [...protocol_actions, ...consumer_actions], ...}]
416
+ * ```
417
+ *
418
+ * Forgetting the spread compiles cleanly but breaks at runtime: client-side
419
+ * heartbeats and `cancel` notifications get `method_not_found` from the
420
+ * dispatcher, so disconnect detection silently regresses and per-request
421
+ * cancel never aborts the matching handler. Catch the mistake at the
422
+ * surface layer rather than at runtime.
423
+ */
424
+ export const assert_ws_endpoints_include_protocol_actions = (surface) => {
425
+ for (const ep of surface.ws_endpoints) {
426
+ const method_names = new Set(ep.methods.map((m) => m.name));
427
+ for (const expected of PROTOCOL_ACTION_METHODS) {
428
+ assert.ok(method_names.has(expected), `WS endpoint '${ep.path}' is missing protocol action method '${expected}' — ` +
429
+ `spread \`protocol_actions\` from 'actions/protocol.js' into \`actions\``);
430
+ }
431
+ }
432
+ };
433
+ /**
434
+ * WS methods follow the kind ⇔ auth biconditional emitted by surface
435
+ * generation: `kind === 'remote_notification' ⟺ auth === null`.
436
+ *
437
+ * `generate_app_surface` produces this shape directly from the action
438
+ * spec union (notifications carry `auth: null` per `ActionSpecUnion`;
439
+ * `request_response` carries a `RouteAuth`). The assertion guards against
440
+ * drift if a future surface emitter, transform, or test fixture violates
441
+ * it — and gives consumers a clear failure message when a hand-built
442
+ * surface mocks the shape incorrectly.
443
+ */
444
+ export const assert_ws_notifications_have_null_auth = (surface) => {
445
+ for (const ep of surface.ws_endpoints) {
446
+ for (const method of ep.methods) {
447
+ const is_notification = method.kind === 'remote_notification';
448
+ const has_null_auth = method.auth === null;
449
+ assert.ok(is_notification === has_null_auth, `WS method '${method.name}' on endpoint '${ep.path}' violates kind ⇔ auth: ` +
450
+ `kind='${method.kind}', auth=${has_null_auth ? 'null' : 'non-null'} ` +
451
+ `(notifications must have auth: null; request_response must have a RouteAuth)`);
452
+ }
453
+ }
454
+ };
377
455
  /** Default patterns for sensitive REST routes that should be rate-limited. */
378
456
  const DEFAULT_SENSITIVE_PATTERNS = [
379
457
  /\/login$/,
@@ -424,7 +502,9 @@ export const assert_no_unexpected_public_mutations = (surface, allowlist = []) =
424
502
  *
425
503
  * Note: RPC endpoints (`create_rpc_endpoint`) use `input: z.null()` on their
426
504
  * route specs — the dispatcher handles body/query parsing internally. Real input
427
- * schemas live in `rpc_endpoints` surface, not on routes.
505
+ * schemas live in `rpc_endpoints` surface, not on routes; see
506
+ * `assert_rpc_ws_surface_invariants` for the parallel checks over RPC/WS
507
+ * method shapes.
428
508
  */
429
509
  export const assert_mutation_routes_use_post = (surface) => {
430
510
  const input_routes = filter_routes_with_input(surface);
@@ -546,6 +626,28 @@ export const assert_surface_invariants = (surface) => {
546
626
  assert_error_code_status_consistency(surface);
547
627
  assert_404_schemas_use_specific_errors(surface);
548
628
  };
629
+ /**
630
+ * Run all RPC / WS structural invariants. Options-free — applies
631
+ * universally to the `surface.rpc_endpoints` and `surface.ws_endpoints`
632
+ * slots produced by `generate_app_surface`.
633
+ *
634
+ * Parallel of `assert_surface_invariants` for the non-REST surfaces.
635
+ * Within-endpoint duplicate method names and the auth-shape biconditional
636
+ * are already enforced at startup by `compile_action_registry` (see
637
+ * `actions/CLAUDE.md` §Registry compile) — these assertions cover only
638
+ * the contract-surface concerns that a runtime registration check
639
+ * cannot: empty descriptions, missing protocol-action spread on WS
640
+ * endpoints, and kind ⇔ auth drift on WS methods.
641
+ *
642
+ * @throws AssertionError on the first invariant violation; the message
643
+ * names the offending endpoint, method, and field.
644
+ */
645
+ export const assert_rpc_ws_surface_invariants = (surface) => {
646
+ assert_rpc_method_descriptions_present(surface);
647
+ assert_ws_method_descriptions_present(surface);
648
+ assert_ws_endpoints_include_protocol_actions(surface);
649
+ assert_ws_notifications_have_null_auth(surface);
650
+ };
549
651
  /**
550
652
  * Run security policy invariants. Configurable with sensible defaults.
551
653
  *
package/dist/ui/CLAUDE.md CHANGED
@@ -5,14 +5,16 @@ utilities. Cookie-based SPA auth; prerendered static HTML served by Hono
5
5
  (no SvelteKit SSR for sessions). State classes hold one or more `AsyncSlot`s
6
6
  via composition (one per distinct async operation — e.g. `list` + `create` +
7
7
  `revoke`); per-row write ops use `KeyedAsyncSlot<K, T = void, E = string>`
8
- (supersedes the old `AsyncSlot` + `SvelteSet<id>` pair) so concurrent rows
9
- don't abort each other and failures surface per-row via `slot.error(key)`.
10
- Payload lives as `$state.raw` fields on the class. Shared dependencies flow
11
- through Svelte context, never through props RPC adapters in particular
12
- are provisioned once at the admin shell and read by every `Admin*.svelte`.
8
+ so concurrent rows don't abort each other and failures surface per-row via
9
+ `slot.error(key)`. Payload lives as `$state.raw` fields on the class.
10
+ Shared dependencies flow through Svelte context, never through props
11
+ RPC adapters are provisioned once at the admin shell and read by every
12
+ `Admin*.svelte`.
13
13
 
14
- See ../../docs/usage.md for end-to-end wiring examples (sections "Role grant
15
- offer UI" and "Admin UI"). This file is a reference, not a tutorial.
14
+ For Svelte 5 patterns (runes, inline `$props`, contexts, snippets,
15
+ attachments), see Skill(fuz-stack) svelte-patterns. See ../../docs/usage.md
16
+ for end-to-end wiring examples ("Role grant offer UI", "Admin UI"). This
17
+ file is a reference, not a tutorial.
16
18
 
17
19
  ## Key patterns
18
20
 
@@ -67,15 +69,8 @@ it. Six methods land on the reducer: `role_grant_offer_received` /
67
69
  `_retracted` / `_accepted` / `_declined` / `_supersede` all merge a
68
70
  `{offer}` payload; `role_grant_revoke` is ignored at this layer (role_grant
69
71
  lifecycle lives in auth/role_grants state). The six notification specs and
70
- their payload shapes are defined in `../auth/role_grant_offer_notifications.ts`
71
- (see `../auth/CLAUDE.md` §WS notifications).
72
-
73
- ### Svelte 5 inline `$props` shape
74
-
75
- Always `const {...}: {...} = $props()` — never `interface Props`.
76
- Destructure defaults in the binding list; put the type literal inline.
77
- This matches the user-memory Svelte props rule and the existing file
78
- conventions.
72
+ their payload shapes are defined in `auth/role_grant_offer_notifications.ts`
73
+ (see `auth/CLAUDE.md` §WS notifications).
79
74
 
80
75
  ### Context over props for shared deps
81
76
 
@@ -184,8 +179,8 @@ destructive actions.
184
179
  reason codes with friendly copy: `ERROR_ROLE_GRANT_OFFER_SELF_TARGET`,
185
180
  `ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE`, `ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED`,
186
181
  `ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH`, `ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH`
187
- — imported from `../auth/role_grant_offer_action_specs.js` (see
188
- `../auth/CLAUDE.md` for `role_grant_offer_action_specs.ts` +
182
+ — imported from `auth/role_grant_offer_action_specs.js` (see
183
+ `auth/CLAUDE.md` for `role_grant_offer_action_specs.ts` +
189
184
  `role_grant_offer_actions.ts`).
190
185
  - `RoleGrantOfferHistory.svelte` — both-directions history (recipient +
191
186
  grantor, including terminal). Props: `current_actor_id: string | null`
@@ -17,6 +17,7 @@
17
17
  is_plain_authenticated_auth,
18
18
  is_public_auth,
19
19
  is_role_auth,
20
+ type RouteAuth,
20
21
  } from '../http/auth_shape.js';
21
22
 
22
23
  const {surface}: {surface: AppSurface} = $props();
@@ -28,6 +29,13 @@
28
29
 
29
30
  const summary = $derived(surface_auth_summary(surface));
30
31
 
32
+ const rpc_method_count = $derived(
33
+ surface.rpc_endpoints.reduce((sum, ep) => sum + ep.methods.length, 0),
34
+ );
35
+ const ws_method_count = $derived(
36
+ surface.ws_endpoints.reduce((sum, ep) => sum + ep.methods.length, 0),
37
+ );
38
+
31
39
  const auth_matches_filter = (
32
40
  auth: AppSurfaceRoute['auth'],
33
41
  filter: (typeof auth_types)[number],
@@ -51,6 +59,8 @@
51
59
  );
52
60
 
53
61
  let expanded_event: string | null = $state.raw(null);
62
+ let expanded_rpc_method: string | null = $state.raw(null);
63
+ let expanded_ws_method: string | null = $state.raw(null);
54
64
 
55
65
  const toggle_route = (key: string): void => {
56
66
  expanded_route = expanded_route === key ? null : key;
@@ -60,7 +70,15 @@
60
70
  expanded_event = expanded_event === method ? null : method;
61
71
  };
62
72
 
63
- const format_auth = (auth: AppSurfaceRoute['auth']): string => {
73
+ const toggle_rpc_method = (key: string): void => {
74
+ expanded_rpc_method = expanded_rpc_method === key ? null : key;
75
+ };
76
+
77
+ const toggle_ws_method = (key: string): void => {
78
+ expanded_ws_method = expanded_ws_method === key ? null : key;
79
+ };
80
+
81
+ const format_auth = (auth: RouteAuth): string => {
64
82
  if (is_public_auth(auth)) return 'none';
65
83
  if (is_keeper_auth(auth)) return 'keeper';
66
84
  if (is_role_auth(auth)) return `role:${auth.roles!.join('|')}`;
@@ -68,7 +86,7 @@
68
86
  return 'other';
69
87
  };
70
88
 
71
- const auth_chip_class = (auth: AppSurfaceRoute['auth']): string => {
89
+ const auth_chip_class = (auth: RouteAuth): string => {
72
90
  if (is_public_auth(auth)) return 'chip color_b';
73
91
  if (is_keeper_auth(auth)) return 'chip color_c';
74
92
  if (is_role_auth(auth)) return 'chip color_d';
@@ -89,6 +107,8 @@
89
107
  {#if role_count > 0}<span class="chip color_d">{role_count} role</span>{/if}
90
108
  {#if summary.keeper > 0}<span class="chip color_c">{summary.keeper} keeper</span>{/if}
91
109
  <span class="chip">{surface.middleware.length} middleware</span>
110
+ {#if rpc_method_count > 0}<span class="chip">{rpc_method_count} rpc methods</span>{/if}
111
+ {#if ws_method_count > 0}<span class="chip">{ws_method_count} ws methods</span>{/if}
92
112
  {#if surface.env.length}<span class="chip">{surface.env.length} env</span>{/if}
93
113
  {#if surface.events.length}<span class="chip">{surface.events.length} events</span>{/if}
94
114
  {#if surface.diagnostics.length}{@const warnings = surface.diagnostics.filter(
@@ -283,6 +303,145 @@
283
303
  </div>
284
304
  {/if}
285
305
 
306
+ {#if surface.rpc_endpoints.length}
307
+ <h3>rpc endpoints</h3>
308
+ {#each surface.rpc_endpoints as endpoint (endpoint.path)}
309
+ <div class="row" style:gap="var(--space_sm)" style:align-items="center">
310
+ <code>{endpoint.path}</code>
311
+ <span class="chip">{endpoint.methods.length} methods</span>
312
+ </div>
313
+ {#if endpoint.methods.length === 0}
314
+ <p class="text_50">no methods</p>
315
+ {:else}
316
+ <div style:overflow-x="auto">
317
+ <table>
318
+ <thead>
319
+ <tr>
320
+ <th>method</th>
321
+ <th>auth</th>
322
+ <th>side effects</th>
323
+ <th>rate limit</th>
324
+ <th>description</th>
325
+ </tr>
326
+ </thead>
327
+ <tbody>
328
+ {#each endpoint.methods as method (method.name)}
329
+ {@const key = `${endpoint.path}|${method.name}`}
330
+ <tr onclick={() => toggle_rpc_method(key)} style:cursor="pointer">
331
+ <td><code>{method.name}</code></td>
332
+ <td>
333
+ <span class={auth_chip_class(method.auth)}>{format_auth(method.auth)}</span>
334
+ </td>
335
+ <td>{method.side_effects ? 'yes' : 'no'}</td>
336
+ <td class="text_50">{method.rate_limit_key ?? '-'}</td>
337
+ <td class="text_50">{method.description}</td>
338
+ </tr>
339
+ {#if expanded_rpc_method === key}
340
+ <tr transition:slide>
341
+ <td colspan="5">
342
+ <div class="column" style:gap="var(--space_sm)">
343
+ <div>
344
+ <strong>input</strong>
345
+ {#if method.input_schema}
346
+ <pre>{JSON.stringify(method.input_schema, null, 2)}</pre>
347
+ {:else}
348
+ <span class="text_50">none (z.void)</span>
349
+ {/if}
350
+ </div>
351
+ <div>
352
+ <strong>output</strong>
353
+ <pre>{JSON.stringify(method.output_schema, null, 2)}</pre>
354
+ </div>
355
+ </div>
356
+ </td>
357
+ </tr>
358
+ {/if}
359
+ {/each}
360
+ </tbody>
361
+ </table>
362
+ </div>
363
+ {/if}
364
+ {/each}
365
+ {/if}
366
+
367
+ {#if surface.ws_endpoints.length}
368
+ <h3>websocket endpoints</h3>
369
+ {#each surface.ws_endpoints as endpoint (endpoint.path)}
370
+ <div
371
+ class="row"
372
+ style:gap="var(--space_sm)"
373
+ style:align-items="center"
374
+ style:flex-wrap="wrap"
375
+ >
376
+ <code>{endpoint.path}</code>
377
+ <span class="chip">{endpoint.methods.length} methods</span>
378
+ {#each endpoint.required_roles as role (role)}
379
+ <span class="chip color_d">role:{role}</span>
380
+ {/each}
381
+ {#each endpoint.allowed_origins as origin (origin)}
382
+ <span class="chip color_b"><code>{origin}</code></span>
383
+ {/each}
384
+ </div>
385
+ {#if endpoint.methods.length === 0}
386
+ <p class="text_50">no methods</p>
387
+ {:else}
388
+ <div style:overflow-x="auto">
389
+ <table>
390
+ <thead>
391
+ <tr>
392
+ <th>method</th>
393
+ <th>kind</th>
394
+ <th>auth</th>
395
+ <th>side effects</th>
396
+ <th>rate limit</th>
397
+ <th>description</th>
398
+ </tr>
399
+ </thead>
400
+ <tbody>
401
+ {#each endpoint.methods as method (method.name)}
402
+ {@const key = `${endpoint.path}|${method.name}`}
403
+ <tr onclick={() => toggle_ws_method(key)} style:cursor="pointer">
404
+ <td><code>{method.name}</code></td>
405
+ <td><span class="chip">{method.kind}</span></td>
406
+ <td>
407
+ {#if method.auth}
408
+ <span class={auth_chip_class(method.auth)}>{format_auth(method.auth)}</span>
409
+ {:else}
410
+ <span class="text_50">—</span>
411
+ {/if}
412
+ </td>
413
+ <td>{method.side_effects ? 'yes' : 'no'}</td>
414
+ <td class="text_50">{method.rate_limit_key ?? '-'}</td>
415
+ <td class="text_50">{method.description}</td>
416
+ </tr>
417
+ {#if expanded_ws_method === key}
418
+ <tr transition:slide>
419
+ <td colspan="6">
420
+ <div class="column" style:gap="var(--space_sm)">
421
+ <div>
422
+ <strong>input</strong>
423
+ {#if method.input_schema}
424
+ <pre>{JSON.stringify(method.input_schema, null, 2)}</pre>
425
+ {:else}
426
+ <span class="text_50">none (z.void)</span>
427
+ {/if}
428
+ </div>
429
+ <div>
430
+ <strong>output</strong>
431
+ <pre>{JSON.stringify(method.output_schema, null, 2)}</pre>
432
+ </div>
433
+ </div>
434
+ </td>
435
+ </tr>
436
+ {/if}
437
+ {/each}
438
+ </tbody>
439
+ </table>
440
+ </div>
441
+ {/if}
442
+ {/each}
443
+ {/if}
444
+
286
445
  {#if surface.diagnostics.length}
287
446
  <h3>diagnostics</h3>
288
447
  <div style:overflow-x="auto">
@@ -1 +1 @@
1
- {"version":3,"file":"SurfaceExplorer.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/SurfaceExplorer.svelte"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,UAAU,EAAwC,MAAM,oBAAoB,CAAC;AASzF,KAAK,gBAAgB,GAAI;IAAC,OAAO,EAAE,UAAU,CAAA;CAAC,CAAC;AAuShD,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"SurfaceExplorer.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/SurfaceExplorer.svelte"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,UAAU,EAAwC,MAAM,oBAAoB,CAAC;AAUzF,KAAK,gBAAgB,GAAI;IAAC,OAAO,EAAE,UAAU,CAAA;CAAC,CAAC;AAgchD,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -56,7 +56,7 @@ export type KeyedAsyncSlotOptions<T, E = string> = Omit<AsyncSlotOptions<T, E>,
56
56
  *
57
57
  * @typeParam K - The key type. Map identity is SameValueZero — branded
58
58
  * strings (`Uuid`) work directly. For composite keys, stringify at
59
- * the call site (e.g. `` `${account_id}:${role}` ``).
59
+ * the call site (e.g. "`${account_id}:${role}`").
60
60
  * @typeParam T - The success payload type. Use `void` for write-only
61
61
  * actions whose response isn't worth retaining.
62
62
  * @typeParam E - The shape of per-key `error(key)`. Defaults to
@@ -48,7 +48,7 @@ import { AsyncSlot } from './async_slot.svelte.js';
48
48
  *
49
49
  * @typeParam K - The key type. Map identity is SameValueZero — branded
50
50
  * strings (`Uuid`) work directly. For composite keys, stringify at
51
- * the call site (e.g. `` `${account_id}:${role}` ``).
51
+ * the call site (e.g. "`${account_id}:${role}`").
52
52
  * @typeParam T - The success payload type. Use `void` for write-only
53
53
  * actions whose response isn't worth retaining.
54
54
  * @typeParam E - The shape of per-key `error(key)`. Defaults to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",