@fuzdev/fuz_app 0.44.0 → 0.45.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.
@@ -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;
@@ -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,25 +2,41 @@
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 — plus the `lookup_action_handler: () => undefined`
7
+ * stub (frontend never registers `request_response` handlers; every method
8
+ * dispatches over the wire).
8
9
  *
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.
10
+ * Returns both Proxy shapes from one factory call:
11
+ *
12
+ * - `api` typed throwing Proxy. `await api.foo(input)` returns the
13
+ * unwrapped value or throws an `Error` carrying `{code, data}` from the
14
+ * JSON-RPC error. Use at hot-path call sites.
15
+ * - `api_result` — typed Result-shaped Proxy. `await api_result.foo(input)`
16
+ * returns `Result<{value}, {error: JsonrpcErrorObject}>`. Use when call
17
+ * sites want to inspect `error.data.reason` without try/catch — and
18
+ * anywhere allocating an `Error` per `{ok: false}` is wasteful (e.g.
19
+ * reconnect-storm `service_unavailable` paths). Result is the protocol
20
+ * primitive; the throwing form is a wrapper over it. Both share the
21
+ * same underlying transport — pick per call site, no construction cost.
12
22
  *
13
- * Companion to `create_throwing_api` typical wiring is two lines:
23
+ * Generic `TApi` is the consumer's typed Proxy interface. The `as unknown
24
+ * as TApi` double cast happens inside the helper so call sites get a
25
+ * typed return value without the cast hostility. `api`'s type is
26
+ * `ThrowingApi<TApi>` — the mapped type strips the Result wrapper.
14
27
  *
15
28
  * ```ts
16
- * const {api: api_raw} = create_frontend_rpc_client<MyActionsApi>({specs: all_specs});
17
- * const api = create_throwing_api(api_raw);
29
+ * const {api, api_result} = create_frontend_rpc_client<MyActionsApi>({
30
+ * specs: all_specs,
31
+ * });
32
+ * // hot path: await api.account_verify()
33
+ * // rare branch: const r = await api_result.account_verify(); if (!r.ok) { … }
18
34
  * ```
19
35
  *
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.
36
+ * Returns the underlying `peer` and `environment` alongside the two api
37
+ * shapes so advanced consumers (zzz-style frontends needing extra
38
+ * transports / WS notification handlers / action-history wiring) can
39
+ * extend without recreating the bundle.
24
40
  *
25
41
  * Note: `local_call` specs in `specs` will silently no-op because
26
42
  * `lookup_action_handler` always returns `undefined` — the frontend
@@ -31,10 +47,12 @@
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,44 @@ 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;
57
97
  }
58
98
  /** Bundle returned by `create_frontend_rpc_client`. */
59
99
  export interface FrontendRpcClient<TApi> {
60
- /** Typed Proxy — call `api.method(input)` for `Promise<Result<...>>`. */
61
- api: TApi;
100
+ /**
101
+ * Typed throwing Proxy. `await api.method(input)` returns the unwrapped
102
+ * value or throws an `Error` with `{code, data}` from the JSON-RPC
103
+ * error. Default for call sites that don't inspect errors.
104
+ */
105
+ api: ThrowingApi<TApi>;
106
+ /**
107
+ * Typed Result-shaped Proxy. `await api_result.method(input)` returns
108
+ * `Result<{value}, {error: JsonrpcErrorObject}>`. Use when call sites
109
+ * inspect `error.data.reason` without try/catch, or when Error
110
+ * allocation per `{ok: false}` would be wasteful.
111
+ */
112
+ api_result: TApi;
62
113
  /** Underlying peer — exposed for consumers that need to register more transports or send raw messages. */
63
114
  peer: ActionPeer;
64
115
  /** Action environment — exposed for consumers that need to share it (e.g. attach a notification handler registry). */
@@ -67,8 +118,11 @@ export interface FrontendRpcClient<TApi> {
67
118
  /**
68
119
  * Build a frontend-only typed RPC client.
69
120
  *
70
- * @param options - `specs` (required), optional `path` / `transports`
71
- * @returns `{api, peer, environment}` — typed Proxy plus the underlying primitives
121
+ * @param options - `specs` (required), optional `path` / `transports` /
122
+ * `transport_for_method` / `on_action_event`
123
+ * @returns `{api, api_result, peer, environment}` — both Proxy shapes plus
124
+ * the underlying primitives. `api` throws on `{ok: false}`; `api_result`
125
+ * returns the Result.
72
126
  */
73
- export declare const create_frontend_rpc_client: <TApi>(options: CreateFrontendRpcClientOptions) => FrontendRpcClient<TApi>;
127
+ export declare const create_frontend_rpc_client: <TApi extends object>(options: CreateFrontendRpcClientOptions<TApi>) => FrontendRpcClient<TApi>;
74
128
  //# 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;CACpE;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,25 +2,41 @@
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 — plus the `lookup_action_handler: () => undefined`
7
+ * stub (frontend never registers `request_response` handlers; every method
8
+ * dispatches over the wire).
8
9
  *
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.
10
+ * Returns both Proxy shapes from one factory call:
11
+ *
12
+ * - `api` typed throwing Proxy. `await api.foo(input)` returns the
13
+ * unwrapped value or throws an `Error` carrying `{code, data}` from the
14
+ * JSON-RPC error. Use at hot-path call sites.
15
+ * - `api_result` — typed Result-shaped Proxy. `await api_result.foo(input)`
16
+ * returns `Result<{value}, {error: JsonrpcErrorObject}>`. Use when call
17
+ * sites want to inspect `error.data.reason` without try/catch — and
18
+ * anywhere allocating an `Error` per `{ok: false}` is wasteful (e.g.
19
+ * reconnect-storm `service_unavailable` paths). Result is the protocol
20
+ * primitive; the throwing form is a wrapper over it. Both share the
21
+ * same underlying transport — pick per call site, no construction cost.
12
22
  *
13
- * Companion to `create_throwing_api` typical wiring is two lines:
23
+ * Generic `TApi` is the consumer's typed Proxy interface. The `as unknown
24
+ * as TApi` double cast happens inside the helper so call sites get a
25
+ * typed return value without the cast hostility. `api`'s type is
26
+ * `ThrowingApi<TApi>` — the mapped type strips the Result wrapper.
14
27
  *
15
28
  * ```ts
16
- * const {api: api_raw} = create_frontend_rpc_client<MyActionsApi>({specs: all_specs});
17
- * const api = create_throwing_api(api_raw);
29
+ * const {api, api_result} = create_frontend_rpc_client<MyActionsApi>({
30
+ * specs: all_specs,
31
+ * });
32
+ * // hot path: await api.account_verify()
33
+ * // rare branch: const r = await api_result.account_verify(); if (!r.ok) { … }
18
34
  * ```
19
35
  *
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.
36
+ * Returns the underlying `peer` and `environment` alongside the two api
37
+ * shapes so advanced consumers (zzz-style frontends needing extra
38
+ * transports / WS notification handlers / action-history wiring) can
39
+ * extend without recreating the bundle.
24
40
  *
25
41
  * Note: `local_call` specs in `specs` will silently no-op because
26
42
  * `lookup_action_handler` always returns `undefined` — the frontend
@@ -33,12 +49,15 @@ 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]);
@@ -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
  };
@@ -5,14 +5,15 @@
5
5
  * - **Tier 1** (simple, for tx): transport send/receive, Result return. No `environment`.
6
6
  * - **Tier 2** (full, for zzz): ActionEvent lifecycle with `environment`.
7
7
  *
8
- * Consumers cast the return to their generated `ActionsApi` interface for full type safety.
8
+ * Pass the consumer's generated `ActionsApi` interface as `<TApi>` to flow
9
+ * full type safety through without an explicit cast at the call site.
9
10
  *
10
11
  * @module
11
12
  */
12
13
  import type { Result } from '@fuzdev/fuz_util/result.js';
13
14
  import type { ActionEventEnvironment } from './action_event_types.js';
15
+ import { type ActionEvent } from './action_event.js';
14
16
  import type { ActionPeer, ActionPeerSendOptions } from './action_peer.js';
15
- import type { ActionEventDataUnion } from './action_event_data.js';
16
17
  import type { TransportName } from './transports.js';
17
18
  import type { JsonrpcErrorObject } from '../http/jsonrpc.js';
18
19
  /**
@@ -24,21 +25,21 @@ import type { JsonrpcErrorObject } from '../http/jsonrpc.js';
24
25
  * RPC surface lives on HTTP.
25
26
  */
26
27
  export type TransportForMethod = (method: string) => TransportName | undefined;
27
- /** Duck-typed action history — consumers pass their concrete Actions cell. */
28
- export interface RpcClientActionHistory {
29
- add_from_json: (json: {
30
- method: string;
31
- action_event_data: ActionEventDataUnion;
32
- }) => {
33
- listen_to_action_event: (event: any) => void;
34
- } | undefined;
35
- }
36
28
  /** Options for `create_rpc_client`. */
37
- export interface CreateRpcClientOptions {
29
+ export interface CreateRpcClientOptions<TApi extends object = object> {
38
30
  peer: ActionPeer;
39
31
  environment: ActionEventEnvironment;
40
- /** Optional action history tracking (duck-typed Actions cell). */
41
- actions?: RpcClientActionHistory;
32
+ /**
33
+ * Optional callback fired once per dispatched action with the live
34
+ * `ActionEvent`. Consumers wire reactive state here — e.g. zzz's `Actions`
35
+ * cell calls `add_from_json` + `listen_to_action_event` inside the
36
+ * callback so its history stays decoupled from the rpc_client surface.
37
+ *
38
+ * `event.spec.method` and `event.data.method` narrow to
39
+ * `keyof TApi & string` — drop the `as ActionMethod` cast at the call
40
+ * site when `TApi` is a generated `ActionsApi` interface.
41
+ */
42
+ on_action_event?: (event: ActionEvent<keyof TApi & string>) => void;
42
43
  /**
43
44
  * Optional per-method transport selector. When provided, the client calls
44
45
  * `peer.send(msg, {transport_name})` with the returned transport for each
@@ -55,10 +56,22 @@ export interface CreateRpcClientOptions {
55
56
  * - `remote_notification` → send notification, return Result
56
57
  * - `local_call` → execute locally (sync or async), return Result or throw
57
58
  *
58
- * @param options - client options (peer, environment, optional action history)
59
- * @returns a Proxy that responds to any method name found in the environment's specs
59
+ * Generic `TApi` is the consumer's typed Proxy interface (typically a
60
+ * codegen-derived `ActionsApi`). Required no default, so forgetting it
61
+ * is a type error rather than a silent slide into `any`. The `as unknown
62
+ * as TApi` coercion lives inside this function so call sites get a typed
63
+ * return without a cast at the seam. `TApi` is a type-layer promise about
64
+ * what the Proxy responds to; the runtime walks `specs` (kept in sync by
65
+ * the consumer, codegen recommended).
66
+ *
67
+ * ```ts
68
+ * const api_result = create_rpc_client<MyActionsApi>({peer, environment});
69
+ * ```
70
+ *
71
+ * @param options - client options (peer, environment, optional callbacks)
72
+ * @returns a Proxy typed as `TApi` that responds to any method name found in the environment's specs
60
73
  */
61
- export declare const create_rpc_client: (options: CreateRpcClientOptions) => Record<string, (...args: Array<any>) => any>;
74
+ export declare const create_rpc_client: <TApi extends object>(options: CreateRpcClientOptions<TApi>) => TApi;
62
75
  /**
63
76
  * Per-call options accepted by every typed Proxy method. Same shape as
64
77
  * `ActionPeerSendOptions` — the client threads these through unchanged
@@ -67,54 +80,6 @@ export declare const create_rpc_client: (options: CreateRpcClientOptions) => Rec
67
80
  */
68
81
  export interface RpcClientCallOptions extends ActionPeerSendOptions {
69
82
  }
70
- /**
71
- * `method, input -> unwrapped output` signature for adapter wiring.
72
- *
73
- * The typed `create_rpc_client` Proxy returns `Result<T, JsonrpcErrorObject>`
74
- * on every call. UI adapters (e.g. `admin_rpc_adapters.ts`) want a
75
- * throw-on-error shape so form components can match on `error.data.reason`
76
- * via catch blocks. `create_throwing_rpc_call` bridges the two.
77
- */
78
- export type ThrowingRpcCall = <TOutput = unknown>(method: string, input?: unknown) => Promise<TOutput>;
79
- /**
80
- * Wrap a typed RPC client so every call returns its unwrapped value or throws.
81
- *
82
- * On `{ok: false}`, throws an `Error` whose `message` comes from the
83
- * JSON-RPC error object, plus `{code, data}` as own properties — so
84
- * catch blocks reading `err.message` / `err.code` / `err.data?.reason`
85
- * all work. On unknown method, throws a clear "rpc method not found"
86
- * error instead of the cryptic `undefined is not a function` that
87
- * would otherwise surface.
88
- *
89
- * Invariant upheld by `create_rpc_client`: every `{ok: false}` return
90
- * carries a well-formed `JsonrpcErrorObject` with `code` + `message`.
91
- * Callers must still use optional chaining on `err.data` because the
92
- * JSON-RPC `data` field is spec-level optional — a handler that throws
93
- * `jsonrpc_errors.forbidden()` without a `data` argument produces
94
- * `err.data === undefined`.
95
- *
96
- * Only `{code, data}` cross onto the thrown Error — `message` flows
97
- * through the `Error` constructor argument, and `name` / `stack` are
98
- * left as the Error's own so attacker-shaped `result.error` payloads
99
- * cannot overwrite them.
100
- *
101
- * The mapped-type generic constraint accepts both shapes without a cast:
102
- * a codegen-derived typed `ActionsApi` (named-method interface, e.g.
103
- * `{account_verify: (input) => Promise<Result<...>>, ...}`) and a loose
104
- * `Record<string, (input?: any) => Promise<any> | void>`. Using `keyof TApi`
105
- * in the constraint avoids the index-signature requirement that would
106
- * otherwise force consumers to `as unknown as Record<string, …>` their
107
- * generated client. The `| void` arm tolerates `remote_notification`
108
- * methods, whose `ActionsApi` signature is `(input) => void` even though
109
- * `create_remote_notification_method` returns a Promise at runtime — the
110
- * throwing wrapper is intended for `request_response` calls but must
111
- * accept mixed `ActionsApi` shapes without forcing a cast at the seam.
112
- *
113
- * @param api - typed RPC client from `create_rpc_client` (or any object
114
- * whose values are all `(input?) => Promise<...> | void` functions —
115
- * notably the consumer's generated `ActionsApi` interface)
116
- */
117
- export declare const create_throwing_rpc_call: <TApi extends Record<keyof TApi, (input?: any) => Promise<any> | void>>(api: TApi) => ThrowingRpcCall;
118
83
  /**
119
84
  * Maps a typed `ActionsApi` to a throwing variant.
120
85
  *
@@ -151,30 +116,27 @@ export type ThrowingApi<TApi> = {
151
116
  *
152
117
  * Only `{code, data}` cross onto the thrown Error — `name` / `stack` are
153
118
  * left as the Error's own properties so attacker-shaped `result.error`
154
- * payloads cannot overwrite them. Same hardening as
155
- * `create_throwing_rpc_call`.
156
- *
157
- * Composable with `create_throwing_rpc_call` — same typed underlying
158
- * client feeds both: the Proxy form for direct call sites, the loose
159
- * method-keyed form for adapter wiring (`ui/admin_rpc_adapters.ts`).
119
+ * payloads cannot overwrite them.
160
120
  *
161
- * Recommended consumer convention: bind the throwing wrapper to `api`
162
- * (the common case at call sites) and the underlying Result-returning
163
- * Proxy to `api_raw` (the composable escape hatch for callers that
164
- * want to inspect `error.data.reason` without try/catch).
121
+ * Recommended consumer convention: `create_frontend_rpc_client` ships
122
+ * both shapes by default `api` (throwing) for hot-path call sites and
123
+ * `api_result` (Result) for sites that inspect `error.data.reason`
124
+ * without try/catch. Result is the protocol primitive; this wrapper is
125
+ * the ergonomic layer over it. Picking is per call site — both Proxies
126
+ * share the same underlying transport.
165
127
  *
166
128
  * Catch blocks read `err.data?.reason` — optional chaining required
167
129
  * because JSON-RPC `data` is spec-level optional.
168
130
  *
169
131
  * On unknown string-keyed methods, the get trap returns a function that
170
- * throws `"rpc method not found: <prop>"` on invocation — symmetric with
171
- * `create_throwing_rpc_call` and clearer than the JS default
172
- * `"api.foo is not a function"`. Symbol props and `then` stay
173
- * undefined so the Proxy isn't accidentally treated as a thenable
132
+ * throws `"rpc method not found: <prop>"` on invocation — clearer than
133
+ * the JS default `"api.foo is not a function"`. Symbol props and `then`
134
+ * stay undefined so the Proxy isn't accidentally treated as a thenable
174
135
  * (`await api` would otherwise probe `then` and trip the thrower).
175
136
  *
176
- * @param api_raw - typed RPC client from `create_rpc_client`, cast
177
- * to a consumer-generated `ActionsApi` interface
137
+ * @param api_result - typed Result-returning RPC client from
138
+ * `create_rpc_client<ActionsApi>(...)`. The "_result" suffix names
139
+ * what the underlying calls return (`Result<{value}, {error}>`).
178
140
  */
179
- export declare const create_throwing_api: <TApi extends object>(api_raw: TApi) => ThrowingApi<TApi>;
141
+ export declare const create_throwing_api: <TApi extends object>(api_result: TApi) => ThrowingApi<TApi>;
180
142
  //# sourceMappingURL=rpc_client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAQvD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAEnD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,IAAI,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,oBAAoB,CAAA;KAAC,KAC5E;QACA,sBAAsB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;KAC5C,GACD,SAAS,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAgB7C,CAAC;AA2DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG;AAgItE;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,GAAG,OAAO,EAC/C,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,OAAO,KACX,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,wBAAwB,GACpC,IAAI,SAAS,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,EAErE,KAAK,IAAI,KACP,eAcF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,CAAC,IAAI,IAAI;KAC9B,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAClC,KAAK,CAAC,EAAE,MAAM,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,QAAQ,KACpB,OAAO,CAAC,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,MAAM,CAAA;KAAC,EAAE;QAAC,KAAK,EAAE,kBAAkB,CAAA;KAAC,CAAC,CAAC,GACrE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,MAAM,CAAC,GACvD,IAAI,CAAC,CAAC,CAAC;CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,mBAAmB,GAAI,IAAI,SAAS,MAAM,EAAE,SAAS,IAAI,KAAG,WAAW,CAAC,IAAI,CA8BxF,CAAC"}
1
+ {"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAQvD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAsB,KAAK,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAMxE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAEnD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,uCAAuC;AACvC,MAAM,WAAW,sBAAsB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC;IACpE;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,iBAAiB,GAAI,IAAI,SAAS,MAAM,EACpD,SAAS,sBAAsB,CAAC,IAAI,CAAC,KACnC,IA4BF,CAAC;AA6DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG;AAoHtE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,CAAC,IAAI,IAAI;KAC9B,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAClC,KAAK,CAAC,EAAE,MAAM,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,QAAQ,KACpB,OAAO,CAAC,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,MAAM,CAAA;KAAC,EAAE;QAAC,KAAK,EAAE,kBAAkB,CAAA;KAAC,CAAC,CAAC,GACrE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,MAAM,CAAC,GACvD,IAAI,CAAC,CAAC,CAAC;CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,mBAAmB,GAAI,IAAI,SAAS,MAAM,EAAE,YAAY,IAAI,KAAG,WAAW,CAAC,IAAI,CA8B3F,CAAC"}