@fuzdev/fuz_app 0.47.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.
@@ -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.47.0",
3
+ "version": "0.49.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",