@fuzdev/fuz_app 0.44.0 → 0.46.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 (31) hide show
  1. package/dist/actions/CLAUDE.md +65 -39
  2. package/dist/actions/action_event.d.ts +10 -1
  3. package/dist/actions/action_event.d.ts.map +1 -1
  4. package/dist/actions/action_event.js +7 -0
  5. package/dist/actions/action_event_helpers.d.ts.map +1 -1
  6. package/dist/actions/action_event_helpers.js +14 -4
  7. package/dist/actions/broadcast_api.d.ts +1 -1
  8. package/dist/actions/broadcast_api.d.ts.map +1 -1
  9. package/dist/actions/frontend_rpc_client.d.ts +95 -23
  10. package/dist/actions/frontend_rpc_client.d.ts.map +1 -1
  11. package/dist/actions/frontend_rpc_client.js +48 -23
  12. package/dist/actions/rpc_client.d.ts +56 -86
  13. package/dist/actions/rpc_client.d.ts.map +1 -1
  14. package/dist/actions/rpc_client.js +51 -106
  15. package/dist/testing/ws_round_trip.d.ts +1 -1
  16. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  17. package/dist/ui/CLAUDE.md +10 -11
  18. package/dist/ui/admin_accounts_state.svelte.d.ts +20 -41
  19. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  20. package/dist/ui/admin_invites_state.svelte.d.ts +9 -18
  21. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  22. package/dist/ui/admin_rpc_adapters.d.ts +41 -29
  23. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  24. package/dist/ui/admin_rpc_adapters.js +28 -31
  25. package/dist/ui/admin_sessions_state.svelte.d.ts +3 -2
  26. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  27. package/dist/ui/app_settings_state.svelte.d.ts +5 -10
  28. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  29. package/dist/ui/audit_log_state.svelte.d.ts +6 -18
  30. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  31. package/package.json +1 -1
@@ -581,11 +581,15 @@ per-method transport selector. Useful when methods are registered on
581
581
  different backend dispatchers (e.g. streaming action on WS, rest on HTTP).
582
582
  Returning `undefined` falls through to the peer's default selection.
583
583
 
584
- `RpcClientActionHistory` duck-typed integration point for consumers
585
- (e.g. zzz's Actions cell) that want to record every dispatched event.
586
- `add_from_json({method, action_event_data})` returns an object with
587
- `listen_to_action_event(event)` the Proxy wires each new event into the
588
- history if supplied.
584
+ `on_action_event: (event: ActionEvent<keyof TApi & string>) => void` —
585
+ optional callback fired once per dispatched action with the live
586
+ `ActionEvent`. Consumers wire reactive state inside the callback — e.g.
587
+ zzz's `Actions` cell calls its own `add_from_json` +
588
+ `listen_to_action_event` here so the history plumbing stays inside zzz
589
+ instead of leaking onto the rpc_client surface. `event.spec.method` and
590
+ `event.data.method` narrow to `keyof TApi & string` so consumers passing
591
+ a generated `ActionsApi` get the literal method-name union without an
592
+ `as ActionMethod` cast at the call site.
589
593
 
590
594
  Cast the return to a generated `ActionsApi` interface for full typing:
591
595
  codegen via `generate_actions_api_method_signature` keeps the shape
@@ -605,26 +609,39 @@ attacker-shaped `result.error` payloads cannot overwrite them.
605
609
  | `create_throwing_rpc_call` | `(method, input?) => Promise<T>` | adapter wiring (e.g. `ui/admin_rpc_adapters.ts`) — method comes from a map |
606
610
  | `create_throwing_api` | typed Proxy over `ActionsApi` | direct call sites — `await api.foo(input)` keeps full inference |
607
611
 
608
- **Recommended consumer convention.** The throwing form is the common
609
- case at call sites; the Result form is the composable escape hatch for
610
- sites that want to inspect `error.data.reason` without try/catch. Bind
611
- them as `api` (throwing wrapper) and `api_raw` (the unwrapped
612
- underlying, returning Results):
612
+ **Layered design.** Result is the protocol primitive — `create_rpc_client`
613
+ returns `Result<{value}, {error}>` per call with no Error allocation. The
614
+ throwing wrappers sit _above_ it as ergonomic adapters; both shapes share
615
+ the same underlying transport and call sites pick per-site. `Result` is
616
+ preferable when the call site inspects `error.data.reason` (no Error
617
+ allocation, no try/catch nesting) or when overhead matters (reconnect
618
+ storms, hot paths). Throwing is preferable when the call site doesn't
619
+ inspect — `await api.foo()` reads cleaner than the `if (!r.ok) throw …`
620
+ ritual.
621
+
622
+ `create_frontend_rpc_client` ships both shapes by default — see
623
+ [Frontend factory](#frontend-factory-frontend_rpc_clientts) below. Direct
624
+ consumers of `create_rpc_client` pass their typed `ActionsApi` as the
625
+ generic to get the typed Result-shaped Proxy without casts, then build
626
+ the throwing form on top:
613
627
 
614
628
  ```ts
615
- const api_raw = create_rpc_client({peer, environment}) as unknown as MyActionsApi;
616
- const api = create_throwing_api(api_raw);
629
+ const api_result = create_rpc_client<MyActionsApi>({peer, environment});
630
+ const api = create_throwing_api(api_result);
617
631
  // hot path: await api.foo(input)
618
- // rare branch: const r = await api_raw.foo(input); if (!r.ok) { … }
632
+ // rare branch: const r = await api_result.foo(input); if (!r.ok) { … }
619
633
  ```
620
634
 
621
- Composable feed the same typed Proxy into both: the loose method-keyed
622
- form for adapter dispatch, the typed Proxy form for hand-written call
623
- sites. `ThrowingApi<TApi>` mapped type strips
624
- `Promise<Result<{value: T}, {error: JsonrpcErrorObject}>>` to `Promise<T>`
625
- on every method that matches the `request_response` / async `local_call`
626
- return shape; `remote_notification` (`=> void`) and sync `local_call`
627
- methods pass through. The Proxy implementation inspects each call's
635
+ `create_throwing_rpc_call` is **not** a peer choice for direct call sites
636
+ it's a niche primitive for method-name-mapping adapter factories
637
+ (`ui/admin_rpc_adapters.ts`) where the method string comes from a domain
638
+ mapping rather than a typed call site. Use it only at adapter boundaries.
639
+
640
+ `ThrowingApi<TApi>` (the mapped type returned by `create_throwing_api`)
641
+ strips `Promise<Result<{value: T}, {error: JsonrpcErrorObject}>>` to
642
+ `Promise<T>` on every method that matches the `request_response` /
643
+ async `local_call` return shape; `remote_notification` (`=> void`) and
644
+ sync `local_call` methods pass through. The Proxy inspects each call's
628
645
  result shape at runtime and only unwraps when it sees a Result, so
629
646
  non-Result returns flow through unchanged.
630
647
 
@@ -637,33 +654,42 @@ probed as a thenable by `await`.
637
654
 
638
655
  ### Frontend factory (`frontend_rpc_client.ts`)
639
656
 
640
- `create_frontend_rpc_client<TApi>({specs, path?, transports?})` bundles
641
- the `ActionRegistry + ActionEventEnvironment + Transports + ActionPeer +
642
- create_rpc_client` boilerplate every consumer repeats — plus the
643
- `lookup_action_handler: () => undefined` stub (frontend never registers
644
- `request_response` handlers; every method dispatches over the wire).
645
- The `as unknown as TApi` cast happens inside the helper, so call sites
646
- get a typed return without the cast hostility. Returns
647
- `{api, peer, environment}` so advanced consumers (zzz-style frontends
648
- needing extra transports / WS notification handlers / action-history
649
- wiring) can extend without recreating the bundle.
657
+ `create_frontend_rpc_client<TApi>({specs, path?, transports?, transport_for_method?, on_action_event?})`
658
+ bundles the `ActionRegistry + ActionEventEnvironment + Transports +
659
+ ActionPeer + create_rpc_client + create_throwing_api` boilerplate every
660
+ consumer repeats — plus the `lookup_action_handler: () => undefined`
661
+ stub (frontend never registers `request_response` handlers; every
662
+ method dispatches over the wire). The `as unknown as TApi` cast happens
663
+ inside the helper, so call sites get a typed return without the cast
664
+ hostility.
650
665
 
651
- Default transport is `FrontendHttpTransport(path ?? '/api/rpc')`. Pass
652
- `transports` for WS-first or mixed setups — when supplied, the default
653
- HTTP transport is **not** registered. `local_call` specs in `specs`
654
- silently no-op because `lookup_action_handler` always returns
655
- `undefined`; this factory targets wire-dispatched actions.
666
+ Returns both Proxy shapes from one factory call:
656
667
 
657
- Pair with `create_throwing_api` to land the recommended `api` /
658
- `api_raw` convention in two lines:
668
+ - `api: ThrowingApi<TApi>` typed throwing Proxy. Default for hot-path call sites.
669
+ - `api_result: TApi` typed Result-shaped Proxy. For sites that inspect `error.data.reason` without try/catch.
670
+ - `peer`, `environment` — exposed for advanced consumers that want to register more transports or share the environment with a separate dispatcher.
659
671
 
660
672
  ```ts
661
- const {api: api_raw} = create_frontend_rpc_client<MyActionsApi>({
673
+ const {api, api_result} = create_frontend_rpc_client<MyActionsApi>({
662
674
  specs: all_standard_action_specs,
663
675
  });
664
- const api = create_throwing_api(api_raw);
676
+ // hot path: await api.account_verify()
677
+ // rare branch: const r = await api_result.account_verify(); if (!r.ok) { … }
665
678
  ```
666
679
 
680
+ Default transport is `FrontendHttpTransport(path ?? '/api/rpc')`. Pass
681
+ `transports` for WS-first or mixed setups — when supplied, the default
682
+ HTTP transport is **not** registered. `local_call` specs in `specs`
683
+ silently no-op because `lookup_action_handler` always returns
684
+ `undefined`; this factory targets wire-dispatched actions.
685
+
686
+ `transport_for_method` and `on_action_event` are pure pass-throughs to
687
+ `create_rpc_client` — exposed so consumers needing per-method routing
688
+ (tx-style WS-for-actions / HTTP-for-rest split) or per-dispatch event
689
+ wiring (zzz-style reactive Cells observing `ActionEvent` lifecycle)
690
+ don't have to drop down to manual `create_rpc_client` construction
691
+ (which forfeits the bundled `api` / `api_result` pair).
692
+
667
693
  `all_standard_action_specs` (in `../auth/standard_action_specs.ts`) is
668
694
  the matching aggregate spec list mirroring `create_standard_rpc_actions`
669
695
  on the backend — see `../auth/CLAUDE.md` §`standard_rpc_actions.ts`.
@@ -17,7 +17,16 @@ export type ActionEventChangeObserver<TMethod extends string = string> = (new_da
17
17
  export declare class ActionEvent<TMethod extends string = string, TPhase extends ActionEventPhase = ActionEventPhase, TStep extends ActionEventStep = ActionEventStep> {
18
18
  #private;
19
19
  readonly environment: ActionEventEnvironment;
20
- readonly spec: ActionSpecUnion;
20
+ /**
21
+ * `method` narrows to `TMethod` so consumers passing a typed `TApi` to
22
+ * `create_rpc_client` get `event.spec.method` typed as the union of
23
+ * their API's method names rather than plain `string`. The runtime
24
+ * value comes from `lookup_action_spec(method)` keyed off the Proxy
25
+ * get trap, so the narrowing matches the dispatched method.
26
+ */
27
+ readonly spec: ActionSpecUnion & {
28
+ method: TMethod;
29
+ };
21
30
  get data(): ActionEventDataUnion<TMethod> & {
22
31
  phase: TPhase;
23
32
  step: TStep;
@@ -1 +1 @@
1
- {"version":3,"file":"action_event.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAC,gBAAgB,EAAc,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAWpF,OAAO,KAAK,EACX,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EAEnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,sBAAsB,EAAE,eAAe,EAAC,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAkB,KAAK,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAclF,MAAM,MAAM,yBAAyB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,CACxE,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KACvB,IAAI,CAAC;AAEV;;GAEG;AACH,qBAAa,WAAW,CACvB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,MAAM,SAAS,gBAAgB,GAAG,gBAAgB,EAClD,KAAK,SAAS,eAAe,GAAG,eAAe;;IAK/C,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAE/B,IAAI,IAAI,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAA;KAAC,CAEvE;gBAGA,WAAW,EAAE,sBAAsB,EACnC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;IAOpC,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC;IAMvC,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI;IAKjE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,IAAI;IAUvD;;OAEG;IACH,KAAK,IAAI,IAAI;IA8Cb;;OAEG;IAGG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA0CnC;;OAEG;IACH,WAAW,IAAI,IAAI;IAkCnB;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAezC,WAAW,IAAI,OAAO;IAItB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIxC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAQ1C,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUpD,gBAAgB,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAyKzD;AAGD;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAClE,aAAa,sBAAsB,EACnC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,gBAAgB,gBAAgB,KAC9B,WAAW,CAAC,OAAO,CAiBrB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAC5E,MAAM,oBAAoB,CAAC,OAAO,CAAC,EACnC,aAAa,sBAAsB,KACjC,WAAW,CAAC,OAAO,CAOrB,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAC9B,UAAU,OAAO,EACjB,aAAa,sBAAsB,KACjC,WAGF,CAAC"}
1
+ {"version":3,"file":"action_event.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAC,gBAAgB,EAAc,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAWpF,OAAO,KAAK,EACX,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EAEnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,sBAAsB,EAAE,eAAe,EAAC,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAkB,KAAK,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAclF,MAAM,MAAM,yBAAyB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,CACxE,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KACvB,IAAI,CAAC;AAEV;;GAEG;AACH,qBAAa,WAAW,CACvB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,MAAM,SAAS,gBAAgB,GAAG,gBAAgB,EAClD,KAAK,SAAS,eAAe,GAAG,eAAe;;IAK/C,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG;QAAC,MAAM,EAAE,OAAO,CAAA;KAAC,CAAC;IAEnD,IAAI,IAAI,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAA;KAAC,CAEvE;gBAGA,WAAW,EAAE,sBAAsB,EACnC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;IAOpC,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC;IAMvC,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI;IAKjE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,IAAI;IAUvD;;OAEG;IACH,KAAK,IAAI,IAAI;IA8Cb;;OAEG;IAGG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA0CnC;;OAEG;IACH,WAAW,IAAI,IAAI;IAkCnB;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAezC,WAAW,IAAI,OAAO;IAItB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIxC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAQ1C,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUpD,gBAAgB,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAyKzD;AAGD;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAClE,aAAa,sBAAsB,EACnC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,gBAAgB,gBAAgB,KAC9B,WAAW,CAAC,OAAO,CAiBrB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAC5E,MAAM,oBAAoB,CAAC,OAAO,CAAC,EACnC,aAAa,sBAAsB,KACjC,WAAW,CAAC,OAAO,CAOrB,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAC9B,UAAU,OAAO,EACjB,aAAa,sBAAsB,KACjC,WAGF,CAAC"}
@@ -19,6 +19,13 @@ export class ActionEvent {
19
19
  #data;
20
20
  #listeners = new Set();
21
21
  environment;
22
+ /**
23
+ * `method` narrows to `TMethod` so consumers passing a typed `TApi` to
24
+ * `create_rpc_client` get `event.spec.method` typed as the union of
25
+ * their API's method names rather than plain `string`. The runtime
26
+ * value comes from `lookup_action_spec(method)` keyed off the Proxy
27
+ * get trap, so the narrowing matches the dispatched method.
28
+ */
22
29
  spec;
23
30
  get data() {
24
31
  return this.#data;
@@ -1 +1 @@
1
- {"version":3,"file":"action_event_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event_helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action_event_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event_helpers.ts"],"names":[],"mappings":"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"}
@@ -85,12 +85,22 @@ export const create_initial_data = (kind, phase, method, executor, input) => ({
85
85
  });
86
86
  export const extract_action_result = (event) => {
87
87
  const { data } = event;
88
+ // `data.error` populated → error path. This covers two cases:
89
+ // 1. `step === 'failed'` — explicit terminal failure.
90
+ // 2. `phase === 'receive_error' | 'send_error'` reached `step === 'handled'`
91
+ // because no handler was registered for the error phase. The dispatcher
92
+ // silently transitions to `handled` in that case but leaves `data.error`
93
+ // populated. Reading `step === 'handled'` first would return
94
+ // `{ok: true, value: null}` and surprise every caller that doesn't
95
+ // register an error-phase handler. Preferring `data.error` lets
96
+ // consumers skip the boilerplate `receive_error` rethrow stub.
97
+ if (data.error) {
98
+ return { ok: false, error: data.error };
99
+ }
88
100
  if (data.step === 'handled') {
89
101
  return { ok: true, value: data.output };
90
102
  }
91
- if (data.step === 'failed') {
92
- return { ok: false, error: data.error };
93
- }
94
- // Programming error - event not in terminal state
103
+ // `step === 'failed'` with `data.error === null` is a malformed event;
104
+ // type narrowing accepts it, runtime never produces it.
95
105
  throw new Error(`cannot extract result: event in non-terminal state (step: ${data.step})`);
96
106
  };
@@ -94,5 +94,5 @@ export type BroadcastApi = Record<string, (input: never) => Promise<void>>;
94
94
  * array in sync. Codegen (`action_collections.gen.ts`) is a natural fit
95
95
  * if the consumer already generates per-method type maps.
96
96
  */
97
- export declare const create_broadcast_api: <TApi = BroadcastApi>(options: CreateBroadcastApiOptions) => TApi;
97
+ export declare const create_broadcast_api: <TApi extends object>(options: CreateBroadcastApiOptions) => TApi;
98
98
  //# sourceMappingURL=broadcast_api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"broadcast_api.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/broadcast_api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,CAC7B,UAAU,EAAE,kBAAkB,EAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,KACV,OAAO,CAAC;AAEb,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,8DAA8D;IAC9D,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACtC,gFAAgF;IAChF,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,GAAG,YAAY,EACvD,SAAS,yBAAyB,KAChC,IAoDF,CAAC"}
1
+ {"version":3,"file":"broadcast_api.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/broadcast_api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,CAC7B,UAAU,EAAE,kBAAkB,EAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,KACV,OAAO,CAAC;AAEb,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,8DAA8D;IAC9D,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACtC,gFAAgF;IAChF,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,SAAS,MAAM,EACvD,SAAS,yBAAyB,KAChC,IAoDF,CAAC"}
@@ -2,39 +2,57 @@
2
2
  * Frontend-only typed RPC client factory.
3
3
  *
4
4
  * Bundles the `ActionRegistry + ActionEventEnvironment + Transports +
5
- * ActionPeer + create_rpc_client` boilerplate every consumer repeats — plus
6
- * the `lookup_action_handler: () => undefined` stub (frontend never registers
7
- * `request_response` handlers; every method dispatches over the wire).
5
+ * ActionPeer + create_rpc_client + create_throwing_api` boilerplate every
6
+ * consumer repeats. `lookup_action_handler` defaults to `() => undefined`
7
+ * (HTTP-only frontends rarely need handlers); pass `options.lookup_action_handler`
8
+ * to wire WS-pushed `remote_notification` dispatch or a `receive_error` /
9
+ * `local_call` hook.
8
10
  *
9
- * Generic `TApi` is the consumer's typed Proxy interface. The `as unknown
10
- * as TApi` double cast happens inside the helper so call sites get a typed
11
- * return value without the cast hostility.
11
+ * Returns both Proxy shapes from one factory call:
12
+ *
13
+ * - `api` typed throwing Proxy. `await api.foo(input)` returns the
14
+ * unwrapped value or throws an `Error` carrying `{code, data}` from the
15
+ * JSON-RPC error. Use at hot-path call sites.
16
+ * - `api_result` — typed Result-shaped Proxy. `await api_result.foo(input)`
17
+ * returns `Result<{value}, {error: JsonrpcErrorObject}>`. Use when call
18
+ * sites want to inspect `error.data.reason` without try/catch — and
19
+ * anywhere allocating an `Error` per `{ok: false}` is wasteful (e.g.
20
+ * reconnect-storm `service_unavailable` paths). Result is the protocol
21
+ * primitive; the throwing form is a wrapper over it. Both share the
22
+ * same underlying transport — pick per call site, no construction cost.
12
23
  *
13
- * Companion to `create_throwing_api` typical wiring is two lines:
24
+ * Generic `TApi` is the consumer's typed Proxy interface. The `as unknown
25
+ * as TApi` double cast happens inside the helper so call sites get a
26
+ * typed return value without the cast hostility. `api`'s type is
27
+ * `ThrowingApi<TApi>` — the mapped type strips the Result wrapper.
14
28
  *
15
29
  * ```ts
16
- * const {api: api_raw} = create_frontend_rpc_client<MyActionsApi>({specs: all_specs});
17
- * const api = create_throwing_api(api_raw);
30
+ * const {api, api_result} = create_frontend_rpc_client<MyActionsApi>({
31
+ * specs: all_specs,
32
+ * });
33
+ * // hot path: await api.account_verify()
34
+ * // rare branch: const r = await api_result.account_verify(); if (!r.ok) { … }
18
35
  * ```
19
36
  *
20
- * Returns the underlying `peer` and `environment` alongside `api` so
21
- * advanced consumers (zzz-style frontends needing extra transports / WS
22
- * notification handlers / action-history wiring) can extend without
23
- * recreating the bundle.
37
+ * Returns the underlying `peer` and `environment` alongside the two api
38
+ * shapes so advanced consumers (zzz-style frontends needing extra
39
+ * transports / WS notification handlers / action-history wiring) can
40
+ * extend without recreating the bundle.
24
41
  *
25
- * Note: `local_call` specs in `specs` will silently no-op because
26
- * `lookup_action_handler` always returns `undefined` the frontend
27
- * factory is for wire-dispatched actions. Frontend-side `local_call`
28
- * needs a different wiring shape (custom `environment.lookup_action_handler`).
42
+ * `local_call` specs in `specs` no-op unless `lookup_action_handler`
43
+ * resolves a handler for the `'execute'` phase. Frontend-side `local_call`
44
+ * is uncommon; the factory targets wire-dispatched actions by default.
29
45
  *
30
46
  * @module
31
47
  */
32
48
  import { ActionPeer } from './action_peer.js';
33
49
  import { type Transport } from './transports.js';
50
+ import { type ThrowingApi, type TransportForMethod } from './rpc_client.js';
51
+ import type { ActionEvent } from './action_event.js';
34
52
  import type { ActionEventEnvironment } from './action_event_types.js';
35
53
  import type { ActionSpecUnion } from './action_spec.js';
36
54
  /** Options for `create_frontend_rpc_client`. */
37
- export interface CreateFrontendRpcClientOptions {
55
+ export interface CreateFrontendRpcClientOptions<TApi extends object = object> {
38
56
  /**
39
57
  * Action specs the typed Proxy can dispatch. Methods absent from this
40
58
  * list silently return `undefined` from the Proxy — the generic `TApi`
@@ -54,11 +72,62 @@ export interface CreateFrontendRpcClientOptions {
54
72
  * WS+HTTP mixed setups.
55
73
  */
56
74
  transports?: ReadonlyArray<Transport>;
75
+ /**
76
+ * Optional per-method transport selector — pure pass-through to
77
+ * `create_rpc_client`. Return the transport name to use for a given
78
+ * method, or `undefined` to fall back to the peer's default selection.
79
+ *
80
+ * Useful when methods are registered on different backend dispatchers
81
+ * (e.g. streaming actions on WS, REST RPC on HTTP) — a tx-style mixed
82
+ * setup. Per-call `RpcClientCallOptions.transport_name` overrides this
83
+ * for individual dispatches.
84
+ */
85
+ transport_for_method?: TransportForMethod;
86
+ /**
87
+ * Optional callback fired once per dispatched action — pure pass-through
88
+ * to `create_rpc_client`. Used by zzz-style consumers that thread the
89
+ * `ActionEvent` into a reactive cell (`add_from_json` + `listen_to_action_event`)
90
+ * for `pending` / `failed` / `value` derivations.
91
+ *
92
+ * `event.spec.method` and `event.data.method` narrow to
93
+ * `keyof TApi & string` — drop the `as ActionMethod` cast at the call
94
+ * site when `TApi` is a generated `ActionsApi` interface.
95
+ */
96
+ on_action_event?: (event: ActionEvent<keyof TApi & string>) => void;
97
+ /**
98
+ * Optional handler resolver. Wired onto `environment.lookup_action_handler`
99
+ * — the registry the dispatcher uses to find handlers for inbound
100
+ * messages and lifecycle phases. Defaults to `() => undefined`, which
101
+ * is fine for HTTP-only frontends that never receive a server-pushed
102
+ * notification or register a `receive_error` recovery hook.
103
+ *
104
+ * Common reasons to provide this:
105
+ * - **Server-pushed notifications over WS** — return a handler for
106
+ * `(method, 'receive')` so a `remote_notification` arriving on the
107
+ * socket dispatches to your subscriber bus (tx-style).
108
+ * - **Per-method retry / telemetry on errors** — return a handler for
109
+ * `(method, 'receive_error')`. Note that as of the
110
+ * `extract_action_result` fix, a missing handler already produces
111
+ * `{ok: false, error}` — the stub is no longer required just to
112
+ * surface server errors.
113
+ */
114
+ lookup_action_handler?: ActionEventEnvironment['lookup_action_handler'];
57
115
  }
58
116
  /** Bundle returned by `create_frontend_rpc_client`. */
59
117
  export interface FrontendRpcClient<TApi> {
60
- /** Typed Proxy — call `api.method(input)` for `Promise<Result<...>>`. */
61
- api: TApi;
118
+ /**
119
+ * Typed throwing Proxy. `await api.method(input)` returns the unwrapped
120
+ * value or throws an `Error` with `{code, data}` from the JSON-RPC
121
+ * error. Default for call sites that don't inspect errors.
122
+ */
123
+ api: ThrowingApi<TApi>;
124
+ /**
125
+ * Typed Result-shaped Proxy. `await api_result.method(input)` returns
126
+ * `Result<{value}, {error: JsonrpcErrorObject}>`. Use when call sites
127
+ * inspect `error.data.reason` without try/catch, or when Error
128
+ * allocation per `{ok: false}` would be wasteful.
129
+ */
130
+ api_result: TApi;
62
131
  /** Underlying peer — exposed for consumers that need to register more transports or send raw messages. */
63
132
  peer: ActionPeer;
64
133
  /** Action environment — exposed for consumers that need to share it (e.g. attach a notification handler registry). */
@@ -67,8 +136,11 @@ export interface FrontendRpcClient<TApi> {
67
136
  /**
68
137
  * Build a frontend-only typed RPC client.
69
138
  *
70
- * @param options - `specs` (required), optional `path` / `transports`
71
- * @returns `{api, peer, environment}` — typed Proxy plus the underlying primitives
139
+ * @param options - `specs` (required), optional `path` / `transports` /
140
+ * `transport_for_method` / `on_action_event`
141
+ * @returns `{api, api_result, peer, environment}` — both Proxy shapes plus
142
+ * the underlying primitives. `api` throws on `{ok: false}`; `api_result`
143
+ * returns the Result.
72
144
  */
73
- export declare const create_frontend_rpc_client: <TApi>(options: CreateFrontendRpcClientOptions) => FrontendRpcClient<TApi>;
145
+ export declare const create_frontend_rpc_client: <TApi extends object>(options: CreateFrontendRpcClientOptions<TApi>) => FrontendRpcClient<TApi>;
74
146
  //# sourceMappingURL=frontend_rpc_client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"frontend_rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/frontend_rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAG3D,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAEtD,gDAAgD;AAChD,MAAM,WAAW,8BAA8B;IAC9C;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACtC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACtC;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB,CAAC,IAAI;IACtC,yEAAyE;IACzE,GAAG,EAAE,IAAI,CAAC;IACV,0GAA0G;IAC1G,IAAI,EAAE,UAAU,CAAC;IACjB,sHAAsH;IACtH,WAAW,EAAE,sBAAsB,CAAC;CACpC;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GAAI,IAAI,EAC9C,SAAS,8BAA8B,KACrC,iBAAiB,CAAC,IAAI,CAgBxB,CAAC"}
1
+ {"version":3,"file":"frontend_rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/frontend_rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAGH,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EAGN,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAEtD,gDAAgD;AAChD,MAAM,WAAW,8BAA8B,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IAC3E;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACtC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACtC;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;IAC1C;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACpE;;;;;;;;;;;;;;;;OAgBG;IACH,qBAAqB,CAAC,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,CAAC;CACxE;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB,CAAC,IAAI;IACtC;;;;OAIG;IACH,GAAG,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB;;;;;OAKG;IACH,UAAU,EAAE,IAAI,CAAC;IACjB,0GAA0G;IAC1G,IAAI,EAAE,UAAU,CAAC;IACjB,sHAAsH;IACtH,WAAW,EAAE,sBAAsB,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GAAI,IAAI,SAAS,MAAM,EAC7D,SAAS,8BAA8B,CAAC,IAAI,CAAC,KAC3C,iBAAiB,CAAC,IAAI,CAsBxB,CAAC"}
@@ -2,30 +2,46 @@
2
2
  * Frontend-only typed RPC client factory.
3
3
  *
4
4
  * Bundles the `ActionRegistry + ActionEventEnvironment + Transports +
5
- * ActionPeer + create_rpc_client` boilerplate every consumer repeats — plus
6
- * the `lookup_action_handler: () => undefined` stub (frontend never registers
7
- * `request_response` handlers; every method dispatches over the wire).
5
+ * ActionPeer + create_rpc_client + create_throwing_api` boilerplate every
6
+ * consumer repeats. `lookup_action_handler` defaults to `() => undefined`
7
+ * (HTTP-only frontends rarely need handlers); pass `options.lookup_action_handler`
8
+ * to wire WS-pushed `remote_notification` dispatch or a `receive_error` /
9
+ * `local_call` hook.
8
10
  *
9
- * Generic `TApi` is the consumer's typed Proxy interface. The `as unknown
10
- * as TApi` double cast happens inside the helper so call sites get a typed
11
- * return value without the cast hostility.
11
+ * Returns both Proxy shapes from one factory call:
12
+ *
13
+ * - `api` typed throwing Proxy. `await api.foo(input)` returns the
14
+ * unwrapped value or throws an `Error` carrying `{code, data}` from the
15
+ * JSON-RPC error. Use at hot-path call sites.
16
+ * - `api_result` — typed Result-shaped Proxy. `await api_result.foo(input)`
17
+ * returns `Result<{value}, {error: JsonrpcErrorObject}>`. Use when call
18
+ * sites want to inspect `error.data.reason` without try/catch — and
19
+ * anywhere allocating an `Error` per `{ok: false}` is wasteful (e.g.
20
+ * reconnect-storm `service_unavailable` paths). Result is the protocol
21
+ * primitive; the throwing form is a wrapper over it. Both share the
22
+ * same underlying transport — pick per call site, no construction cost.
12
23
  *
13
- * Companion to `create_throwing_api` typical wiring is two lines:
24
+ * Generic `TApi` is the consumer's typed Proxy interface. The `as unknown
25
+ * as TApi` double cast happens inside the helper so call sites get a
26
+ * typed return value without the cast hostility. `api`'s type is
27
+ * `ThrowingApi<TApi>` — the mapped type strips the Result wrapper.
14
28
  *
15
29
  * ```ts
16
- * const {api: api_raw} = create_frontend_rpc_client<MyActionsApi>({specs: all_specs});
17
- * const api = create_throwing_api(api_raw);
30
+ * const {api, api_result} = create_frontend_rpc_client<MyActionsApi>({
31
+ * specs: all_specs,
32
+ * });
33
+ * // hot path: await api.account_verify()
34
+ * // rare branch: const r = await api_result.account_verify(); if (!r.ok) { … }
18
35
  * ```
19
36
  *
20
- * Returns the underlying `peer` and `environment` alongside `api` so
21
- * advanced consumers (zzz-style frontends needing extra transports / WS
22
- * notification handlers / action-history wiring) can extend without
23
- * recreating the bundle.
37
+ * Returns the underlying `peer` and `environment` alongside the two api
38
+ * shapes so advanced consumers (zzz-style frontends needing extra
39
+ * transports / WS notification handlers / action-history wiring) can
40
+ * extend without recreating the bundle.
24
41
  *
25
- * Note: `local_call` specs in `specs` will silently no-op because
26
- * `lookup_action_handler` always returns `undefined` the frontend
27
- * factory is for wire-dispatched actions. Frontend-side `local_call`
28
- * needs a different wiring shape (custom `environment.lookup_action_handler`).
42
+ * `local_call` specs in `specs` no-op unless `lookup_action_handler`
43
+ * resolves a handler for the `'execute'` phase. Frontend-side `local_call`
44
+ * is uncommon; the factory targets wire-dispatched actions by default.
29
45
  *
30
46
  * @module
31
47
  */
@@ -33,19 +49,22 @@ import { ActionRegistry } from './action_registry.js';
33
49
  import { ActionPeer } from './action_peer.js';
34
50
  import { Transports } from './transports.js';
35
51
  import { FrontendHttpTransport } from './transports_http.js';
36
- import { create_rpc_client } from './rpc_client.js';
52
+ import { create_rpc_client, create_throwing_api, } from './rpc_client.js';
37
53
  /**
38
54
  * Build a frontend-only typed RPC client.
39
55
  *
40
- * @param options - `specs` (required), optional `path` / `transports`
41
- * @returns `{api, peer, environment}` — typed Proxy plus the underlying primitives
56
+ * @param options - `specs` (required), optional `path` / `transports` /
57
+ * `transport_for_method` / `on_action_event`
58
+ * @returns `{api, api_result, peer, environment}` — both Proxy shapes plus
59
+ * the underlying primitives. `api` throws on `{ok: false}`; `api_result`
60
+ * returns the Result.
42
61
  */
43
62
  export const create_frontend_rpc_client = (options) => {
44
63
  const registry = new ActionRegistry([...options.specs]);
45
64
  const environment = {
46
65
  executor: 'frontend',
47
66
  lookup_action_spec: (method) => registry.spec_by_method.get(method),
48
- lookup_action_handler: () => undefined,
67
+ lookup_action_handler: options.lookup_action_handler ?? (() => undefined),
49
68
  };
50
69
  const transports = new Transports();
51
70
  if (options.transports) {
@@ -56,6 +75,12 @@ export const create_frontend_rpc_client = (options) => {
56
75
  transports.register_transport(new FrontendHttpTransport(options.path ?? '/api/rpc'));
57
76
  }
58
77
  const peer = new ActionPeer({ environment, transports });
59
- const api = create_rpc_client({ peer, environment });
60
- return { api, peer, environment };
78
+ const api_result = create_rpc_client({
79
+ peer,
80
+ environment,
81
+ on_action_event: options.on_action_event,
82
+ transport_for_method: options.transport_for_method,
83
+ });
84
+ const api = create_throwing_api(api_result);
85
+ return { api, api_result, peer, environment };
61
86
  };