@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.
- package/dist/actions/CLAUDE.md +82 -39
- package/dist/actions/action_codegen.d.ts +168 -14
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +264 -32
- package/dist/actions/action_registry.d.ts +41 -9
- package/dist/actions/action_registry.d.ts.map +1 -1
- package/dist/actions/action_registry.js +109 -32
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
`
|
|
75
|
-
|
|
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 `
|
|
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
|
|
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
|
-
`
|
|
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)
|
|
117
|
-
`
|
|
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
|
|
123
|
-
appends `.input` / `.output` to the
|
|
124
|
-
`generate_action_inputs_outputs` automatically;
|
|
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` + `
|
|
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`,
|
|
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
|
|
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
|
-
- `
|
|
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'
|
|
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 `
|
|
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 `
|
|
649
|
-
codegen via `generate_actions_api_method_signature` keeps the
|
|
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
|
|
662
|
-
| -------------------------- |
|
|
663
|
-
| `create_throwing_rpc_call` | `(method, input?) => Promise<T>`
|
|
664
|
-
| `create_throwing_api` | typed Proxy over `
|
|
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 `
|
|
679
|
-
generic to get the typed Result-shaped Proxy without casts, then
|
|
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<
|
|
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<
|
|
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 `
|
|
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
|
-
*
|
|
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 `
|
|
136
|
-
* spec. Encapsulates the input/options/return-type signature shape so
|
|
137
|
-
* surface evolves in one place when fields like `signal` or
|
|
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
|
-
*
|
|
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
|
|
173
|
-
*
|
|
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
|
-
*
|
|
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 `
|
|
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
|
|
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;
|
|
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 `
|
|
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
|
-
*
|
|
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 `
|
|
316
|
-
* spec. Encapsulates the input/options/return-type signature shape so
|
|
317
|
-
* surface evolves in one place when fields like `signal` or
|
|
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
|
-
*
|
|
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`, `
|
|
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
|
|
395
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
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 `
|
|
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
|
|
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
|
-
*
|
|
646
|
-
* Async methods (request_response, remote_notification,
|
|
647
|
-
* return \`Promise<Result<...>>\` and accept an optional
|
|
648
|
-
* second arg that threads \`signal\`, \`transport_name\`,
|
|
649
|
-
* the peer. Sync local_call methods return values
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
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
|
|
29
|
-
get
|
|
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
|
|
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
|
|
7
|
-
// `
|
|
8
|
-
// `
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
get
|
|
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
|
|
39
|
-
return this.
|
|
80
|
+
get methods_relevant_to_frontend() {
|
|
81
|
+
return this.specs_relevant_to_frontend.map((spec) => spec.method);
|
|
40
82
|
}
|
|
41
|
-
get
|
|
42
|
-
return this.
|
|
83
|
+
get methods_relevant_to_backend() {
|
|
84
|
+
return this.specs_relevant_to_backend.map((spec) => spec.method);
|
|
43
85
|
}
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
48
|
-
return this.
|
|
93
|
+
get backend_handled_specs() {
|
|
94
|
+
return this.request_response_specs.filter((spec) => spec.initiator !== 'backend');
|
|
49
95
|
}
|
|
50
|
-
get
|
|
51
|
-
return this.
|
|
96
|
+
get frontend_handled_methods() {
|
|
97
|
+
return this.frontend_handled_specs.map((spec) => spec.method);
|
|
52
98
|
}
|
|
53
|
-
get
|
|
54
|
-
return this.
|
|
99
|
+
get backend_handled_methods() {
|
|
100
|
+
return this.backend_handled_specs.map((spec) => spec.method);
|
|
55
101
|
}
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
60
|
-
return this.
|
|
112
|
+
get broadcast_methods() {
|
|
113
|
+
return this.broadcast_specs.map((spec) => spec.method);
|
|
61
114
|
}
|
|
62
|
-
get
|
|
63
|
-
|
|
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
|
|
66
|
-
return this.
|
|
119
|
+
get backend_initiated_methods() {
|
|
120
|
+
return this.backend_initiated_specs.map((spec) => spec.method);
|
|
67
121
|
}
|
|
68
|
-
|
|
69
|
-
|
|
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
|
}
|