@fuzdev/fuz_app 0.48.0 → 0.49.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.
@@ -68,11 +68,45 @@ throws on any non-`remote_notification` kind.
68
68
 
69
69
  ## Registry + codegen (`action_registry.ts`, `action_codegen.ts`)
70
70
 
71
+ **Symmetric design — universal calling abstraction.** SAES is one spec
72
+ shape that drives dispatch across (a) network boundaries (frontend ⇄
73
+ backend over HTTP / WS) and (b) within the same runtime (`local_call`
74
+ actions). `ActionPeer` is symmetric on both sides (`send` + `receive`).
75
+ The two typed surfaces are paired: `FrontendActionsApi` is "what the
76
+ frontend can call" (typed Proxy from `create_rpc_client`);
77
+ `BackendActionsApi` is "what the backend can call" (typed object from
78
+ `create_broadcast_api` today; broader runtime constructors will join).
79
+ The remaining asymmetry today is runtime: there is no
80
+ `create_backend_rpc_client` and `create_broadcast_api` returns
81
+ `Promise<void>` (fire-and-forget broadcast) rather than the
82
+ `Promise<Result<{value}, {error}>>` shape `FrontendActionsApi` methods
83
+ return. Closing those gaps is captured in the SAES quest's API
84
+ review III deferred set — wait for a second backend runtime case.
85
+
71
86
  `ActionRegistry(specs)` is a query/filter wrapper over `ActionSpecUnion[]`.
72
- Used getters today are `spec_by_method`, `request_response_specs`,
73
- `remote_notification_specs`, `local_call_specs`, `frontend_methods`,
74
- `backend_methods`, `methods`. The rest are pre-built stubs — revisit when
75
- the system matures.
87
+ Codegen-used getter groups:
88
+
89
+ - Identity: `spec_by_method`, `methods`.
90
+ - Kind-narrow specs + matching `_methods`: `request_response_specs`,
91
+ `remote_notification_specs`, `local_call_specs`.
92
+ - Narrow handler-side (request_response only, `initiator` excludes own
93
+ side, drives the typed `BackendActionHandlers` map):
94
+ `frontend_handled_specs`, `frontend_handled_methods`,
95
+ `backend_handled_specs`, `backend_handled_methods`.
96
+ - Loose "relevant to this side" (everything the side might encounter,
97
+ drives the typed-Proxy method enums `FrontendActionMethod` and
98
+ `BackendActionMethod`): `specs_relevant_to_frontend`,
99
+ `methods_relevant_to_frontend`, `specs_relevant_to_backend`,
100
+ `methods_relevant_to_backend`.
101
+ - Broadcast (kind-narrow `remote_notification`, `initiator !== 'frontend'`,
102
+ excludes `streams` targets): `broadcast_specs`, `broadcast_methods`.
103
+ - Backend-initiated (forward-looking kind-agnostic version of broadcast;
104
+ same content today, will widen when local_calls or backend
105
+ `request_response` join): `backend_initiated_specs`,
106
+ `backend_initiated_methods`.
107
+
108
+ Other getters (auth, initiator-direction) are pre-built stubs flagged
109
+ `@action-system-review`.
76
110
 
77
111
  `action_codegen.ts` provides gen helpers (used by consumer `*.gen.ts` files,
78
112
  not the runtime):
@@ -83,7 +117,7 @@ not the runtime):
83
117
  - `get_executor_phases(spec, executor)` — phases a given executor (`'frontend' | 'backend'`) participates in for the spec. Deduplicates via `Set` (handles `initiator: 'both'` overlap).
84
118
  - `get_handler_return_type(spec, phase, imports, collections_path?)` — the TS type a phase handler must return; triggers the `ActionOutputs` import (sourced from `collections_path`, default `'./action_collections.js'`) as a side effect.
85
119
  - `generate_phase_handlers(spec, executor, imports, {action_event_type?, collections_path?})` — emits the typed handler-map fragment for one action; consumers compose these into `ActionHandlers` types.
86
- - `generate_actions_api_method_signature(spec, {sync_returns_value?})` — single source of truth for the typed `ActionsApi` method shape. Threads `options?: RpcClientCallOptions` (`{signal?, transport_name?, queue?}`) onto every async method — `request_response`, `remote_notification`, and async `local_call` — and wraps the return in `Promise<Result<...>>`. Notifications were previously emitted as `=> void`, mismatching the runtime (`create_remote_notification_method` returns a Promise that resolves to `Result<{value: void}>`); regenerate consumer typed clients to pick up the corrected shape.
120
+ - `generate_actions_api_method_signature(spec, {sync_returns_value?})` — single source of truth for the typed `FrontendActionsApi` method shape. Threads `options?: RpcClientCallOptions` (`{signal?, transport_name?, queue?}`) onto every async method — `request_response`, `remote_notification`, and async `local_call` — and wraps the return in `Promise<Result<...>>`. Notifications were previously emitted as `=> void`, mismatching the runtime (`create_remote_notification_method` returns a Promise that resolves to `Result<{value: void}>`); regenerate consumer typed clients to pick up the corrected shape.
87
121
  - `create_banner(origin_path)` — gen banner comment.
88
122
  - `to_action_spec_identifier(method)` / `to_action_spec_input_identifier` / `to_action_spec_output_identifier` — naming convention helpers (emit `foo_action_spec` / `foo_action_spec.input` / `foo_action_spec.output`).
89
123
  - `COMPOSABLE_ACTION_METHODS` (+ `ComposableActionMethod` type) — readonly tuple `['heartbeat', 'cancel']`. Consumers spread when filtering backend `request_response` methods so dispatcher-owned composables don't leak into `BackendRequestResponseMethod` / handler maps.
@@ -92,18 +126,19 @@ not the runtime):
92
126
  - `DEFAULT_SPECS_MODULE = './action_specs.js'` — shared default for helpers that emit `specs.{method}_action_spec` and need a `* as specs` namespace import.
93
127
  - `DEFAULT_METATYPES_PATH = './action_metatypes.js'` — shared default for the sibling module carrying the generated `ActionMethod` enum.
94
128
 
95
- ### High-level helpers (Step 1 of codegen orchestration upstream — 2026-04-26)
129
+ ### High-level helpers
96
130
 
97
131
  Each accepts `(specs, imports, options?)` and returns one block of declarations.
98
132
  Composed by consumer `*.gen.ts` producers; outputs do not include the banner or
99
- surrounding `imports.build()`.
133
+ surrounding `imports.build()`. Use `compose_gen_file` to assemble the block
134
+ list + banner + imports into the final file body in one call.
100
135
 
101
136
  **Composables are filtered by default.** Every spec-iterating helper accepts
102
137
  `{include_composables?: boolean}` (default `false`) and drops `heartbeat` /
103
138
  `cancel` from the emitted output. Composables ship from fuz_app and are
104
139
  spread into each consumer's `actions` array at registration time — they
105
140
  should not appear in consumer-owned typed surfaces (`ActionMethod`,
106
- `ActionsApi`, `ActionInputs`, `FrontendActionHandlers`, etc.). Pass
141
+ `FrontendActionsApi`, `ActionInputs`, `FrontendActionHandlers`, etc.). Pass
107
142
  `include_composables: true` only if a consumer genuinely owns composables
108
143
  in their typed API.
109
144
 
@@ -113,32 +148,40 @@ into the helpers and accept the default `* as specs from specs_module`
113
148
  namespace import. Multi-source consumers (tx, visiones — which stitch
114
149
  local specs together with `all_admin_action_specs` /
115
150
  `all_permit_offer_action_specs` / `all_account_action_specs` /
116
- `all_self_service_role_action_specs` from fuz_app) pass
117
- `qualify_spec?: (spec) => string` to the three multi-source helpers
151
+ `all_self_service_role_action_specs` from fuz_app) call
152
+ `create_namespace_qualifier(sources, imports)` once, then pass the
153
+ returned `qualify_spec` callback to the multi-source helpers
118
154
  (`generate_action_specs_record`, `generate_action_inputs_outputs`,
119
155
  `generate_backend_actions_api`). When `qualify_spec` is set, the helper
120
156
  emits the callback's return value (e.g.
121
157
  `admin_specs.account_list_action_spec`) and skips the default `* as specs`
122
- import — the consumer manages its own multi-namespace imports. The helper
123
- appends `.input` / `.output` to the qualified identifier in
124
- `generate_action_inputs_outputs` automatically; the callback returns the
125
- bare spec identifier.
158
+ import — the consumer (or the namespace-qualifier helper) owns the
159
+ multi-namespace imports. The helper appends `.input` / `.output` to the
160
+ qualified identifier in `generate_action_inputs_outputs` automatically;
161
+ the callback returns the bare spec identifier.
126
162
 
127
163
  Tier 1 (HTTP-only, e.g. tx/visiones) emits a smaller surface — typically just
128
- `ActionMethod` + `ActionsApi` + `ActionInputs` / `ActionOutputs` interfaces —
129
- and never calls `generate_typed_action_event_alias` or
164
+ `ActionMethod` + `FrontendActionsApi` + `ActionInputs` / `ActionOutputs`
165
+ interfaces — and never calls `generate_typed_action_event_alias` or
130
166
  `generate_frontend_action_handlers`. Tier 2 (`TypedActionEvent`-aware, e.g.
131
- zzz) emits the full set including `ActionEventDatas`, `TypedActionEvent`, and
132
- `FrontendActionHandlers`.
167
+ zzz) emits the full set including `ActionEventDatas`, `TypedActionEvent`,
168
+ and `FrontendActionHandlers`.
133
169
 
134
- - `generate_action_method_enums(specs, imports, {emit?})` — up to six `z.enum` + `z.infer` pairs (`ActionMethod`, `RequestResponseActionMethod`, `RemoteNotificationActionMethod`, `LocalCallActionMethod`, `FrontendActionMethod`, `BackendActionMethod`). `emit: ReadonlySet<ActionMethodEnumKind>` restricts to a subset (Tier 1 HTTP-only consumers don't need all six). Skips kinds whose method list is empty (`z.enum([])` is invalid) and skips the `zod` import when no blocks are emitted. Adds `import {z} from 'zod'` only when at least one block is produced.
170
+ - `generate_action_method_enums(specs, imports, {emit?, include_composables?})` — up to nine `z.enum` + `z.infer` pairs (`ActionMethod`, `RequestResponseActionMethod`, `RemoteNotificationActionMethod`, `LocalCallActionMethod`, `FrontendActionMethod`, `BackendActionMethod`, `FrontendRequestResponseMethod`, `BackendRequestResponseMethod`, `BroadcastActionMethod`). `emit: ReadonlySet<ActionMethodEnumKind>` restricts to a subset (Tier 1 HTTP-only consumers don't need all nine). Skips kinds whose method list is empty (`z.enum([])` is invalid) and skips the `zod` import when no blocks are emitted. Adds `import {z} from 'zod'` only when at least one block is produced. The `frontend_handled` / `backend_handled` / `broadcast` kinds use the registry's narrow handler-side / streams-aware getters; the loose `frontend` / `backend` kinds preserve the everything-relevant-to-this-side semantic for the typed-Proxy method enum.
171
+ - `generate_action_method_enum_block(specs, imports, {name, jsdoc, predicate, include_composables?})` — lower-level escape hatch for genuinely cross-product enums the discriminator doesn't cover. Caller owns the predicate, name, and jsdoc.
135
172
  - `generate_typed_action_event_alias(imports, {collections_path?, metatypes_path?})` — fixed-shape `TypedActionEvent<TMethod, TPhase, TStep>` alias narrowing `ActionEvent.data` against `ActionEventDatas`. Adds the three fuz_app type imports + `ActionEventDatas` (from `collections_path`) + `ActionMethod` (from `metatypes_path`).
136
- - `generate_action_specs_record(specs, imports, {specs_module?, qualify_spec?})` — `ActionSpecs` runtime const + interface + `action_specs: Array<ActionSpecUnion>` value. Adds `* as specs` from `specs_module` unless `qualify_spec` is set (then `specs_module` is ignored and the consumer owns namespace imports).
137
- - `generate_action_inputs_outputs(specs, imports, {specs_module?, qualify_spec?})` — `ActionInputs` and `ActionOutputs` runtime consts + interfaces. Same `qualify_spec` semantics as `generate_action_specs_record`; the helper appends `.input` / `.output` to the qualified identifier.
138
- - `generate_action_event_datas(specs, imports, {same_file?, collections_path?})` — `ActionEventDatas` interface; per-spec variant by kind (`ActionEventRequestResponseData` / `ActionEventRemoteNotificationData` / `ActionEventLocalCallData`). `same_file` (default `true`) is the file-layout switch: when `true`, assumes `ActionInputs` / `ActionOutputs` are in the same module and adds no import (the zzz pattern); when `false`, adds the type imports from `collections_path` (default `'./action_collections.js'`). `collections_path` alone is a no-op — the surprising omit-vs-default behavior of earlier versions has been replaced.
139
- - `generate_actions_api(specs, imports, {method_filter?, collections_path?, sync_returns_value?})` — `ActionsApi` interface. Composables filtered by default; `method_filter: (spec) => boolean` runs after the composable filter for tx-style additional subsets.
140
- - `generate_frontend_action_handlers(specs, imports, {collections_path?})` — `FrontendActionHandlers` interface (Tier 2 only — wraps `generate_phase_handlers` with `action_event_type: 'TypedActionEvent'`). Pair with `generate_typed_action_event_alias`.
141
- - `generate_backend_actions_api(specs, imports, {specs_module?, collections_path?, qualify_spec?})` — `BackendActionsApi` interface AND `broadcast_action_specs: ReadonlyArray<ActionSpecUnion>` array. Filter: `kind === 'remote_notification' && initiator !== 'frontend'`. Adds `ActionInputs` (from `collections_path`) + `ActionSpecUnion`, plus `* as specs` from `specs_module` unless `qualify_spec` is set.
173
+ - `generate_action_specs_record(specs, imports, {specs_module?, qualify_spec?, include_composables?})` — `ActionSpecs` runtime const + interface + `action_specs: Array<ActionSpecUnion>` value. Adds `* as specs` from `specs_module` unless `qualify_spec` is set (then `specs_module` is ignored and the consumer owns namespace imports).
174
+ - `generate_action_inputs_outputs(specs, imports, {specs_module?, qualify_spec?, include_composables?})` — `ActionInputs` and `ActionOutputs` runtime consts + interfaces. Same `qualify_spec` semantics as `generate_action_specs_record`; the helper appends `.input` / `.output` to the qualified identifier.
175
+ - `generate_action_event_datas(specs, imports, {same_file?, collections_path?, include_composables?})` — `ActionEventDatas` interface; per-spec variant by kind (`ActionEventRequestResponseData` / `ActionEventRemoteNotificationData` / `ActionEventLocalCallData`). `same_file` (default `true`) is the file-layout switch: when `true`, assumes `ActionInputs` / `ActionOutputs` are in the same module and adds no import (the zzz pattern); when `false`, adds the type imports from `collections_path` (default `'./action_collections.js'`). `collections_path` alone is a no-op — the surprising omit-vs-default behavior of earlier versions has been replaced.
176
+ - `generate_frontend_actions_api(specs, imports, {interface_name?, method_filter?, collections_path?, sync_returns_value?, include_composables?})` — emits the typed `FrontendActionsApi` interface (configurable via `interface_name`, default `'FrontendActionsApi'`). One method signature per spec via `generate_actions_api_method_signature`. Composables filtered by default; `method_filter: (spec) => boolean` runs after the composable filter. Renamed from `generate_actions_api` in API review III to make the side-of-the-wire intent visible at every consumer site.
177
+ - `generate_frontend_action_handlers(specs, imports, {collections_path?, include_composables?})` — `FrontendActionHandlers` interface (Tier 2 only — wraps `generate_phase_handlers` with `action_event_type: 'TypedActionEvent'`). Pair with `generate_typed_action_event_alias`.
178
+ - `generate_backend_actions_api(specs, imports, {interface_name?, spec_array_name?, specs_module?, collections_path?, qualify_spec?, include_composables?})` — `BackendActionsApi` interface AND `broadcast_action_specs: ReadonlyArray<ActionSpecUnion>` array (both names configurable). Filter: `kind === 'remote_notification' && initiator !== 'frontend'`, with `streams`-target methods (request-scoped progress notifications invoked via `ctx.notify`) excluded — the discriminator is `ActionSpec.streams`, not a manual list. Adds `ActionInputs` (from `collections_path`) + `ActionSpecUnion`, plus `* as specs` from `specs_module` unless `qualify_spec` is set. Method shape today is `(input) => Promise<void>` (matches `create_broadcast_api`'s fire-and-forget runtime); generalizing to per-kind shapes via `generate_actions_api_method_signature` is deferred until a second backend runtime constructor lands (see SAES quest § API review III deferred set).
179
+ - `generate_backend_action_handlers_map(imports, options?)` — emits the `BackendActionHandlers` mapped type (`{[K in BackendRequestResponseMethod]: (input: ActionInputs[K], ctx: BackendHandlerContext) => ActionOutputs[K] | Promise<ActionOutputs[K]>}`). Replaces the hand-maintained `Exclude<>` + parallel mapped-type pattern (zzz had this at `zzz/src/lib/server/zzz_action_handlers.ts:42-66`). Configurable type name, method enum name, and context type name; configurable `collections_path` / `metatypes_path` for the type imports.
180
+
181
+ ### Wrapper + multi-source helper
182
+
183
+ - `compose_gen_file({origin_path, imports, blocks})` — encapsulates the per-`*.gen.ts` boilerplate (banner + `imports.build()` + blocks join + template literal). Returns the full file body. Each consumer producer collapses to one `compose_gen_file` call wrapping the helper invocations.
184
+ - `create_namespace_qualifier(sources, imports)` — multi-source consumer helper. Takes `ReadonlyArray<{ns, module, specs}>`, registers `import * as ns from module` for each on `imports`, builds the `method_to_ns` lookup with duplicate-method detection, returns `{qualify_spec, all_specs}` ready to thread through the high-level helpers. Closes the per-file boilerplate gap that kept tx + visiones on hand-rolled template strings even after the `qualify_spec?` callback landed (the per-call callback wasn't enough — the import dance + dup-check was the real boilerplate).
142
185
 
143
186
  ## HTTP bridge (`action_bridge.ts`)
144
187
 
@@ -642,12 +685,12 @@ zzz's `Actions` cell calls its own `add_from_json` +
642
685
  `listen_to_action_event` here so the history plumbing stays inside zzz
643
686
  instead of leaking onto the rpc_client surface. `event.spec.method` and
644
687
  `event.data.method` narrow to `keyof TApi & string` so consumers passing
645
- a generated `ActionsApi` get the literal method-name union without an
646
- `as ActionMethod` cast at the call site.
688
+ a generated `FrontendActionsApi` get the literal method-name union without
689
+ an `as ActionMethod` cast at the call site.
647
690
 
648
- Cast the return to a generated `ActionsApi` interface for full typing:
649
- codegen via `generate_actions_api_method_signature` keeps the shape
650
- consistent. See ../../docs/usage.md §Typed Client Codegen.
691
+ Cast the return to a generated `FrontendActionsApi` interface for full
692
+ typing: codegen via `generate_actions_api_method_signature` keeps the
693
+ shape consistent. See ../../docs/usage.md §Typed Client Codegen.
651
694
 
652
695
  ### Throwing variants — `create_throwing_rpc_call` + `create_throwing_api`
653
696
 
@@ -658,10 +701,10 @@ is spec-level optional). Same hardening on both: only `{code, data}` cross
658
701
  onto the Error, leaving `name` / `stack` as the native Error's own so
659
702
  attacker-shaped `result.error` payloads cannot overwrite them.
660
703
 
661
- | Helper | Shape | Use at |
662
- | -------------------------- | -------------------------------- | -------------------------------------------------------------------------- |
663
- | `create_throwing_rpc_call` | `(method, input?) => Promise<T>` | adapter wiring (e.g. `ui/admin_rpc_adapters.ts`) — method comes from a map |
664
- | `create_throwing_api` | typed Proxy over `ActionsApi` | direct call sites — `await api.foo(input)` keeps full inference |
704
+ | Helper | Shape | Use at |
705
+ | -------------------------- | ------------------------------------- | -------------------------------------------------------------------------- |
706
+ | `create_throwing_rpc_call` | `(method, input?) => Promise<T>` | adapter wiring (e.g. `ui/admin_rpc_adapters.ts`) — method comes from a map |
707
+ | `create_throwing_api` | typed Proxy over `FrontendActionsApi` | direct call sites — `await api.foo(input)` keeps full inference |
665
708
 
666
709
  **Layered design.** Result is the protocol primitive — `create_rpc_client`
667
710
  returns `Result<{value}, {error}>` per call with no Error allocation. The
@@ -675,12 +718,12 @@ ritual.
675
718
 
676
719
  `create_frontend_rpc_client` ships both shapes by default — see
677
720
  [Frontend factory](#frontend-factory-frontend_rpc_clientts) below. Direct
678
- consumers of `create_rpc_client` pass their typed `ActionsApi` as the
679
- generic to get the typed Result-shaped Proxy without casts, then build
680
- the throwing form on top:
721
+ consumers of `create_rpc_client` pass their typed `FrontendActionsApi`
722
+ as the generic to get the typed Result-shaped Proxy without casts, then
723
+ build the throwing form on top:
681
724
 
682
725
  ```ts
683
- const api_result = create_rpc_client<MyActionsApi>({peer, environment});
726
+ const api_result = create_rpc_client<FrontendActionsApi>({peer, environment});
684
727
  const api = create_throwing_api(api_result);
685
728
  // hot path: await api.foo(input)
686
729
  // rare branch: const r = await api_result.foo(input); if (!r.ok) { … }
@@ -724,7 +767,7 @@ Returns both Proxy shapes from one factory call:
724
767
  - `peer`, `environment` — exposed for advanced consumers that want to register more transports or share the environment with a separate dispatcher.
725
768
 
726
769
  ```ts
727
- const {api, api_result} = create_frontend_rpc_client<MyActionsApi>({
770
+ const {api, api_result} = create_frontend_rpc_client<FrontendActionsApi>({
728
771
  specs: all_standard_action_specs,
729
772
  });
730
773
  // hot path: await api.account_verify()
@@ -9,12 +9,12 @@ export declare const COMPOSABLE_ACTION_METHODS: readonly ["heartbeat", "cancel"]
9
9
  /** Methods that ship from fuz_app, kept out of consumer-owned method enums + handler maps. */
10
10
  export type ComposableActionMethod = (typeof COMPOSABLE_ACTION_METHODS)[number];
11
11
  /**
12
- * Type predicate for filtering composable methods out of a typed `ActionsApi`
12
+ * Type predicate for filtering composable methods out of a typed `FrontendActionsApi`
13
13
  * `method_filter`. Avoids the `(... as never)` cast required to call
14
14
  * `Array.prototype.includes` on the readonly tuple at narrow string types.
15
15
  *
16
16
  * @example
17
- * generate_actions_api(specs, imports, {
17
+ * generate_frontend_actions_api(specs, imports, {
18
18
  * method_filter: (s) => !is_composable_action_method(s.method),
19
19
  * });
20
20
  */
@@ -132,10 +132,10 @@ export declare const to_action_spec_identifier: (method: string) => string;
132
132
  export declare const to_action_spec_input_identifier: (method: string) => string;
133
133
  export declare const to_action_spec_output_identifier: (method: string) => string;
134
134
  /**
135
- * Generates one method line of the typed `ActionsApi` interface for a single
136
- * spec. Encapsulates the input/options/return-type signature shape so the
137
- * surface evolves in one place when fields like `signal` or `transport_name`
138
- * are added to per-call options.
135
+ * Generates one method line of the typed `FrontendActionsApi` interface for a
136
+ * single spec. Encapsulates the input/options/return-type signature shape so
137
+ * the surface evolves in one place when fields like `signal` or
138
+ * `transport_name` are added to per-call options.
139
139
  *
140
140
  * Async methods (`request_response`, `remote_notification`, async
141
141
  * `local_call`) get an optional second `options?: RpcClientCallOptions` arg
@@ -156,21 +156,23 @@ export declare const to_action_spec_output_identifier: (method: string) => strin
156
156
  * @param options.sync_returns_value - when true (default), sync local_call
157
157
  * methods return the output value directly; when false they're wrapped in
158
158
  * `Result<{value, error}>` like async methods. Set to `false` if your
159
- * ActionsApi treats every method uniformly.
159
+ * FrontendActionsApi treats every method uniformly.
160
160
  * @returns one line like `foo: (input: ActionInputs['foo'], options?: RpcClientCallOptions) => Promise<Result<...>>;`
161
161
  */
162
162
  export declare const generate_actions_api_method_signature: (spec: ActionSpecUnion, options?: {
163
163
  sync_returns_value?: boolean;
164
164
  }) => string;
165
165
  /** Discriminator for `generate_action_method_enums` — which method-set enums to emit. */
166
- export type ActionMethodEnumKind = 'all' | 'request_response' | 'remote_notification' | 'local_call' | 'frontend' | 'backend';
166
+ export type ActionMethodEnumKind = 'all' | 'request_response' | 'remote_notification' | 'local_call' | 'frontend' | 'backend' | 'frontend_handled' | 'backend_handled' | 'broadcast';
167
167
  /** Default emit set — every enum kind. */
168
168
  export declare const ACTION_METHOD_ENUM_KINDS_ALL: ReadonlySet<ActionMethodEnumKind>;
169
169
  /**
170
170
  * Emit one or more `z.enum([...])` declarations for action method names —
171
171
  * `ActionMethod`, `RequestResponseActionMethod`, `RemoteNotificationActionMethod`,
172
- * `LocalCallActionMethod`, `FrontendActionMethod`, `BackendActionMethod`. Pairs
173
- * each runtime const with a `z.infer` type alias under the same identifier.
172
+ * `LocalCallActionMethod`, `FrontendActionMethod`, `BackendActionMethod`,
173
+ * `FrontendRequestResponseMethod`, `BackendRequestResponseMethod`,
174
+ * `BroadcastActionMethod`. Pairs each runtime const with a `z.infer` type
175
+ * alias under the same identifier.
174
176
  *
175
177
  * Composable methods (`heartbeat`, `cancel`) are filtered out by default —
176
178
  * pass `include_composables: true` if a consumer genuinely wants them on
@@ -180,7 +182,11 @@ export declare const ACTION_METHOD_ENUM_KINDS_ALL: ReadonlySet<ActionMethodEnumK
180
182
  * Adds `import {z} from 'zod';` to `imports` only when at least one block
181
183
  * is emitted (idempotent).
182
184
  *
183
- * @param options.emit - subset of enums to emit; defaults to all six.
185
+ * For genuinely cross-product enums the discriminator doesn't cover, use
186
+ * `generate_action_method_enum_block` — caller owns the predicate, name,
187
+ * and jsdoc.
188
+ *
189
+ * @param options.emit - subset of enums to emit; defaults to all nine.
184
190
  * @param options.include_composables - when true, retains `heartbeat` /
185
191
  * `cancel` in the emitted enums. Default `false`.
186
192
  */
@@ -188,6 +194,26 @@ export declare const generate_action_method_enums: (specs: ReadonlyArray<ActionS
188
194
  emit?: ReadonlySet<ActionMethodEnumKind>;
189
195
  include_composables?: boolean;
190
196
  }) => string;
197
+ /**
198
+ * Emit a single named `z.enum([...])` + `z.infer` block for an arbitrary
199
+ * spec subset. Lower-level escape hatch from `generate_action_method_enums` —
200
+ * for cross-product or domain-specific enums the built-in discriminator
201
+ * doesn't cover.
202
+ *
203
+ * Mirrors the built-in helper's contract: composables filtered by default,
204
+ * empty subsets return `''` (skip rather than emit `z.enum([])`), `zod`
205
+ * import registered idempotently only when at least one method qualifies.
206
+ *
207
+ * The cross-product space is open-ended; rather than grow the
208
+ * `ActionMethodEnumKind` discriminator one cross-product at a time, callers
209
+ * own the subset shape — name, jsdoc, predicate.
210
+ */
211
+ export declare const generate_action_method_enum_block: (specs: ReadonlyArray<ActionSpecUnion>, imports: ImportBuilder, options: {
212
+ name: string;
213
+ jsdoc: string;
214
+ predicate: (spec: ActionSpecUnion) => boolean;
215
+ include_composables?: boolean;
216
+ }) => string;
191
217
  /**
192
218
  * Emit the fixed-shape `TypedActionEvent` alias used by `FrontendActionHandlers`
193
219
  * to narrow `ActionEvent.data` against the consumer's generated `ActionEventDatas`
@@ -265,14 +291,20 @@ export declare const generate_action_event_datas: (specs: ReadonlyArray<ActionSp
265
291
  include_composables?: boolean;
266
292
  }) => string;
267
293
  /**
268
- * Emit the `ActionsApi` interface — one method signature per spec via
294
+ * Emit the `FrontendActionsApi` interface — one method signature per spec via
269
295
  * `generate_actions_api_method_signature`. Optionally filter the spec set
270
296
  * (e.g. omit composable methods) via `method_filter`.
271
297
  *
272
298
  * Adds the `Result`, `JsonrpcErrorObject`, and `RpcClientCallOptions` type
273
299
  * imports plus `ActionInputs` / `ActionOutputs` (sourced from `collections_path`).
300
+ *
301
+ * The interface name is fixed at `FrontendActionsApi` — the symmetric counterpart
302
+ * of `BackendActionsApi`. Earlier consumer-named variants (`MyActionsApi`,
303
+ * `VisionesActionsApi`) were retired in API review III to make the side-of-the-wire
304
+ * intent visible at every call site. If a consumer needs a different name they
305
+ * hand-roll the interface (the helper's job is the standard symmetric shape).
274
306
  */
275
- export declare const generate_actions_api: (specs: ReadonlyArray<ActionSpecUnion>, imports: ImportBuilder, options?: {
307
+ export declare const generate_frontend_actions_api: (specs: ReadonlyArray<ActionSpecUnion>, imports: ImportBuilder, options?: {
276
308
  method_filter?: (spec: ActionSpecUnion) => boolean;
277
309
  collections_path?: string;
278
310
  sync_returns_value?: boolean;
@@ -295,12 +327,23 @@ export declare const generate_frontend_action_handlers: (specs: ReadonlyArray<Ac
295
327
  * each `(input) => Promise<void>`. The array bundles the matching specs as a
296
328
  * `ReadonlyArray<ActionSpecUnion>`.
297
329
  *
298
- * Filter: `kind === 'remote_notification' && initiator !== 'frontend'`.
330
+ * Filter: `kind === 'remote_notification' && initiator !== 'frontend'`,
331
+ * additionally excluding methods that are the target of another spec's
332
+ * `streams` field. Streams targets (e.g. `completion_progress`,
333
+ * `ollama_progress`) are request-scoped notifications invoked via
334
+ * `ctx.notify` inside their parent handler — they're never callable through
335
+ * the broadcast API. The discriminator is `ActionSpec.streams`, not a manual
336
+ * exclusion list.
299
337
  *
300
338
  * Adds the `* as specs` namespace import (from `specs_module`), the
301
339
  * `ActionInputs` type import (from `collections_path`), and the
302
340
  * `ActionSpecUnion` type import.
303
341
  *
342
+ * Method signature shape today is `(input) => Promise<void>` — matches the
343
+ * fire-and-forget runtime of `create_broadcast_api`. Generalizing per-kind
344
+ * via `generate_actions_api_method_signature` is deferred until a second
345
+ * backend runtime constructor lands (see SAES quest § API review III).
346
+ *
304
347
  * @param options.qualify_spec - per-spec qualified identifier callback for
305
348
  * multi-source consumers. When set, the helper emits the callback's return
306
349
  * value instead of ``specs.${method}_action_spec`` in the broadcast array
@@ -314,5 +357,116 @@ export declare const generate_backend_actions_api: (specs: ReadonlyArray<ActionS
314
357
  qualify_spec?: (spec: ActionSpecUnion) => string;
315
358
  include_composables?: boolean;
316
359
  }) => string;
360
+ /**
361
+ * Emit the `BackendActionHandlers` mapped type — one entry per
362
+ * `BackendRequestResponseMethod`, each `(input, ctx) => output | Promise<output>`.
363
+ * Replaces the hand-maintained `Exclude<>` + parallel mapped-type pattern
364
+ * (zzz had this at `zzz/src/lib/server/zzz_action_handlers.ts:42-66`).
365
+ *
366
+ * The context type is consumer-defined (e.g. zzz's `ZzzHandlerContext`). Pass
367
+ * `context_type` to name it; the helper assumes it's importable or defined
368
+ * in the emitted module's scope (consumer's responsibility).
369
+ *
370
+ * Adds `ActionInputs` / `ActionOutputs` type imports from `collections_path`
371
+ * and the `BackendRequestResponseMethod` import from `metatypes_path`.
372
+ *
373
+ * @param options.type_name - default `'BackendActionHandlers'`.
374
+ * @param options.method_enum_name - default `'BackendRequestResponseMethod'`.
375
+ * Pair with `generate_action_method_enums` emitting the `'backend_handled'` kind.
376
+ * @param options.context_type - default `'BackendHandlerContext'`. Caller's
377
+ * handler context type — must be in scope at the emit site.
378
+ * @param options.collections_path - default `'./action_collections.js'`.
379
+ * @param options.metatypes_path - default `'./action_metatypes.js'`.
380
+ */
381
+ export declare const generate_backend_action_handlers_map: (imports: ImportBuilder, options?: {
382
+ type_name?: string;
383
+ method_enum_name?: string;
384
+ context_type?: string;
385
+ collections_path?: string;
386
+ metatypes_path?: string;
387
+ }) => string;
388
+ /**
389
+ * One source in a multi-source consumer's namespace map. `ns` is the local
390
+ * alias used inside the generated file; `module` is the import path; `specs`
391
+ * is the runtime spec array. `create_namespace_qualifier` consumes a list of
392
+ * these.
393
+ */
394
+ export interface SpecSource {
395
+ ns: string;
396
+ module: string;
397
+ specs: ReadonlyArray<ActionSpecUnion>;
398
+ }
399
+ /**
400
+ * Multi-source consumer helper. Takes a list of `{ns, module, specs}` rows,
401
+ * registers `import * as ns from module` for each on `imports`, builds the
402
+ * `method_to_ns` lookup with duplicate-method detection, and returns
403
+ * `{qualify_spec, all_specs}` ready to thread through the high-level
404
+ * helpers.
405
+ *
406
+ * Closes the per-file boilerplate gap that kept tx + visiones on hand-rolled
407
+ * template strings even after `qualify_spec?` landed in API review II — the
408
+ * per-call callback wasn't enough; the import dance + dup-check was the
409
+ * real boilerplate.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * const sources = [
414
+ * {ns: 'tx_specs', module: './action_specs.js', specs: all_tx_action_specs},
415
+ * {ns: 'admin_specs', module: '@fuzdev/fuz_app/auth/admin_action_specs.js', specs: all_admin_action_specs},
416
+ * ];
417
+ *
418
+ * export const gen: Gen = ({origin_path}) => {
419
+ * const imports = new ImportBuilder();
420
+ * const {qualify_spec, all_specs} = create_namespace_qualifier(sources, imports);
421
+ * return compose_gen_file({
422
+ * origin_path,
423
+ * imports,
424
+ * blocks: [
425
+ * generate_action_specs_record(all_specs, imports, {qualify_spec}),
426
+ * generate_action_inputs_outputs(all_specs, imports, {qualify_spec}),
427
+ * ],
428
+ * });
429
+ * };
430
+ * ```
431
+ *
432
+ * @throws if two sources contain the same method name (same-method detection
433
+ * is the consumer's primary debugging signal).
434
+ */
435
+ export declare const create_namespace_qualifier: (sources: ReadonlyArray<SpecSource>, imports: ImportBuilder) => {
436
+ qualify_spec: (spec: ActionSpecUnion) => string;
437
+ all_specs: ReadonlyArray<ActionSpecUnion>;
438
+ };
439
+ /**
440
+ * Wrap the per-`*.gen.ts` boilerplate (banner + `imports.build()` +
441
+ * blocks join + template literal) into one call. Returns the full file body
442
+ * as a string ready to return from a `Gen` function.
443
+ *
444
+ * Each consumer producer collapses to one `compose_gen_file` call wrapping
445
+ * the helper invocations.
446
+ *
447
+ * @example
448
+ * ```ts
449
+ * export const gen: Gen = ({origin_path}) => {
450
+ * const imports = new ImportBuilder();
451
+ * return compose_gen_file({
452
+ * origin_path,
453
+ * imports,
454
+ * blocks: [
455
+ * generate_action_specs_record(all_action_specs, imports),
456
+ * generate_action_inputs_outputs(all_action_specs, imports),
457
+ * generate_action_event_datas(all_action_specs, imports),
458
+ * ],
459
+ * });
460
+ * };
461
+ * ```
462
+ *
463
+ * Empty blocks (`''`) are filtered out so helpers that short-circuit on
464
+ * empty spec sets don't introduce stray double blank lines.
465
+ */
466
+ export declare const compose_gen_file: (input: {
467
+ origin_path: string;
468
+ imports: ImportBuilder;
469
+ blocks: ReadonlyArray<string>;
470
+ }) => string;
317
471
  export {};
318
472
  //# sourceMappingURL=action_codegen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAGxE;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kCAAmC,CAAC;AAE1E,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAIhF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GAAI,QAAQ,MAAM,KAAG,MAAM,IAAI,sBACrC,CAAC;AAEnC;;GAEG;AACH,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAa;IAE1D;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAOrD;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAgCtD;;;OAGG;IACH,KAAK,IAAI,MAAM;IAIf;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAqDb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,KAC9B,KAAK,CAAC,gBAAgB,CA4DxB,CAAC;AAEF,gHAAgH;AAChH,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAElE,4FAA4F;AAC5F,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,OAAO,gBAAgB,EACvB,SAAS,aAAa,EACtB,mBAAkB,MAAiC,KACjD,MAkBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,EACtB,UAAU;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAC,KAC/D,MA2BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC;AAG/D,eAAO,MAAM,yBAAyB,GAAI,QAAQ,MAAM,KAAG,MAAiC,CAAC;AAC7F,eAAO,MAAM,+BAA+B,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAC9C,eAAO,MAAM,gCAAgC,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,eAAe,EACrB,UAAU;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAC,KACtC,MAoBF,CAAC;AAqBF,yFAAyF;AACzF,MAAM,MAAM,oBAAoB,GAC7B,KAAK,GACL,kBAAkB,GAClB,qBAAqB,GACrB,YAAY,GACZ,UAAU,GACV,SAAS,CAAC;AAEb,0CAA0C;AAC1C,eAAO,MAAM,4BAA4B,EAAE,WAAW,CAAC,oBAAoB,CAOzE,CAAC;AAoCH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IAAC,IAAI,CAAC,EAAE,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAC,KACjF,MA6DF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,aAAa,EACtB,UAAU;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAC,KAC5D,MAcF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IACT,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,CAAC;IACjD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B,KACC,MAkCF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IACT,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,CAAC;IACjD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B,KACC,MA0DF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAC,KACvF,MA0CF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IACT,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B,KACC,MAuCF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,GAC7C,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAC,KAClE,MA+BF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,4BAA4B,GACxC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,SAAS,aAAa,EACtB,UAAU;IACT,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,CAAC;IACjD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B,KACC,MAsCF,CAAC"}
1
+ {"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"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"}
@@ -10,12 +10,12 @@ import { ActionRegistry } from './action_registry.js';
10
10
  export const COMPOSABLE_ACTION_METHODS = ['heartbeat', 'cancel'];
11
11
  const COMPOSABLE_METHOD_SET = new Set(COMPOSABLE_ACTION_METHODS);
12
12
  /**
13
- * Type predicate for filtering composable methods out of a typed `ActionsApi`
13
+ * Type predicate for filtering composable methods out of a typed `FrontendActionsApi`
14
14
  * `method_filter`. Avoids the `(... as never)` cast required to call
15
15
  * `Array.prototype.includes` on the readonly tuple at narrow string types.
16
16
  *
17
17
  * @example
18
- * generate_actions_api(specs, imports, {
18
+ * generate_frontend_actions_api(specs, imports, {
19
19
  * method_filter: (s) => !is_composable_action_method(s.method),
20
20
  * });
21
21
  */
@@ -312,10 +312,10 @@ export const to_action_spec_identifier = (method) => `${method}_action_spec`;
312
312
  export const to_action_spec_input_identifier = (method) => `${to_action_spec_identifier(method)}.input`;
313
313
  export const to_action_spec_output_identifier = (method) => `${to_action_spec_identifier(method)}.output`;
314
314
  /**
315
- * Generates one method line of the typed `ActionsApi` interface for a single
316
- * spec. Encapsulates the input/options/return-type signature shape so the
317
- * surface evolves in one place when fields like `signal` or `transport_name`
318
- * are added to per-call options.
315
+ * Generates one method line of the typed `FrontendActionsApi` interface for a
316
+ * single spec. Encapsulates the input/options/return-type signature shape so
317
+ * the surface evolves in one place when fields like `signal` or
318
+ * `transport_name` are added to per-call options.
319
319
  *
320
320
  * Async methods (`request_response`, `remote_notification`, async
321
321
  * `local_call`) get an optional second `options?: RpcClientCallOptions` arg
@@ -336,7 +336,7 @@ export const to_action_spec_output_identifier = (method) => `${to_action_spec_id
336
336
  * @param options.sync_returns_value - when true (default), sync local_call
337
337
  * methods return the output value directly; when false they're wrapped in
338
338
  * `Result<{value, error}>` like async methods. Set to `false` if your
339
- * ActionsApi treats every method uniformly.
339
+ * FrontendActionsApi treats every method uniformly.
340
340
  * @returns one line like `foo: (input: ActionInputs['foo'], options?: RpcClientCallOptions) => Promise<Result<...>>;`
341
341
  */
342
342
  export const generate_actions_api_method_signature = (spec, options) => {
@@ -356,6 +356,40 @@ export const generate_actions_api_method_signature = (spec, options) => {
356
356
  : result_return;
357
357
  return `${spec.method}: (${input_param}${options_param}) => ${return_type};`;
358
358
  };
359
+ // --------------------------------------------------------------------------
360
+ // High-level codegen helpers — compose the lower-level primitives above into
361
+ // the literal blocks consumer `*.gen.ts` producers emit. Tier 1 consumers
362
+ // (HTTP-only, e.g. tx) call the value-side helpers; Tier 2 (`TypedActionEvent`-
363
+ // aware, e.g. zzz) also call the typed-event + frontend-handlers helpers.
364
+ //
365
+ // **Multi-source consumers.** Helpers that reference specs at runtime
366
+ // (`generate_action_specs_record`, `generate_action_inputs_outputs`,
367
+ // `generate_backend_actions_api`) default to a single `* as specs from
368
+ // specs_module` namespace import and emit `specs.{method}_action_spec`. Pass
369
+ // `qualify_spec?: (spec) => string` to emit a per-spec qualified identifier
370
+ // (e.g. `admin_specs.account_list_action_spec`) for consumers stitching local
371
+ // specs together with multiple upstream sources (`all_admin_action_specs` /
372
+ // `all_permit_offer_action_specs` / `all_account_action_specs` /
373
+ // `all_self_service_role_action_specs` from fuz_app). When `qualify_spec` is
374
+ // set, the helper does NOT add a `* as specs` import — the consumer manages
375
+ // the multiple `* as ns` imports itself — and `specs_module` is ignored.
376
+ // `create_namespace_qualifier` automates the source-table → qualifier wiring.
377
+ // --------------------------------------------------------------------------
378
+ /**
379
+ * Format a `z.enum([...])` runtime const + matching `z.infer` type alias.
380
+ * Caller is responsible for ensuring `methods` is non-empty (`z.enum([])` is
381
+ * invalid) and registering the `zod` import on the `ImportBuilder`.
382
+ */
383
+ const format_method_enum_block = (name, jsdoc, methods) => {
384
+ const lines = methods.map((m) => `\t'${m}',`).join('\n');
385
+ return `/**
386
+ * ${jsdoc}
387
+ */
388
+ export const ${name} = z.enum([
389
+ ${lines}
390
+ ]);
391
+ export type ${name} = z.infer<typeof ${name}>;`;
392
+ };
359
393
  /** Default emit set — every enum kind. */
360
394
  export const ACTION_METHOD_ENUM_KINDS_ALL = new Set([
361
395
  'all',
@@ -364,12 +398,16 @@ export const ACTION_METHOD_ENUM_KINDS_ALL = new Set([
364
398
  'local_call',
365
399
  'frontend',
366
400
  'backend',
401
+ 'frontend_handled',
402
+ 'backend_handled',
403
+ 'broadcast',
367
404
  ]);
368
405
  /**
369
406
  * Filter `heartbeat` / `cancel` out of `specs` unless the consumer opts back in.
370
407
  * Composables ship from fuz_app and are spread into every consumer's `actions`
371
408
  * array at registration time — they should not appear in consumer-owned typed
372
- * surfaces (`ActionMethod`, `ActionsApi`, `ActionInputs`, etc.) by default.
409
+ * surfaces (`ActionMethod`, `FrontendActionsApi`, `ActionInputs`, etc.) by
410
+ * default.
373
411
  */
374
412
  const filter_composables = (specs, include_composables) => include_composables ? specs : specs.filter((s) => !is_composable_action_method(s.method));
375
413
  /**
@@ -391,8 +429,10 @@ const resolve_spec_qualifier = (imports, options) => {
391
429
  /**
392
430
  * Emit one or more `z.enum([...])` declarations for action method names —
393
431
  * `ActionMethod`, `RequestResponseActionMethod`, `RemoteNotificationActionMethod`,
394
- * `LocalCallActionMethod`, `FrontendActionMethod`, `BackendActionMethod`. Pairs
395
- * each runtime const with a `z.infer` type alias under the same identifier.
432
+ * `LocalCallActionMethod`, `FrontendActionMethod`, `BackendActionMethod`,
433
+ * `FrontendRequestResponseMethod`, `BackendRequestResponseMethod`,
434
+ * `BroadcastActionMethod`. Pairs each runtime const with a `z.infer` type
435
+ * alias under the same identifier.
396
436
  *
397
437
  * Composable methods (`heartbeat`, `cancel`) are filtered out by default —
398
438
  * pass `include_composables: true` if a consumer genuinely wants them on
@@ -402,7 +442,11 @@ const resolve_spec_qualifier = (imports, options) => {
402
442
  * Adds `import {z} from 'zod';` to `imports` only when at least one block
403
443
  * is emitted (idempotent).
404
444
  *
405
- * @param options.emit - subset of enums to emit; defaults to all six.
445
+ * For genuinely cross-product enums the discriminator doesn't cover, use
446
+ * `generate_action_method_enum_block` — caller owns the predicate, name,
447
+ * and jsdoc.
448
+ *
449
+ * @param options.emit - subset of enums to emit; defaults to all nine.
406
450
  * @param options.include_composables - when true, retains `heartbeat` /
407
451
  * `cancel` in the emitted enums. Default `false`.
408
452
  */
@@ -418,21 +462,48 @@ export const generate_action_method_enums = (specs, imports, options) => {
418
462
  // Consumers that need a kind to exist should check their spec set, not the helper.
419
463
  if (methods.length === 0)
420
464
  return;
421
- const lines = methods.map((m) => `\t'${m}',`).join('\n');
422
- blocks.push(`/**\n * ${jsdoc}\n */\nexport const ${name} = z.enum([\n${lines}\n]);
423
- export type ${name} = z.infer<typeof ${name}>;`);
465
+ blocks.push(format_method_enum_block(name, jsdoc, methods));
424
466
  };
425
467
  emit_block('all', 'ActionMethod', registry.methods, 'All action method names. Request/response actions have two types per method.');
426
468
  emit_block('request_response', 'RequestResponseActionMethod', registry.request_response_methods, 'Names of all request_response actions.');
427
469
  emit_block('remote_notification', 'RemoteNotificationActionMethod', registry.remote_notification_methods, 'Names of all remote_notification actions.');
428
470
  emit_block('local_call', 'LocalCallActionMethod', registry.local_call_methods, 'Names of all local_call actions.');
429
- emit_block('frontend', 'FrontendActionMethod', registry.frontend_methods, 'Names of all actions that may be handled on the client.');
430
- emit_block('backend', 'BackendActionMethod', registry.backend_methods, 'Names of all actions that may be handled on the server.');
471
+ // Loose: every spec the side might encounter (call, receive, or execute).
472
+ // Drives the typed-Proxy method enum keyed by FrontendActionsApi.
473
+ emit_block('frontend', 'FrontendActionMethod', registry.methods_relevant_to_frontend, 'Names of all actions in the typed FrontendActionsApi surface — every spec the frontend may encounter (call, receive, or execute locally).');
474
+ emit_block('backend', 'BackendActionMethod', registry.methods_relevant_to_backend, 'Names of all actions the backend may encounter — request_response and remote_notification (local_call is frontend-only).');
475
+ // Narrow: request_response actions this side handles (receives).
476
+ emit_block('frontend_handled', 'FrontendRequestResponseMethod', registry.frontend_handled_methods, 'Names of request_response actions the frontend handles (initiator excludes frontend).');
477
+ emit_block('backend_handled', 'BackendRequestResponseMethod', registry.backend_handled_methods, 'Names of request_response actions the backend handles (initiator excludes backend).');
478
+ // Broadcast: backend-initiated remote_notification, excluding `streams` targets.
479
+ emit_block('broadcast', 'BroadcastActionMethod', registry.broadcast_methods, "Names of remote_notification actions exposed by the broadcast API (backend-initiated, excluding request-scoped progress notifications named by another action's `streams`).");
431
480
  if (blocks.length === 0)
432
481
  return '';
433
482
  imports.add('zod', 'z');
434
483
  return blocks.join('\n\n');
435
484
  };
485
+ /**
486
+ * Emit a single named `z.enum([...])` + `z.infer` block for an arbitrary
487
+ * spec subset. Lower-level escape hatch from `generate_action_method_enums` —
488
+ * for cross-product or domain-specific enums the built-in discriminator
489
+ * doesn't cover.
490
+ *
491
+ * Mirrors the built-in helper's contract: composables filtered by default,
492
+ * empty subsets return `''` (skip rather than emit `z.enum([])`), `zod`
493
+ * import registered idempotently only when at least one method qualifies.
494
+ *
495
+ * The cross-product space is open-ended; rather than grow the
496
+ * `ActionMethodEnumKind` discriminator one cross-product at a time, callers
497
+ * own the subset shape — name, jsdoc, predicate.
498
+ */
499
+ export const generate_action_method_enum_block = (specs, imports, options) => {
500
+ const filtered = filter_composables(specs, options.include_composables);
501
+ const methods = filtered.filter(options.predicate).map((s) => s.method);
502
+ if (methods.length === 0)
503
+ return '';
504
+ imports.add('zod', 'z');
505
+ return format_method_enum_block(options.name, options.jsdoc, methods);
506
+ };
436
507
  /**
437
508
  * Emit the fixed-shape `TypedActionEvent` alias used by `FrontendActionHandlers`
438
509
  * to narrow `ActionEvent.data` against the consumer's generated `ActionEventDatas`
@@ -630,29 +701,36 @@ ${lines.join('\n')}
630
701
  }`;
631
702
  };
632
703
  /**
633
- * Emit the `ActionsApi` interface — one method signature per spec via
704
+ * Emit the `FrontendActionsApi` interface — one method signature per spec via
634
705
  * `generate_actions_api_method_signature`. Optionally filter the spec set
635
706
  * (e.g. omit composable methods) via `method_filter`.
636
707
  *
637
708
  * Adds the `Result`, `JsonrpcErrorObject`, and `RpcClientCallOptions` type
638
709
  * imports plus `ActionInputs` / `ActionOutputs` (sourced from `collections_path`).
710
+ *
711
+ * The interface name is fixed at `FrontendActionsApi` — the symmetric counterpart
712
+ * of `BackendActionsApi`. Earlier consumer-named variants (`MyActionsApi`,
713
+ * `VisionesActionsApi`) were retired in API review III to make the side-of-the-wire
714
+ * intent visible at every call site. If a consumer needs a different name they
715
+ * hand-roll the interface (the helper's job is the standard symmetric shape).
639
716
  */
640
- export const generate_actions_api = (specs, imports, options) => {
717
+ export const generate_frontend_actions_api = (specs, imports, options) => {
641
718
  const composable_filtered = filter_composables(specs, options?.include_composables);
642
719
  const filter = options?.method_filter;
643
720
  const filtered = filter ? composable_filtered.filter((s) => filter(s)) : composable_filtered;
644
721
  const interface_doc = `/**
645
- * Interface for action dispatch functions.
646
- * Async methods (request_response, remote_notification, async local_call)
647
- * return \`Promise<Result<...>>\` and accept an optional \`RpcClientCallOptions\`
648
- * second arg that threads \`signal\`, \`transport_name\`, and \`queue\` through to
649
- * the peer. Sync local_call methods return values directly.
722
+ * Typed dispatch surface for the frontend's RPC client. Symmetric counterpart
723
+ * of \`BackendActionsApi\`. Async methods (request_response, remote_notification,
724
+ * async local_call) return \`Promise<Result<...>>\` and accept an optional
725
+ * \`RpcClientCallOptions\` second arg that threads \`signal\`, \`transport_name\`,
726
+ * and \`queue\` through to the peer. Sync local_call methods return values
727
+ * directly.
650
728
  */`;
651
729
  if (filtered.length === 0) {
652
- // Empty spec list — emit `ActionsApi {}` and skip every import. None
653
- // of the symbols would be referenced by the empty body.
730
+ // Empty spec list — emit `FrontendActionsApi {}` and skip every import.
731
+ // None of the symbols would be referenced by the empty body.
654
732
  return `${interface_doc}
655
- export interface ActionsApi {}`;
733
+ export interface FrontendActionsApi {}`;
656
734
  }
657
735
  const collections_path = options?.collections_path ?? DEFAULT_COLLECTIONS_PATH;
658
736
  imports.add_type('@fuzdev/fuz_util/result.js', 'Result');
@@ -666,7 +744,7 @@ export interface ActionsApi {}`;
666
744
  .map((line) => `\t${line}`)
667
745
  .join('\n');
668
746
  return `${interface_doc}
669
- export interface ActionsApi {
747
+ export interface FrontendActionsApi {
670
748
  ${lines}
671
749
  }`;
672
750
  };
@@ -712,12 +790,23 @@ ${lines};
712
790
  * each `(input) => Promise<void>`. The array bundles the matching specs as a
713
791
  * `ReadonlyArray<ActionSpecUnion>`.
714
792
  *
715
- * Filter: `kind === 'remote_notification' && initiator !== 'frontend'`.
793
+ * Filter: `kind === 'remote_notification' && initiator !== 'frontend'`,
794
+ * additionally excluding methods that are the target of another spec's
795
+ * `streams` field. Streams targets (e.g. `completion_progress`,
796
+ * `ollama_progress`) are request-scoped notifications invoked via
797
+ * `ctx.notify` inside their parent handler — they're never callable through
798
+ * the broadcast API. The discriminator is `ActionSpec.streams`, not a manual
799
+ * exclusion list.
716
800
  *
717
801
  * Adds the `* as specs` namespace import (from `specs_module`), the
718
802
  * `ActionInputs` type import (from `collections_path`), and the
719
803
  * `ActionSpecUnion` type import.
720
804
  *
805
+ * Method signature shape today is `(input) => Promise<void>` — matches the
806
+ * fire-and-forget runtime of `create_broadcast_api`. Generalizing per-kind
807
+ * via `generate_actions_api_method_signature` is deferred until a second
808
+ * backend runtime constructor lands (see SAES quest § API review III).
809
+ *
721
810
  * @param options.qualify_spec - per-spec qualified identifier callback for
722
811
  * multi-source consumers. When set, the helper emits the callback's return
723
812
  * value instead of ``specs.${method}_action_spec`` in the broadcast array
@@ -727,12 +816,16 @@ ${lines};
727
816
  */
728
817
  export const generate_backend_actions_api = (specs, imports, options) => {
729
818
  const composable_filtered = filter_composables(specs, options?.include_composables);
730
- const broadcast = composable_filtered.filter((s) => s.kind === 'remote_notification' && s.initiator !== 'frontend');
819
+ const registry = new ActionRegistry([...composable_filtered]);
820
+ const broadcast = registry.broadcast_specs;
731
821
  imports.add_type('@fuzdev/fuz_app/actions/action_spec.js', 'ActionSpecUnion');
732
822
  const interface_doc = `/**
733
- * Broadcast-style notifications from the backend to all connected clients.
734
- * Request-scoped streaming goes through \`ctx.notify\` instead — it's
735
- * socket-scoped, not a broadcast.
823
+ * Typed dispatch surface for backend-initiated calls. Symmetric counterpart
824
+ * of \`FrontendActionsApi\`. Today exposes broadcast-style \`remote_notification\`
825
+ * methods (1→N fan-out via \`create_broadcast_api\`); request-scoped streaming
826
+ * goes through \`ctx.notify\` inside a handler — it's socket-scoped, not a
827
+ * broadcast. Will widen when a second backend runtime constructor (targeted
828
+ * send, backend-initiated request_response) lands.
736
829
  */`;
737
830
  if (broadcast.length === 0) {
738
831
  // No backend-initiated remote_notifications — skip `* as specs` and
@@ -756,3 +849,142 @@ export interface BackendActionsApi {${interface_body}}
756
849
 
757
850
  export const broadcast_action_specs: ReadonlyArray<ActionSpecUnion> = [${array_body}];`;
758
851
  };
852
+ /**
853
+ * Emit the `BackendActionHandlers` mapped type — one entry per
854
+ * `BackendRequestResponseMethod`, each `(input, ctx) => output | Promise<output>`.
855
+ * Replaces the hand-maintained `Exclude<>` + parallel mapped-type pattern
856
+ * (zzz had this at `zzz/src/lib/server/zzz_action_handlers.ts:42-66`).
857
+ *
858
+ * The context type is consumer-defined (e.g. zzz's `ZzzHandlerContext`). Pass
859
+ * `context_type` to name it; the helper assumes it's importable or defined
860
+ * in the emitted module's scope (consumer's responsibility).
861
+ *
862
+ * Adds `ActionInputs` / `ActionOutputs` type imports from `collections_path`
863
+ * and the `BackendRequestResponseMethod` import from `metatypes_path`.
864
+ *
865
+ * @param options.type_name - default `'BackendActionHandlers'`.
866
+ * @param options.method_enum_name - default `'BackendRequestResponseMethod'`.
867
+ * Pair with `generate_action_method_enums` emitting the `'backend_handled'` kind.
868
+ * @param options.context_type - default `'BackendHandlerContext'`. Caller's
869
+ * handler context type — must be in scope at the emit site.
870
+ * @param options.collections_path - default `'./action_collections.js'`.
871
+ * @param options.metatypes_path - default `'./action_metatypes.js'`.
872
+ */
873
+ export const generate_backend_action_handlers_map = (imports, options) => {
874
+ const type_name = options?.type_name ?? 'BackendActionHandlers';
875
+ const method_enum_name = options?.method_enum_name ?? 'BackendRequestResponseMethod';
876
+ const context_type = options?.context_type ?? 'BackendHandlerContext';
877
+ const collections_path = options?.collections_path ?? DEFAULT_COLLECTIONS_PATH;
878
+ const metatypes_path = options?.metatypes_path ?? DEFAULT_METATYPES_PATH;
879
+ imports.add_types(collections_path, 'ActionInputs', 'ActionOutputs');
880
+ imports.add_type(metatypes_path, method_enum_name);
881
+ return `/**
882
+ * Typed handler map for request_response actions the backend handles.
883
+ * One entry per ${method_enum_name}; each handler receives the typed input
884
+ * and returns the typed output (sync or async).
885
+ */
886
+ export type ${type_name} = {
887
+ [K in ${method_enum_name}]: (
888
+ input: ActionInputs[K],
889
+ ctx: ${context_type},
890
+ ) => ActionOutputs[K] | Promise<ActionOutputs[K]>;
891
+ };`;
892
+ };
893
+ /**
894
+ * Multi-source consumer helper. Takes a list of `{ns, module, specs}` rows,
895
+ * registers `import * as ns from module` for each on `imports`, builds the
896
+ * `method_to_ns` lookup with duplicate-method detection, and returns
897
+ * `{qualify_spec, all_specs}` ready to thread through the high-level
898
+ * helpers.
899
+ *
900
+ * Closes the per-file boilerplate gap that kept tx + visiones on hand-rolled
901
+ * template strings even after `qualify_spec?` landed in API review II — the
902
+ * per-call callback wasn't enough; the import dance + dup-check was the
903
+ * real boilerplate.
904
+ *
905
+ * @example
906
+ * ```ts
907
+ * const sources = [
908
+ * {ns: 'tx_specs', module: './action_specs.js', specs: all_tx_action_specs},
909
+ * {ns: 'admin_specs', module: '@fuzdev/fuz_app/auth/admin_action_specs.js', specs: all_admin_action_specs},
910
+ * ];
911
+ *
912
+ * export const gen: Gen = ({origin_path}) => {
913
+ * const imports = new ImportBuilder();
914
+ * const {qualify_spec, all_specs} = create_namespace_qualifier(sources, imports);
915
+ * return compose_gen_file({
916
+ * origin_path,
917
+ * imports,
918
+ * blocks: [
919
+ * generate_action_specs_record(all_specs, imports, {qualify_spec}),
920
+ * generate_action_inputs_outputs(all_specs, imports, {qualify_spec}),
921
+ * ],
922
+ * });
923
+ * };
924
+ * ```
925
+ *
926
+ * @throws if two sources contain the same method name (same-method detection
927
+ * is the consumer's primary debugging signal).
928
+ */
929
+ export const create_namespace_qualifier = (sources, imports) => {
930
+ const method_to_ns = new Map();
931
+ const all_specs = [];
932
+ for (const { ns, module, specs } of sources) {
933
+ imports.add(module, `* as ${ns}`);
934
+ for (const spec of specs) {
935
+ if (method_to_ns.has(spec.method)) {
936
+ throw new Error(`duplicate action method across sources: ${spec.method} (in ${method_to_ns.get(spec.method)} and ${ns})`);
937
+ }
938
+ method_to_ns.set(spec.method, ns);
939
+ all_specs.push(spec);
940
+ }
941
+ }
942
+ const qualify_spec = (spec) => {
943
+ const ns = method_to_ns.get(spec.method);
944
+ if (!ns) {
945
+ throw new Error(`unknown action method passed to qualify_spec: ${spec.method} — not in any registered source`);
946
+ }
947
+ return `${ns}.${spec.method}_action_spec`;
948
+ };
949
+ return { qualify_spec, all_specs };
950
+ };
951
+ /**
952
+ * Wrap the per-`*.gen.ts` boilerplate (banner + `imports.build()` +
953
+ * blocks join + template literal) into one call. Returns the full file body
954
+ * as a string ready to return from a `Gen` function.
955
+ *
956
+ * Each consumer producer collapses to one `compose_gen_file` call wrapping
957
+ * the helper invocations.
958
+ *
959
+ * @example
960
+ * ```ts
961
+ * export const gen: Gen = ({origin_path}) => {
962
+ * const imports = new ImportBuilder();
963
+ * return compose_gen_file({
964
+ * origin_path,
965
+ * imports,
966
+ * blocks: [
967
+ * generate_action_specs_record(all_action_specs, imports),
968
+ * generate_action_inputs_outputs(all_action_specs, imports),
969
+ * generate_action_event_datas(all_action_specs, imports),
970
+ * ],
971
+ * });
972
+ * };
973
+ * ```
974
+ *
975
+ * Empty blocks (`''`) are filtered out so helpers that short-circuit on
976
+ * empty spec sets don't introduce stray double blank lines.
977
+ */
978
+ export const compose_gen_file = (input) => {
979
+ const banner = create_banner(input.origin_path);
980
+ const body = input.blocks.filter(Boolean).join('\n\n');
981
+ return `
982
+ // ${banner}
983
+
984
+ ${input.imports.build()}
985
+
986
+ ${body}
987
+
988
+ // ${banner}
989
+ `;
990
+ };
@@ -1,6 +1,29 @@
1
1
  /**
2
2
  * `ActionRegistry` — query and filter utility over `ActionSpecUnion[]`.
3
3
  *
4
+ * Vocabulary (set in API review III, see the `docs/` directory and the SAES quest):
5
+ * - `*_handled_*` — request_response specs the named side **receives**
6
+ * (so the named side owns the handler). Used by codegen to emit typed
7
+ * handler maps.
8
+ * - `*_relevant_to_*` — the loose "everything this side might encounter"
9
+ * set, used by the typed-Proxy method enums (`FrontendActionMethod`,
10
+ * `BackendActionMethod`).
11
+ * - `broadcast_*` — kind-narrow `remote_notification` set with the
12
+ * `streams`-target exclusion. Today this matches what the broadcast
13
+ * API exposes.
14
+ * - `backend_initiated_*` — forward-looking kind-agnostic version of the
15
+ * broadcast set. Same content today; will diverge when local_calls or
16
+ * backend `request_response` join the backend's typed surface.
17
+ *
18
+ * Cache discipline: `spec_by_method` (Map) and the internal streams-target
19
+ * set lazy-memoize because the Map is consulted per-RPC dispatch
20
+ * (`frontend_rpc_client.ts` wires it into `lookup_action_spec`) and the
21
+ * streams set is rebuilt by two public getters. Array-returning getters
22
+ * recompute on each call so callers can mutate the result freely
23
+ * (`.sort()`, `.push(injected)` on a copy, etc.) without affecting the
24
+ * registry — codegen is a build-time path where the extra `.filter` /
25
+ * `.map` work is negligible.
26
+ *
4
27
  * @module
5
28
  */
6
29
  import type { ActionSpecUnion, RequestResponseActionSpec, RemoteNotificationActionSpec, LocalCallActionSpec } from './action_spec.js';
@@ -9,26 +32,35 @@ import type { ActionSpecUnion, RequestResponseActionSpec, RemoteNotificationActi
9
32
  * Provides helper methods to get actions by various criteria.
10
33
  */
11
34
  export declare class ActionRegistry {
35
+ #private;
12
36
  readonly specs: Array<ActionSpecUnion>;
13
37
  constructor(specs: Array<ActionSpecUnion>);
14
38
  get spec_by_method(): Map<string, ActionSpecUnion>;
39
+ get methods(): Array<string>;
15
40
  get request_response_specs(): Array<RequestResponseActionSpec>;
16
41
  get remote_notification_specs(): Array<RemoteNotificationActionSpec>;
17
42
  get local_call_specs(): Array<LocalCallActionSpec>;
18
- get backend_specs(): Array<ActionSpecUnion>;
19
- get frontend_specs(): Array<ActionSpecUnion>;
20
- get backend_to_frontend_specs(): Array<ActionSpecUnion>;
21
- get frontend_to_backend_specs(): Array<ActionSpecUnion>;
22
- get public_specs(): Array<ActionSpecUnion>;
23
- get authenticated_specs(): Array<ActionSpecUnion>;
24
- get methods(): Array<string>;
25
43
  get request_response_methods(): Array<string>;
26
44
  get remote_notification_methods(): Array<string>;
27
45
  get local_call_methods(): Array<string>;
28
- get backend_methods(): Array<string>;
29
- get frontend_methods(): Array<string>;
46
+ get specs_relevant_to_frontend(): Array<ActionSpecUnion>;
47
+ get specs_relevant_to_backend(): Array<ActionSpecUnion>;
48
+ get methods_relevant_to_frontend(): Array<string>;
49
+ get methods_relevant_to_backend(): Array<string>;
50
+ get frontend_handled_specs(): Array<RequestResponseActionSpec>;
51
+ get backend_handled_specs(): Array<RequestResponseActionSpec>;
52
+ get frontend_handled_methods(): Array<string>;
53
+ get backend_handled_methods(): Array<string>;
54
+ get broadcast_specs(): Array<RemoteNotificationActionSpec>;
55
+ get broadcast_methods(): Array<string>;
56
+ get backend_initiated_specs(): Array<ActionSpecUnion>;
57
+ get backend_initiated_methods(): Array<string>;
58
+ get backend_to_frontend_specs(): Array<ActionSpecUnion>;
59
+ get frontend_to_backend_specs(): Array<ActionSpecUnion>;
30
60
  get frontend_to_backend_methods(): Array<string>;
31
61
  get backend_to_frontend_methods(): Array<string>;
62
+ get public_specs(): Array<ActionSpecUnion>;
63
+ get authenticated_specs(): Array<ActionSpecUnion>;
32
64
  get public_methods(): Array<string>;
33
65
  get authenticated_methods(): Array<string>;
34
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"action_registry.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACX,eAAe,EACf,yBAAyB,EACzB,4BAA4B,EAC5B,mBAAmB,EACnB,MAAM,kBAAkB,CAAC;AAQ1B;;;GAGG;AACH,qBAAa,cAAc;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;gBAE3B,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC;IAIzC,IAAI,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAEjD;IAED,IAAI,sBAAsB,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAE7D;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAEnE;IAED,IAAI,gBAAgB,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAEjD;IAKD,IAAI,aAAa,IAAI,KAAK,CAAC,eAAe,CAAC,CAE1C;IAED,IAAI,cAAc,IAAI,KAAK,CAAC,eAAe,CAAC,CAE3C;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEtD;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEtD;IAED,IAAI,YAAY,IAAI,KAAK,CAAC,eAAe,CAAC,CAEzC;IAED,IAAI,mBAAmB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEhD;IAED,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,CAE3B;IAED,IAAI,wBAAwB,IAAI,KAAK,CAAC,MAAM,CAAC,CAE5C;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAED,IAAI,kBAAkB,IAAI,KAAK,CAAC,MAAM,CAAC,CAEtC;IAED,IAAI,eAAe,IAAI,KAAK,CAAC,MAAM,CAAC,CAEnC;IAED,IAAI,gBAAgB,IAAI,KAAK,CAAC,MAAM,CAAC,CAEpC;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAED,IAAI,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC,CAElC;IAED,IAAI,qBAAqB,IAAI,KAAK,CAAC,MAAM,CAAC,CAEzC;CACD"}
1
+ {"version":3,"file":"action_registry.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EACX,eAAe,EACf,yBAAyB,EACzB,4BAA4B,EAC5B,mBAAmB,EACnB,MAAM,kBAAkB,CAAC;AAS1B;;;GAGG;AACH,qBAAa,cAAc;;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;gBAE3B,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC;IAKzC,IAAI,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAEjD;IAED,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,CAE3B;IAID,IAAI,sBAAsB,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAE7D;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAEnE;IAED,IAAI,gBAAgB,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAEjD;IAED,IAAI,wBAAwB,IAAI,KAAK,CAAC,MAAM,CAAC,CAE5C;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAED,IAAI,kBAAkB,IAAI,KAAK,CAAC,MAAM,CAAC,CAEtC;IAOD,IAAI,0BAA0B,IAAI,KAAK,CAAC,eAAe,CAAC,CAEvD;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEtD;IAED,IAAI,4BAA4B,IAAI,KAAK,CAAC,MAAM,CAAC,CAEhD;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAOD,IAAI,sBAAsB,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAE7D;IAED,IAAI,qBAAqB,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAE5D;IAED,IAAI,wBAAwB,IAAI,KAAK,CAAC,MAAM,CAAC,CAE5C;IAED,IAAI,uBAAuB,IAAI,KAAK,CAAC,MAAM,CAAC,CAE3C;IASD,IAAI,eAAe,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAKzD;IAED,IAAI,iBAAiB,IAAI,KAAK,CAAC,MAAM,CAAC,CAErC;IAED,IAAI,uBAAuB,IAAI,KAAK,CAAC,eAAe,CAAC,CAKpD;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,MAAM,CAAC,CAE7C;IAID,IAAI,yBAAyB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEtD;IAED,IAAI,yBAAyB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEtD;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAED,IAAI,2BAA2B,IAAI,KAAK,CAAC,MAAM,CAAC,CAE/C;IAID,IAAI,YAAY,IAAI,KAAK,CAAC,eAAe,CAAC,CAEzC;IAED,IAAI,mBAAmB,IAAI,KAAK,CAAC,eAAe,CAAC,CAEhD;IAED,IAAI,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC,CAElC;IAED,IAAI,qBAAqB,IAAI,KAAK,CAAC,MAAM,CAAC,CAEzC;CAaD"}
@@ -1,13 +1,37 @@
1
1
  /**
2
2
  * `ActionRegistry` — query and filter utility over `ActionSpecUnion[]`.
3
3
  *
4
+ * Vocabulary (set in API review III, see the `docs/` directory and the SAES quest):
5
+ * - `*_handled_*` — request_response specs the named side **receives**
6
+ * (so the named side owns the handler). Used by codegen to emit typed
7
+ * handler maps.
8
+ * - `*_relevant_to_*` — the loose "everything this side might encounter"
9
+ * set, used by the typed-Proxy method enums (`FrontendActionMethod`,
10
+ * `BackendActionMethod`).
11
+ * - `broadcast_*` — kind-narrow `remote_notification` set with the
12
+ * `streams`-target exclusion. Today this matches what the broadcast
13
+ * API exposes.
14
+ * - `backend_initiated_*` — forward-looking kind-agnostic version of the
15
+ * broadcast set. Same content today; will diverge when local_calls or
16
+ * backend `request_response` join the backend's typed surface.
17
+ *
18
+ * Cache discipline: `spec_by_method` (Map) and the internal streams-target
19
+ * set lazy-memoize because the Map is consulted per-RPC dispatch
20
+ * (`frontend_rpc_client.ts` wires it into `lookup_action_spec`) and the
21
+ * streams set is rebuilt by two public getters. Array-returning getters
22
+ * recompute on each call so callers can mutate the result freely
23
+ * (`.sort()`, `.push(injected)` on a copy, etc.) without affecting the
24
+ * registry — codegen is a build-time path where the extra `.filter` /
25
+ * `.map` work is negligible.
26
+ *
4
27
  * @module
5
28
  */
6
- // TODO @action-system-review Many getters below are stub API surface — only `spec_by_method`,
7
- // `request_response_specs`, `remote_notification_specs`, `local_call_specs`,
8
- // `frontend_methods`, `backend_methods`, and `methods` are used by consumers (codegen).
9
- // The rest are pre-built for future use. Revisit which getters to keep when the action
10
- // system matures. Also consider lazy memoization (`??=` or derived).
29
+ // TODO @action-system-review Many getters below are stub API surface — only
30
+ // `spec_by_method`, `methods`, the kind-narrow `*_specs` (`request_response_specs`,
31
+ // `remote_notification_specs`, `local_call_specs`), the `*_handled_*` pair,
32
+ // the `*_relevant_to_*` pair, and `broadcast_*` / `backend_initiated_*` are
33
+ // used by codegen. The auth + initiator-direction getters are pre-built for
34
+ // future use. Revisit which getters to keep when the action system matures.
11
35
  /**
12
36
  * Utility class to manage and query action specifications.
13
37
  * Provides helper methods to get actions by various criteria.
@@ -17,9 +41,14 @@ export class ActionRegistry {
17
41
  constructor(specs) {
18
42
  this.specs = specs;
19
43
  }
44
+ #spec_by_method;
20
45
  get spec_by_method() {
21
- return new Map(this.specs.map((spec) => [spec.method, spec]));
46
+ return (this.#spec_by_method ??= new Map(this.specs.map((spec) => [spec.method, spec])));
47
+ }
48
+ get methods() {
49
+ return this.specs.map((spec) => spec.method);
22
50
  }
51
+ // --- Kind-narrow getters ---
23
52
  get request_response_specs() {
24
53
  return this.specs.filter((spec) => spec.kind === 'request_response');
25
54
  }
@@ -29,44 +58,73 @@ export class ActionRegistry {
29
58
  get local_call_specs() {
30
59
  return this.specs.filter((spec) => spec.kind === 'local_call');
31
60
  }
32
- // TODO @action-system-review `backend_specs` filters out local_call (can't run on backend);
33
- // `frontend_specs` returns all specs (all action kinds are relevant to the frontend).
34
- // Revisit whether these filters are correct as the action system matures.
35
- get backend_specs() {
61
+ get request_response_methods() {
62
+ return this.request_response_specs.map((spec) => spec.method);
63
+ }
64
+ get remote_notification_methods() {
65
+ return this.remote_notification_specs.map((spec) => spec.method);
66
+ }
67
+ get local_call_methods() {
68
+ return this.local_call_specs.map((spec) => spec.method);
69
+ }
70
+ // --- Loose "relevant to side" getters ---
71
+ // Backs the `FrontendActionMethod` / `BackendActionMethod` enums — the
72
+ // typed-Proxy method enums where every spec the side might encounter
73
+ // (call, receive, or execute) belongs in the union.
74
+ get specs_relevant_to_frontend() {
75
+ return this.specs.slice();
76
+ }
77
+ get specs_relevant_to_backend() {
36
78
  return this.specs.filter((spec) => spec.kind !== 'local_call');
37
79
  }
38
- get frontend_specs() {
39
- return this.specs;
80
+ get methods_relevant_to_frontend() {
81
+ return this.specs_relevant_to_frontend.map((spec) => spec.method);
40
82
  }
41
- get backend_to_frontend_specs() {
42
- return this.specs.filter((spec) => spec.initiator === 'backend' || spec.initiator === 'both');
83
+ get methods_relevant_to_backend() {
84
+ return this.specs_relevant_to_backend.map((spec) => spec.method);
43
85
  }
44
- get frontend_to_backend_specs() {
45
- return this.specs.filter((spec) => spec.initiator === 'frontend' || spec.initiator === 'both');
86
+ // --- Narrow handler-side getters (request_response only) ---
87
+ // "Handled" = this side **receives** (initiator excludes own side).
88
+ // Drives `FrontendRequestResponseMethod` / `BackendRequestResponseMethod`
89
+ // enums and the typed `BackendActionHandlers` mapped type.
90
+ get frontend_handled_specs() {
91
+ return this.request_response_specs.filter((spec) => spec.initiator !== 'frontend');
46
92
  }
47
- get public_specs() {
48
- return this.specs.filter((spec) => spec.auth === 'public');
93
+ get backend_handled_specs() {
94
+ return this.request_response_specs.filter((spec) => spec.initiator !== 'backend');
49
95
  }
50
- get authenticated_specs() {
51
- return this.specs.filter((spec) => spec.auth === 'authenticated');
96
+ get frontend_handled_methods() {
97
+ return this.frontend_handled_specs.map((spec) => spec.method);
52
98
  }
53
- get methods() {
54
- return this.specs.map((spec) => spec.method);
99
+ get backend_handled_methods() {
100
+ return this.backend_handled_specs.map((spec) => spec.method);
55
101
  }
56
- get request_response_methods() {
57
- return this.request_response_specs.map((spec) => spec.method);
102
+ // --- Broadcast / backend-initiated getters ---
103
+ // Excludes `streams` targets (request-scoped progress notifications
104
+ // invoked via `ctx.notify` inside the parent handler). Today
105
+ // `broadcast_*` and `backend_initiated_*` return the same set;
106
+ // `backend_initiated_*` is the forward-looking name that will widen
107
+ // when local_calls or backend-initiated `request_response` join.
108
+ get broadcast_specs() {
109
+ const streams_targets = this.#get_streams_target_methods();
110
+ return this.remote_notification_specs.filter((spec) => spec.initiator !== 'frontend' && !streams_targets.has(spec.method));
58
111
  }
59
- get remote_notification_methods() {
60
- return this.remote_notification_specs.map((spec) => spec.method);
112
+ get broadcast_methods() {
113
+ return this.broadcast_specs.map((spec) => spec.method);
61
114
  }
62
- get local_call_methods() {
63
- return this.local_call_specs.map((spec) => spec.method);
115
+ get backend_initiated_specs() {
116
+ const streams_targets = this.#get_streams_target_methods();
117
+ return this.specs.filter((spec) => spec.initiator !== 'frontend' && !streams_targets.has(spec.method));
64
118
  }
65
- get backend_methods() {
66
- return this.backend_specs.map((spec) => spec.method);
119
+ get backend_initiated_methods() {
120
+ return this.backend_initiated_specs.map((spec) => spec.method);
67
121
  }
68
- get frontend_methods() {
69
- return this.frontend_specs.map((spec) => spec.method);
122
+ // --- Initiator-direction (pre-built, unused by codegen today) ---
123
+ get backend_to_frontend_specs() {
124
+ return this.specs.filter((spec) => spec.initiator === 'backend' || spec.initiator === 'both');
125
+ }
126
+ get frontend_to_backend_specs() {
127
+ return this.specs.filter((spec) => spec.initiator === 'frontend' || spec.initiator === 'both');
70
128
  }
71
129
  get frontend_to_backend_methods() {
72
130
  return this.frontend_to_backend_specs.map((spec) => spec.method);
@@ -74,10 +132,29 @@ export class ActionRegistry {
74
132
  get backend_to_frontend_methods() {
75
133
  return this.backend_to_frontend_specs.map((spec) => spec.method);
76
134
  }
135
+ // --- Auth (pre-built, unused by codegen today) ---
136
+ get public_specs() {
137
+ return this.specs.filter((spec) => spec.auth === 'public');
138
+ }
139
+ get authenticated_specs() {
140
+ return this.specs.filter((spec) => spec.auth === 'authenticated');
141
+ }
77
142
  get public_methods() {
78
143
  return this.public_specs.map((spec) => spec.method);
79
144
  }
80
145
  get authenticated_methods() {
81
146
  return this.authenticated_specs.map((spec) => spec.method);
82
147
  }
148
+ // --- Internal ---
149
+ #streams_target_methods;
150
+ #get_streams_target_methods() {
151
+ if (this.#streams_target_methods)
152
+ return this.#streams_target_methods;
153
+ const targets = new Set();
154
+ for (const spec of this.specs) {
155
+ if (spec.streams)
156
+ targets.add(spec.streams);
157
+ }
158
+ return (this.#streams_target_methods = targets);
159
+ }
83
160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",