@nodegx/export 0.0.0-stage → 0.2.5
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/README.md +59 -2
- package/dist/analyze/appState.d.ts +296 -0
- package/dist/analyze/plan.d.ts +3757 -0
- package/dist/analyze/querySettings.d.ts +75 -0
- package/dist/analyze/reach.d.ts +41 -0
- package/dist/analyze/scriptUses.d.ts +29 -0
- package/dist/catalog.d.ts +129 -0
- package/dist/cli/args.d.ts +90 -0
- package/dist/cli/exitCodes.d.ts +125 -0
- package/dist/cli/run.d.ts +30 -0
- package/dist/cli.inputs.json +174 -0
- package/dist/cli.mjs +53746 -0
- package/dist/cli.mjs.map +7 -0
- package/dist/emit/animateLib.d.ts +43 -0
- package/dist/emit/component.d.ts +144 -0
- package/dist/emit/dateLib.d.ts +45 -0
- package/dist/emit/emitApp.d.ts +38 -0
- package/dist/emit/idLib.d.ts +68 -0
- package/dist/emit/kits.d.ts +85 -0
- package/dist/emit/mediaLib.d.ts +75 -0
- package/dist/emit/naming.d.ts +76 -0
- package/dist/emit/parseLib.d.ts +106 -0
- package/dist/emit/preflight.d.ts +174 -0
- package/dist/emit/readme.d.ts +51 -0
- package/dist/emit/report.d.ts +309 -0
- package/dist/emit/scaffold.d.ts +67 -0
- package/dist/emit/statesLib.d.ts +52 -0
- package/dist/emit/style.d.ts +165 -0
- package/dist/emit/utilLib.d.ts +78 -0
- package/dist/errorMessage.d.ts +28 -0
- package/dist/index.cjs +52848 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.mjs +52769 -0
- package/dist/index.mjs.map +7 -0
- package/dist/ir/types.d.ts +567 -0
- package/dist/ledger.cjs +971 -0
- package/dist/ledger.cjs.map +7 -0
- package/dist/ledger.d.ts +75 -0
- package/dist/ledger.mjs +948 -0
- package/dist/ledger.mjs.map +7 -0
- package/dist/node-catalog.json +65333 -0
- package/dist/parse/kitSource.d.ts +135 -0
- package/dist/parse/parseModules.d.ts +42 -0
- package/dist/parse/parseProject.d.ts +13 -0
- package/dist/scriptFiles.d.ts +30 -0
- package/dist/structurePorts.d.ts +70 -0
- package/dist/write/writeExport.d.ts +67 -0
- package/package.json +53 -4
|
@@ -0,0 +1,3757 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component analysis — EXP-002 step 4's decision stage, extended by step 5. Parsing recorded
|
|
3
|
+
* what the project says; this walks each component's visual tree and decides what the generator
|
|
4
|
+
* will do about it: which nodes render (and as what), which collapse, which become stubs, and
|
|
5
|
+
* which defer to EXP-003. Dispositions are analysis output about the graph, never facts of it
|
|
6
|
+
* (IR design).
|
|
7
|
+
*
|
|
8
|
+
* Step 5 adds the app-state constructs (EXP-002-STEP5-TARGET-OUTPUT.md): Variables become the
|
|
9
|
+
* stores module, Send/Receive Event pairs become channels, and signal wires compile into
|
|
10
|
+
* handler *actions* — a navigate, a store `.set`, a channel `.emit` — attached to whichever
|
|
11
|
+
* handler owns the triggering signal (a rendered element's DOM event, or a receiver's
|
|
12
|
+
* `useSignal`). Value wires resolve by sink context: the same `Variable2.value` read is a
|
|
13
|
+
* `useValue` hook in rendered content and a `.get()` snapshot inside a handler.
|
|
14
|
+
*
|
|
15
|
+
* Step 6 adds statically-knowable logic (EXP-002-LOGIC-TARGET-OUTPUT.md): String Format and
|
|
16
|
+
* Condition resolve into *expression trees* over the same source vocabulary, rendered inline
|
|
17
|
+
* wherever their output lands — the `derived()` row of EXP-001's table compiled away, because
|
|
18
|
+
* the hooks a component already earns are the reactivity a Derived would provide. A Condition
|
|
19
|
+
* in a handler chain (`trigger → eval`, `ontrue → sink`) becomes a branch action — an `if`
|
|
20
|
+
* statement in the trigger's handler — and only when the author unticked Run On Value Change,
|
|
21
|
+
* because Evaluate is additive and an onClick cannot carry on-change firing.
|
|
22
|
+
*
|
|
23
|
+
* Everything here is pure decision-making over the IR; no text is generated. The emit layer
|
|
24
|
+
* (emit/component.ts) turns a ComponentPlan into TSX/CSS.
|
|
25
|
+
*/
|
|
26
|
+
import { CatalogIndex } from '../catalog';
|
|
27
|
+
import { ComponentIR, Disposition, ExportIR, KitNodeIR, ModuleIR, RefusedNode } from '../ir/types';
|
|
28
|
+
import { Reachability } from './reach';
|
|
29
|
+
import { ScaffoldPage } from '../emit/scaffold';
|
|
30
|
+
import { DateHelper } from '../emit/dateLib';
|
|
31
|
+
import { UtilHelper } from '../emit/utilLib';
|
|
32
|
+
import { IdHelper } from '../emit/idLib';
|
|
33
|
+
import { StyleRole } from '../emit/style';
|
|
34
|
+
import { AnimateEaseName } from '../emit/animateLib';
|
|
35
|
+
import { StatesValueType } from '../emit/statesLib';
|
|
36
|
+
import { ScriptUsePlan } from './scriptUses';
|
|
37
|
+
import { QueryParamsPlan } from './querySettings';
|
|
38
|
+
import { ChannelPlan, CollectionPlan, StorePlan, VariablePlan } from './appState';
|
|
39
|
+
export type { ChannelPlan, CollectionKeyPlan, CollectionPlan, StoreKeyPlan, StorePlan, VariablePlan } from './appState';
|
|
40
|
+
/**
|
|
41
|
+
* How a node participates in the render, or null for pure logic nodes.
|
|
42
|
+
*
|
|
43
|
+
* `'custom'` is EXP-010's: a visual node from a `noodl_modules` kit. It is its own role rather
|
|
44
|
+
* than a `StyleRole` because a kit node has no Noodl style ports at all — every parameter on one
|
|
45
|
+
* is a port the kit itself declared (measured across every project on this machine: not one custom
|
|
46
|
+
* node carries a `width`, a `margin` or a `backgroundColor`), so the style tables have nothing to
|
|
47
|
+
* say about it and `computeNodeStyle` must not be asked.
|
|
48
|
+
*/
|
|
49
|
+
export type RenderRole = StyleRole | 'instance' | 'repeater' | 'custom' | 'slot';
|
|
50
|
+
/**
|
|
51
|
+
* EXP-011 §51. The marker the runtime reads structurally rather than as a node: `nodescope.ts:217`
|
|
52
|
+
* creates nothing for it and, when it has a parent, makes that parent the instance's child root
|
|
53
|
+
* (`componentinstance.ts` `setChildRoot`). The placed children of an instance are inserted into
|
|
54
|
+
* that parent at the marker's index, in order, contiguously — React's `children`, rendered where
|
|
55
|
+
* the marker sits.
|
|
56
|
+
*/
|
|
57
|
+
export declare const CHILD_SLOT_TYPE = "Component Children";
|
|
58
|
+
/** Parent of every node, read from both `parent` fields and `children` arrays (two corpus files carry only the latter). */
|
|
59
|
+
export declare function parentMapOf(component: ComponentIR): Map<string, string>;
|
|
60
|
+
/**
|
|
61
|
+
* Which `Component Children` receives an instance's children, by the runtime's own two rules:
|
|
62
|
+
* `createNodeFromModel` runs per node in file order and every marker with a parent calls
|
|
63
|
+
* `setChildRoot(parent)`, so the LAST marker's parent wins; then `getChildRootIndex` inserts at the
|
|
64
|
+
* FIRST marker among that parent's children. A parentless marker sets nothing. Null when no marker
|
|
65
|
+
* has a parent — an instance's children are then never drawn.
|
|
66
|
+
*/
|
|
67
|
+
export declare function chooseChildSlot(component: ComponentIR): {
|
|
68
|
+
nodeId: string;
|
|
69
|
+
parentId: string;
|
|
70
|
+
} | null;
|
|
71
|
+
/** `Hash` (EXP-011 §59). A SHA-2 digest of a UTF-8 string through `crypto.subtle.digest` — asynchronous. */
|
|
72
|
+
export declare const HASH_TYPE = "net.noodl.Hash";
|
|
73
|
+
/** `Random Bytes` (EXP-011 §59). `crypto.getRandomValues` rendered as text — synchronous, never `Math.random()`. */
|
|
74
|
+
export declare const RANDOM_BYTES_TYPE = "net.noodl.RandomBytes";
|
|
75
|
+
/**
|
|
76
|
+
* `Pattern Extractor` (EXP-011 §76). A regular expression over a text — `Hash`'s shape (an action port, an
|
|
77
|
+
* outcome pair, a raise on failure) with SEVEN value outputs where Hash has one, and a third signal pair
|
|
78
|
+
* (`Found` / `Not Found`) the runtime fires BEFORE `Done` off the same Extract: two RESULTS, one outcome.
|
|
79
|
+
*/
|
|
80
|
+
export declare const PATTERN_EXTRACTOR_TYPE = "net.noodl.PatternExtractor";
|
|
81
|
+
/** The crypto family's members — `node` on {@link CRYPTO_NODES}, on `crypto-out` and on `crypto-call`. */
|
|
82
|
+
export type CryptoNodeKind = 'hash' | 'random-bytes' | 'pattern';
|
|
83
|
+
/**
|
|
84
|
+
* `Pattern Extractor`'s value outputs (EXP-011 §76), each a row of its own when read outside the chain —
|
|
85
|
+
* and, unlike the two crypto rows, each SEEDED: `initialize` writes every internal (`match: null`, `[]`,
|
|
86
|
+
* `{}`, `''`) and the getters answer them, so nothing here is ever undefined. `tsType` is what the getter
|
|
87
|
+
* publishes; `boot` / `bootCode` is what `initialize` leaves for the getter to read.
|
|
88
|
+
*/
|
|
89
|
+
export declare const PATTERN_VALUE_OUTPUTS: Record<string, {
|
|
90
|
+
label: string;
|
|
91
|
+
tsType: string;
|
|
92
|
+
boot: string | number;
|
|
93
|
+
bootCode?: string;
|
|
94
|
+
}>;
|
|
95
|
+
/**
|
|
96
|
+
* The two crypto verbs (EXP-011 §59), which are `UUID`'s shape (§37) and not a request's: read the
|
|
97
|
+
* inputs the setters stored, answer a value or a failure in the one call (`_run` / `_generate`),
|
|
98
|
+
* write the value row or leave it as it was, clear or write the Error, report the outcome. Two
|
|
99
|
+
* things differ from `UUID` and the export keeps both: **the value row boots empty** (neither node
|
|
100
|
+
* has an `initialize` that seeds it — `Digest` / `Value` read undefined until the first Do) and
|
|
101
|
+
* **a failure raises on the error channel** (`reportOutcome(…, 'failure', { code })` raises
|
|
102
|
+
* `hash/failed` / `random-bytes/failed` before the Failure pulse — `node.ts`). `Hash` awaits.
|
|
103
|
+
*
|
|
104
|
+
* `inputs` is the port order the emitted verb takes its arguments in; `numeric` names the one
|
|
105
|
+
* port whose setter is `Number(value)`, applied at the delivery site (a wire) and not in the lib.
|
|
106
|
+
*/
|
|
107
|
+
export declare const CRYPTO_NODES: Record<string, {
|
|
108
|
+
node: CryptoNodeKind;
|
|
109
|
+
/** The value output port — the ONE for the two verbs, the first of {@link PATTERN_VALUE_OUTPUTS} for the extractor. */
|
|
110
|
+
output: string;
|
|
111
|
+
/**
|
|
112
|
+
* EXP-011 §76. Every value output where a node has more than one, each typed and seeded; a read of any of
|
|
113
|
+
* them is a `crypto-out` carrying the port as `field`. Absent for the two verbs (their one output is `output`).
|
|
114
|
+
*/
|
|
115
|
+
values?: Record<string, {
|
|
116
|
+
label: string;
|
|
117
|
+
tsType: string;
|
|
118
|
+
boot: string | number;
|
|
119
|
+
bootCode?: string;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* EXP-011 §76. The RESULT pulses the node fires before `Done` — `Found` when the pattern matched at least
|
|
123
|
+
* once, `Not Found` when it ran and matched nothing (ERG-001 §4: a result, not an `Unchanged`). Two more
|
|
124
|
+
* chains compiled off one Extract, printed inside the Done arm ahead of the Done chain.
|
|
125
|
+
*/
|
|
126
|
+
results?: readonly ['found', 'notFound'];
|
|
127
|
+
/** The action port, and what the editor shows it as. */
|
|
128
|
+
trigger: string;
|
|
129
|
+
triggerLabel: string;
|
|
130
|
+
/** The value output's display name, for the sentence about a read nothing ever writes. */
|
|
131
|
+
outputLabel: string;
|
|
132
|
+
/** The lib verb, the raised code, whether the call is awaited. */
|
|
133
|
+
fn: 'tryHash' | 'tryRandomBytes' | 'tryExtract';
|
|
134
|
+
code: string;
|
|
135
|
+
async: boolean;
|
|
136
|
+
inputs: readonly string[];
|
|
137
|
+
numeric?: string;
|
|
138
|
+
/** What the node is called in a deferral sentence. */
|
|
139
|
+
label: string;
|
|
140
|
+
/** The sentence for a read while nothing fires the trigger. */
|
|
141
|
+
neverFired: string;
|
|
142
|
+
}>;
|
|
143
|
+
/** `Screen Resolution` (EXP-011 §59). The type id IS the display name. Three numbers off one `resize` listener. */
|
|
144
|
+
export declare const SCREEN_RESOLUTION_TYPE = "Screen Resolution";
|
|
145
|
+
export declare const SCREEN_RESOLUTION_OUTPUTS: readonly ["width", "height", "aspectRatio"];
|
|
146
|
+
/**
|
|
147
|
+
* `Drag` (EXP-011 §63). The type id IS the display name. A visual node that renders no element of its own — `Drag.tsx`
|
|
148
|
+
* wraps its FIRST child in `react-draggable` — so the export drags a wrapper `<div>` bound to a `useDrag` handle.
|
|
149
|
+
*/
|
|
150
|
+
export declare const DRAG_TYPE = "Drag";
|
|
151
|
+
/** The option ports in the order the hook prints them: the port, the `useDrag` option key, and the panel's label. */
|
|
152
|
+
export declare const DRAG_OPTION_PORTS: ReadonlyArray<{
|
|
153
|
+
port: string;
|
|
154
|
+
key: string;
|
|
155
|
+
label: string;
|
|
156
|
+
}>;
|
|
157
|
+
/** The four value outputs → the handle's live getters. */
|
|
158
|
+
export declare const DRAG_VALUE_OUTPUTS: Readonly<Record<string, 'x' | 'y' | 'deltaX' | 'deltaY'>>;
|
|
159
|
+
/** The three pulses → the hook's listeners, and the outcome trio the two snaps share (`outcomeOutputs`). */
|
|
160
|
+
export declare const DRAG_PULSE_OUTPUTS: Readonly<Record<string, {
|
|
161
|
+
listener: 'onStart' | 'onMove' | 'onEnd' | 'done' | 'failure' | 'completed';
|
|
162
|
+
label: string;
|
|
163
|
+
}>>;
|
|
164
|
+
/** The two action ports — `Do|Snap To Position X` and `Do|Snap To Position Y` — and the axis each snaps. */
|
|
165
|
+
export declare const DRAG_SNAP_TRIGGERS: Readonly<Record<string, 'x' | 'y'>>;
|
|
166
|
+
declare const TIMER_TRIGGERS: readonly ["start", "restart", "stop"];
|
|
167
|
+
type TimerTrigger = (typeof TIMER_TRIGGERS)[number];
|
|
168
|
+
/** `log.ts`'s `LEVELS`, in its order — an authored level outside it falls back to `info` as the setter does. */
|
|
169
|
+
declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
|
|
170
|
+
export type LogLevel = (typeof LOG_LEVELS)[number];
|
|
171
|
+
/**
|
|
172
|
+
* EXP-011 §61. The component-stack trio: the visual container (display name "Component Stack"), the pusher
|
|
173
|
+
* ("Push Component To Stack") and the popper ("Pop Component Stack"). A push is a call and a pop is its
|
|
174
|
+
* return — the pusher grows a `backResult-<k>` output per result and a `backAction-<a>` signal per way of
|
|
175
|
+
* closing that any Pop in the target declares (`navigate.ts` setup), and the Pop's `result-<k>` values and
|
|
176
|
+
* `backAction-<a>` signals are the other end of the same wire (`navigate-back.ts`).
|
|
177
|
+
*/
|
|
178
|
+
export declare const PAGE_STACK_TYPE = "Page Stack";
|
|
179
|
+
export declare const STACK_PUSH_TYPE = "PageStackNavigate";
|
|
180
|
+
export declare const STACK_POP_TYPE = "PageStackNavigateBack";
|
|
181
|
+
export declare const PAGE_STACK_OUTPUTS: readonly ["topPageName", "stackDepth"];
|
|
182
|
+
/** The reserved prop the stack row passes to the component it shows — the Pop's way back (§61.0). */
|
|
183
|
+
export declare const PAGE_STACK_ENTRY_PROP = "pageStackEntry";
|
|
184
|
+
/**
|
|
185
|
+
* EXP-011 §40. The outputs each node's **own compile** consumes when the node attaches — its
|
|
186
|
+
* Done/Failure chains and the sibling pulses it drops with a reason of its own.
|
|
187
|
+
*
|
|
188
|
+
* 🔴 **The attach pass must skip a wire off any of these, whatever order the file lists it in.**
|
|
189
|
+
* It walks connections in file order, and a chain wire listed *before* the wire that fires its
|
|
190
|
+
* node used to fall to *"the trigger is not a rendered element event or a receiver"* — reported
|
|
191
|
+
* dropped, marked consumed, and then emitted anyway when the node attached (`doneChainOf` filters
|
|
192
|
+
* by port, not by `consumed`). §39.3 found it on `Log`, `Delay` and `Value Changed` and measured
|
|
193
|
+
* the rest as unowned; §40.1 measured every family here order-dependent, and a node fired only
|
|
194
|
+
* from a reactive Condition or a Value Changed reported the same false note in *every* order,
|
|
195
|
+
* because that trigger wire is never the attach pass's to take.
|
|
196
|
+
*
|
|
197
|
+
* ⚠️ A port here must be one the node's compile answers for — consumed, deferred by name, or
|
|
198
|
+
* dropped with a reason — or the skip turns a false note into a silent one. Every entry below was
|
|
199
|
+
* read off the compile it names.
|
|
200
|
+
*/
|
|
201
|
+
/**
|
|
202
|
+
* EXP-011 §53 — `Run Tasks` (type id `RunTasks`; `Run Tasks` is its display name, and a match on the
|
|
203
|
+
* display name never fires — §5.7's `runtasks` branch was unreachable until this row spelled it right).
|
|
204
|
+
* The five signal outputs are the node's whole output set (`outcomeOutputs` + `aborted`); the four
|
|
205
|
+
* contract names are `runtasks-template-contract.ts`'s defaults, applied when the parameter is absent
|
|
206
|
+
* or empty (`resolveTemplateContract`: "an empty name falls back rather than being honoured").
|
|
207
|
+
*/
|
|
208
|
+
/** `On App Error` (EXP-011 §54) — the app-wide error boundary. The type id IS the display name. */
|
|
209
|
+
export declare const ON_APP_ERROR_TYPE = "On App Error";
|
|
210
|
+
/** The boundary's six value outputs — read off the `useAppError` handle's `last` (onapperror.ts's getters). */
|
|
211
|
+
export declare const ON_APP_ERROR_VALUE_OUTPUTS: readonly ["message", "code", "nodeId", "componentName", "nodeType", "errorObject"];
|
|
212
|
+
/** The Error Object's type, structurally — `src/lib/errors.ts`'s AppError, spelled so a store row needs no import. */
|
|
213
|
+
export declare const APP_ERROR_TS_TYPE = "{ code: string; message: string; nodeId: string; componentName: string; nodeType: string; detail?: unknown }";
|
|
214
|
+
/**
|
|
215
|
+
* EXP-011 §58 — the streaming trio (`agent/json-stream-parser.ts`, `stream-buffer.ts`, `text-accumulator.ts`).
|
|
216
|
+
* The type ids carry the `net.noodl.` prefix; the display names do not. Each is one hook over one `_internal`
|
|
217
|
+
* (`src/lib/streaming.ts`): the data port is read at the pulse, the config ports live off an options object, the
|
|
218
|
+
* signal outputs are listeners, the value outputs are live getters on the handle. The table is the catalog's
|
|
219
|
+
* port set, read off the node files — every port of the three translates, so what is refused is only the graph
|
|
220
|
+
* shapes every hook refuses (two wires, no static source, a pulse read as a value, an uncompilable chain).
|
|
221
|
+
*/
|
|
222
|
+
export declare const STREAM_PARSER_TYPE = "net.noodl.JSONStreamParser";
|
|
223
|
+
export declare const STREAM_BUFFER_TYPE = "net.noodl.StreamBuffer";
|
|
224
|
+
export declare const TEXT_ACCUMULATOR_TYPE = "net.noodl.TextAccumulator";
|
|
225
|
+
export type StreamKind = 'parser' | 'buffer' | 'accumulator' | 'sse' | 'websocket' | 'subscription' | 'repeat' | 'dispatcher' | 'handler' | 'history' | 'undo' | 'snapshot' | 'optimistic' | 'signin';
|
|
226
|
+
/** EXP-011 §64. `Server-Sent Events` — the fourth member of the table: no data port, two Actions, its own module. */
|
|
227
|
+
export declare const SSE_TYPE = "net.noodl.SSE";
|
|
228
|
+
/** EXP-011 §65. `WebSocket` — the fifth member: a data port (Message) that only the Send verb carries, three Actions, its own module. */
|
|
229
|
+
export declare const WEBSOCKET_TYPE = "net.noodl.WebSocket";
|
|
230
|
+
/**
|
|
231
|
+
* EXP-011 §66. `Subscribe To Changes` — the sixth member, and the last scheduled node: no data port, no Actions, two config
|
|
232
|
+
* ports (the Class — a runtime-discovered `collectionName` parameter, hence `param` — and Enabled), five signals, seven values;
|
|
233
|
+
* its own module, riding EXP-009's client. The Backend picker, the Filter and its `qp-` ports are refused by name (streamPlanOf).
|
|
234
|
+
*/
|
|
235
|
+
export declare const SUBSCRIBE_TO_CHANGES_TYPE = "SubscribeToChanges";
|
|
236
|
+
/**
|
|
237
|
+
* GAM-013 (P88). `Repeat` — the seventh member: no data port, one config port (Interval), two Actions (Start, Stop), Tick
|
|
238
|
+
* and the outcome trio as listeners, Count a live getter; its own module, `src/lib/repeat.ts`. The table fits it exactly,
|
|
239
|
+
* where Delay's per-verb callbacks (§39) could not carry a node whose Tick belongs to the node and whose Count is a value.
|
|
240
|
+
*/
|
|
241
|
+
export declare const REPEAT_TYPE = "Repeat";
|
|
242
|
+
/**
|
|
243
|
+
* EXP-011 §77. `Action Dispatcher` and `Action Handler` — the eighth and ninth members, one module (`src/lib/actions.ts`)
|
|
244
|
+
* holding the app's one registry, so the two meet across components by channel name alone, as in the runtime.
|
|
245
|
+
*/
|
|
246
|
+
export declare const ACTION_DISPATCHER_TYPE = "net.noodl.ActionDispatcher";
|
|
247
|
+
export declare const ACTION_HANDLER_TYPE = "net.noodl.ActionHandler";
|
|
248
|
+
/** The four built-in store actions (action-dispatcher.ts BUILT_IN_ACTIONS) — a dispatcher that enables one is refused in this slice. */
|
|
249
|
+
export declare const ACTION_BUILT_INS: readonly ["SET_STORE", "MERGE_STORE", "DELETE_STORE_KEY", "CLEAR_STORE"];
|
|
250
|
+
/**
|
|
251
|
+
* EXP-011 §78. `State History`, `Undo / Redo` and `State Snapshot` — the tenth, eleventh and twelfth members, one module
|
|
252
|
+
* (`src/lib/history.ts`) holding the app's one history manager and its named checkpoints over the exported Global Store.
|
|
253
|
+
* Each names its store by a literal Store Name, printed as the store module itself (`store: app`), so the history reads
|
|
254
|
+
* and writes the same `store()` the app's Set and Subscribe nodes do.
|
|
255
|
+
*/
|
|
256
|
+
export declare const STATE_HISTORY_TYPE = "net.noodl.StateHistory";
|
|
257
|
+
export declare const UNDO_REDO_TYPE = "net.noodl.StateHistory.Undo";
|
|
258
|
+
export declare const STATE_SNAPSHOT_TYPE = "net.noodl.StateSnapshot";
|
|
259
|
+
/**
|
|
260
|
+
* EXP-011 §78 part 2. `Optimistic Update` — the thirteenth member, in the same module: a patch on the exported Global Store
|
|
261
|
+
* (apply now, commit or roll back later, overlapping patches handing values to each other) is one write through the
|
|
262
|
+
* history's store seam, so the history records it and every Subscribe sees it.
|
|
263
|
+
*/
|
|
264
|
+
export declare const OPTIMISTIC_UPDATE_TYPE = "net.noodl.OptimisticUpdate";
|
|
265
|
+
/**
|
|
266
|
+
* EXP-011 §79. `Sign In With` — the fourteenth member, its own module (`src/lib/signIn.ts`): a launcher (Do hands the browser
|
|
267
|
+
* over to the backend's `/oauth/<provider>/start`) and a receiver (at mount it hears the sign-in coming back, which the
|
|
268
|
+
* client consumes when the app starts). Rides EXP-009's client, so it is kept only where the project declares a backend.
|
|
269
|
+
*/
|
|
270
|
+
export declare const SIGN_IN_WITH_TYPE = "net.noodl.user.SignInWith";
|
|
271
|
+
/** EXP-011 §66. The structural RealtimeError — `src/lib/realtime.ts`'s, spelled so a store row needs no import. */
|
|
272
|
+
export declare const REALTIME_ERROR_TS_TYPE = "{ message: string; code: string; kind: string } | null";
|
|
273
|
+
export interface StreamValueField {
|
|
274
|
+
tsType: string;
|
|
275
|
+
/** How the value casts at a sink: the port's declared type, which is what NDA-014's typecast keys on. */
|
|
276
|
+
cast: 'string' | 'number' | 'boolean' | 'array' | 'unknown';
|
|
277
|
+
/** Undefined before anything happened (must agree with component.ts). */
|
|
278
|
+
maybeUndefined: boolean;
|
|
279
|
+
}
|
|
280
|
+
export interface StreamNodeSpec {
|
|
281
|
+
kind: StreamKind;
|
|
282
|
+
displayName: string;
|
|
283
|
+
hook: string;
|
|
284
|
+
localStem: string;
|
|
285
|
+
/** The module the hook lives in (component.ts maps it to a path; emitApp.ts ships it). */
|
|
286
|
+
lib: 'streaming' | 'sse' | 'websocket' | 'realtime' | 'repeat' | 'actions' | 'history' | 'signIn';
|
|
287
|
+
/** The runtime file the hook transcribes, for the emitted comment. */
|
|
288
|
+
sourceFile: string;
|
|
289
|
+
/** The data port, delivered with the pulse — absent on a node whose actions carry nothing (§64's SSE). */
|
|
290
|
+
data?: {
|
|
291
|
+
port: string;
|
|
292
|
+
displayName: string;
|
|
293
|
+
};
|
|
294
|
+
/** EXP-011 §78. The input naming the store module the hook is handed (StreamPlan.store) — never an option. */
|
|
295
|
+
store?: {
|
|
296
|
+
port: string;
|
|
297
|
+
displayName: string;
|
|
298
|
+
};
|
|
299
|
+
/** The config ports, read live off the options object. `param` names the on-disk parameter / wire port where it differs from the option (§66's Class is `collectionName` on disk). */
|
|
300
|
+
config: Array<{
|
|
301
|
+
port: string;
|
|
302
|
+
displayName: string;
|
|
303
|
+
param?: string;
|
|
304
|
+
}>;
|
|
305
|
+
/** The action ports: the verb on the handle, and whether the call carries the data port's value. */
|
|
306
|
+
actions: Record<string, {
|
|
307
|
+
verb: string;
|
|
308
|
+
takesData: boolean;
|
|
309
|
+
}>;
|
|
310
|
+
/** The signal outputs, as the listeners the hook takes, in the runtime's declaration order. */
|
|
311
|
+
signals: readonly string[];
|
|
312
|
+
values: Record<string, StreamValueField>;
|
|
313
|
+
}
|
|
314
|
+
export declare const STREAM_NODES: Record<string, StreamNodeSpec>;
|
|
315
|
+
/** EXP-011 §64. The type id behind a `stream-out`'s kind — the one ladder component.ts and the maybe-undefined answer share. */
|
|
316
|
+
export declare function streamTypeOfKind(kind: StreamKind): string;
|
|
317
|
+
export declare const RUN_TASKS_TYPE = "RunTasks";
|
|
318
|
+
/** EXP-011 §57. `Repeater Item` — the type id is the runtime's `name`, not the display name. */
|
|
319
|
+
export declare const REPEATER_ITEM_TYPE = "For Each Actions";
|
|
320
|
+
/** EXP-011 §60. The component-object trio — the Set, and the parent pair that read/write an ancestor's record. */
|
|
321
|
+
export declare const SET_COMPONENT_OBJECT_TYPE = "net.noodl.SetComponentObjectProperties";
|
|
322
|
+
export declare const PARENT_COMPONENT_OBJECT_TYPE = "net.noodl.ParentComponentObject";
|
|
323
|
+
export declare const SET_PARENT_COMPONENT_OBJECT_TYPE = "net.noodl.SetParentComponentObjectProperties";
|
|
324
|
+
/** EXP-011 §60. The message setparentcomponentobjectproperties.ts raises on a miss — verbatim (the lib exports the same constant). */
|
|
325
|
+
export declare const NO_ANCESTOR_WRITE_MESSAGE = "No ancestor component has a Component Object node \u2014 nothing was written";
|
|
326
|
+
export declare const RUN_TASKS_OUTPUTS: readonly string[];
|
|
327
|
+
export declare const RUN_TASKS_CONTRACT_TEMPLATE_PARAM = "taskTemplate";
|
|
328
|
+
export declare const RUN_TASKS_CONTRACT: {
|
|
329
|
+
readonly start: {
|
|
330
|
+
readonly parameter: "taskStartInput";
|
|
331
|
+
readonly default: "Do";
|
|
332
|
+
};
|
|
333
|
+
readonly success: {
|
|
334
|
+
readonly parameter: "taskSuccessOutput";
|
|
335
|
+
readonly default: "Success";
|
|
336
|
+
};
|
|
337
|
+
readonly failure: {
|
|
338
|
+
readonly parameter: "taskFailureOutput";
|
|
339
|
+
readonly default: "Failure";
|
|
340
|
+
};
|
|
341
|
+
readonly error: {
|
|
342
|
+
readonly parameter: "taskErrorOutput";
|
|
343
|
+
readonly default: "Error";
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
/** EXP-011 §62. The api-call / MutationPlan verb the pair compiles to. */
|
|
347
|
+
export type RelationVerb = 'add-relation' | 'remove-relation';
|
|
348
|
+
export type UserVerb = 'login' | 'logout' | 'signup' | 'update-user' | 'magic-link';
|
|
349
|
+
/** One refused wire, as EXP-004's marker needs to talk about it. */
|
|
350
|
+
export interface DroppedWire {
|
|
351
|
+
key: string;
|
|
352
|
+
fromId: string;
|
|
353
|
+
fromProperty: string;
|
|
354
|
+
toId: string;
|
|
355
|
+
toProperty: string;
|
|
356
|
+
/** The author's own label on the wire — irrecoverable once the wire is gone, so it travels. */
|
|
357
|
+
label?: string;
|
|
358
|
+
/** The exporter's own reason, in the words the note carries. */
|
|
359
|
+
reason: string;
|
|
360
|
+
}
|
|
361
|
+
export type BindingSource = {
|
|
362
|
+
kind: 'prop';
|
|
363
|
+
name: string;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* A Variable read. `untyped` marks one whose writers this analysis could not type — the
|
|
367
|
+
* emitted `value<unknown>` (EXP-011 §10). It is not a defect and not a deferral: the render
|
|
368
|
+
* sinks coerce it, exactly as the runtime's own sinks coerce whatever the variable holds.
|
|
369
|
+
*/
|
|
370
|
+
| {
|
|
371
|
+
kind: 'store';
|
|
372
|
+
variableName: string;
|
|
373
|
+
untyped?: true;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* A single-key Global Store read. `untyped` is the Variable rule above, one construct over
|
|
377
|
+
* (EXP-011 §10.5): a key whose value this analysis could not type as `string`/`number` — a
|
|
378
|
+
* `boolean`, or an `unknown` written from an HTTP body or a Function output — binds and is
|
|
379
|
+
* coerced at the sink, rather than dropping the read the way it did before.
|
|
380
|
+
*
|
|
381
|
+
* 🔴 The gate is widened **here only**. `resolveExpr` still refuses the same key, because an
|
|
382
|
+
* expression position is arithmetic, a date argument or a url segment, and none of those has
|
|
383
|
+
* a sink that can state what it holds. §10.5 named that as the reason not to lift the gate
|
|
384
|
+
* wholesale, and it is still the reason.
|
|
385
|
+
*/
|
|
386
|
+
| {
|
|
387
|
+
kind: 'store-key';
|
|
388
|
+
storeName: string;
|
|
389
|
+
key: string;
|
|
390
|
+
untyped?: true;
|
|
391
|
+
} | {
|
|
392
|
+
kind: 'computed';
|
|
393
|
+
expr: ValueExpr;
|
|
394
|
+
} | {
|
|
395
|
+
kind: 'unresolved';
|
|
396
|
+
fromId: string;
|
|
397
|
+
fromProperty: string;
|
|
398
|
+
};
|
|
399
|
+
/**
|
|
400
|
+
* A value read inside a handler or binding, resolved to what the generated code can actually
|
|
401
|
+
* say. `input-text` and `payload` are context-bound: they only exist inside the owning input's
|
|
402
|
+
* onChange and the owning receiver's handler respectively — attachment validates that.
|
|
403
|
+
*
|
|
404
|
+
* Step 6's additions compose: `format` is a String Format resolved to alternating static text
|
|
405
|
+
* and sub-expressions (a template literal at emit), and `store-key-get` is a single-key
|
|
406
|
+
* Subscribe read usable in either context (the selector hook local in render, `.get().<key>`
|
|
407
|
+
* in a handler).
|
|
408
|
+
*
|
|
409
|
+
* The boolean kinds (`logical`, `not`, `truthy` — LOGIC-TARGET §6) are truthiness devices:
|
|
410
|
+
* their short forms (`a && b`, `!x`, the bare operand) are truthiness-equal to the runtime's
|
|
411
|
+
* strict-boolean outputs, not value-equal, so analysis admits them into truthiness sinks only
|
|
412
|
+
* (a Condition's test, another logical's operand, the `enabled` render sink). `truthy` marks a
|
|
413
|
+
* Condition's `result` — provenance the value-sink gates need, emitted as the bare condition.
|
|
414
|
+
*
|
|
415
|
+
* `undefined` is a Component Object property's boot value (COMPONENT-OBJECT-TARGET §3): the
|
|
416
|
+
* record boots empty and no wire writes the key, so the read is the constant the runtime
|
|
417
|
+
* would deliver. It is maybe-undefined by definition and folds at its sinks the way the
|
|
418
|
+
* runtime folds it — format parts to '', truthiness to false, render children/attrs to the
|
|
419
|
+
* empty/omitted form.
|
|
420
|
+
*/
|
|
421
|
+
/**
|
|
422
|
+
* EXP-011 §56. The neutral operators a `Filter Records` condition may carry and still translate — the ones
|
|
423
|
+
* `queryutils.ts`'s local matcher answers from a loaded record alone, after `toParseWhere`'s lowering. Everything
|
|
424
|
+
* schema-bound (`pointsTo`, `relatedTo`, `textSearch`, the id and geo operators) is refused by name in
|
|
425
|
+
* `recordFilterReadOf`, because the runtime resolves those through the backend or not at all.
|
|
426
|
+
*/
|
|
427
|
+
export type RecordFilterOp = 'equalTo' | 'notEqualTo' | 'lessThan' | 'greaterThan' | 'lessThanOrEqualTo' | 'greaterThanOrEqualTo' | 'containedIn' | 'notContainedIn' | 'exists' | 'matchesRegex' | 'contains' | 'notContains' | 'containsIgnoreCase' | 'startsWith' | 'notStartsWith' | 'startsWithIgnoreCase' | 'endsWith' | 'notEndsWith' | 'endsWithIgnoreCase' | 'between' | 'notBetween' | 'isEmpty' | 'isNotEmpty';
|
|
428
|
+
/**
|
|
429
|
+
* EXP-011 §56. A `Filter Records` filter tree as the emitted app evaluates it — the builder's saved shape (either
|
|
430
|
+
* generation) read the way `savedFilterToNeutral` / `visualQueryToNeutral` read it, with every value either a
|
|
431
|
+
* static JSON value or the expression wired into the condition's `fp-<name>` port. `connected` carries the runtime's
|
|
432
|
+
* `dropUnresolvedConnected` rule to the client: a connected condition whose value is `undefined` does not narrow.
|
|
433
|
+
*/
|
|
434
|
+
export type RecordWhere = {
|
|
435
|
+
and: RecordWhere[];
|
|
436
|
+
} | {
|
|
437
|
+
or: RecordWhere[];
|
|
438
|
+
} | {
|
|
439
|
+
field: string;
|
|
440
|
+
op: RecordFilterOp;
|
|
441
|
+
value: {
|
|
442
|
+
kind: 'json';
|
|
443
|
+
value: unknown;
|
|
444
|
+
} | {
|
|
445
|
+
kind: 'expr';
|
|
446
|
+
expr: ValueExpr;
|
|
447
|
+
};
|
|
448
|
+
connected: boolean;
|
|
449
|
+
};
|
|
450
|
+
export type ValueExpr = {
|
|
451
|
+
kind: 'prop';
|
|
452
|
+
name: string;
|
|
453
|
+
} | {
|
|
454
|
+
kind: 'input-text';
|
|
455
|
+
inputId: string;
|
|
456
|
+
} | {
|
|
457
|
+
kind: 'store-get';
|
|
458
|
+
variableName: string;
|
|
459
|
+
} | {
|
|
460
|
+
kind: 'store-key-get';
|
|
461
|
+
storeName: string;
|
|
462
|
+
key: string;
|
|
463
|
+
} | {
|
|
464
|
+
kind: 'payload';
|
|
465
|
+
key: string;
|
|
466
|
+
receiverId: string;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* A `Page Inputs` parameter (EXP-011 Tier 2.5) — one `pm-*` output, read from the url.
|
|
470
|
+
*
|
|
471
|
+
* 🔴 **One expression for both stringlists, because the runtime has one namespace.** The node
|
|
472
|
+
* declares `pathParams` and `queryParams` separately and the editor draws a port per name, but
|
|
473
|
+
* the Router hands the node a single flat map built as
|
|
474
|
+
* `Object.assign({}, match.params, urlQuery)` (`router.tsx:456`) — so a name in both lists is
|
|
475
|
+
* one port, and **every** query parameter in the url overrides the path segment of the same
|
|
476
|
+
* name, whether or not the author listed it. `?id=99` on `/product/42` reads `99`.
|
|
477
|
+
*
|
|
478
|
+
* That is why this carries no `from: 'path' | 'query'` discriminator: a read that consulted
|
|
479
|
+
* only the list the name was declared in would be a different function from the one the
|
|
480
|
+
* running app computes, and would differ exactly on the urls a user can type by hand.
|
|
481
|
+
*
|
|
482
|
+
* Maybe-undefined by definition — an unmatched segment or an absent query key — and it folds
|
|
483
|
+
* at its sinks the way the runtime's own Text node folds it ("an empty value renders nothing
|
|
484
|
+
* rather than the words null or undefined", text.ts).
|
|
485
|
+
*/
|
|
486
|
+
| {
|
|
487
|
+
kind: 'page-param';
|
|
488
|
+
name: string;
|
|
489
|
+
} | {
|
|
490
|
+
kind: 'literal';
|
|
491
|
+
value: string | number | boolean;
|
|
492
|
+
} | {
|
|
493
|
+
kind: 'format';
|
|
494
|
+
parts: Array<string | ValueExpr>;
|
|
495
|
+
} | {
|
|
496
|
+
kind: 'logical';
|
|
497
|
+
op: 'and' | 'or';
|
|
498
|
+
operands: ValueExpr[];
|
|
499
|
+
} | {
|
|
500
|
+
kind: 'not';
|
|
501
|
+
operand: ValueExpr;
|
|
502
|
+
} | {
|
|
503
|
+
kind: 'truthy';
|
|
504
|
+
operand: ValueExpr;
|
|
505
|
+
} | {
|
|
506
|
+
kind: 'undefined';
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* A re-hosted Function/Expression output (EXP-003 §4): in render it reads the node's render
|
|
510
|
+
* local (`formatShoutOut.text`); in a handler it inlines the call over `.get()` snapshots —
|
|
511
|
+
* legal because the gate admits only pure bodies, so recomputation is unobservable. `fold`
|
|
512
|
+
* carries Expression's typed getters (`asString` → `String(x ?? '')`, `asNumber` →
|
|
513
|
+
* `Number(x) || 0`, `asBoolean` → `!!x` — expression.ts, verbatim semantics). `viaState` is
|
|
514
|
+
* the 4f landing zone (CONTROLLED-STATE-TARGET): an *invoked* node whose outputs feed render
|
|
515
|
+
* sinks materializes its output record as a state var, and render reads go through it
|
|
516
|
+
* (`stockCheckOut?.warning`) — maybe-undefined until the first invocation, the runtime's own
|
|
517
|
+
* pre-first-run contract.
|
|
518
|
+
*/
|
|
519
|
+
| {
|
|
520
|
+
kind: 'jsfun-out';
|
|
521
|
+
nodeId: string;
|
|
522
|
+
output: string;
|
|
523
|
+
fold?: 'string' | 'number' | 'boolean';
|
|
524
|
+
viaState?: string;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* A state var read (CONTROLLED-STATE-TARGET §3.2): the render closure's value in both render
|
|
528
|
+
* and handler positions. Inside a handler chain the attachment pass applies the chain-local
|
|
529
|
+
* snapshot rule — a read after a `state-set` in the same chain is rewritten to the written
|
|
530
|
+
* expression, because the runtime updates state synchronously mid-chain and React closures
|
|
531
|
+
* do not.
|
|
532
|
+
*/
|
|
533
|
+
| {
|
|
534
|
+
kind: 'state-get';
|
|
535
|
+
name: string;
|
|
536
|
+
maybeUndefined?: boolean;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* EXP-011 §100. A repeater's relayed row value (`itemOutput-<output>`) read inside a chain one of its rows' relayed
|
|
540
|
+
* signals runs: the firing row's own value, which the runtime copies onto the repeater before it sends the signal
|
|
541
|
+
* (`foreach.tsx` `itemOutputSignalTriggered`). Minted only while such a chain is compiled (`rowChainScopes`), valid only
|
|
542
|
+
* in that repeater's row callbacks, and printed there as what the row is handed for `input` (the row's input the output
|
|
543
|
+
* passes straight through). `tsType` is the row template's declared output type.
|
|
544
|
+
*/
|
|
545
|
+
| {
|
|
546
|
+
kind: 'row-relay';
|
|
547
|
+
repeaterId: string;
|
|
548
|
+
output: string;
|
|
549
|
+
input: string;
|
|
550
|
+
tsType: string;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* The user-path value inside a control's own onChange (CONTROLLED-STATE-TARGET §4c) — the
|
|
554
|
+
* `input-text` context rule generalized per control role: `event.target.checked` for a
|
|
555
|
+
* checkbox, `Number(event.target.value)` for a range, `event.target.value` for a dropdown.
|
|
556
|
+
*/
|
|
557
|
+
| {
|
|
558
|
+
kind: 'control-event';
|
|
559
|
+
controlId: string;
|
|
560
|
+
form: 'string' | 'checked' | 'number';
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* A read of the signed-in user (USER-FAMILY-TARGET §4c) — the `User` node's outputs, which are
|
|
564
|
+
* getters over the session rather than stored values. `authenticated` is
|
|
565
|
+
* `model !== undefined` in the runtime, so it is a real boolean and lands at value sinks as
|
|
566
|
+
* well as truthiness ones; the other three are `model.get(…)` and read undefined while nobody
|
|
567
|
+
* is signed in, which is exactly the state the stub reports.
|
|
568
|
+
*/
|
|
569
|
+
| {
|
|
570
|
+
kind: 'session-get';
|
|
571
|
+
nodeId: string;
|
|
572
|
+
field: 'authenticated' | 'id' | 'username' | 'email';
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* A named client-side array, read as a list (EXP-011 Tier 1.1).
|
|
576
|
+
*
|
|
577
|
+
* The collections slice could reach a named array only as a repeater's feed, through
|
|
578
|
+
* {@link RepeaterPlan.itemsCollectionName} — a dedicated field, not an expression. That was
|
|
579
|
+
* enough while `For Each` was the only consumer; `Array Filter` and `Array Map` are a second
|
|
580
|
+
* and third, and they compose (`notes → filter → map → For Each`), which a per-consumer field
|
|
581
|
+
* cannot express. So the array becomes an ordinary value expression and the transforms are
|
|
582
|
+
* ordinary functions of it.
|
|
583
|
+
*
|
|
584
|
+
* In render it is the `useCollection` local, so the component re-renders when the array
|
|
585
|
+
* changes; in a handler it is `.peek()` — a read that deliberately creates no dependency edge,
|
|
586
|
+
* which is what a handler wants and what `Collection.get()` would get wrong.
|
|
587
|
+
*/
|
|
588
|
+
| {
|
|
589
|
+
kind: 'collection-get';
|
|
590
|
+
collectionName: string;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* FLD-015. A `Static Data` node's `items`, read as a value rather than fed to a repeater —
|
|
594
|
+
* the hoisted module constant by name.
|
|
595
|
+
*
|
|
596
|
+
* 🔴 **Before this existed, `items` had exactly one destination: a `For Each`.** The wire was
|
|
597
|
+
* matched by target (`toNode.type === 'For Each'`), so an array going anywhere else — a chart
|
|
598
|
+
* kit's `series`, any custom node with an `array` port — reached no branch at all and left as
|
|
599
|
+
* *"has no deterministic translation"*. The rows were already hoisted, already typed and
|
|
600
|
+
* already sitting in the emitted file; nothing could say their name.
|
|
601
|
+
*
|
|
602
|
+
* The constant is module scope and frozen at build time, so this reads in every context and is
|
|
603
|
+
* never undefined — `collection-get`'s answer, for a stronger reason: there is no hook and no
|
|
604
|
+
* boot value, the array IS the literal.
|
|
605
|
+
*/
|
|
606
|
+
| {
|
|
607
|
+
kind: 'static-rows';
|
|
608
|
+
nodeId: string;
|
|
609
|
+
constName: string;
|
|
610
|
+
rowType: string;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* EXP-011 §55. A `Create New Array`'s array, read through an `Array` node bound to it **by wire** — the
|
|
614
|
+
* handle in the minting component's state row. In render it is the `useCollection` hook local over
|
|
615
|
+
* `<row> ?? noArray`; in a handler `(<row>?.peek() ?? [])`; inside the mint's own Done chain the
|
|
616
|
+
* chain-local (`viaLocal`) — `setX` does not change `x` in that closure, and the chain must see the array
|
|
617
|
+
* it just made (the id nodes' Done-arm rule, §37). Type `any[]`, exactly as `collection-get` answers.
|
|
618
|
+
*/
|
|
619
|
+
| {
|
|
620
|
+
kind: 'minted-array-get';
|
|
621
|
+
nodeId: string;
|
|
622
|
+
viaLocal?: string;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* `Array Map` over a list (EXP-011 Tier 1.1) — `mapcollectionnode.ts`'s `map({…})` with every
|
|
626
|
+
* mapping a **property name**, which is `m.set(key, model.get(mapping))` in the runtime and
|
|
627
|
+
* `{ key: row.mapping }` here.
|
|
628
|
+
*
|
|
629
|
+
* ⚠️ A mapping whose value is a *function* is arbitrary JavaScript over a live `Model` and
|
|
630
|
+
* defers to EXP-003 — {@link parseIdentityMapping} answers null for it, which is the same
|
|
631
|
+
* gate `For Each`'s own mapping script goes through.
|
|
632
|
+
*/
|
|
633
|
+
| {
|
|
634
|
+
kind: 'list-map';
|
|
635
|
+
source: ValueExpr;
|
|
636
|
+
entries: Array<{
|
|
637
|
+
key: string;
|
|
638
|
+
field: string;
|
|
639
|
+
}>;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* `Array Filter` over a list (EXP-011 Tier 1.1) — the panel-authored filter, sort and
|
|
643
|
+
* skip/limit, which are `filterSettings` and not a script (`filtercollectionnode.ts`).
|
|
644
|
+
*
|
|
645
|
+
* Each piece is optional and they compose in the runtime's own order: filter, then sort, then
|
|
646
|
+
* skip, then limit (`scheduleFilter`). Emitting them in any other order would silently change
|
|
647
|
+
* which rows survive a limit.
|
|
648
|
+
*/
|
|
649
|
+
| {
|
|
650
|
+
kind: 'list-filter';
|
|
651
|
+
source: ValueExpr;
|
|
652
|
+
tests: Array<{
|
|
653
|
+
field: string;
|
|
654
|
+
op: 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte';
|
|
655
|
+
value: string | number | boolean;
|
|
656
|
+
}>;
|
|
657
|
+
sort: Array<{
|
|
658
|
+
field: string;
|
|
659
|
+
direction: 'ascending' | 'descending';
|
|
660
|
+
}>;
|
|
661
|
+
skip?: number;
|
|
662
|
+
limit?: number;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* EXP-011 §56. A `Query Records`' result, read as a list: the state row its fetch effect fills (`QueryPlan`),
|
|
666
|
+
* typed `<TypeName>[]` off the project's declared collection — the same row the repeater branch reads directly.
|
|
667
|
+
*/
|
|
668
|
+
| {
|
|
669
|
+
kind: 'query-get';
|
|
670
|
+
nodeId: string;
|
|
671
|
+
stateName: string;
|
|
672
|
+
typeName: string;
|
|
673
|
+
collectionName: string;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* EXP-011 §56. `Filter Records` over a list — the saved filter tree, the visual sorting and skip/limit, applied
|
|
677
|
+
* in `scheduleFilter`'s order by `src/lib/filterRecords.ts` (the runtime's local matcher, transcribed). Unlike
|
|
678
|
+
* `list-filter` the rows keep their static type: a filter selects, it does not reshape.
|
|
679
|
+
*/
|
|
680
|
+
| {
|
|
681
|
+
kind: 'record-filter';
|
|
682
|
+
source: ValueExpr;
|
|
683
|
+
where: RecordWhere | null;
|
|
684
|
+
sort: Array<{
|
|
685
|
+
field: string;
|
|
686
|
+
direction: 'ascending' | 'descending';
|
|
687
|
+
}>;
|
|
688
|
+
skip?: number;
|
|
689
|
+
limit?: number;
|
|
690
|
+
}
|
|
691
|
+
/** EXP-011 §56. A list transform's `Count` — the derived list's length, always current. */
|
|
692
|
+
| {
|
|
693
|
+
kind: 'list-count';
|
|
694
|
+
source: ValueExpr;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* An `HTTP Request` output (EXP-011 Tier 1.2) — the answer the last request produced.
|
|
698
|
+
*
|
|
699
|
+
* Two forms, and which one a read takes is decided by **where the read is**, not by the port:
|
|
700
|
+
*
|
|
701
|
+
* - Inside the node's own outcome chain, `viaState` is absent and the read is the chain's
|
|
702
|
+
* local (`quoteAnswer.response`, `quoteMessage`). It has to be: the setter has been called
|
|
703
|
+
* but React state does not change inside the closure that called it, so a state read here
|
|
704
|
+
* would deliver the *previous* request's answer — the chain-local snapshot rule
|
|
705
|
+
* (CONTROLLED-STATE-TARGET §3.2) reaching a construct that cannot recompute itself.
|
|
706
|
+
* - Anywhere else — render, another handler — `viaState` names the state row the call writes,
|
|
707
|
+
* maybe-undefined until the first request, which is the runtime's own pre-first-fetch state.
|
|
708
|
+
*
|
|
709
|
+
* `output` is a runtime port name: `response`, `statusCode`, `responseHeaders`, `error`, or
|
|
710
|
+
* an author's Response Mapping as `out-<name>` (`registerOutputIfNeeded` prefixes every one of
|
|
711
|
+
* them, so a mapping an author calls "response" is `out-response` and cannot collide).
|
|
712
|
+
*/
|
|
713
|
+
| {
|
|
714
|
+
kind: 'http-out';
|
|
715
|
+
nodeId: string;
|
|
716
|
+
output: string;
|
|
717
|
+
viaState?: string;
|
|
718
|
+
}
|
|
719
|
+
/** EXP-011 §41 — a `Cloud Function`'s `Error` or an `out-<result>`, the HTTP shape one node over. */
|
|
720
|
+
| {
|
|
721
|
+
kind: 'cloud-out';
|
|
722
|
+
nodeId: string;
|
|
723
|
+
output: string;
|
|
724
|
+
viaState?: string;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* EXP-011 §43 — a `Record`'s `Error` or a `prop-<column>`. `tsType` is the column's declared
|
|
728
|
+
* type off the project's schema snapshot (`tsColumnType`), `unknown` for a column the snapshot
|
|
729
|
+
* does not carry — the sink decides what an `unknown` needs, as it does for an untyped Variable.
|
|
730
|
+
*/
|
|
731
|
+
| {
|
|
732
|
+
kind: 'record-out';
|
|
733
|
+
nodeId: string;
|
|
734
|
+
output: string;
|
|
735
|
+
tsType: string;
|
|
736
|
+
viaState?: string;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* EXP-011 §45 — a files node's answer, the Record shape with three families behind one kind:
|
|
740
|
+
* `pick` reads the chosen `File` (`file` is the object itself; `name`/`size`/`type` its
|
|
741
|
+
* members), `upload` reads the stored file (`cloudFile` the object; `url`/`name`/`contentType`
|
|
742
|
+
* /`size` its members — and `name` is `cloudFileName(...)`, the prefix stripped as the Cloud
|
|
743
|
+
* File node strips it), `sign` reads the minted link (`url`/`kind`/`isShareable`/`expiresAt`/
|
|
744
|
+
* `ttlSeconds`). `error` is the message in every family. A `Cloud File` node resolves to the
|
|
745
|
+
* `upload` family of the Upload File that feeds it — it holds nothing of its own.
|
|
746
|
+
*/
|
|
747
|
+
| {
|
|
748
|
+
kind: 'file-out';
|
|
749
|
+
family: 'pick' | 'upload' | 'sign';
|
|
750
|
+
nodeId: string;
|
|
751
|
+
output: string;
|
|
752
|
+
tsType: string;
|
|
753
|
+
viaState?: string;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* EXP-011 §46 — a member of a stored file that is *not* a files node's own answer: a `Cloud
|
|
757
|
+
* File` fed from a `Record`'s File-typed column reads `source.url` / `cloudFileName(source)` /
|
|
758
|
+
* `source.contentType` / `source.size`, where `source` is the record's column read (a
|
|
759
|
+
* `record-out` typed `CloudFile`, in either of its forms). `name` goes through `cloudFileName()`
|
|
760
|
+
* as the upload family's does; the two optional members are optional here too.
|
|
761
|
+
*/
|
|
762
|
+
| {
|
|
763
|
+
kind: 'file-field';
|
|
764
|
+
source: ValueExpr;
|
|
765
|
+
field: 'url' | 'name' | 'contentType' | 'size';
|
|
766
|
+
tsType: string;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* A `Now` output (EXP-011 Tier 1.3) — the instant of the last Read.
|
|
770
|
+
*
|
|
771
|
+
* 🔴 **`Now` is not a live clock, and reading it as one is the trap this kind exists to avoid.**
|
|
772
|
+
* The node's own port description says the outputs "hold the instant of the last Read, not a
|
|
773
|
+
* live value", and `initialize` seeds `_internal.now` so they are never empty before the first
|
|
774
|
+
* one. That is a `useState` with a **lazy initializer** — `useState(() => new Date())`, read
|
|
775
|
+
* once per mount exactly as `initialize` runs once per node. A bare `new Date()` in render
|
|
776
|
+
* would recompute on every pass and re-render forever; the runtime's own `runOnChange`
|
|
777
|
+
* vocabulary answered this rather than the export choosing.
|
|
778
|
+
*
|
|
779
|
+
* Two forms, and which one a read takes is decided by **where the read is** — the `http-out`
|
|
780
|
+
* rule (§8.2) reaching the second construct that needs it. `setNow(...)` does not change `now`
|
|
781
|
+
* inside the closure that called it, so a state read inside the Read chain would deliver the
|
|
782
|
+
* *previous* instant; the chain binds the instant to a local and reads that. Two bare
|
|
783
|
+
* `new Date()` calls in one chain could also straddle a millisecond boundary, which is the
|
|
784
|
+
* same bug wearing a different hat.
|
|
785
|
+
*
|
|
786
|
+
* ⚠️ Never maybe-undefined, alone in this family: the row is seeded at mount and every write
|
|
787
|
+
* is a fresh `Date`, so `now.getTime()` needs no guard.
|
|
788
|
+
*/
|
|
789
|
+
| {
|
|
790
|
+
kind: 'now-out';
|
|
791
|
+
nodeId: string;
|
|
792
|
+
output: 'date' | 'timestamp' | 'iso';
|
|
793
|
+
viaState?: string;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* A `Unique Id`'s or `UUID`'s `Id` output (EXP-011 §37) — the row anywhere, the chain's own
|
|
797
|
+
* local inside the `New` chain, on `now-out`'s rule and for `now-out`'s reason.
|
|
798
|
+
*
|
|
799
|
+
* `fn` rides on the expression rather than being looked up from the node, because the one
|
|
800
|
+
* question every consumer asks about this kind — can the read be empty — is answered by which
|
|
801
|
+
* generator it is: `randomId` cannot fail, so its row is seeded; `randomUuid` can, so its row
|
|
802
|
+
* boots `undefined` on a host with no CSPRNG.
|
|
803
|
+
*/
|
|
804
|
+
| {
|
|
805
|
+
kind: 'id-out';
|
|
806
|
+
nodeId: string;
|
|
807
|
+
fn: 'randomId' | 'randomUuid';
|
|
808
|
+
viaState?: string;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* `Hash`'s Digest or `Random Bytes`' Value (EXP-011 §59) — `id-out`'s two forms: the row anywhere,
|
|
812
|
+
* the chain-local (`<local>.digest` / `<local>.value`) inside the node's own Done arm. The row is
|
|
813
|
+
* always maybe-undefined: neither node seeds it, so it reads nothing until the first Do.
|
|
814
|
+
*/
|
|
815
|
+
| {
|
|
816
|
+
kind: 'crypto-out';
|
|
817
|
+
nodeId: string;
|
|
818
|
+
node: CryptoNodeKind;
|
|
819
|
+
viaState?: string;
|
|
820
|
+
field?: string;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* EXP-011 §76. A `Pattern Extractor`'s value output carries its port as `field` (`match`, `matches`,
|
|
824
|
+
* `groups`, `firstGroup`, `namedGroups`, `matchCount`); the row is one per field, seeded, never
|
|
825
|
+
* undefined; inside ANY of the node's four arms the read is the local's field — the Failure arm too,
|
|
826
|
+
* because a failed pattern clears the values and the render closure's row is the stale one.
|
|
827
|
+
*/
|
|
828
|
+
/**
|
|
829
|
+
* A `Screen Resolution`'s width, height or aspect ratio (EXP-011 §59) — a number off the
|
|
830
|
+
* `useScreenResolution` handle, never undefined (the hook seeds from the window at mount).
|
|
831
|
+
*/
|
|
832
|
+
| {
|
|
833
|
+
kind: 'screen-out';
|
|
834
|
+
nodeId: string;
|
|
835
|
+
local: string;
|
|
836
|
+
field: (typeof SCREEN_RESOLUTION_OUTPUTS)[number];
|
|
837
|
+
}
|
|
838
|
+
/** EXP-011 §61. A Component Stack's two outputs off its `usePageStack` handle — a string and a number, never undefined. */
|
|
839
|
+
| {
|
|
840
|
+
kind: 'stack-out';
|
|
841
|
+
nodeId: string;
|
|
842
|
+
local: string;
|
|
843
|
+
field: (typeof PAGE_STACK_OUTPUTS)[number];
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* EXP-011 §61. A pusher's `backResult-<k>`: inside one of its `backAction-*` chains the callback's own
|
|
847
|
+
* `results` argument (the runtime flags the outputs dirty BEFORE sending the signal, and a state read
|
|
848
|
+
* there would be the stale closure); anywhere else the row `viaState`, typed `unknown` — the port is `*`.
|
|
849
|
+
*/
|
|
850
|
+
| {
|
|
851
|
+
kind: 'stack-back-result';
|
|
852
|
+
nodeId: string;
|
|
853
|
+
key: string;
|
|
854
|
+
viaState?: string;
|
|
855
|
+
local?: string;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* EXP-011 §60. A key of a component-object record: `<local>.value.<key>` in render, `<local>.get().<key>` in a
|
|
859
|
+
* handler (live, as `model.get` is). `parent` reads the nearest ancestor's record through the context hook, whose
|
|
860
|
+
* handle is `undefined` at the root — the read prints `?.` and answers undefined, as the unbound node's output does.
|
|
861
|
+
* `tsType` is the key's own type (`unknown` for a parent read, or a key with no statically-known own writer).
|
|
862
|
+
*/
|
|
863
|
+
| {
|
|
864
|
+
kind: 'component-object-out';
|
|
865
|
+
nodeId: string;
|
|
866
|
+
local: string;
|
|
867
|
+
key: string;
|
|
868
|
+
parent: boolean;
|
|
869
|
+
tsType: string;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* A `Drag`'s Drag X / Drag Y / Delta X / Delta Y (EXP-011 §63) — a number off the `useDrag` handle's live getters,
|
|
873
|
+
* never undefined (the hook seeds all four at mount from Start Drag X/Y).
|
|
874
|
+
*/
|
|
875
|
+
| {
|
|
876
|
+
kind: 'drag-out';
|
|
877
|
+
nodeId: string;
|
|
878
|
+
local: string;
|
|
879
|
+
field: 'x' | 'y' | 'deltaX' | 'deltaY';
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* The `Error` output of `External Link` or `Navigate To Path` (EXP-011 §24) — the message the
|
|
883
|
+
* node wrote just before it fired `Failure`.
|
|
884
|
+
*
|
|
885
|
+
* One kind for two nodes, because the two ports are the same port: a short string out of
|
|
886
|
+
* `_internal.lastError`, written immediately before the Failure fork and **never cleared**.
|
|
887
|
+
* `http-out`'s two forms (§8.2) reaching the third and fourth construct that needs them, and
|
|
888
|
+
* which form a read takes is decided by **where the read is**:
|
|
889
|
+
*
|
|
890
|
+
* - Inside the node's own **Failure** chain, `viaState` is absent and the read is `local` — the
|
|
891
|
+
* `const` the failure arm binds the message to. It has to be: the setter has been called but
|
|
892
|
+
* React state does not change inside the closure that called it, so a state read there would
|
|
893
|
+
* deliver the *previous* failure's message.
|
|
894
|
+
* - Everywhere else — render, another handler, and **the node's own Done chain** — `viaState`
|
|
895
|
+
* names the row the failure arm writes. In the Done chain that is not a concession: the
|
|
896
|
+
* interpreter never clears `lastError` either, so a read there is the previous failure's
|
|
897
|
+
* message in both, and the stale row is the faithful answer rather than the tolerable one.
|
|
898
|
+
*
|
|
899
|
+
* ⚠️ **`Navigate To Path`'s Completed chain gets neither form, and that is the asymmetry this
|
|
900
|
+
* kind is easiest to get wrong at.** `Completed` is printed as a join *beneath* both arms, so
|
|
901
|
+
* the failure arm's `const` is out of scope there, while the row is still the pre-render value
|
|
902
|
+
* — and unlike the Done chain that is now *wrong*, because Completed runs after the failure
|
|
903
|
+
* that just wrote the message. Neither form is faithful, so the read stays refused.
|
|
904
|
+
*
|
|
905
|
+
* `local` is carried on the expression rather than looked up from the node, so the emitter
|
|
906
|
+
* needs no second table to agree with about a name.
|
|
907
|
+
*/
|
|
908
|
+
| {
|
|
909
|
+
kind: 'outcome-error';
|
|
910
|
+
nodeId: string;
|
|
911
|
+
local: string;
|
|
912
|
+
viaState?: string;
|
|
913
|
+
seeded?: true;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* EXP-011 §76. `seeded` marks an Error row that `initialize` wrote `''` into and every run rewrites — the
|
|
917
|
+
* extractor's — so the row form is never undefined either. Absent for every other node, whose row reads
|
|
918
|
+
* undefined until the first failure.
|
|
919
|
+
*/
|
|
920
|
+
/**
|
|
921
|
+
* One of the five **pure** date nodes (EXP-011 Tier 1.3) — `Date Add`, `Date Compare`,
|
|
922
|
+
* `Date Difference`, `Date Parts`, `Date To String`.
|
|
923
|
+
*
|
|
924
|
+
* They are value-driven: each recomputes whenever any input changes and holds nothing else, so
|
|
925
|
+
* a node becomes **an ordinary function call** the way Tier 1.1's arrays became ordinary value
|
|
926
|
+
* expressions. `fn` names a helper in the emitted `src/lib/date.ts`, and `args` are its
|
|
927
|
+
* arguments in position order — a wired input is the expression that feeds it, an authored one
|
|
928
|
+
* a literal. They compose, which is the whole point: `Now → Date Add → Date To String` is one
|
|
929
|
+
* nested expression and needs no per-consumer field to carry it.
|
|
930
|
+
*
|
|
931
|
+
* Every input may be wired, `Unit` and `Granularity` included since P109-S21-EXPORTDATEUNITWIRED:
|
|
932
|
+
* the emitted helpers coerce both as the setters do (`unitOrDays`; an unknown Granularity
|
|
933
|
+
* truncates nothing), and none of them has a throwing branch.
|
|
934
|
+
*/
|
|
935
|
+
| {
|
|
936
|
+
kind: 'date-call';
|
|
937
|
+
fn: DateHelper;
|
|
938
|
+
args: ValueExpr[];
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* One of the three small **pure** utilities (EXP-011 Tier 2.7) — `Substring`,
|
|
942
|
+
* `Number Remapper`, `String Mapper`.
|
|
943
|
+
*
|
|
944
|
+
* Structurally `date-call`'s twin: a node that is a function of its inputs and holds nothing
|
|
945
|
+
* else becomes one call into an emitted library, `args` in the helper's parameter order, and
|
|
946
|
+
* the calls compose. Every clause in this package that walks a `date-call`'s arguments walks
|
|
947
|
+
* this one's for the same reason, which is why the two are handled together at each of them
|
|
948
|
+
* rather than by a second copy of each walker.
|
|
949
|
+
*
|
|
950
|
+
* They are a separate kind rather than a widened `DateHelper` because the two libraries are
|
|
951
|
+
* separate modules in the emitted app: a project that formats a date should not ship the
|
|
952
|
+
* string utilities, and the import lines are earned per helper.
|
|
953
|
+
*
|
|
954
|
+
* ⚠️ **`cases` is `mapString`'s table and prints between `args[0]` and `args[1]`.** The two
|
|
955
|
+
* numbered-input families are index-aligned lists in the interpreter — `inputs[n]` matched by
|
|
956
|
+
* `indexOf`, `mappings[n]` read off the index it found — and an object literal reproduces that
|
|
957
|
+
* exactly once two things are true of how it is built: **first-wins on a repeated Input**,
|
|
958
|
+
* because `indexOf` finds the first and an object literal keeps the last; and a **present key
|
|
959
|
+
* with an `undefined` value** for an Input whose Mapping was never filled in, because that
|
|
960
|
+
* publishes empty rather than falling through to Default. Both are done where the table is
|
|
961
|
+
* built, and the helper's `hasOwnProperty` is the second half of the second one.
|
|
962
|
+
*/
|
|
963
|
+
| {
|
|
964
|
+
kind: 'util-call';
|
|
965
|
+
fn: UtilHelper;
|
|
966
|
+
args: ValueExpr[];
|
|
967
|
+
cases?: Array<{
|
|
968
|
+
from: string;
|
|
969
|
+
to: string | undefined;
|
|
970
|
+
}>;
|
|
971
|
+
/**
|
|
972
|
+
* P109-S21-EXPORTBLENDNAN — `blendColorKept`'s first argument: the `useRef` local holding the blend this
|
|
973
|
+
* Color Blend last received and the colour it last showed, declared once per node at the top of the component. 🔴 Both expression walkers
|
|
974
|
+
* (`collectExprUse` and `hookExprSources`) record it; missing one prints a call naming a local nobody declared.
|
|
975
|
+
*/
|
|
976
|
+
kept?: string;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* A `States` node's value outputs (EXP-011 §49) — a read off the `useStates` handle the
|
|
980
|
+
* component declares: `state` is the State output (`<local>.state`), `value` one of the
|
|
981
|
+
* authored values (`<local>.values.<name>`, typed by its `type-<name>`), `at` an `At <state>`
|
|
982
|
+
* (`<local>.state === '<state>'`, a real boolean, so it lands at value sinks too), `error` the
|
|
983
|
+
* last Error (`<local>.error`, undefined until a failure). No `viaState`: the handle *is* the
|
|
984
|
+
* render-scope value, read the same way in a handler — the hook re-renders the component on
|
|
985
|
+
* every frame of a tween, and a handler closes over the latest render, which is what the
|
|
986
|
+
* interpreter's getter answers too.
|
|
987
|
+
*/
|
|
988
|
+
| {
|
|
989
|
+
kind: 'states-out';
|
|
990
|
+
nodeId: string;
|
|
991
|
+
local: string;
|
|
992
|
+
field: 'state' | 'value' | 'at' | 'error';
|
|
993
|
+
name?: string;
|
|
994
|
+
tsType: string;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* An `Animate To Value`'s `Current Value` (EXP-011 §49) — the number `useAnimatedValue` returns,
|
|
998
|
+
* never undefined (it boots 0 and only ever holds a number).
|
|
999
|
+
*/
|
|
1000
|
+
/**
|
|
1001
|
+
* A `Script` node's value output (EXP-011 §52) — read off the `useScript` handle's `outputs`, which
|
|
1002
|
+
* reads the live record in both modes: render re-renders on every write, and a handler that fires a
|
|
1003
|
+
* signal reads what the code has written so far, exactly as the runtime getter answers. Always
|
|
1004
|
+
* maybe-undefined — an output the code has not written yet reads undefined.
|
|
1005
|
+
*/
|
|
1006
|
+
| {
|
|
1007
|
+
kind: 'script-out';
|
|
1008
|
+
nodeId: string;
|
|
1009
|
+
local: string;
|
|
1010
|
+
output: string;
|
|
1011
|
+
tsType: string;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* An `On App Error` boundary's value output (EXP-011 §54) — read off the `useAppError` handle's `last`, the
|
|
1015
|
+
* latest error it accepted, in both modes: the hook re-renders on every accepted error, and a chain fired
|
|
1016
|
+
* by its Error signal reads the error that fired it (the values are updated before the signal, as
|
|
1017
|
+
* onapperror.ts flags them dirty before `sendSignalOnOutput('error')`). Always maybe-undefined — before
|
|
1018
|
+
* the first error there is nothing to read.
|
|
1019
|
+
*/
|
|
1020
|
+
| {
|
|
1021
|
+
kind: 'app-error-out';
|
|
1022
|
+
nodeId: string;
|
|
1023
|
+
local: string;
|
|
1024
|
+
field: (typeof ON_APP_ERROR_VALUE_OUTPUTS)[number];
|
|
1025
|
+
tsType: string;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* A streaming node's value output (EXP-011 §58) — a live getter on the hook's handle, read the same way in
|
|
1029
|
+
* both contexts: the host re-renders once per invocation, and a chain fired by one of the node's signals
|
|
1030
|
+
* reads the state the runtime's getter would answer at that moment. `maybeUndefined` is the table's.
|
|
1031
|
+
*/
|
|
1032
|
+
| {
|
|
1033
|
+
kind: 'stream-out';
|
|
1034
|
+
nodeId: string;
|
|
1035
|
+
local: string;
|
|
1036
|
+
node: StreamKind;
|
|
1037
|
+
field: string;
|
|
1038
|
+
tsType: string;
|
|
1039
|
+
} | {
|
|
1040
|
+
kind: 'animate-out';
|
|
1041
|
+
nodeId: string;
|
|
1042
|
+
local: string;
|
|
1043
|
+
};
|
|
1044
|
+
/**
|
|
1045
|
+
* A `Script` node's signal input pulsed (EXP-011 §52) — `<local>.signals.<port>()`, one call on the
|
|
1046
|
+
* handle. The code's function for that signal runs after the handler that asked (a microtask), which is
|
|
1047
|
+
* where `scheduleSignal` puts it in the runtime; nothing rides on the action, and the node's signal
|
|
1048
|
+
* outputs are its listeners ({@link ScriptPlan.listeners}), passed once to the hook.
|
|
1049
|
+
*/
|
|
1050
|
+
/**
|
|
1051
|
+
* A `Run Tasks` node's two action ports (EXP-011 §53) — `Do` is `<local>.run(<items>)`, the list read at
|
|
1052
|
+
* the pulse (the runtime reads `internal.items`, the last value delivered, at `run()`); `Abort` is
|
|
1053
|
+
* `<local>.abort()`. Neither awaits: `run()` schedules its work through the node's own serial queue and
|
|
1054
|
+
* answers through the listeners ({@link RunTasksPlan.listeners}), passed once to the hook.
|
|
1055
|
+
*/
|
|
1056
|
+
export type RunTasksAction = {
|
|
1057
|
+
kind: 'runtasks-run';
|
|
1058
|
+
nodeId: string;
|
|
1059
|
+
local: string;
|
|
1060
|
+
items: ValueExpr;
|
|
1061
|
+
} | {
|
|
1062
|
+
kind: 'runtasks-abort';
|
|
1063
|
+
nodeId: string;
|
|
1064
|
+
local: string;
|
|
1065
|
+
};
|
|
1066
|
+
/**
|
|
1067
|
+
* P18-S116-QUERYFETCH. A `Query Records`' Fetch (`storageFetch` → `scheduleFetch`): the query asks again, its wired
|
|
1068
|
+
* filter values read at the pulse, and the list is replaced by the answer. `void fetch<Plural>(…).then(set<Plural>)`:
|
|
1069
|
+
* the runtime's fetch is scheduled, so the chain does not wait for it. `values` are {@link QueryPlan.wiredValues}, carried
|
|
1070
|
+
* so every walker sees what the action reads. A query the export does not keep prints nothing (`expandActions`).
|
|
1071
|
+
*/
|
|
1072
|
+
export type QueryFetchAction = {
|
|
1073
|
+
kind: 'query-fetch';
|
|
1074
|
+
nodeId: string;
|
|
1075
|
+
values: Array<{
|
|
1076
|
+
name: string;
|
|
1077
|
+
expr: ValueExpr;
|
|
1078
|
+
}>;
|
|
1079
|
+
};
|
|
1080
|
+
export type ScriptSignalAction = {
|
|
1081
|
+
kind: 'script-signal';
|
|
1082
|
+
nodeId: string;
|
|
1083
|
+
/** The `useScript` local. */
|
|
1084
|
+
local: string;
|
|
1085
|
+
port: string;
|
|
1086
|
+
};
|
|
1087
|
+
export type HandlerAction =
|
|
1088
|
+
/**
|
|
1089
|
+
* A Router navigation (EXP-011 Tier 2.5). `to` is the target page's **authored** url path,
|
|
1090
|
+
* braces and all, and `pathParams` names the expression each braced segment is filled from —
|
|
1091
|
+
* the emitter builds the concrete url, because it is the only place the values exist.
|
|
1092
|
+
*
|
|
1093
|
+
* ⚠️ `query` is the runtime's *leftover* rule, not a second authored list: `getRelativeURL`
|
|
1094
|
+
* substitutes every `pm-` value whose name appears in the path and appends **whatever is
|
|
1095
|
+
* left** as a query string (`router.tsx:625-633`). So a `pm-sort` on a Navigate whose target
|
|
1096
|
+
* path has no `{sort}` is `?sort=…`, and that is a feature authors use rather than an
|
|
1097
|
+
* accident — it is how a page takes an optional parameter without putting it in the route.
|
|
1098
|
+
*/
|
|
1099
|
+
{
|
|
1100
|
+
kind: 'navigate';
|
|
1101
|
+
to: string;
|
|
1102
|
+
pathParams: Array<{
|
|
1103
|
+
name: string;
|
|
1104
|
+
expr: ValueExpr;
|
|
1105
|
+
}>;
|
|
1106
|
+
query: Array<{
|
|
1107
|
+
name: string;
|
|
1108
|
+
expr: ValueExpr;
|
|
1109
|
+
}>;
|
|
1110
|
+
/**
|
|
1111
|
+
* P18-S110-NEWTAB. `Open In New Tab` ticked: router.tsx `navigateAsync` builds the page's
|
|
1112
|
+
* url and calls `window.open(url, '_blank')` — no features string, no blocked test, `Done`
|
|
1113
|
+
* either way. The exported url is the BrowserRouter path the in-place arm would push.
|
|
1114
|
+
*/
|
|
1115
|
+
newTab?: true;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* `Navigate To Path` (EXP-011 §15, session 44) — a navigation to an **authored path string**
|
|
1119
|
+
* rather than to a page component, and four things about it differ from {@link HandlerAction}'s
|
|
1120
|
+
* `navigate` even though the two nodes look like a pair:
|
|
1121
|
+
*
|
|
1122
|
+
* | | `getRelativeURL` (RouterNavigate) | `navigate()` (this node) |
|
|
1123
|
+
* |---|---|---|
|
|
1124
|
+
* | an unset placeholder | left as the literal `{id}`, **and** `?id=undefined` appended | substituted with `''` |
|
|
1125
|
+
* | encoding | `encodeURIComponent` on every value | none — `String(v)` and `q + '=' + v` |
|
|
1126
|
+
* | the query set | whatever is **left over** after substitution | the authored `Query` list |
|
|
1127
|
+
* | an unset query value | n/a | **omitted from the url** |
|
|
1128
|
+
*
|
|
1129
|
+
* 🔴 **So §11.6's rules do not carry across, and copying them would have been wrong in all
|
|
1130
|
+
* four places.** The unset placeholder is the sharpest: it is deferred one node over precisely
|
|
1131
|
+
* because the Router's three readings of it disagree, and it is *translated* here because this
|
|
1132
|
+
* node's own function is coherent about it.
|
|
1133
|
+
*
|
|
1134
|
+
* {@link to} is normalised to exactly one leading slash. The runtime compares trimmed paths
|
|
1135
|
+
* (`_trimUrlPart`, `router.tsx:39`) so `note/42` and `/note/42` are one route there; in
|
|
1136
|
+
* react-router the first is **relative to the current route** and the second is absolute.
|
|
1137
|
+
*
|
|
1138
|
+
* `then` is the `Done` chain and `completedThen` the `Completed` one, in that order, because
|
|
1139
|
+
* `reportOutcome` sends the outcome and then `Completed` (`node.ts:958-995`).
|
|
1140
|
+
*
|
|
1141
|
+
* 🔴 **`newTab` decides which of two different actions this is** (EXP-011 §17, session 46).
|
|
1142
|
+
* Off, the node is `history.pushState` and cannot fail, so `failThen` is empty and
|
|
1143
|
+
* `completedThen` follows the one outcome there is. On, it is `window.open` — which *can* be
|
|
1144
|
+
* refused — so `Failure` becomes live, `Error` becomes writable, and `Completed` stops being
|
|
1145
|
+
* a continuation of `Done` and becomes a **join beneath both arms**. §15.4 earned `Completed`
|
|
1146
|
+
* on "the gates leave exactly one outcome reachable", and opening this arm is exactly the
|
|
1147
|
+
* change that retires that argument rather than inheriting it.
|
|
1148
|
+
*/
|
|
1149
|
+
| {
|
|
1150
|
+
kind: 'navigate-path';
|
|
1151
|
+
/** Needed by every registry keyed on the node — the Error row, and the attachment sweep. */
|
|
1152
|
+
nodeId: string;
|
|
1153
|
+
to: string;
|
|
1154
|
+
pathParams: Array<{
|
|
1155
|
+
name: string;
|
|
1156
|
+
expr: ValueExpr;
|
|
1157
|
+
}>;
|
|
1158
|
+
/** `omittable` values print inside the `if` the runtime's `v !== undefined && v !== null` test describes (P107-D21). */
|
|
1159
|
+
query: Array<{
|
|
1160
|
+
name: string;
|
|
1161
|
+
expr: ValueExpr;
|
|
1162
|
+
omittable: boolean;
|
|
1163
|
+
}>;
|
|
1164
|
+
/** The `const` the omittable form collects into; unused when every value is present. */
|
|
1165
|
+
queryLocal: string;
|
|
1166
|
+
/**
|
|
1167
|
+
* 🔴 **This field means “the new-tab arm is reachable”, not “the port is on”** (EXP-011 §18,
|
|
1168
|
+
* session 47). Every consumer already asks it that question — is `Failure` live, is `Error`
|
|
1169
|
+
* writable, is `Completed` a join, is there an outcome to bind — and a wired port answers
|
|
1170
|
+
* all four the same way an authored `true` does, because the tab can still be refused.
|
|
1171
|
+
* What a wire adds is that the *other* arm is reachable too, and that is {@link newTabExpr}
|
|
1172
|
+
* rather than a second reading of this one.
|
|
1173
|
+
*
|
|
1174
|
+
* ⚠️ **`default: false` here and `default: true` on `External Link`** — the two nodes look
|
|
1175
|
+
* like a pair and their unset state lands on opposite sides.
|
|
1176
|
+
*/
|
|
1177
|
+
newTab: boolean;
|
|
1178
|
+
/**
|
|
1179
|
+
* The wired `Open In New Tab`, as the expression the emitted handler branches on — set
|
|
1180
|
+
* **only** where the port carries a wire, which is the only way both of this node's two
|
|
1181
|
+
* actions become reachable in one handler (EXP-011 §18).
|
|
1182
|
+
*
|
|
1183
|
+
* 🔴 **The port is answerable because the runtime reads it exactly once, as `!!value`**
|
|
1184
|
+
* (`navigate-to-path.ts:89`, an input `set` that stores a boolean), and `navigate` then
|
|
1185
|
+
* reads that stored boolean (`:204`). ⚠️ `External Link`'s identically-named port is read
|
|
1186
|
+
* **two different ways in two adjacent lines** — truthiness for the features string and
|
|
1187
|
+
* strict equality for the target — which is why a wire defers there and translates here.
|
|
1188
|
+
* The two nodes are not a pair on this port either.
|
|
1189
|
+
*
|
|
1190
|
+
* It is a truthiness sink, so a logic truth value lands here rather than deferring on the
|
|
1191
|
+
* standing rule: the emitted read is the `if` test itself, which coerces exactly as `!!`
|
|
1192
|
+
* does. Read **once**, at the top of the handler, so there is no local to bind it to.
|
|
1193
|
+
*/
|
|
1194
|
+
newTabExpr?: ValueExpr;
|
|
1195
|
+
/**
|
|
1196
|
+
* The `const` the `window.open` result binds to. Emitted only where something branches on
|
|
1197
|
+
* it: a new tab with no chains and no Error row is the bare call the graph asked for.
|
|
1198
|
+
*/
|
|
1199
|
+
openedLocal: string;
|
|
1200
|
+
/**
|
|
1201
|
+
* The `Error` output as a state row, allocated **by the read** (§14.2's rule, fifth
|
|
1202
|
+
* instance). Only ever set for `newTab`: in-tab, neither of the node's two failures can
|
|
1203
|
+
* fire, so there is no read to earn it.
|
|
1204
|
+
*/
|
|
1205
|
+
errorState?: string;
|
|
1206
|
+
/**
|
|
1207
|
+
* The `const` the failure arm binds the message to, set only where something inside that
|
|
1208
|
+
* arm reads `Error` (EXP-011 §24). Minted by the read, like the row above it — the
|
|
1209
|
+
* commonest node here reads neither and emits neither.
|
|
1210
|
+
*/
|
|
1211
|
+
errorLocal?: string;
|
|
1212
|
+
then: HandlerAction[];
|
|
1213
|
+
/** The `Failure` chain — reachable only for `newTab`, where the browser can refuse. */
|
|
1214
|
+
failThen: HandlerAction[];
|
|
1215
|
+
completedThen: HandlerAction[];
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* `External Link` (EXP-011 Tier 2.5) — `window.open`, plus the two outcome chains the
|
|
1219
|
+
* runtime's own fork owes (`externallink.ts`).
|
|
1220
|
+
*
|
|
1221
|
+
* The emitted shape is the interpreter's, guard for guard:
|
|
1222
|
+
*
|
|
1223
|
+
* ```
|
|
1224
|
+
* const link = donateUrl; // bound once — the guard and the open must
|
|
1225
|
+
* if (link === undefined || link === null || link === '') { …failThen } // read one value
|
|
1226
|
+
* else if (!window.open(link, '_blank', 'noopener,noreferrer')) { …failThen }
|
|
1227
|
+
* else { …then }
|
|
1228
|
+
* ```
|
|
1229
|
+
*
|
|
1230
|
+
* 🔴 **`Open In New Tab` unset is `true`, not `undefined`.** The port declares `default: true`
|
|
1231
|
+
* and `registerInput` writes a declared default straight into `_inputValues` (`node.ts:138`),
|
|
1232
|
+
* so `getInputValue` never sees the `undefined` its own target expression tests for — which is
|
|
1233
|
+
* what makes {@link newTab} a plain boolean here rather than a third state.
|
|
1234
|
+
*
|
|
1235
|
+
* That question is worth asking because the runtime reads this one input **two different ways
|
|
1236
|
+
* in two adjacent lines**: `params` is truthiness (`openInNewTab ? … : ''`) and `target` is
|
|
1237
|
+
* strict equality (`=== true || === undefined`). They agree for `true`, for `false` and for
|
|
1238
|
+
* the unset port — every value the *editor* can put there — and disagree for every other
|
|
1239
|
+
* truthy value, which only a wire can deliver. So a wired `Open In New Tab` defers, and the
|
|
1240
|
+
* three cases that remain are the three an author can actually author.
|
|
1241
|
+
*
|
|
1242
|
+
* ⚠️ **There is no `unchanged` arm and its absence is a decision.** The runtime reports
|
|
1243
|
+
* `unchanged` when `typeof window === 'undefined'` — a server-side render. The scaffold
|
|
1244
|
+
* mounts with `createRoot` (`scaffold.ts:249`) and has no server pass at all, so that chain
|
|
1245
|
+
* is unreachable in the exported app exactly as it is in the app running in a browser. The
|
|
1246
|
+
* wire is dropped with a note rather than deferring the node, on `Clear Array`'s rule: a wire
|
|
1247
|
+
* already dead in the interpreter must not cost a translation.
|
|
1248
|
+
*/
|
|
1249
|
+
| {
|
|
1250
|
+
kind: 'external-link';
|
|
1251
|
+
/** The node, so the attachment pass and the late row sweep can find it (§14). */
|
|
1252
|
+
nodeId: string;
|
|
1253
|
+
link: ValueExpr;
|
|
1254
|
+
/** `Open In New Tab` — resolved here, because only an unwired port reaches this action. */
|
|
1255
|
+
newTab: boolean;
|
|
1256
|
+
/**
|
|
1257
|
+
* The chain-local the link binds to, emitted only where the empty-link guard needs it —
|
|
1258
|
+
* the guard and the `window.open` must read one value, not two evaluations of one
|
|
1259
|
+
* expression (a link read from a Variable can change between them).
|
|
1260
|
+
*/
|
|
1261
|
+
local: string;
|
|
1262
|
+
/**
|
|
1263
|
+
* The local the blocked-tab test binds to, emitted only for a new tab whose outcome is
|
|
1264
|
+
* read (EXP-011 §14).
|
|
1265
|
+
*
|
|
1266
|
+
* 🔴 **The binding is the correctness, and it is not a tidiness choice.** `window.open`
|
|
1267
|
+
* **consumes** the transient activation — measured in Chrome 151, one control arm varying
|
|
1268
|
+
* only whether the call sits between two reads of `navigator.userActivation.isActive`:
|
|
1269
|
+
* without it both reads are `true`, with it the second is `false` while a tab opens. So
|
|
1270
|
+
* the emitted test had to move *before* the call, exactly where the runtime reads it
|
|
1271
|
+
* (`externallink.ts:96`). Read after, it is `false` on every tab successfully opened —
|
|
1272
|
+
* which is DEF-016's own symptom, reintroduced on the export side by DEF-016's fix.
|
|
1273
|
+
*/
|
|
1274
|
+
blockedLocal: string;
|
|
1275
|
+
/**
|
|
1276
|
+
* False only where the link is provably non-empty — a non-empty literal. The runtime
|
|
1277
|
+
* refuses before touching the browser on `undefined`, `null` and `''`, and `window.open('')`
|
|
1278
|
+
* opens a blank tab, so this guard is observable even with no Failure chain wired.
|
|
1279
|
+
*/
|
|
1280
|
+
guardLink: boolean;
|
|
1281
|
+
/**
|
|
1282
|
+
* The `Error` output as a state row (EXP-011 §14), filled by the late sweep and undefined
|
|
1283
|
+
* where nothing reads it — which is the ordinary case, and why a button opening a literal
|
|
1284
|
+
* url still emits one expression and no `useState`.
|
|
1285
|
+
*
|
|
1286
|
+
* 🔴 **This is the field that makes the two failures tell themselves apart.** Everywhere
|
|
1287
|
+
* else in this action they are one arm, because "no link" and "the browser blocked the
|
|
1288
|
+
* tab" both run the Failure chain and nothing else. `Error` is the one port that
|
|
1289
|
+
* distinguishes them — two static strings, `externallink.ts:62` and `:101` — so the
|
|
1290
|
+
* emitted failure arm re-tests the link to pick the message. Both strings are the node's
|
|
1291
|
+
* own `_internal.lastError`, which is the SHORT one; the longer sentence beside it goes to
|
|
1292
|
+
* the outcome channel, not to this port.
|
|
1293
|
+
*/
|
|
1294
|
+
errorState?: string;
|
|
1295
|
+
/**
|
|
1296
|
+
* The `const` the failure arm binds the message to (EXP-011 §24), set only where something
|
|
1297
|
+
* inside that arm reads `Error`.
|
|
1298
|
+
*
|
|
1299
|
+
* 🔴 **It is what stops the message being computed twice.** The write above picks between
|
|
1300
|
+
* two strings by re-testing the link; a chain reading `Error` would otherwise have to print
|
|
1301
|
+
* that same ternary again at every sink, and the two copies would be free to disagree.
|
|
1302
|
+
*/
|
|
1303
|
+
errorLocal?: string;
|
|
1304
|
+
then: HandlerAction[];
|
|
1305
|
+
failThen: HandlerAction[];
|
|
1306
|
+
} | {
|
|
1307
|
+
kind: 'emit';
|
|
1308
|
+
channelName: string;
|
|
1309
|
+
payload: Array<{
|
|
1310
|
+
key: string;
|
|
1311
|
+
expr: ValueExpr;
|
|
1312
|
+
}>;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* EXP-011 §74. `fallback`: the value typed into the Set's own port UNDER the wire `expr` came from, kept only when
|
|
1316
|
+
* `expr` may read `undefined` — the runtime writes the wire's last defined delivery, else the typed-in value
|
|
1317
|
+
* (measured: `exp-011-s96-a-literal-under-a-wire.test.ts`), so the handler prints `expr ?? fallback`. Absent when
|
|
1318
|
+
* nothing is typed in, or when the wire's source always carries a value (the typed-in value is then never written,
|
|
1319
|
+
* in the runtime or here — a note says so).
|
|
1320
|
+
*
|
|
1321
|
+
* P18-S127-SETDONE (EXP-011 §95). `then`: the Set's Done chain, then its Completed chain, as following statements
|
|
1322
|
+
* (`expandActions`), present only when something hangs off them. The runtime writes, notifies every Variable reading
|
|
1323
|
+
* the name, then sends Done and Completed (`setvariablenode.ts` `scheduleStore`), and a Done's target is updated before
|
|
1324
|
+
* the click's next wire (it is flagged dirty with its sender and pulls it first): depth first, as printed.
|
|
1325
|
+
*/
|
|
1326
|
+
| {
|
|
1327
|
+
kind: 'store-set';
|
|
1328
|
+
variableName: string;
|
|
1329
|
+
expr: ValueExpr;
|
|
1330
|
+
fallback?: string | number | boolean;
|
|
1331
|
+
then?: HandlerAction[];
|
|
1332
|
+
} | {
|
|
1333
|
+
kind: 'globalstore-set';
|
|
1334
|
+
storeName: string;
|
|
1335
|
+
key: string;
|
|
1336
|
+
expr: ValueExpr;
|
|
1337
|
+
fallback?: string | number | boolean;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* `Set Object Properties` on a named Object (EXP-011 §47): one patch over every wired property
|
|
1341
|
+
* the node's list admits — `profile.set({ name: …, city: … })` — then its Done chain as
|
|
1342
|
+
* following statements. No failure arm: with a literal Id the runtime's only failure (no
|
|
1343
|
+
* object bound) is unreachable, because `Model.get` creates the object on read.
|
|
1344
|
+
*/
|
|
1345
|
+
| {
|
|
1346
|
+
kind: 'object-set';
|
|
1347
|
+
storeName: string;
|
|
1348
|
+
entries: Array<{
|
|
1349
|
+
key: string;
|
|
1350
|
+
expr: ValueExpr;
|
|
1351
|
+
fallback?: string | number | boolean;
|
|
1352
|
+
}>;
|
|
1353
|
+
then: HandlerAction[];
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* EXP-011 §60. `Set Component Object Properties` (`parent: false`) — `<local>.set({ … })`, then Done, then Completed, as
|
|
1357
|
+
* following statements (one arm, always taken: the self variant cannot miss its own record). `Set Parent Component
|
|
1358
|
+
* Object Properties` (`parent: true`) — the block form: `if (<local> === undefined) { raise no-ancestor; failThen }
|
|
1359
|
+
* else { <local>.set({ … }); then }` — the runtime's `resolution.id === undefined` fork in base.ts, verbatim.
|
|
1360
|
+
*/
|
|
1361
|
+
| {
|
|
1362
|
+
kind: 'component-object-set';
|
|
1363
|
+
nodeId: string;
|
|
1364
|
+
local: string;
|
|
1365
|
+
parent: boolean;
|
|
1366
|
+
entries: Array<{
|
|
1367
|
+
key: string;
|
|
1368
|
+
expr: ValueExpr;
|
|
1369
|
+
}>;
|
|
1370
|
+
then: HandlerAction[];
|
|
1371
|
+
completedThen: HandlerAction[];
|
|
1372
|
+
failThen: HandlerAction[];
|
|
1373
|
+
} | {
|
|
1374
|
+
kind: 'collection-add';
|
|
1375
|
+
collectionName: string;
|
|
1376
|
+
entries: Array<{
|
|
1377
|
+
key: string;
|
|
1378
|
+
expr: ValueExpr;
|
|
1379
|
+
}>;
|
|
1380
|
+
minted?: MintedTarget;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* `Clear Array` (EXP-011 Tier 1.1) — `notes.clear()`, plus the two chains the runtime's own
|
|
1384
|
+
* outcome fork owes.
|
|
1385
|
+
*
|
|
1386
|
+
* `collectionnode-clear.ts` measures `wasEmpty` **before** `set([])` and reports `unchanged`
|
|
1387
|
+
* for an array that was already empty, `done` otherwise (ERG-001: "the post-condition already
|
|
1388
|
+
* held"). So a `done` chain emitted unconditionally would fire where the interpreter stays
|
|
1389
|
+
* silent. The emitted form tests the length instead of keeping a local, which is exact rather
|
|
1390
|
+
* than merely close: `Collection.clear()` is itself `if (length === 0) return`, so skipping
|
|
1391
|
+
* the call on the empty arm is the same no-op the guard would have made.
|
|
1392
|
+
*/
|
|
1393
|
+
| {
|
|
1394
|
+
kind: 'collection-clear';
|
|
1395
|
+
collectionName: string;
|
|
1396
|
+
then: HandlerAction[];
|
|
1397
|
+
unchangedThen: HandlerAction[];
|
|
1398
|
+
minted?: MintedTarget;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* `Remove Object From Array` (EXP-011 §30) — `notes.remove(item)`, inside the row callback of
|
|
1402
|
+
* the repeater named by {@link repeaterId}.
|
|
1403
|
+
*
|
|
1404
|
+
* 🔴 **This action is only meaningful in one place, and the type says so.** The node's Object
|
|
1405
|
+
* Id is a `For Each`'s `itemActionItemId`, which the runtime sets to `model.getId()` of the
|
|
1406
|
+
* row that fired *immediately before* pulsing `itemOutputSignal-<name>` (`foreach.tsx`). Read
|
|
1407
|
+
* anywhere else it is "whichever row fired last", which the emitted list holds no state for —
|
|
1408
|
+
* so `actionsValidIn` refuses this action outside that repeater's own row signal, and the row
|
|
1409
|
+
* the interpreter names becomes the `item` the callback already closes over.
|
|
1410
|
+
*
|
|
1411
|
+
* There is no `unchangedThen` and no failure arm because in this shape neither can fire, and
|
|
1412
|
+
* the compile proves it rather than assuming it: see `compileCollectionRemove`.
|
|
1413
|
+
*/
|
|
1414
|
+
| {
|
|
1415
|
+
kind: 'collection-remove';
|
|
1416
|
+
collectionName: string;
|
|
1417
|
+
repeaterId: string;
|
|
1418
|
+
then: HandlerAction[];
|
|
1419
|
+
minted?: MintedTarget;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* A Condition in a handler chain: `if (cond) whenTrue; else whenFalse;` (LOGIC-TARGET §3). `flat` (EXP-011 §87): a
|
|
1423
|
+
* Condition whose answer is a literal and which writes its sinks' cells has one side; its `cond` is `true`, its
|
|
1424
|
+
* `whenFalse` empty, and `whenTrue` prints as following statements.
|
|
1425
|
+
*/
|
|
1426
|
+
| {
|
|
1427
|
+
kind: 'branch';
|
|
1428
|
+
cond: ValueExpr;
|
|
1429
|
+
whenTrue: HandlerAction[];
|
|
1430
|
+
whenFalse: HandlerAction[];
|
|
1431
|
+
flat?: true;
|
|
1432
|
+
}
|
|
1433
|
+
/** Fires the component's own signal output: `onWaved?.()` (COMPONENT-OUTPUTS-TARGET §4). */
|
|
1434
|
+
| {
|
|
1435
|
+
kind: 'output-signal';
|
|
1436
|
+
prop: string;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Opens a popup slot: `setOpenPopup('AboutDialog')`, then the Show Popup's `done`-chain as
|
|
1440
|
+
* following statements in the same handler (POPUPS-TARGET §3).
|
|
1441
|
+
*/
|
|
1442
|
+
| {
|
|
1443
|
+
kind: 'popup-show';
|
|
1444
|
+
slotKey: string;
|
|
1445
|
+
then: HandlerAction[];
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Closes the enclosing popup through the reserved prop (POPUPS-TARGET §4): with a
|
|
1449
|
+
* `done`-chain, `if (onClose) { onClose('ok'); …then }`; without one, `onClose?.('ok')`.
|
|
1450
|
+
* `action` undefined is the plain `Close` (the runtime's `Closed` outcome).
|
|
1451
|
+
*/
|
|
1452
|
+
| {
|
|
1453
|
+
kind: 'popup-close';
|
|
1454
|
+
action?: string;
|
|
1455
|
+
then: HandlerAction[];
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* A pure Function/Expression fired from a handler chain (EXP-003 §4 A2h): the actions its
|
|
1459
|
+
* `done` wires described, in wire order. The node's own compute needs no statement — output
|
|
1460
|
+
* reads inside the chain inline the call at their sinks, and a pure body run without reading
|
|
1461
|
+
* its outputs is unobservable. `done` is invocation-only in both runtimes, so this is exact.
|
|
1462
|
+
* `materialize` (CONTROLLED-STATE-TARGET §4f) names the state var the run writes when the
|
|
1463
|
+
* node's outputs also feed render sinks: `setStockCheckOut(stockCheck({ … }))` precedes the
|
|
1464
|
+
* chain, and render reads go through the var.
|
|
1465
|
+
* `ticked` (P18-S119-TICKEDRUN) marks the run a ticked input's new value starts (`TickedRunPlan`): it has no Done, and
|
|
1466
|
+
* its effect has already recorded the values it read.
|
|
1467
|
+
*/
|
|
1468
|
+
| {
|
|
1469
|
+
kind: 'jsfun-run';
|
|
1470
|
+
nodeId: string;
|
|
1471
|
+
then: HandlerAction[];
|
|
1472
|
+
materialize?: string;
|
|
1473
|
+
ticked?: true;
|
|
1474
|
+
/**
|
|
1475
|
+
* EXP-019 slice 1b (session 134). The body awaits at its top level: where in `then` the chain the runtime sends after
|
|
1476
|
+
* the run's promise settles begins (Success, then Done), after the chains of the signals the body sent before its
|
|
1477
|
+
* first `await`. Printed whole (`runFunction`, the pulses, `await <run>.settled`, the rest) and awaited like a request,
|
|
1478
|
+
* so §90's `detached` lets the click's later wires run before it settles.
|
|
1479
|
+
*/
|
|
1480
|
+
awaitsAt?: number;
|
|
1481
|
+
/** EXP-011 §90, as an api call's. */
|
|
1482
|
+
detached?: true;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* EXP-011 §86. The chains of a Function's own signals (`Outputs.go()`), first in its run's `then`: one loop over the
|
|
1486
|
+
* pulses the run sent, in the order it sent them, each arm (`jsfun-pulse`) the chain of one signal. Inside an arm the
|
|
1487
|
+
* Function's outputs read as that pulse's targets read them (C7 lockstep, `src/lib/functionRun.ts`).
|
|
1488
|
+
*/
|
|
1489
|
+
| {
|
|
1490
|
+
kind: 'jsfun-pulses';
|
|
1491
|
+
nodeId: string;
|
|
1492
|
+
then: HandlerAction[];
|
|
1493
|
+
}
|
|
1494
|
+
/** EXP-011 §86. One signal's chain inside `jsfun-pulses`: the actions its wires describe, in wire order. */
|
|
1495
|
+
| {
|
|
1496
|
+
kind: 'jsfun-pulse';
|
|
1497
|
+
nodeId: string;
|
|
1498
|
+
signal: string;
|
|
1499
|
+
then: HandlerAction[];
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* A state var write (CONTROLLED-STATE-TARGET §3.3). `op` is a functional update
|
|
1503
|
+
* (`setX(v => !v)`) — immune to closure staleness, which is why Switch's `flip` and
|
|
1504
|
+
* Counter's arithmetic use it, never `expr`.
|
|
1505
|
+
*/
|
|
1506
|
+
| {
|
|
1507
|
+
kind: 'state-set';
|
|
1508
|
+
name: string;
|
|
1509
|
+
expr?: ValueExpr;
|
|
1510
|
+
op?: 'toggle' | 'inc' | 'dec';
|
|
1511
|
+
field?: FieldWrite;
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* P18-S128-FIELDSET (EXP-011 §97). A text field's Focus or Blur: `focus()` / `blur()` on the exported `<input>` through a
|
|
1515
|
+
* ref, as the runtime's focus tracker calls them on the viewer's own (`_focus`, `_blur`). A field that is not on the page
|
|
1516
|
+
* takes nothing, and the Focus is not held (GAM-012 R13): the ref is `null` then.
|
|
1517
|
+
*/
|
|
1518
|
+
| {
|
|
1519
|
+
kind: 'field-focus';
|
|
1520
|
+
inputId: string;
|
|
1521
|
+
focus: boolean;
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* An awaited call into an api stub (RECORD-VERBS-TARGET §4, USER-FAMILY-TARGET §4e): the
|
|
1525
|
+
* vocabulary's asynchronous action. The emitted handler becomes `async`, the call is awaited,
|
|
1526
|
+
* `then` is the `done` chain — which is where `reportOutcomes(…, 'done')` sits in the runtime,
|
|
1527
|
+
* after the service answers — and the catch writes `errorState`, the node's `Error` output,
|
|
1528
|
+
* which the runtime never clears once set.
|
|
1529
|
+
*
|
|
1530
|
+
* ⚠️ **One kind, two families.** The record verbs and the user family emit the *same* action;
|
|
1531
|
+
* they differ only in `fnName` and in the shape of `args`. USER-FAMILY §4e records why this is
|
|
1532
|
+
* a rename of `record-op` rather than a sibling case beside it: a new discriminant recruits
|
|
1533
|
+
* every switch site silently and the wrong ones fail without throwing (s19's dispatcher trap),
|
|
1534
|
+
* whereas a rename is a compile error at each site until it is handled.
|
|
1535
|
+
*/
|
|
1536
|
+
| {
|
|
1537
|
+
kind: 'api-call';
|
|
1538
|
+
/**
|
|
1539
|
+
* EXP-011 §90. Set by the planner's last pass where a later action of the same list follows it: printed as
|
|
1540
|
+
* `void (async () => { … })();`, so the click's later wires run before the answer arrives, as the runtime's do
|
|
1541
|
+
* (each node acts after the update; the request answers later). A failure before the first await (the
|
|
1542
|
+
* `Missing Record Id` guard) still runs its chain first, as the runtime's synchronous `setError` does.
|
|
1543
|
+
*/
|
|
1544
|
+
detached?: true;
|
|
1545
|
+
nodeId: string;
|
|
1546
|
+
/** What produced the call — for notes and the stub's provenance line. EXP-011 §62: the relation pair rides the same action. */
|
|
1547
|
+
verb: 'create' | 'update' | 'delete' | RelationVerb | UserVerb;
|
|
1548
|
+
/** The api module's exported function: `createPuppy` / `updatePuppy` / `logIn` / `logOut`. */
|
|
1549
|
+
fnName: string;
|
|
1550
|
+
/**
|
|
1551
|
+
* The call's arguments, in position order. `updatePuppy(id, {…})` is `[expr, data]`,
|
|
1552
|
+
* `logIn(u, p)` is `[expr, expr]`, `logOut()` is `[]`.
|
|
1553
|
+
*/
|
|
1554
|
+
args: ApiCallArg[];
|
|
1555
|
+
/**
|
|
1556
|
+
* Refuse before calling when the leading id argument is absent or empty, the way the
|
|
1557
|
+
* runtime does (RECORD-VERBS-TARGET §1): `setModelID` reads `undefined`/`null`/`''` as
|
|
1558
|
+
* *clear the binding*, after which every verb answers `setError('Missing Record Id')` and
|
|
1559
|
+
* never reaches the backend. Gate 9 already defers an id that is **statically** absent;
|
|
1560
|
+
* this is its dynamic twin, and the corpus reaches it because every emitted component prop
|
|
1561
|
+
* is optional, so an id wired from one is `string | undefined` at the call.
|
|
1562
|
+
*
|
|
1563
|
+
* False only where the guard is provably dead — a non-empty literal id, and every `create`,
|
|
1564
|
+
* which takes no id at all.
|
|
1565
|
+
*/
|
|
1566
|
+
guardId: boolean;
|
|
1567
|
+
/**
|
|
1568
|
+
* EXP-011 §72. The id argument is read from a source typed `unknown` — a Variable nothing
|
|
1569
|
+
* statically writes, or one written by a Function or an HTTP body — and the client's
|
|
1570
|
+
* parameter is `string`: the emitter prints `String(x ?? '')` around the read. The runtime
|
|
1571
|
+
* keys `Model.get(id)` by the value and the client `encodeURIComponent`s it, so the
|
|
1572
|
+
* coercion transcribes both; an empty read still meets `guardId`'s `Missing Record Id`.
|
|
1573
|
+
* Absent for a `string` source — a `string | undefined` narrows on its own.
|
|
1574
|
+
*/
|
|
1575
|
+
coerceId?: 'string';
|
|
1576
|
+
/**
|
|
1577
|
+
* EXP-011 §62. The relation verbs' dynamic pre-flight, in the runtime's own order and its own
|
|
1578
|
+
* words (`validateInputs`): a positional argument that is empty at the click throws that
|
|
1579
|
+
* sentence into the catch — *No target record Id (the record to add a relation to) specified*
|
|
1580
|
+
* before *No record Id specified (the record that should get the relation)*, because the
|
|
1581
|
+
* runtime asks about the target first. Printed after `guardId`'s line; emitted only for an
|
|
1582
|
+
* argument that is not a literal (a literal is never empty — the planner refused that).
|
|
1583
|
+
*/
|
|
1584
|
+
guards?: Array<{
|
|
1585
|
+
index: number;
|
|
1586
|
+
local: string;
|
|
1587
|
+
message: string;
|
|
1588
|
+
coerce?: 'string';
|
|
1589
|
+
}>;
|
|
1590
|
+
errorState: string;
|
|
1591
|
+
/**
|
|
1592
|
+
* EXP-011 §44 — write `undefined` to the Error row the moment the call answers, before the
|
|
1593
|
+
* done chain: `Request Magic Link` is the one verb whose success clears its Error. Every
|
|
1594
|
+
* other `api-call` keeps the runtime's "never cleared" (RECORD-VERBS-TARGET §1).
|
|
1595
|
+
*/
|
|
1596
|
+
clearErrorOnDone?: true;
|
|
1597
|
+
then: HandlerAction[];
|
|
1598
|
+
/**
|
|
1599
|
+
* EXP-011 §90. The `failure` chain, printed in the catch after the Error row and the raise: the runtime's
|
|
1600
|
+
* `setError` writes the row, raises, then pulses Failure (`node.ts` `reportOutcome`). A read of this node's Error
|
|
1601
|
+
* inside it is the catch's `<errorState>Message` local (`outcome-error`), never the row.
|
|
1602
|
+
*/
|
|
1603
|
+
failThen: HandlerAction[];
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* An `HTTP Request`'s `Fetch` (EXP-011 Tier 1.2) — the vocabulary's second asynchronous action,
|
|
1607
|
+
* and the first whose *failure* is a chain rather than only a value.
|
|
1608
|
+
*
|
|
1609
|
+
* The emitted shape is the runtime's own three outcomes, not two:
|
|
1610
|
+
*
|
|
1611
|
+
* ```
|
|
1612
|
+
* try {
|
|
1613
|
+
* const answer = await fetchQuote({ … }); // throws only where no answer arrived
|
|
1614
|
+
* setQuoteOut(answer); // response/statusCode/headers/fields — set for ANY answer
|
|
1615
|
+
* if (answer.ok) { …then }
|
|
1616
|
+
* else { const message = answer.error; setQuoteError(message); …failThen }
|
|
1617
|
+
* } catch (error) {
|
|
1618
|
+
* const message = error instanceof Error ? error.message : String(error);
|
|
1619
|
+
* setQuoteError(message); …failThen
|
|
1620
|
+
* }
|
|
1621
|
+
* ```
|
|
1622
|
+
*
|
|
1623
|
+
* 🔴 **A non-2xx still writes the value outputs, and a network error still does not.**
|
|
1624
|
+
* `processResponse` runs before `doFetch` reports `failure`, so a 404's body and status are on
|
|
1625
|
+
* the node's outputs while `Failure` fires; a request that never reached the server leaves
|
|
1626
|
+
* `Response` and `Status Code` holding exactly what they held. Splitting the answer from the
|
|
1627
|
+
* throw is what reproduces both — the record verbs' single try/catch could not, because for
|
|
1628
|
+
* them a refused write has nothing to publish.
|
|
1629
|
+
*
|
|
1630
|
+
* `failThen` is therefore emitted **twice**, once per arm, and the two arms are identical by
|
|
1631
|
+
* construction because both bind `message` first. A join would need a `finally` that can tell
|
|
1632
|
+
* which arm it is in, which is a variable the arms already are.
|
|
1633
|
+
*/
|
|
1634
|
+
| {
|
|
1635
|
+
kind: 'http-call';
|
|
1636
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1637
|
+
detached?: true;
|
|
1638
|
+
nodeId: string;
|
|
1639
|
+
/** `src/api/http.ts`'s exported function for this node. */
|
|
1640
|
+
fnName: string;
|
|
1641
|
+
/** Wired inputs, in the emitted parameter order — `{ tag: tagText }` at the call. */
|
|
1642
|
+
args: Array<{
|
|
1643
|
+
param: string;
|
|
1644
|
+
expr: ValueExpr;
|
|
1645
|
+
}>;
|
|
1646
|
+
/** The state row the answer is written to, when anything outside the chain reads a value. */
|
|
1647
|
+
materialize?: string;
|
|
1648
|
+
/** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
|
|
1649
|
+
errorState: string;
|
|
1650
|
+
then: HandlerAction[];
|
|
1651
|
+
failThen: HandlerAction[];
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* A `Cloud Function`'s `Call` (EXP-011 §41) — the request's shape one hop in: the function
|
|
1655
|
+
* throws where the interpreter reports Failure (an answered error or an unreachable backend),
|
|
1656
|
+
* so the two outcome chains are a try/catch's two arms.
|
|
1657
|
+
*
|
|
1658
|
+
* ```
|
|
1659
|
+
* try {
|
|
1660
|
+
* const answer = await callPublishPage({ pageId }); // throws on any failure
|
|
1661
|
+
* setPublishOut(answer); // only where something outside the chain reads a result
|
|
1662
|
+
* …then
|
|
1663
|
+
* } catch (error) {
|
|
1664
|
+
* const message = error instanceof Error ? error.message : String(error);
|
|
1665
|
+
* setPublishError(message); …failThen
|
|
1666
|
+
* }
|
|
1667
|
+
* ```
|
|
1668
|
+
*/
|
|
1669
|
+
| {
|
|
1670
|
+
kind: 'cloud-call';
|
|
1671
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1672
|
+
detached?: true;
|
|
1673
|
+
nodeId: string;
|
|
1674
|
+
/** `src/api/functions.ts`'s exported function for this node. */
|
|
1675
|
+
fnName: string;
|
|
1676
|
+
/** Wired parameters, in the emitted parameter order — `{ pageId: pageIdValue }` at the call. */
|
|
1677
|
+
args: Array<{
|
|
1678
|
+
param: string;
|
|
1679
|
+
expr: ValueExpr;
|
|
1680
|
+
}>;
|
|
1681
|
+
/** The results row, when anything outside the chain reads a result. */
|
|
1682
|
+
materialize?: string;
|
|
1683
|
+
/** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
|
|
1684
|
+
errorState: string;
|
|
1685
|
+
then: HandlerAction[];
|
|
1686
|
+
failThen: HandlerAction[];
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* A `Record`'s `Fetch` (EXP-011 §43) — the Cloud Function shape with one argument, the Id:
|
|
1690
|
+
*
|
|
1691
|
+
* ```
|
|
1692
|
+
* try {
|
|
1693
|
+
* const pageRecord = await fetchPageById(pageId.get());
|
|
1694
|
+
* setPageRow(pageRecord); // only where something outside the chain reads a column
|
|
1695
|
+
* …then
|
|
1696
|
+
* } catch (error) {
|
|
1697
|
+
* const message = error instanceof Error ? error.message : String(error);
|
|
1698
|
+
* setPageError(message); …failThen
|
|
1699
|
+
* }
|
|
1700
|
+
* ```
|
|
1701
|
+
* An empty Id throws `Missing Id.` inside the try — the runtime's own sentence, reported as
|
|
1702
|
+
* Failure before any request (`scheduleFetch`) — so it lands in the catch like every other failure.
|
|
1703
|
+
*/
|
|
1704
|
+
| {
|
|
1705
|
+
kind: 'record-fetch';
|
|
1706
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1707
|
+
detached?: true;
|
|
1708
|
+
nodeId: string;
|
|
1709
|
+
/** The collection module's exported `fetch<Type>ById`. */
|
|
1710
|
+
fnName: string;
|
|
1711
|
+
id: ValueExpr;
|
|
1712
|
+
/** EXP-011 §72. `id` is typed `unknown` — printed `String(id ?? '')`, as `api-call`'s `coerceId`. */
|
|
1713
|
+
coerceId?: 'string';
|
|
1714
|
+
/** The record row, when anything outside the chain reads a column. */
|
|
1715
|
+
materialize?: string;
|
|
1716
|
+
/** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
|
|
1717
|
+
errorState: string;
|
|
1718
|
+
then: HandlerAction[];
|
|
1719
|
+
failThen: HandlerAction[];
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* `Open File Picker`'s `Open` (EXP-011 §45) — `pickFile()` from `src/lib/util.ts`, which is the
|
|
1723
|
+
* node's `<input type=file>` and its two listeners as one promise: a `File` on `change` with a
|
|
1724
|
+
* file, `undefined` on `change` with none or on `cancel`, a rejection where `click()` threw.
|
|
1725
|
+
*
|
|
1726
|
+
* ```
|
|
1727
|
+
* try {
|
|
1728
|
+
* const picked = await pickFile({ accept: 'image/*' });
|
|
1729
|
+
* if (picked === undefined) { …unchangedThen }
|
|
1730
|
+
* else { setPickedFile(picked); …then }
|
|
1731
|
+
* } catch (error) {
|
|
1732
|
+
* const message = error instanceof Error ? error.message : String(error);
|
|
1733
|
+
* setPickError(message); …failThen
|
|
1734
|
+
* }
|
|
1735
|
+
* ```
|
|
1736
|
+
* The row is written only on Done — Unchanged leaves every output exactly as it was, which is
|
|
1737
|
+
* the port's definition, and Failure writes only the Error (openfilepicker.ts).
|
|
1738
|
+
*/
|
|
1739
|
+
| {
|
|
1740
|
+
kind: 'file-pick';
|
|
1741
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1742
|
+
detached?: true;
|
|
1743
|
+
nodeId: string;
|
|
1744
|
+
accept?: ValueExpr;
|
|
1745
|
+
capture?: ValueExpr;
|
|
1746
|
+
/** The chosen-file row, when anything outside the Done chain reads the file or its metadata. */
|
|
1747
|
+
materialize?: string;
|
|
1748
|
+
errorState: string;
|
|
1749
|
+
then: HandlerAction[];
|
|
1750
|
+
unchangedThen: HandlerAction[];
|
|
1751
|
+
failThen: HandlerAction[];
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* `Upload File`'s `Upload` (EXP-011 §45) — the Cloud Function shape with the file as the one
|
|
1755
|
+
* argument: `uploadFile(file, { private })` from `src/api/files.ts`, which is the client's
|
|
1756
|
+
* `POST /files/<name>` with the bytes as the body and `X-NodeGX-File-Private` where asked.
|
|
1757
|
+
* `No file specified` is thrown inside the try where the file is unset — the node's own
|
|
1758
|
+
* sentence, reported as Failure before any request (uploadfile.ts).
|
|
1759
|
+
*/
|
|
1760
|
+
| {
|
|
1761
|
+
kind: 'file-upload';
|
|
1762
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1763
|
+
detached?: true;
|
|
1764
|
+
nodeId: string;
|
|
1765
|
+
file: ValueExpr;
|
|
1766
|
+
/** `Private` — absent where unset or authored false; a literal `true` or the wired expression otherwise. */
|
|
1767
|
+
isPrivate?: ValueExpr;
|
|
1768
|
+
materialize?: string;
|
|
1769
|
+
errorState: string;
|
|
1770
|
+
then: HandlerAction[];
|
|
1771
|
+
failThen: HandlerAction[];
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* `Sign File URL`'s `Sign` (EXP-011 §45) — `signFileUrl(file)` from `src/api/files.ts`, the
|
|
1775
|
+
* client's `GET /files/<name>/sign`; `No file specified` thrown for an unset file, as above.
|
|
1776
|
+
*/
|
|
1777
|
+
| {
|
|
1778
|
+
kind: 'file-sign';
|
|
1779
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1780
|
+
detached?: true;
|
|
1781
|
+
nodeId: string;
|
|
1782
|
+
file: ValueExpr;
|
|
1783
|
+
materialize?: string;
|
|
1784
|
+
errorState: string;
|
|
1785
|
+
then: HandlerAction[];
|
|
1786
|
+
failThen: HandlerAction[];
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* `Now`'s `Read` (EXP-011 Tier 1.3) — re-read the clock, then run the `done` chain.
|
|
1790
|
+
*
|
|
1791
|
+
* ```
|
|
1792
|
+
* const nowRead = new Date(); // bound, so every read in the chain is the SAME instant
|
|
1793
|
+
* setNow(nowRead); // only where something outside the chain reads it
|
|
1794
|
+
* …then
|
|
1795
|
+
* ```
|
|
1796
|
+
*
|
|
1797
|
+
* Synchronous, so `done` is the statements that follow rather than an arm: `_read` flags the
|
|
1798
|
+
* three outputs dirty and reports `done` in the same call, with no branch where it declines.
|
|
1799
|
+
* There is deliberately no failure arm — the node's own comment says reading the clock cannot
|
|
1800
|
+
* fail, and two Reads in the same millisecond still re-read.
|
|
1801
|
+
*
|
|
1802
|
+
* `materialize` is filled in the verdict sweep, never at compile time: a render read of
|
|
1803
|
+
* `Timestamp` resolves passes later, so asking "does anything read this" here answers *no* for
|
|
1804
|
+
* every render read there is (§8.3's allocation-order rule, third instance).
|
|
1805
|
+
*/
|
|
1806
|
+
| {
|
|
1807
|
+
kind: 'date-now-read';
|
|
1808
|
+
nodeId: string;
|
|
1809
|
+
/** The chain-local the instant binds to — what a read inside `then` says. */
|
|
1810
|
+
local: string;
|
|
1811
|
+
/** The state row the instant is written to, where anything outside the chain reads it. */
|
|
1812
|
+
materialize?: string;
|
|
1813
|
+
then: HandlerAction[];
|
|
1814
|
+
} | IdNewAction | CryptoCallAction | StackPushAction | StackPopAction | StackResetAction | ArrayNewAction | LogAction | DelayAction | StatesGoAction | DragSnapAction | ScriptSignalAction | RunTasksAction | QueryFetchAction | StreamAction;
|
|
1815
|
+
/**
|
|
1816
|
+
* A streaming node's action port pulsed (EXP-011 §58) — `<local>.<verb>(<data>)`. `value` is the data port's
|
|
1817
|
+
* expression, read AT THE PULSE (the runtime appends the last delivered Chunk / Data when the pulse lands, and
|
|
1818
|
+
* the chain that pulses may have written it a statement earlier — `runtasks-run`'s rule); absent when the port
|
|
1819
|
+
* has no source, in which case the call takes no argument and the setter never ran. The node's signal outputs
|
|
1820
|
+
* are its listeners ({@link StreamPlan.listeners}), passed once to the hook.
|
|
1821
|
+
*/
|
|
1822
|
+
export type StreamAction = {
|
|
1823
|
+
kind: 'stream-action';
|
|
1824
|
+
nodeId: string;
|
|
1825
|
+
local: string;
|
|
1826
|
+
verb: string;
|
|
1827
|
+
value?: ValueExpr;
|
|
1828
|
+
};
|
|
1829
|
+
/**
|
|
1830
|
+
* A `States` node's `Toggle` or `To <state>` (EXP-011 §49) — one call on the `useStates` handle:
|
|
1831
|
+
* `<local>.toggle()` or `<local>.goTo('<state>')`.
|
|
1832
|
+
*
|
|
1833
|
+
* ⚠️ **No chains ride on the action, and that is the node's own shape, not a shortcut.** Done,
|
|
1834
|
+
* Unchanged and Failure are *ports of the node*, pulsed by `reportOutcome` whichever trigger asked
|
|
1835
|
+
* — a chain wired off `done` fires for a Toggle and for every `To <state>` alike — so they are
|
|
1836
|
+
* listeners on the node ({@link StatesPlan.listeners}), compiled once, not arms printed at each
|
|
1837
|
+
* call site. The interpreter drains requests after the pass, so the outcome arrives from a
|
|
1838
|
+
* microtask; the handler that asked has already finished, exactly as it has there.
|
|
1839
|
+
*/
|
|
1840
|
+
export type StatesGoAction = {
|
|
1841
|
+
kind: 'states-go';
|
|
1842
|
+
nodeId: string;
|
|
1843
|
+
/** The `useStates` local. */
|
|
1844
|
+
local: string;
|
|
1845
|
+
verb: 'toggle' | 'to';
|
|
1846
|
+
/** The state a `To <state>` names; absent for Toggle. */
|
|
1847
|
+
state?: string;
|
|
1848
|
+
};
|
|
1849
|
+
/**
|
|
1850
|
+
* A `Drag`'s Snap To Position X / Y — Do (EXP-011 §63): one call on the handle, always an expression. Its Done is the
|
|
1851
|
+
* node's listener — `outcomeOnInnerComponent` reports `done` for every snap call whichever port asked — so it is passed
|
|
1852
|
+
* once to the hook, not printed as an arm here; Failure cannot fire in the emitted app and is dropped with a note.
|
|
1853
|
+
*/
|
|
1854
|
+
export type DragSnapAction = {
|
|
1855
|
+
kind: 'drag-snap';
|
|
1856
|
+
nodeId: string;
|
|
1857
|
+
/** The `useDrag` local. */
|
|
1858
|
+
local: string;
|
|
1859
|
+
axis: 'x' | 'y';
|
|
1860
|
+
};
|
|
1861
|
+
/**
|
|
1862
|
+
* A `Log`'s `Log` (EXP-011 §39) — `log(level, message, data)` into `src/lib/util.ts`, then the
|
|
1863
|
+
* Done chain as following statements (`popup-show`'s treatment: the write is synchronous and has
|
|
1864
|
+
* one outcome, so the chain is not an arm).
|
|
1865
|
+
*
|
|
1866
|
+
* `message` is `undefined` where nothing is authored or wired — the helper prints `''` for it,
|
|
1867
|
+
* as `_write` does. `data` is absent (not `undefined`) where the port is unset, because the
|
|
1868
|
+
* runtime writes the data argument only when one arrived, and `console.info('x', undefined)`
|
|
1869
|
+
* prints a trailing `undefined` the interpreter never prints.
|
|
1870
|
+
*/
|
|
1871
|
+
export type LogAction = {
|
|
1872
|
+
kind: 'log';
|
|
1873
|
+
nodeId: string;
|
|
1874
|
+
level: LogLevel;
|
|
1875
|
+
message: ValueExpr;
|
|
1876
|
+
data?: ValueExpr;
|
|
1877
|
+
then: HandlerAction[];
|
|
1878
|
+
};
|
|
1879
|
+
/**
|
|
1880
|
+
* One of a `Delay`'s three actions (EXP-011 §39) — `startDelay`/`restartDelay`/`stopDelay` from
|
|
1881
|
+
* `src/lib/timer.ts` over a `useRef` the component declares per node.
|
|
1882
|
+
*
|
|
1883
|
+
* Four chains, and which ones an arm prints depends on the verb:
|
|
1884
|
+
*
|
|
1885
|
+
* | verb | `then` (Done) | `unchangedThen` | `startedThen` / `finishedThen` |
|
|
1886
|
+
* |---|---|---|---|
|
|
1887
|
+
* | `start` | began a countdown | one was already running | passed as the two callbacks |
|
|
1888
|
+
* | `restart` | always | never — Restart cannot no-op (timer.ts) | passed as the two callbacks |
|
|
1889
|
+
* | `stop` | abandoned a running countdown | nothing was running | not this verb's — a stopped countdown never fires them |
|
|
1890
|
+
*
|
|
1891
|
+
* `startDelay` and `duration` are read where the handler is, so a `Set Variable` earlier in the
|
|
1892
|
+
* chain reaches them (the snapshot rule). The two callback chains run *later*, in a timeout, and
|
|
1893
|
+
* close over the same handler scope — the shape `http-call`'s continuations already have.
|
|
1894
|
+
*/
|
|
1895
|
+
export type DelayAction = {
|
|
1896
|
+
kind: 'delay';
|
|
1897
|
+
nodeId: string;
|
|
1898
|
+
verb: TimerTrigger;
|
|
1899
|
+
/** The `useRef` local this node's timer handle lives in — one per node, shared by its verbs. */
|
|
1900
|
+
ref: string;
|
|
1901
|
+
startDelay: ValueExpr;
|
|
1902
|
+
duration: ValueExpr;
|
|
1903
|
+
then: HandlerAction[];
|
|
1904
|
+
unchangedThen: HandlerAction[];
|
|
1905
|
+
startedThen: HandlerAction[];
|
|
1906
|
+
finishedThen: HandlerAction[];
|
|
1907
|
+
};
|
|
1908
|
+
/**
|
|
1909
|
+
* A `Unique Id`'s or `UUID`'s `New` (EXP-011 §37) — `date-now-read`'s shape, with a failure arm
|
|
1910
|
+
* on one of the two nodes and none on the other.
|
|
1911
|
+
*
|
|
1912
|
+
* 🔴 **One kind for both nodes, discriminated by `fn`.** §9.4 counted eleven sites that enumerate
|
|
1913
|
+
* this union and named the five the compiler does not hold; a second action kind would have paid
|
|
1914
|
+
* that toll twice for two nodes whose only structural difference is whether `failThen` can be
|
|
1915
|
+
* non-empty. `ID_NODES.canFail` is the fact, and it is stated once.
|
|
1916
|
+
*
|
|
1917
|
+
* Like `date-now-read`, `materialize` and `errorMaterialize` are filled in the verdict sweep and
|
|
1918
|
+
* never at compile time — a render read of `Id` resolves passes later, so asking "does anything
|
|
1919
|
+
* read this" here answers *no* for every render read there is (§8.3's allocation-order rule).
|
|
1920
|
+
*/
|
|
1921
|
+
/**
|
|
1922
|
+
* EXP-011 §55. A mutator's Array Id fed by a `Create New Array`'s `id` — the handle, never a string.
|
|
1923
|
+
*
|
|
1924
|
+
* `stateName` is the minting component's row; `viaLocal` is set when the mutator sits inside the mint's own
|
|
1925
|
+
* Done chain (the chain-local is the array just made). Outside that chain the handle can be `null` — no Do
|
|
1926
|
+
* has fired — which is the runtime's `<prefix>/no-array` Failure: the emitter prints the guard, the raise on
|
|
1927
|
+
* the error channel (§54) with the node's own message, and `failThen`. `Remove` never guards: its row exists
|
|
1928
|
+
* only because the handle did (§30's gate 3, restated over the handle).
|
|
1929
|
+
*/
|
|
1930
|
+
export type MintedTarget = {
|
|
1931
|
+
/** The `Create New Array` node. */
|
|
1932
|
+
nodeId: string;
|
|
1933
|
+
stateName: string;
|
|
1934
|
+
viaLocal?: string;
|
|
1935
|
+
/** The mutator node — the raise's provenance. */
|
|
1936
|
+
sinkId: string;
|
|
1937
|
+
action: 'insert' | 'clear' | 'remove';
|
|
1938
|
+
failThen: HandlerAction[];
|
|
1939
|
+
};
|
|
1940
|
+
/**
|
|
1941
|
+
* EXP-011 §55. `Create New Array`'s Do: `const <local> = collection<T>([...<source>]); set<Row>(<local>);`
|
|
1942
|
+
* then the Done chain. `source` absent is the empty literal (`items` never received). Every Do mints another
|
|
1943
|
+
* array, exactly as `Collection.get()` with no name does.
|
|
1944
|
+
*/
|
|
1945
|
+
export type ArrayNewAction = {
|
|
1946
|
+
kind: 'array-new';
|
|
1947
|
+
nodeId: string;
|
|
1948
|
+
local: string;
|
|
1949
|
+
stateName: string;
|
|
1950
|
+
rowType: string;
|
|
1951
|
+
source?: ValueExpr;
|
|
1952
|
+
/** Whether `source` is statically a list; an untyped source is copied when it is an array and empty otherwise. */
|
|
1953
|
+
sourceIsList?: boolean;
|
|
1954
|
+
then: HandlerAction[];
|
|
1955
|
+
};
|
|
1956
|
+
/** EXP-011 §55. One `Create New Array`'s handle row, and the row type the emitter spells it with. */
|
|
1957
|
+
export interface MintedArrayPlan {
|
|
1958
|
+
nodeId: string;
|
|
1959
|
+
stateName: string;
|
|
1960
|
+
/** `any`, or a named array's interface when the snapshot source is that array (or a filter over it) and nothing inserts. */
|
|
1961
|
+
rowType: string;
|
|
1962
|
+
/** The named array whose interface `rowType` names, for the import. */
|
|
1963
|
+
rowCollection?: string;
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* A `Hash`'s Do or a `Random Bytes`' New (EXP-011 §59) — {@link IdNewAction}'s two-arm shape with a
|
|
1967
|
+
* value row that boots empty, a raise in the Failure arm, and (Hash) an `await`.
|
|
1968
|
+
*/
|
|
1969
|
+
export type CryptoCallAction = {
|
|
1970
|
+
kind: 'crypto-call';
|
|
1971
|
+
/** EXP-011 §90, as `api-call`'s. */
|
|
1972
|
+
detached?: true;
|
|
1973
|
+
nodeId: string;
|
|
1974
|
+
node: CryptoNodeKind;
|
|
1975
|
+
fn: 'tryHash' | 'tryRandomBytes' | 'tryExtract';
|
|
1976
|
+
/** The raised code — `hash/failed` / `random-bytes/failed` (the node files). */
|
|
1977
|
+
code: string;
|
|
1978
|
+
/** Whether the call is awaited (`crypto.subtle.digest` is a promise; `getRandomValues` is not). */
|
|
1979
|
+
async: boolean;
|
|
1980
|
+
/**
|
|
1981
|
+
* The verb's arguments in port order. A wire is the expression the handler closes over, a literal
|
|
1982
|
+
* is the literal, and an input nobody set is absent — the lib applies the node's own fallback
|
|
1983
|
+
* there, as `_run` / `_generate` do. `numeric` marks the one argument printed inside `Number(…)`
|
|
1984
|
+
* (Length's setter), and only where a wire delivers it.
|
|
1985
|
+
*/
|
|
1986
|
+
inputs: Array<{
|
|
1987
|
+
port: string;
|
|
1988
|
+
expr?: ValueExpr;
|
|
1989
|
+
numeric?: true;
|
|
1990
|
+
}>;
|
|
1991
|
+
/** The chain-local the call binds — the whole result; the Done arm reads `.digest` / `.value`, the Failure arm `.error`. */
|
|
1992
|
+
local: string;
|
|
1993
|
+
/** The value row, where anything outside the chain reads it. Allocated by a read (§14.2's rule). */
|
|
1994
|
+
materialize?: string;
|
|
1995
|
+
/** The Error row, allocated by a read. */
|
|
1996
|
+
errorMaterialize?: string;
|
|
1997
|
+
/**
|
|
1998
|
+
* EXP-011 §76. A `Pattern Extractor`'s rows, one per value output something reads outside the chain —
|
|
1999
|
+
* written from the local's field of the same name BEFORE the arms, on every run, as `doExtract` writes them.
|
|
2000
|
+
*/
|
|
2001
|
+
materializeByPort?: Array<{
|
|
2002
|
+
port: string;
|
|
2003
|
+
state: string;
|
|
2004
|
+
}>;
|
|
2005
|
+
then: HandlerAction[];
|
|
2006
|
+
failThen: HandlerAction[];
|
|
2007
|
+
/** EXP-011 §76. The `Found` / `Not Found` chains — printed inside the Done arm, ahead of `then`. Empty for the two verbs. */
|
|
2008
|
+
foundThen: HandlerAction[];
|
|
2009
|
+
notFoundThen: HandlerAction[];
|
|
2010
|
+
};
|
|
2011
|
+
/**
|
|
2012
|
+
* A `Push Component To Stack`'s Navigate (EXP-011 §61) — `navigate.ts` `navigate()`, printed as the runtime's own call:
|
|
2013
|
+
* `pushComponent(name, { target, params, backCallback, hasNavigated, hasUnchanged })` (or `replaceComponent`, which
|
|
2014
|
+
* installs no callback — `replaceAsync` never does). The three failures are excluded statically (a literal Target inside
|
|
2015
|
+
* the stack's Components list, no transitions), so `hasFailed` is never printed and a Failure wire is dropped with a note.
|
|
2016
|
+
*/
|
|
2017
|
+
export type StackPushAction = {
|
|
2018
|
+
kind: 'stack-push';
|
|
2019
|
+
nodeId: string;
|
|
2020
|
+
/** The stack's authored name (`'Main'` by default) — the registry key. */
|
|
2021
|
+
stack: string;
|
|
2022
|
+
mode: 'push' | 'replace';
|
|
2023
|
+
/** The target page id, resolved statically against the stack's Components list. */
|
|
2024
|
+
target: string;
|
|
2025
|
+
/** The component the target page shows — the emit resolves each param's prop identifier off its plan. */
|
|
2026
|
+
targetLegacy: string;
|
|
2027
|
+
/** The target's Component Inputs the pusher sets, by port; the prop identifier is the target's own, read at emit. */
|
|
2028
|
+
params: Array<{
|
|
2029
|
+
port: string;
|
|
2030
|
+
expr: ValueExpr;
|
|
2031
|
+
coerce?: 'string';
|
|
2032
|
+
}>;
|
|
2033
|
+
/** The pusher's local stem — the callback's `results` argument is `<local>Results`. */
|
|
2034
|
+
local: string;
|
|
2035
|
+
/** The back-results row, where anything outside the back chains reads a `backResult-<k>`. Allocated by a read. */
|
|
2036
|
+
backResults?: string;
|
|
2037
|
+
/** The `backAction-<a>` chains, by action, in wire order. */
|
|
2038
|
+
backActions: Array<{
|
|
2039
|
+
action: string;
|
|
2040
|
+
then: HandlerAction[];
|
|
2041
|
+
}>;
|
|
2042
|
+
then: HandlerAction[];
|
|
2043
|
+
unchangedThen: HandlerAction[];
|
|
2044
|
+
};
|
|
2045
|
+
/**
|
|
2046
|
+
* A `Pop Component Stack`'s Navigate or one of its `backAction-<a>` inputs (EXP-011 §61) — `navigate-back.ts`
|
|
2047
|
+
* `navigate()`: `const <local> = popComponent(pageStackEntry, { backAction, results })`, the Done arm, and the
|
|
2048
|
+
* Failure arm, which raises `pop-component-stack/no-stack-in-scope` on the channel (the one failure the export can
|
|
2049
|
+
* reach — `transition-in-progress` needs a transition). Always the block form, on the crypto verbs' reason.
|
|
2050
|
+
*/
|
|
2051
|
+
export type StackPopAction = {
|
|
2052
|
+
kind: 'stack-pop';
|
|
2053
|
+
nodeId: string;
|
|
2054
|
+
/** The back action this pop was asked under, or none for the plain Navigate. */
|
|
2055
|
+
backAction?: string;
|
|
2056
|
+
/** The `result-<k>` values, read where the setters stored them — the render expression the handler closes over. */
|
|
2057
|
+
results: Array<{
|
|
2058
|
+
key: string;
|
|
2059
|
+
expr: ValueExpr;
|
|
2060
|
+
}>;
|
|
2061
|
+
local: string;
|
|
2062
|
+
then: HandlerAction[];
|
|
2063
|
+
failThen: HandlerAction[];
|
|
2064
|
+
};
|
|
2065
|
+
/** A `Component Stack`'s Reset (EXP-011 §61): `<local>.reset()` then the Done chain — `resetAsync` reports done last. */
|
|
2066
|
+
export type StackResetAction = {
|
|
2067
|
+
kind: 'stack-reset';
|
|
2068
|
+
nodeId: string;
|
|
2069
|
+
local: string;
|
|
2070
|
+
then: HandlerAction[];
|
|
2071
|
+
};
|
|
2072
|
+
export type IdNewAction = {
|
|
2073
|
+
kind: 'id-new';
|
|
2074
|
+
nodeId: string;
|
|
2075
|
+
fn: 'randomId' | 'randomUuid';
|
|
2076
|
+
/**
|
|
2077
|
+
* The chain-local the `New` binds to.
|
|
2078
|
+
*
|
|
2079
|
+
* ⚠️ **It is not the id in both cases.** For `randomId` the local *is* the string; for
|
|
2080
|
+
* `randomUuid` it is the `UuidResult`, and a read of `Id` inside the Done chain is
|
|
2081
|
+
* `<local>.uuid` — which narrows to `string` there and only there. `idLocalReadOf` is the one
|
|
2082
|
+
* place that spelling lives, so the expression side and the action side cannot disagree.
|
|
2083
|
+
*/
|
|
2084
|
+
local: string;
|
|
2085
|
+
/** The id row, where anything outside the chain reads it. */
|
|
2086
|
+
materialize?: string;
|
|
2087
|
+
/**
|
|
2088
|
+
* `UUID` only — the `Error` row, allocated by a *read* rather than by the node (§14.2's rule).
|
|
2089
|
+
* A UUID whose Error nobody reads emits no row and no clearing setter.
|
|
2090
|
+
*/
|
|
2091
|
+
errorMaterialize?: string;
|
|
2092
|
+
then: HandlerAction[];
|
|
2093
|
+
/** `UUID` only. Always empty for `Unique Id`, which has no Failure port to wire. */
|
|
2094
|
+
failThen: HandlerAction[];
|
|
2095
|
+
};
|
|
2096
|
+
/**
|
|
2097
|
+
* One argument of an {@link HandlerAction} `api-call`: either a plain value, or the object
|
|
2098
|
+
* literal a record verb's `prop-*` set (and a Sign Up's credentials) collapses into.
|
|
2099
|
+
*/
|
|
2100
|
+
export type ApiCallArg = {
|
|
2101
|
+
kind: 'expr';
|
|
2102
|
+
expr: ValueExpr;
|
|
2103
|
+
} | {
|
|
2104
|
+
kind: 'data';
|
|
2105
|
+
props: Array<{
|
|
2106
|
+
key: string;
|
|
2107
|
+
expr: ValueExpr;
|
|
2108
|
+
}>;
|
|
2109
|
+
};
|
|
2110
|
+
/**
|
|
2111
|
+
* One re-hosted Function/Expression node (EXP-003-JS-TARGET-OUTPUT §4): the verbatim body plus
|
|
2112
|
+
* everything the wrapper reproduces of the runtime's contract — inputs arriving as a record,
|
|
2113
|
+
* assignments to `Outputs` publishing, script-declared signal outputs callable (as no-ops when
|
|
2114
|
+
* nothing consumes them, exactly as an unwired pulse lands nowhere).
|
|
2115
|
+
*/
|
|
2116
|
+
/**
|
|
2117
|
+
* A node whose authored JavaScript did not reach the emitted code, preserved so the repo carries
|
|
2118
|
+
* a record of it. Not a `JsFunctionPlan`: these nodes have no wrapper and no ports mined from the
|
|
2119
|
+
* body — the script is a *parameter* of a node that does something else (a Map Collection's
|
|
2120
|
+
* mapping, a Repeater's template, a Script node's code).
|
|
2121
|
+
*/
|
|
2122
|
+
export interface RefusedScriptPlan {
|
|
2123
|
+
nodeId: string;
|
|
2124
|
+
/** The graph type, for the comment's noun — "Map Collection", "Javascript2". */
|
|
2125
|
+
typeName: string;
|
|
2126
|
+
/** The author's label for the node, when it has one. */
|
|
2127
|
+
label?: string;
|
|
2128
|
+
/** Verbatim, from `NodeIR.sourceText`. Never trimmed, never reformatted. */
|
|
2129
|
+
source: string;
|
|
2130
|
+
}
|
|
2131
|
+
export interface JsFunctionPlan {
|
|
2132
|
+
nodeId: string;
|
|
2133
|
+
kind: 'function' | 'expression' | 'visual';
|
|
2134
|
+
/** Module-scope wrapper name — the sanitized node label, deduped per component file. */
|
|
2135
|
+
fnName: string;
|
|
2136
|
+
/** The verbatim body (Function), expression text (Expression) or generated code (Visual). */
|
|
2137
|
+
body: string;
|
|
2138
|
+
/**
|
|
2139
|
+
* The wrapper's input record, in script order: wire-fed inputs carry the resolved source,
|
|
2140
|
+
* literal `in-*` parameters fold, mined-but-unfed inputs stay fields so the body's reads
|
|
2141
|
+
* typecheck (they read `undefined`, exactly as a never-delivered runtime input does).
|
|
2142
|
+
*/
|
|
2143
|
+
inputs: Array<{
|
|
2144
|
+
name: string;
|
|
2145
|
+
tsType: string;
|
|
2146
|
+
expr?: ValueExpr;
|
|
2147
|
+
}>;
|
|
2148
|
+
/** Function only: the Outputs record's value fields (mined + declared + consumed). */
|
|
2149
|
+
outputs: Array<{
|
|
2150
|
+
name: string;
|
|
2151
|
+
tsType: string;
|
|
2152
|
+
}>;
|
|
2153
|
+
/** Function only: signal outputs (mined call syntax / `outtype-*: signal`), seeded no-op. */
|
|
2154
|
+
signals: string[];
|
|
2155
|
+
/** Expression only: preamble Math aliases the expression references (`pi` → `Math.PI`). */
|
|
2156
|
+
mathAliases: string[];
|
|
2157
|
+
/**
|
|
2158
|
+
* Visual only: app-wide Variables the block program reads or writes, from the workspace's
|
|
2159
|
+
* `noodl_get_variable`/`noodl_set_variable` fields — never mined from the generated text.
|
|
2160
|
+
*
|
|
2161
|
+
* These become the `Noodl.Variables` facade the wrapper binds (LOGIC-BUILDER-TARGET §3.4):
|
|
2162
|
+
* the body keeps its verbatim `Noodl.Variables["x"]` reads and writes, and they land on the
|
|
2163
|
+
* export's own variables store. Every name here is registered in `ProjectPlan.variables`,
|
|
2164
|
+
* minted by the block program where no `Variable` node declares it.
|
|
2165
|
+
*/
|
|
2166
|
+
variables?: string[];
|
|
2167
|
+
/**
|
|
2168
|
+
* P109 ISL-005. Function only, present when its `uses` lists script files: each file the body
|
|
2169
|
+
* reaches as `Scripts.<alias>`, in the order listed. The wrapper binds `Scripts` from the one
|
|
2170
|
+
* shared registry (`src/lib/projectScriptFiles.ts`); the files are emitted once, never pasted here.
|
|
2171
|
+
*/
|
|
2172
|
+
scripts?: ScriptUsePlan[];
|
|
2173
|
+
/**
|
|
2174
|
+
* reactive — no `run` wire: one render local per instance, recomputed per render (grade Q).
|
|
2175
|
+
* invoked — `run` wired: calls inline inside its own handler chain only (grade I).
|
|
2176
|
+
*/
|
|
2177
|
+
mode: 'reactive' | 'invoked';
|
|
2178
|
+
/**
|
|
2179
|
+
* EXP-019. Function only: the host traits the body carries ("reads the clock (Date)", "keeps state on `this`", …,
|
|
2180
|
+
* `jsHostTraitsOf`). Present ⇒ the run is `pulsed` (a kept node, one run per trigger, outputs read from the node's
|
|
2181
|
+
* record), because a body with a trait must never run at a read site. A `this` trait binds the body to the node's
|
|
2182
|
+
* `self`. In `reactive` mode (no Run wire, slice 2) the trigger is its inputs' arrival and the load (`TickedRunPlan.atLoad`).
|
|
2183
|
+
*/
|
|
2184
|
+
host?: string[];
|
|
2185
|
+
/**
|
|
2186
|
+
* EXP-019 (session 133). Function only: the body may write an output or send a signal after its run has returned, from a
|
|
2187
|
+
* timer or a listener it sets up (`jsCallsBackOf`). Its kept node then hands those to the component (`FunctionNode.late`),
|
|
2188
|
+
* which writes the row and runs the signal's chain, as the runtime's node delivers them when they happen.
|
|
2189
|
+
*/
|
|
2190
|
+
late?: true;
|
|
2191
|
+
/**
|
|
2192
|
+
* EXP-019 slice 1b (session 134). Function only: the body awaits at its top level (`jsAwaitsAtTopLevel`). Its wrapper is
|
|
2193
|
+
* `async`, its run hands back the promise (`FunctionRun.settled`), and Success and Done run after it settles, as the
|
|
2194
|
+
* runtime's `runScript` awaits the body before sending them. What it writes after its first `await` arrives `late`.
|
|
2195
|
+
*/
|
|
2196
|
+
awaits?: true;
|
|
2197
|
+
/**
|
|
2198
|
+
* EXP-020 (session 134). Function only: the Noodl API members the body reads (`jsNoodlUseOf`), and the Variables it
|
|
2199
|
+
* reads or writes by name. The wrapper binds `Noodl` to `src/lib/noodl.ts`'s object, with a facade over each Variable.
|
|
2200
|
+
*/
|
|
2201
|
+
noodl?: {
|
|
2202
|
+
members: string[];
|
|
2203
|
+
variables: string[];
|
|
2204
|
+
};
|
|
2205
|
+
/**
|
|
2206
|
+
* EXP-020 slice 2 (session 135). Function only: the body reads `Component.Object`, its component instance's record. The
|
|
2207
|
+
* component keeps one scope per mount (`src/lib/componentScope.ts`), its kept nodes carry it, and `runFunction` hands it
|
|
2208
|
+
* to the body as the runtime hands it the fourth parameter. Always `pulsed` (it is a host trait).
|
|
2209
|
+
*/
|
|
2210
|
+
component?: true;
|
|
2211
|
+
/**
|
|
2212
|
+
* EXP-011 §86. Function only, invoked only: a run of it sends a signal of its own that a chain consumes, or (P18-S121-
|
|
2213
|
+
* FRESHOUTPUTS) it materializes a row a reader reads between runs. The wrapper then takes its `Outputs` record as a
|
|
2214
|
+
* parameter, so `runFunction` can hand it the recording one, and the component keeps the node's last values across
|
|
2215
|
+
* runs (`functionNode`), as the runtime's node does.
|
|
2216
|
+
*/
|
|
2217
|
+
pulsed?: true;
|
|
2218
|
+
/**
|
|
2219
|
+
* P18-S110-EVALATLOAD. Expression only, reactive only: present when nothing is sure to arrive at
|
|
2220
|
+
* load — no input has a literal (a parameter is set at load, so it always arrives) and the body
|
|
2221
|
+
* reads at least one port. `unset` names the wire-fed inputs; while every one reads `undefined`
|
|
2222
|
+
* nothing has arrived, because the runtime never delivers `undefined` over a wire.
|
|
2223
|
+
*
|
|
2224
|
+
* That is the state GAM-001's load evaluation runs in (`expression.ts` `_scheduleEvaluateExpression`):
|
|
2225
|
+
* ticked (`evaluate`, the default) it evaluates over the unset inputs but publishes no answer for
|
|
2226
|
+
* a throw or a NaN — Result stays `null`, where an arrival's evaluation answers `0` and `NaN`;
|
|
2227
|
+
* unticked (`wait`) it publishes nothing until an input arrives.
|
|
2228
|
+
*/
|
|
2229
|
+
loadGate?: {
|
|
2230
|
+
unset: string[];
|
|
2231
|
+
atLoad: 'evaluate' | 'wait';
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* P18-S126-INSIGNAL (EXP-011 §94). A signal input's chain: the actions the Component Inputs port's wires describe, in wire
|
|
2236
|
+
* order, run from an effect keyed on the pulse count the parent passes (`InstancePulsePlan`).
|
|
2237
|
+
*/
|
|
2238
|
+
export interface InputPulsePlan {
|
|
2239
|
+
port: string;
|
|
2240
|
+
actions: HandlerAction[];
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* P18-S126-INSIGNAL. A placed component's signal input the parent pulses: the count (`stateName`, a state row) and the
|
|
2244
|
+
* count the child has acted on (`takenLocal`, a ref the parent keeps, so it outlives the child's unmount). Passed as one
|
|
2245
|
+
* prop, `{ sent, taken }`: the child runs its chain when `sent` passes `taken`, at a mount too (the runtime's instance exists
|
|
2246
|
+
* while it is not drawn and takes the pulse then), and never twice for one pulse (StrictMode's second mount included).
|
|
2247
|
+
*/
|
|
2248
|
+
export interface InstancePulsePlan {
|
|
2249
|
+
nodeId: string;
|
|
2250
|
+
port: string;
|
|
2251
|
+
stateName: string;
|
|
2252
|
+
takenLocal: string;
|
|
2253
|
+
/**
|
|
2254
|
+
* P18-S127-SIBLINGVALUE (EXP-011 §96). Set when the placed component also reads an input the parent lifts from another
|
|
2255
|
+
* placed component: the count it is handed (`sent`) is a second row that follows the count from the parent's own
|
|
2256
|
+
* effect, one commit later. A lifted value reaches the parent from the child's push effect, which runs in the commit the
|
|
2257
|
+
* pulse lands in; the parent's effects run after its children's, so the delivered count and the lifted value arrive in
|
|
2258
|
+
* the same render, and the chain inside reads the value sent with the pulse, as the runtime's does.
|
|
2259
|
+
*/
|
|
2260
|
+
delivered?: {
|
|
2261
|
+
name: string;
|
|
2262
|
+
setterName: string;
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
/** P18-S126-INSIGNAL. The TypeScript type of a signal input's prop, the pair the parent passes. */
|
|
2266
|
+
export declare const INPUT_PULSE_TS_TYPE = "{ sent: number; taken: { current: number } }";
|
|
2267
|
+
export interface ReceiverPlan {
|
|
2268
|
+
nodeId: string;
|
|
2269
|
+
channelName: string;
|
|
2270
|
+
/** Actions in trigger-wire source order — statement order in the useSignal handler. */
|
|
2271
|
+
actions: HandlerAction[];
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* One `useState` row in the component (CONTROLLED-STATE-TARGET §3.1) — the first construct
|
|
2275
|
+
* that materializes state in the emitted component rather than compiling it away. Names live
|
|
2276
|
+
* in the component's one identifier space (props, hooks, wrappers — the stores rule).
|
|
2277
|
+
*/
|
|
2278
|
+
export interface StateVarPlan {
|
|
2279
|
+
name: string;
|
|
2280
|
+
setterName: string;
|
|
2281
|
+
/** The useState type parameter, `' | undefined'` included where the boot is undefined. */
|
|
2282
|
+
tsType: string;
|
|
2283
|
+
/** Boot value; null is the `undefined` boot (`useState<T | undefined>()`). */
|
|
2284
|
+
boot: string | number | boolean | null;
|
|
2285
|
+
/**
|
|
2286
|
+
* An initializer expression emitted verbatim, used **instead of** `boot` (EXP-011 Tier 1.3).
|
|
2287
|
+
*
|
|
2288
|
+
* 🔴 The one row that needs it is `Now`'s, and it needs it to be a *lazy* initializer:
|
|
2289
|
+
* `useState(() => new Date())` constructs once per mount, which is what `initialize` does,
|
|
2290
|
+
* where `useState(new Date())` would construct on every render. No other origin has a boot
|
|
2291
|
+
* value that is not a literal, which is why this is an exception rather than the field's
|
|
2292
|
+
* general form.
|
|
2293
|
+
*/
|
|
2294
|
+
bootCode?: string;
|
|
2295
|
+
/**
|
|
2296
|
+
* The emitted-library helper {@link bootCode} calls, where it calls one (EXP-011 §37).
|
|
2297
|
+
*
|
|
2298
|
+
* 🔴 **The import is earned from this field and never by reading `bootCode`.** `bootCode` is a
|
|
2299
|
+
* string emitted verbatim, and the two id rows are the first whose initializer calls into
|
|
2300
|
+
* `src/lib/id.ts` — every other earn site in `component.ts` walks *expressions*, and a row's
|
|
2301
|
+
* boot is not an expression it can see. A `Unique Id` with nothing on `New` has a row and no
|
|
2302
|
+
* action at all, so there is nothing else left to derive the import from.
|
|
2303
|
+
*/
|
|
2304
|
+
bootHelper?: IdHelper;
|
|
2305
|
+
originNodeId: string;
|
|
2306
|
+
origin: 'switch' | 'counter' | 'control' | 'lifted' | 'jsfun' | 'record-error' | 'variable' | 'http' | 'http-error'
|
|
2307
|
+
/** EXP-011 §41 — a `Cloud Function`'s results row and its Error row. */
|
|
2308
|
+
| 'cloud' | 'cloud-error'
|
|
2309
|
+
/** EXP-011 §43 — a `Record`'s row and its Error row. */
|
|
2310
|
+
| 'record-row' | 'record-row-error'
|
|
2311
|
+
/** EXP-011 §45 — a files node's answer row (a `File`, a `CloudFile`, a `SignedFileUrl`) and its Error row. */
|
|
2312
|
+
| 'file' | 'file-error' | 'external-link-error' | 'navigate-path-error' | 'now'
|
|
2313
|
+
/** EXP-011 §37 — the id a `Unique Id` or a `UUID` holds. */
|
|
2314
|
+
| 'id' | 'array'
|
|
2315
|
+
/** EXP-011 §37 — a `UUID`'s Error. `Unique Id` has no Failure port and never allocates one. */
|
|
2316
|
+
| 'id-error'
|
|
2317
|
+
/** EXP-011 §59 — a `Hash`'s Digest / a `Random Bytes`' Value row, and its Error row. Both allocated by a read. */
|
|
2318
|
+
| 'crypto' | 'crypto-error'
|
|
2319
|
+
/** EXP-011 §61 — a pusher's back-results row (`Record<string, unknown>`, boots `{}`). */
|
|
2320
|
+
| 'stack-back'
|
|
2321
|
+
/** EXP-011 §87 — a Mounted or Visible written by the Evaluate of the unticked Conditions wired into it. */
|
|
2322
|
+
| 'condition'
|
|
2323
|
+
/** EXP-011 §99 — a repeater's relayed row value, written in the rows' relayed-signal callbacks. */
|
|
2324
|
+
| 'relay';
|
|
2325
|
+
/** The provenance comment above the row. */
|
|
2326
|
+
comment: string;
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* The graph path of a wired control-state input (CONTROLLED-STATE-TARGET §3.4): a useEffect
|
|
2330
|
+
* running the *input setter's* semantics — coercion, abstain guards, clamping, per §1's table —
|
|
2331
|
+
* and never the `Changed` chain (the runtime's own asymmetry).
|
|
2332
|
+
*/
|
|
2333
|
+
export interface SyncEffectPlan {
|
|
2334
|
+
stateName: string;
|
|
2335
|
+
source: ValueExpr;
|
|
2336
|
+
/**
|
|
2337
|
+
* The `variable-*` forms are the value Variables (EXP-011 Tier 1.4) reaching the same
|
|
2338
|
+
* construct from the other direction: their `value` input under Run On Value Change is a
|
|
2339
|
+
* graph path that stores without firing `Changed`, exactly as a control's is, so
|
|
2340
|
+
* `variablebase.setValueTo`'s table — abstain on `undefined`, `Treat empty as` on `null`,
|
|
2341
|
+
* `args.cast` otherwise, `NaN` banned — is written here as one more coercion. `variable-none`
|
|
2342
|
+
* is Color's identity cast.
|
|
2343
|
+
*/
|
|
2344
|
+
coerce: 'checkbox' | 'slider' | 'dropdown' | 'textinput' | 'variable-string' | 'variable-number' | 'variable-boolean' | 'variable-none';
|
|
2345
|
+
/** Slider only: the literal clamp bounds (a wired min/max defers the node before this). */
|
|
2346
|
+
min?: number;
|
|
2347
|
+
max?: number;
|
|
2348
|
+
/** `variable-*` only: what a `null` arrival stores — the selected `Treat empty as` coercion. */
|
|
2349
|
+
empty?: string | number | boolean | null;
|
|
2350
|
+
}
|
|
2351
|
+
/**
|
|
2352
|
+
* The lifted value output's child side (CONTROLLED-STATE-TARGET §3.5, CO §6 built): a push
|
|
2353
|
+
* effect firing the optional callback prop on change and once at mount — the boot delivery a
|
|
2354
|
+
* parent wire gets from the interpreter, so the mount fire is the faithful part.
|
|
2355
|
+
*/
|
|
2356
|
+
export interface PushEffectPlan {
|
|
2357
|
+
prop: string;
|
|
2358
|
+
expr: ValueExpr;
|
|
2359
|
+
/**
|
|
2360
|
+
* P18-S122-TPLBUILD (TS2345). The port's declared type, when the pushed value's static type is not it (an untyped
|
|
2361
|
+
* Variable is `unknown`, an Expression's answer keeps `null` for a NaN at load): the push says the port's type, as a
|
|
2362
|
+
* cast. The runtime's Component Outputs hands the value on as it is, and so does the cast; it changes no value.
|
|
2363
|
+
*/
|
|
2364
|
+
castTo?: string;
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* A reactive Condition (LOGIC-TARGET §10): the box is ticked, so the node re-tests on every
|
|
2368
|
+
* arrival on `condition` and fires exactly one arm — a re-run keyed on the condition, which is
|
|
2369
|
+
* a `useEffect`, not a handler. The Evaluate-only Condition is the other half and compiles to a
|
|
2370
|
+
* `branch` action inside whatever handler drives it (§3).
|
|
2371
|
+
*
|
|
2372
|
+
* `action` is always a `branch`, so every emit-side sweep that already walks branch arms — the
|
|
2373
|
+
* import, navigate and state-reference collectors — reaches these arms for free.
|
|
2374
|
+
*/
|
|
2375
|
+
export interface BranchEffectPlan {
|
|
2376
|
+
nodeId: string;
|
|
2377
|
+
action: HandlerAction;
|
|
2378
|
+
/** The provenance comment above the effect — the node's authored label when it has one. */
|
|
2379
|
+
comment: string;
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* A `Value Changed` (EXP-011 §39): the node fires whenever its Input becomes a different value,
|
|
2383
|
+
* the first arrival included, and compares by identity — `useEffect` over the value, with the
|
|
2384
|
+
* last value seen in a `useRef` so a re-render that delivers the same value fires nothing.
|
|
2385
|
+
*
|
|
2386
|
+
* ⚠️ React's dependency compare is `Object.is` and the node's is `===`; they differ only on
|
|
2387
|
+
* `NaN` (the node fires on every `NaN` arrival, the effect never re-runs for one). Recorded in
|
|
2388
|
+
* the ledger rather than reproduced — a value that is `NaN` on every arrival is not a change
|
|
2389
|
+
* an author is watching for.
|
|
2390
|
+
*/
|
|
2391
|
+
export interface ValueChangedEffectPlan {
|
|
2392
|
+
nodeId: string;
|
|
2393
|
+
/** The Input, as the render-mode expression the effect watches. */
|
|
2394
|
+
watch: ValueExpr;
|
|
2395
|
+
/** The `useRef` local holding the last value the effect saw. */
|
|
2396
|
+
lastLocal: string;
|
|
2397
|
+
actions: HandlerAction[];
|
|
2398
|
+
comment: string;
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* P18-S119-TICKEDRUN. A Function or Expression whose Run is wired also runs when one of its value inputs whose Run On Value
|
|
2402
|
+
* Change box is ticked (or unset, which is ticked) gets a new value (NDA-017 §2, `setScriptInputValue`). A wired value
|
|
2403
|
+
* arrives when its source first has one and whenever it changes after; a literal arrives once, at load. Such a run sends
|
|
2404
|
+
* the Function's own signals and writes its outputs, but never Done (no Run sent it). Two arrivals, or an arrival and a
|
|
2405
|
+
* Run, in one update make one run.
|
|
2406
|
+
*
|
|
2407
|
+
* Printed as a `useEffect` keyed on the watched values' sources, beside the Run chains: it asks `tickedArrival` whether
|
|
2408
|
+
* a value arrived (not `undefined`, not the one held), and runs `action` if so. A Run records what it read the same way,
|
|
2409
|
+
* so a click that sets a watched value and sends Run makes one run, the Run's, with its Done (measured, s121 drive S4).
|
|
2410
|
+
* Measured in the viewer (s121 drive S0): at load the arrival's run comes first, then a Did Mount's Run, two runs.
|
|
2411
|
+
*/
|
|
2412
|
+
export interface TickedRunPlan {
|
|
2413
|
+
nodeId: string;
|
|
2414
|
+
/** The ticked inputs the runtime delivers, in input order: a wired one as the value it reads, a literal as itself. */
|
|
2415
|
+
watch: ValueExpr[];
|
|
2416
|
+
/** The run without its Done: the body, its signals' chains, its §4f write. `ticked: true`. */
|
|
2417
|
+
action: Extract<HandlerAction, {
|
|
2418
|
+
kind: 'jsfun-run';
|
|
2419
|
+
}>;
|
|
2420
|
+
/**
|
|
2421
|
+
* EXP-019 slice 2. A Function with a host trait and no Run wire: the runtime's script setter also runs it once at load
|
|
2422
|
+
* (`simplejavascript.ts`, `if (!this.isInputConnected('run')) this.scheduleRun()`), whatever has arrived, inputs or none.
|
|
2423
|
+
* The effect then runs on its first pass even when no watched value arrived (a ref, so StrictMode's second mount does
|
|
2424
|
+
* not run it twice).
|
|
2425
|
+
*/
|
|
2426
|
+
atLoad?: true;
|
|
2427
|
+
comment: string;
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* A `CSS Definition`'s stylesheet (EXP-011 §48): a module constant holding the authored CSS
|
|
2431
|
+
* verbatim, and a mount effect that appends it to `document.head` in a `<style>` and removes it
|
|
2432
|
+
* on unmount — the node's own `updateStyle`/`removeStyleDeclaration`, per instance.
|
|
2433
|
+
*
|
|
2434
|
+
* ⚠️ One difference, invisible on the page: the runtime keys the element by *node id* and
|
|
2435
|
+
* ref-counts it, so N mounted instances of one component share one `<style>`; the export appends
|
|
2436
|
+
* one per instance. Identical text N times has the same cascade as once.
|
|
2437
|
+
*/
|
|
2438
|
+
export interface StyleSheetPlan {
|
|
2439
|
+
nodeId: string;
|
|
2440
|
+
/** `DESK_STYLES` — SCREAMING_SNAKE of the authored label, deduped like a Static Data constant. */
|
|
2441
|
+
constName: string;
|
|
2442
|
+
/** The authored CSS, verbatim — never trimmed, never reformatted. */
|
|
2443
|
+
css: string;
|
|
2444
|
+
comment: string;
|
|
2445
|
+
}
|
|
2446
|
+
/** One `transitiondef-<state>` / `transition-<state>-<value>` — the node's `curve` port shape. */
|
|
2447
|
+
export interface StatesCurve {
|
|
2448
|
+
curve: number[];
|
|
2449
|
+
dur: number;
|
|
2450
|
+
delay: number;
|
|
2451
|
+
}
|
|
2452
|
+
/** One authored value of a States node — its `type-<name>` and its `value-<state>-<name>` per state. */
|
|
2453
|
+
export interface StatesValueDef {
|
|
2454
|
+
name: string;
|
|
2455
|
+
type: StatesValueType;
|
|
2456
|
+
/** The declared TypeScript type of the read: `number`, `string`, `boolean`; a colour is a `string`. */
|
|
2457
|
+
tsType: string;
|
|
2458
|
+
byState: Record<string, string | number | boolean>;
|
|
2459
|
+
transitions: Record<string, StatesCurve>;
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* A `States` node (EXP-011 §49): a definition constant above the component (`defineStates({…})`),
|
|
2463
|
+
* a `useStates` handle inside it, the listeners its signal outputs compiled into, and the wired
|
|
2464
|
+
* State input as an effect.
|
|
2465
|
+
*
|
|
2466
|
+
* The values are held by the emitted hook, not by state rows: every frame of a tween publishes a
|
|
2467
|
+
* new snapshot, and a row per value would be five setters per frame for the same re-render.
|
|
2468
|
+
*/
|
|
2469
|
+
export interface StatesPlan {
|
|
2470
|
+
nodeId: string;
|
|
2471
|
+
/** The `useStates` local — `panel`. */
|
|
2472
|
+
local: string;
|
|
2473
|
+
/** The definition constant — `PANEL_STATES`. */
|
|
2474
|
+
constName: string;
|
|
2475
|
+
states: string[];
|
|
2476
|
+
values: StatesValueDef[];
|
|
2477
|
+
/** `transitiondef-<state>`, by state, where authored. */
|
|
2478
|
+
transitions: Record<string, StatesCurve>;
|
|
2479
|
+
useTransitions: boolean;
|
|
2480
|
+
/** The authored State, when it names a state; the node starts in the first state otherwise. */
|
|
2481
|
+
start: string | undefined;
|
|
2482
|
+
/** The wired State input — `useEffect(() => { local.follow(expr) }, [deps])`. */
|
|
2483
|
+
follow?: ValueExpr;
|
|
2484
|
+
/** The signal outputs, as the chains wired off them. `reached` is by state. */
|
|
2485
|
+
listeners: {
|
|
2486
|
+
stateChanged?: HandlerAction[];
|
|
2487
|
+
reached: Record<string, HandlerAction[]>;
|
|
2488
|
+
done?: HandlerAction[];
|
|
2489
|
+
unchanged?: HandlerAction[];
|
|
2490
|
+
failure?: HandlerAction[];
|
|
2491
|
+
};
|
|
2492
|
+
comment: string;
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* A `Script` node (EXP-011 §52): `const <local> = useScript(<defName>, { <inputs> }, { <listeners> })`,
|
|
2496
|
+
* with the node's code in its own file under `src/scripts/`, verbatim and unchecked, exported as
|
|
2497
|
+
* `<defName>`. The port set is the one on disk (`scriptPortsOf`); the input record carries only the
|
|
2498
|
+
* inputs the graph feeds, typed by what is delivered.
|
|
2499
|
+
*/
|
|
2500
|
+
export interface ScriptPlan {
|
|
2501
|
+
nodeId: string;
|
|
2502
|
+
/** The authored label, or `Script`. */
|
|
2503
|
+
label: string;
|
|
2504
|
+
/** The `useScript` local — `ticker`. */
|
|
2505
|
+
local: string;
|
|
2506
|
+
/** The exported definition constant and the file's base name — `tickerScript`. */
|
|
2507
|
+
defName: string;
|
|
2508
|
+
/** `src/scripts/<dir>/<fileBase>/<defName>.ts` — the file the code lives in. */
|
|
2509
|
+
file: string;
|
|
2510
|
+
/** The author's code, verbatim (`sourceText`'s contract: never trimmed, never reformatted). */
|
|
2511
|
+
code: string;
|
|
2512
|
+
/** Value inputs, in port order: wire-fed carry the resolved source, literals fold, unfed are absent from the record. */
|
|
2513
|
+
inputs: Array<{
|
|
2514
|
+
name: string;
|
|
2515
|
+
tsType: string;
|
|
2516
|
+
expr?: ValueExpr;
|
|
2517
|
+
}>;
|
|
2518
|
+
signalInputs: string[];
|
|
2519
|
+
/** Value outputs, in port order, typed by the declared port type. */
|
|
2520
|
+
outputs: Array<{
|
|
2521
|
+
name: string;
|
|
2522
|
+
tsType: string;
|
|
2523
|
+
}>;
|
|
2524
|
+
signalOutputs: string[];
|
|
2525
|
+
/** The ports as `defineScript` registers them — every port on disk, typed as the editor spelled it. */
|
|
2526
|
+
ports: {
|
|
2527
|
+
inputs: Record<string, string>;
|
|
2528
|
+
outputs: Record<string, string>;
|
|
2529
|
+
};
|
|
2530
|
+
/** The signal outputs, as the chains wired off them. */
|
|
2531
|
+
listeners: Record<string, HandlerAction[]>;
|
|
2532
|
+
comment: string;
|
|
2533
|
+
}
|
|
2534
|
+
/**
|
|
2535
|
+
* A `Run Tasks` node (EXP-011 §53): `const <local> = useRunTasks(<label>, { maxRunningTasks, stopOnFailure },
|
|
2536
|
+
* { <listeners> })` in the host, and the active task instances rendered as the last child of the host's
|
|
2537
|
+
* root container — `{<local>.tasks.map((task) => <Template key={task.key} <inputs from task.item>
|
|
2538
|
+
* onSuccess={task.succeed} onFailure={task.fail} />)}`. The template is a project component with no
|
|
2539
|
+
* visual root, emitted as a null-rendering component whose start-input chain runs on mount
|
|
2540
|
+
* ({@link TaskPlan}). The contract between the two is by NAME, as the runtime's is (`runtasks.ts`
|
|
2541
|
+
* matches the template's outputs by string; there is no wire between host and template).
|
|
2542
|
+
*/
|
|
2543
|
+
export interface RunTasksPlan {
|
|
2544
|
+
nodeId: string;
|
|
2545
|
+
/** The authored label, or `Run Tasks`. */
|
|
2546
|
+
label: string;
|
|
2547
|
+
/** The `useRunTasks` local — `sendAll`. */
|
|
2548
|
+
local: string;
|
|
2549
|
+
/** The template's legacy path — `/Notify`. The symbol and file are read off its plan at emit. */
|
|
2550
|
+
templateLegacy: string;
|
|
2551
|
+
/** The resolved contract names (defaults applied). */
|
|
2552
|
+
contract: {
|
|
2553
|
+
start: string;
|
|
2554
|
+
success: string;
|
|
2555
|
+
failure: string;
|
|
2556
|
+
error: string;
|
|
2557
|
+
};
|
|
2558
|
+
/** The list, read at the pulse. */
|
|
2559
|
+
items: ValueExpr;
|
|
2560
|
+
/** Literal or wired (a render read); the hook reads both live, as the runtime reads `_internal`. */
|
|
2561
|
+
maxRunningTasks: ValueExpr;
|
|
2562
|
+
stopOnFailure: ValueExpr;
|
|
2563
|
+
/**
|
|
2564
|
+
* The template's value inputs the host binds from the item, by identity (`runtasks.ts`:
|
|
2565
|
+
* `for (const inputKey in itemNode._inputs) if (model.data[inputKey] !== undefined) setInputValue`).
|
|
2566
|
+
* `prop` is the template's prop identifier for the port; `field` the item key (the port name).
|
|
2567
|
+
*/
|
|
2568
|
+
inputs: Array<{
|
|
2569
|
+
port: string;
|
|
2570
|
+
field: string;
|
|
2571
|
+
}>;
|
|
2572
|
+
/** The template's `Id` / `id` inputs, fed the task's model id (`item.id`, else a generated one). */
|
|
2573
|
+
idInputs: string[];
|
|
2574
|
+
/** The template's callback props for the contract's success and failure outputs. */
|
|
2575
|
+
onSuccess?: string;
|
|
2576
|
+
onFailure?: string;
|
|
2577
|
+
/** The signal outputs, as the chains wired off them. */
|
|
2578
|
+
listeners: Partial<Record<'done' | 'failure' | 'unchanged' | 'completed' | 'aborted', HandlerAction[]>>;
|
|
2579
|
+
comment: string;
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* A component that a `Run Tasks` names as its template (EXP-011 §53) — logic-only, emitted anyway,
|
|
2583
|
+
* rendering `null`; the chain wired off its start input runs once in a mount effect, which is exactly
|
|
2584
|
+
* when `startTask` pulses it (right after `createNode`). Every other signal input stays as before
|
|
2585
|
+
* (declared, unconsumed — COMPONENT-OUTPUTS-TARGET §6). Set only when every host names the same
|
|
2586
|
+
* start input and the chain compiled; otherwise the component keeps no file and
|
|
2587
|
+
* {@link ComponentPlan.taskRefusal} says why, for the hosts to repeat.
|
|
2588
|
+
*/
|
|
2589
|
+
export interface TaskPlan {
|
|
2590
|
+
startPort: string;
|
|
2591
|
+
actions: HandlerAction[];
|
|
2592
|
+
/** The Run Tasks nodes that name this component, for the doc comment. */
|
|
2593
|
+
hosts: Array<{
|
|
2594
|
+
componentPath: string;
|
|
2595
|
+
nodeId: string;
|
|
2596
|
+
}>;
|
|
2597
|
+
comment: string;
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* A `Repeater Item` (EXP-011 §57) whose `Added` drives a chain: once, on mount, guarded against StrictMode's
|
|
2601
|
+
* second mount — `signalAdded` is called synchronously inside `addItem` after `createNode`, once per row
|
|
2602
|
+
* (`foreach.tsx`). Its Item Id, when read, is a row prop ({@link ComponentPlan.rowProps}) rather than anything
|
|
2603
|
+
* here — the parent binds it from `item.id`.
|
|
2604
|
+
*/
|
|
2605
|
+
export interface RepeaterItemPlan {
|
|
2606
|
+
nodeId: string;
|
|
2607
|
+
/** The authored label, or `Repeater Item`. */
|
|
2608
|
+
label: string;
|
|
2609
|
+
/** The Added chain, snapped for render context. */
|
|
2610
|
+
actions: HandlerAction[];
|
|
2611
|
+
comment: string;
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* An `On App Error` node (EXP-011 §54): `const <local> = useAppError({ filter }, () => { …the Error chain… })`.
|
|
2615
|
+
* The boundary subscribes to the app's error channel (`src/lib/errors.ts`) for the life of the component
|
|
2616
|
+
* that hosts it — a page's boundary hears errors while the page is mounted, the root component's for the
|
|
2617
|
+
* app's life (the router shell keeps a file for it, {@link ComponentPlan.shell}) — and every instance
|
|
2618
|
+
* fires (onapperror.ts: no claiming, no consumption). Its value outputs read off the handle's `last`.
|
|
2619
|
+
*/
|
|
2620
|
+
export interface AppErrorPlan {
|
|
2621
|
+
nodeId: string;
|
|
2622
|
+
/** The authored label, or `On App Error`. */
|
|
2623
|
+
label: string;
|
|
2624
|
+
/** The `useAppError` local — `catchAll`. */
|
|
2625
|
+
local: string;
|
|
2626
|
+
/** The Filter — a literal, or a render read where wired; absent means every error. Read live by the hook. */
|
|
2627
|
+
filter?: ValueExpr;
|
|
2628
|
+
/** The chain wired off `Error`, when any. */
|
|
2629
|
+
listener?: HandlerAction[];
|
|
2630
|
+
comment: string;
|
|
2631
|
+
}
|
|
2632
|
+
/**
|
|
2633
|
+
* A streaming node (EXP-011 §58): `const <local> = use<Hook>({ label, nodeId, componentName }, { <config> },
|
|
2634
|
+
* { <listeners> })`. The data port's expression rides every action that takes it; the config is the options
|
|
2635
|
+
* object the hook re-reads every render (a literal, or a render read where wired); the signal outputs are the
|
|
2636
|
+
* chains wired off them.
|
|
2637
|
+
*/
|
|
2638
|
+
export interface StreamPlan {
|
|
2639
|
+
nodeId: string;
|
|
2640
|
+
type: string;
|
|
2641
|
+
kind: StreamKind;
|
|
2642
|
+
/** The authored label, or the display name. */
|
|
2643
|
+
label: string;
|
|
2644
|
+
/** The hook local — `parser`. */
|
|
2645
|
+
local: string;
|
|
2646
|
+
/** The data port's source — a wire's expression or an authored literal; absent when the port has none. */
|
|
2647
|
+
data?: ValueExpr;
|
|
2648
|
+
/** The config ports that are authored or wired, by port name; an absent port keeps the runtime's initialize() value. */
|
|
2649
|
+
config: Array<{
|
|
2650
|
+
port: string;
|
|
2651
|
+
expr: ValueExpr;
|
|
2652
|
+
}>;
|
|
2653
|
+
/** The signal outputs, as the chains wired off them. */
|
|
2654
|
+
listeners: Record<string, HandlerAction[]>;
|
|
2655
|
+
/** EXP-011 §78. The store a history-family node names — printed as its store module (`store: app`) and imported. */
|
|
2656
|
+
store?: string;
|
|
2657
|
+
comment: string;
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* A `Screen Resolution` node (EXP-011 §59): `const <local> = useScreenResolution()`. Registered on first
|
|
2661
|
+
* read, the boundary's shape without a listener; its three outputs read off the handle. A node nobody
|
|
2662
|
+
* reads is never registered — the hook would be a listener nothing observes.
|
|
2663
|
+
*/
|
|
2664
|
+
export interface ScreenResolutionPlan {
|
|
2665
|
+
nodeId: string;
|
|
2666
|
+
label: string;
|
|
2667
|
+
local: string;
|
|
2668
|
+
comment: string;
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* EXP-011 §60. A `Component Object` node in **record** mode — its component hosts a `Set Component Object Properties`,
|
|
2672
|
+
* or a descendant reaches the record through the parent pair: `const <local> = useComponentObject<<typeName>>()`,
|
|
2673
|
+
* one mirror effect per wired `value-*` input (`useEffect(() => { <local>.set({ key: src }); }, [deps])`), and the
|
|
2674
|
+
* component's root wrapped in `ParentComponentObjectContext.Provider` — the transcription of "this component owns a
|
|
2675
|
+
* Component Object", which is what `findAncestorWithComponentObject` tests. Alias mode (no writer, no descendant
|
|
2676
|
+
* reach) is COMPONENT-OBJECT-TARGET §3 unchanged and registers nothing here.
|
|
2677
|
+
*/
|
|
2678
|
+
export interface ComponentObjectRecordPlan {
|
|
2679
|
+
nodeId: string;
|
|
2680
|
+
label: string;
|
|
2681
|
+
local: string;
|
|
2682
|
+
/** The record's type alias, printed at module level: `type <typeName> = { key?: tsType; … }`. */
|
|
2683
|
+
typeName: string;
|
|
2684
|
+
keys: Array<{
|
|
2685
|
+
key: string;
|
|
2686
|
+
tsType: string;
|
|
2687
|
+
}>;
|
|
2688
|
+
mirrors: Array<{
|
|
2689
|
+
key: string;
|
|
2690
|
+
source: ValueExpr;
|
|
2691
|
+
wireKey: string;
|
|
2692
|
+
}>;
|
|
2693
|
+
comment: string;
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* A `Component Stack` that renders (EXP-011 §61): `const <local> = usePageStack({ name, pages, startPage })`, and a
|
|
2697
|
+
* visual row that renders the TOP entry only — one `&&` line per page, the entry's params spread as the page
|
|
2698
|
+
* component's props, the reserved `pageStackEntry` prop where the page keeps a Pop.
|
|
2699
|
+
*/
|
|
2700
|
+
export interface PageStackPlan {
|
|
2701
|
+
nodeId: string;
|
|
2702
|
+
label: string;
|
|
2703
|
+
local: string;
|
|
2704
|
+
/** The registry key — the authored Name, `Main` by default. */
|
|
2705
|
+
name: string;
|
|
2706
|
+
/** The Components list in authored order, each resolved to the component it shows. */
|
|
2707
|
+
pages: Array<{
|
|
2708
|
+
id: string;
|
|
2709
|
+
label: string;
|
|
2710
|
+
componentLegacy: string;
|
|
2711
|
+
}>;
|
|
2712
|
+
/** The authored Start Page, or the first page's id. */
|
|
2713
|
+
startPage: string;
|
|
2714
|
+
comment: string;
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* EXP-011 §60. The nearest ancestor's record, read from below — one hook per component, shared by every
|
|
2718
|
+
* `Parent Component Object` and `Set Parent Component Object Properties` in it (each resolves the same nearest
|
|
2719
|
+
* owner): `const <local> = useParentComponentObject<<typeName>>([readers])`. `readers` are the reader nodes that
|
|
2720
|
+
* collapsed — each raises `parent-component-object/no-ancestor` once at mount when the hook answers undefined.
|
|
2721
|
+
*/
|
|
2722
|
+
export interface ParentComponentObjectPlan {
|
|
2723
|
+
local: string;
|
|
2724
|
+
typeName: string;
|
|
2725
|
+
keys: Array<{
|
|
2726
|
+
key: string;
|
|
2727
|
+
tsType: string;
|
|
2728
|
+
}>;
|
|
2729
|
+
readers: Array<{
|
|
2730
|
+
nodeId: string;
|
|
2731
|
+
label: string;
|
|
2732
|
+
}>;
|
|
2733
|
+
/** Whether a parent Set prints — the hook prints when a reader collapsed or a writer attached. */
|
|
2734
|
+
comment: string;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* A `Drag` node (EXP-011 §63): `const <local> = useDrag({ label, nodeId, componentName }, { …options }, { …listeners })`,
|
|
2738
|
+
* and a wrapper `<div ref style {...handlers}>` where the node sits. Registered for every rendered Drag — the wrapper
|
|
2739
|
+
* needs the hook whether or not anything reads the node.
|
|
2740
|
+
*/
|
|
2741
|
+
export interface DragPlan {
|
|
2742
|
+
nodeId: string;
|
|
2743
|
+
label: string;
|
|
2744
|
+
local: string;
|
|
2745
|
+
comment: string;
|
|
2746
|
+
/**
|
|
2747
|
+
* The options that print, in `DRAG_OPTION_PORTS` order — a wire is the render expression the hook reads on arrival,
|
|
2748
|
+
* an authored value a literal, a port nothing set absent (the lib applies the declared default there).
|
|
2749
|
+
*/
|
|
2750
|
+
options: Array<{
|
|
2751
|
+
key: string;
|
|
2752
|
+
expr: ValueExpr;
|
|
2753
|
+
}>;
|
|
2754
|
+
/** The listener chains that translated. `done` fires after every snap call; Failure never (dropped with a note). */
|
|
2755
|
+
listeners: {
|
|
2756
|
+
onStart?: HandlerAction[];
|
|
2757
|
+
onMove?: HandlerAction[];
|
|
2758
|
+
onEnd?: HandlerAction[];
|
|
2759
|
+
done?: HandlerAction[];
|
|
2760
|
+
};
|
|
2761
|
+
/** Wires dropped by name — printed as in-code markers on the wrapper, counted by the pre-flight. */
|
|
2762
|
+
drops: Array<{
|
|
2763
|
+
subject: string;
|
|
2764
|
+
reason: string;
|
|
2765
|
+
}>;
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* An `Animate To Value` node (EXP-011 §49): `const <local> = useAnimatedValue(target, { duration,
|
|
2769
|
+
* delay, ease }, onArrive)`. `duration` and `delay` are read where the hook is — a wired one is
|
|
2770
|
+
* the render expression, an authored one a literal — and `ease` is the authored enum, which a
|
|
2771
|
+
* wire cannot deliver (the node indexes `EaseCurves` by it and an unknown name throws).
|
|
2772
|
+
*/
|
|
2773
|
+
export interface AnimationPlan {
|
|
2774
|
+
nodeId: string;
|
|
2775
|
+
local: string;
|
|
2776
|
+
target: ValueExpr;
|
|
2777
|
+
duration: ValueExpr;
|
|
2778
|
+
delay: ValueExpr;
|
|
2779
|
+
/**
|
|
2780
|
+
* P18-S122-TPLBUILD (TS2322). The inputs whose wired value is not statically a number (an untyped Variable is `unknown`):
|
|
2781
|
+
* the runtime's Duration setter stores the value as it is, and the hook's option says `number`, so the option is a cast.
|
|
2782
|
+
*/
|
|
2783
|
+
castToNumber?: Array<'duration' | 'delay'>;
|
|
2784
|
+
ease: AnimateEaseName;
|
|
2785
|
+
/** The At Target Value chain, when wired. */
|
|
2786
|
+
arrive?: HandlerAction[];
|
|
2787
|
+
comment: string;
|
|
2788
|
+
}
|
|
2789
|
+
export interface PropPlan {
|
|
2790
|
+
name: string;
|
|
2791
|
+
tsType: string;
|
|
2792
|
+
}
|
|
2793
|
+
export interface QueryPlan {
|
|
2794
|
+
nodeId: string;
|
|
2795
|
+
collectionName: string;
|
|
2796
|
+
/** `puppies` / `setPuppies` / `puppy` / `fetchPuppies` / `Puppy` / api module base `puppies`. */
|
|
2797
|
+
stateName: string;
|
|
2798
|
+
setterName: string;
|
|
2799
|
+
itemName: string;
|
|
2800
|
+
fetchName: string;
|
|
2801
|
+
typeName: string;
|
|
2802
|
+
moduleBase: string;
|
|
2803
|
+
/**
|
|
2804
|
+
* P18-S115-QUERYFILTER (EXP-011 §82). The `where` / `order` / `limit` / `skip` the runtime sends for this node, its
|
|
2805
|
+
* filter translated by the runtime's own translator (`querySettings.ts`). Absent when it sends none of them, so an
|
|
2806
|
+
* unfiltered query's call is the bare `fetch<Plural>()` it always was.
|
|
2807
|
+
*/
|
|
2808
|
+
params?: QueryParamsPlan;
|
|
2809
|
+
/**
|
|
2810
|
+
* P18-S115-QUERYPARAMS. With `params.wired`: each wired filter value as the render read its wire resolves to, in
|
|
2811
|
+
* `params.wired.params` order. The query re-runs whenever one changes (`setQueryParameter`, its box ticked).
|
|
2812
|
+
*/
|
|
2813
|
+
wiredValues?: Array<{
|
|
2814
|
+
name: string;
|
|
2815
|
+
expr: ValueExpr;
|
|
2816
|
+
}>;
|
|
2817
|
+
/** The project declares a backend, so `src/api/client.ts` exists to put each value on the wire (`filterValue`). */
|
|
2818
|
+
wiredClient?: boolean;
|
|
2819
|
+
/**
|
|
2820
|
+
* P18-S116-QUERYFETCH. Its Fetch is wired, so the query asks when Fetch is sent (`query-fetch` in the sender's
|
|
2821
|
+
* chain). `mount`: it also asks as the node is made, its Class box ticked or its Query settings box ticked with a
|
|
2822
|
+
* setting authored (a wired Fetch settles both unticked, NDA-017; an author can tick them again). `onValues`: the
|
|
2823
|
+
* wired filter values' boxes are ticked, so a new value asks too (the effect, mount included).
|
|
2824
|
+
*/
|
|
2825
|
+
fetchWired?: {
|
|
2826
|
+
mount: boolean;
|
|
2827
|
+
onValues: boolean;
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
/**
|
|
2831
|
+
* One translated record verb (RECORD-VERBS-TARGET §4d): what the api stub module has to export
|
|
2832
|
+
* for it. The naming is a pure function of the class name, identical to {@link QueryPlan}'s, so a
|
|
2833
|
+
* project that both reads and writes one collection gets a single module.
|
|
2834
|
+
*/
|
|
2835
|
+
export interface MutationPlan {
|
|
2836
|
+
nodeId: string;
|
|
2837
|
+
/** EXP-011 §62: `add-relation` / `remove-relation` join the class's module beside the three record verbs. */
|
|
2838
|
+
verb: 'create' | 'update' | 'delete' | RelationVerb;
|
|
2839
|
+
collectionName: string;
|
|
2840
|
+
/** `createPuppy` / `updatePuppy` / `deletePuppy`. */
|
|
2841
|
+
fnName: string;
|
|
2842
|
+
typeName: string;
|
|
2843
|
+
moduleBase: string;
|
|
2844
|
+
/**
|
|
2845
|
+
* The columns this verb actually writes, with the type its argument resolves to — the graph's
|
|
2846
|
+
* own evidence of the record's shape (RECORD-VERBS-TARGET §4d).
|
|
2847
|
+
*
|
|
2848
|
+
* The interface is minted from the project's collection **schema snapshot**, and 32 of the 39
|
|
2849
|
+
* corpus projects carry none at all: `metadata.dbCollections` is absent, so every collection
|
|
2850
|
+
* types as `{ id: string }` and *any* `create`/`update` that writes a property emits a call
|
|
2851
|
+
* that does not compile. The schema is the authority where it exists and silent where it does
|
|
2852
|
+
* not; a `prop-` the graph writes is direct evidence of a column, so the two union. Empty for
|
|
2853
|
+
* `delete`, which writes nothing.
|
|
2854
|
+
*/
|
|
2855
|
+
writes: Array<{
|
|
2856
|
+
name: string;
|
|
2857
|
+
tsType: string;
|
|
2858
|
+
}>;
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* One translated user-family node (USER-FAMILY-TARGET §4d): what `src/api/session.ts` has to
|
|
2862
|
+
* export for it. Unlike {@link MutationPlan} there is nothing to key on — a project has one
|
|
2863
|
+
* session — so the three writes and the read all land in a single module.
|
|
2864
|
+
*/
|
|
2865
|
+
export interface SessionCallPlan {
|
|
2866
|
+
nodeId: string;
|
|
2867
|
+
verb: UserVerb | 'read';
|
|
2868
|
+
/** `logIn` / `logOut` / `signUp` / `useSession` / `setUserProperties` / `requestMagicLink`. */
|
|
2869
|
+
fnName: string;
|
|
2870
|
+
/**
|
|
2871
|
+
* EXP-011 §44 — the `_User` columns a `Set User Properties` writes (its `prop-*` inputs, the
|
|
2872
|
+
* prefix stripped), typed the record verbs' way: the wire's type where it is one of the three
|
|
2873
|
+
* JSON scalars, else `unknown`. The session module's `UserProperties` interface is the union
|
|
2874
|
+
* over every site — a project has one `_User` class, as it has one session.
|
|
2875
|
+
*/
|
|
2876
|
+
writes?: Array<{
|
|
2877
|
+
name: string;
|
|
2878
|
+
tsType: string;
|
|
2879
|
+
}>;
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* One value an `HTTP Request` sends (EXP-011 Tier 1.2): a path segment, a query parameter, a
|
|
2883
|
+
* header, a body field or a credential.
|
|
2884
|
+
*
|
|
2885
|
+
* `from` is the whole difference between configuration and data. An authored parameter is folded
|
|
2886
|
+
* into the emitted module as a literal — it is what the interpreter would have read out of
|
|
2887
|
+
* `inputValues` on every fetch, and nothing can change it. A wired one becomes a function
|
|
2888
|
+
* parameter the call site fills. An input that is *neither* never appears: the runtime reads
|
|
2889
|
+
* `undefined` for it, and `undefined` omits the header, the query parameter and the body field
|
|
2890
|
+
* alike, so emitting the guard would be emitting a branch that cannot be taken.
|
|
2891
|
+
*/
|
|
2892
|
+
export interface HttpValuePlan {
|
|
2893
|
+
/** The wire name — the header, query parameter, path placeholder or field, verbatim. */
|
|
2894
|
+
name: string;
|
|
2895
|
+
from: {
|
|
2896
|
+
kind: 'literal';
|
|
2897
|
+
value: string | number | boolean;
|
|
2898
|
+
} | {
|
|
2899
|
+
kind: 'param';
|
|
2900
|
+
param: string;
|
|
2901
|
+
tsType: string;
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* One translated `HTTP Request` (EXP-011 Tier 1.2) — everything `src/api/http.ts` needs to
|
|
2906
|
+
* rebuild the request the node would have sent.
|
|
2907
|
+
*
|
|
2908
|
+
* The node's configuration is `allowEditOnly` almost throughout (method, body type, the four
|
|
2909
|
+
* string lists, the auth preset), which is why this is a plan and not a solver: what the request
|
|
2910
|
+
* *is* was decided in the editor and cannot change at runtime. What can change is the values, and
|
|
2911
|
+
* those are {@link HttpValuePlan}s.
|
|
2912
|
+
*/
|
|
2913
|
+
export interface HttpCallPlan {
|
|
2914
|
+
nodeId: string;
|
|
2915
|
+
/** `fetchQuote` — from the node's authored label, deduped across the project's HTTP nodes. */
|
|
2916
|
+
fnName: string;
|
|
2917
|
+
/** `QuoteAnswer` — the exported result type the component's state row is typed by. */
|
|
2918
|
+
typeName: string;
|
|
2919
|
+
/**
|
|
2920
|
+
* The `const answer = await …` local, and the `const message = …` both failure arms bind.
|
|
2921
|
+
*
|
|
2922
|
+
* They live here rather than on the action because the *expression* side needs them too — a
|
|
2923
|
+
* chain-local read is emitted from a `http-out` that knows only its node — and two places
|
|
2924
|
+
* deriving the same name independently is the divergence FINDINGS B-iv is about.
|
|
2925
|
+
*/
|
|
2926
|
+
answerLocal: string;
|
|
2927
|
+
messageLocal: string;
|
|
2928
|
+
/** The authored URL, `{placeholders}` included. */
|
|
2929
|
+
url: string;
|
|
2930
|
+
method: string;
|
|
2931
|
+
/** Milliseconds; `timeout || 30000`, the runtime's own defaulting, already applied. */
|
|
2932
|
+
timeout: number;
|
|
2933
|
+
/**
|
|
2934
|
+
* P18-S104-RESPONSETYPE — how the body is read (FED-001 §3.2). `setResponseType` stores `value || 'auto'` and the
|
|
2935
|
+
* fetch tests for `'text'` and `'json'` only, so any other value reads as Auto; that defaulting is applied here.
|
|
2936
|
+
*/
|
|
2937
|
+
responseType: 'auto' | 'text' | 'json';
|
|
2938
|
+
pathParams: HttpValuePlan[];
|
|
2939
|
+
queryParams: HttpValuePlan[];
|
|
2940
|
+
headers: HttpValuePlan[];
|
|
2941
|
+
/** Null for GET/HEAD/OPTIONS, which send none — `buildBody` returns undefined for them. */
|
|
2942
|
+
body: {
|
|
2943
|
+
type: 'json' | 'form' | 'urlencoded';
|
|
2944
|
+
fields: HttpValuePlan[];
|
|
2945
|
+
} | {
|
|
2946
|
+
type: 'raw';
|
|
2947
|
+
raw: HttpValuePlan;
|
|
2948
|
+
} | null;
|
|
2949
|
+
auth: {
|
|
2950
|
+
kind: 'bearer';
|
|
2951
|
+
token: HttpValuePlan;
|
|
2952
|
+
} | {
|
|
2953
|
+
kind: 'basic';
|
|
2954
|
+
username: HttpValuePlan;
|
|
2955
|
+
password: HttpValuePlan;
|
|
2956
|
+
} | {
|
|
2957
|
+
kind: 'apiKey';
|
|
2958
|
+
name: HttpValuePlan;
|
|
2959
|
+
value: HttpValuePlan;
|
|
2960
|
+
location: 'header' | 'query';
|
|
2961
|
+
} | null;
|
|
2962
|
+
/**
|
|
2963
|
+
* The Response Mapping, compiled (`$.data.items[0].name` → `?.data?.items?.[0]?.name`).
|
|
2964
|
+
*
|
|
2965
|
+
* `steps` is empty for the default path `$`, which is the whole body — and `null` for a path
|
|
2966
|
+
* `extractByPath` can never resolve (one that does not start with `$`), where the runtime
|
|
2967
|
+
* publishes `undefined` on every answer and so does the emitted module.
|
|
2968
|
+
*/
|
|
2969
|
+
fields: Array<{
|
|
2970
|
+
name: string;
|
|
2971
|
+
steps: Array<string | number> | null;
|
|
2972
|
+
}>;
|
|
2973
|
+
}
|
|
2974
|
+
/** One `Cloud Function` node's call (EXP-011 §41) — what `src/api/functions.ts` emits for it. */
|
|
2975
|
+
export interface CloudCallPlan {
|
|
2976
|
+
nodeId: string;
|
|
2977
|
+
/** The backend function's name, verbatim — the `function` parameter. */
|
|
2978
|
+
functionName: string;
|
|
2979
|
+
/** `callPublishPage` — from the node's authored label, else the function name. */
|
|
2980
|
+
fnName: string;
|
|
2981
|
+
/** `PublishPageResults` — the exported result type the component's state row is typed by. */
|
|
2982
|
+
typeName: string;
|
|
2983
|
+
answerLocal: string;
|
|
2984
|
+
messageLocal: string;
|
|
2985
|
+
/** The `in-*` parameters — a folded literal, or the parameter the call site fills. */
|
|
2986
|
+
params: HttpValuePlan[];
|
|
2987
|
+
/** The `out-*` result names the node declares, in port order. */
|
|
2988
|
+
results: string[];
|
|
2989
|
+
}
|
|
2990
|
+
/** One `Record` node's read (EXP-011 §43) — what its collection module has to export for it. */
|
|
2991
|
+
export interface RecordReadPlan {
|
|
2992
|
+
nodeId: string;
|
|
2993
|
+
collectionName: string;
|
|
2994
|
+
/** `Page` — the collection module's interface, the row's type. */
|
|
2995
|
+
typeName: string;
|
|
2996
|
+
moduleBase: string;
|
|
2997
|
+
/** `fetchPageById`. */
|
|
2998
|
+
fnName: string;
|
|
2999
|
+
answerLocal: string;
|
|
3000
|
+
messageLocal: string;
|
|
3001
|
+
/** The Id, bound before the guard so the emitted `Missing Id.` test and the call read one value. */
|
|
3002
|
+
idLocal: string;
|
|
3003
|
+
/** Columns the graph reads off this node that the schema snapshot does not carry — `unknown` in the interface. */
|
|
3004
|
+
reads: Array<{
|
|
3005
|
+
name: string;
|
|
3006
|
+
tsType: string;
|
|
3007
|
+
}>;
|
|
3008
|
+
/**
|
|
3009
|
+
* P18-S111-RECORDLIVE. "Record properties" ticked under Run On Value Change, the default: the node shows the app's one
|
|
3010
|
+
* copy of its record, so a write anywhere in the app reaches its outputs (dbmodelnode2.ts `onModelChangedCallback`).
|
|
3011
|
+
* Unticked, the callback returns early and the outputs move only on the node's own read, which is the row as it was.
|
|
3012
|
+
* False where the project declares no backend: the read is a stub that throws and no client is emitted.
|
|
3013
|
+
*/
|
|
3014
|
+
live: boolean;
|
|
3015
|
+
/**
|
|
3016
|
+
* P18-S113-IDBINDREAD. Fetch unwired: the node never reads, its Id only binds the row to the client's copy of the
|
|
3017
|
+
* record (`RecordEffectPlan`). Its class's module carries no `fetch<Type>ById` for it, and the client holds records.
|
|
3018
|
+
*/
|
|
3019
|
+
binds?: true;
|
|
3020
|
+
}
|
|
3021
|
+
/**
|
|
3022
|
+
* One files node that translated (EXP-011 §45) — what `src/api/files.ts` must export (`upload` and
|
|
3023
|
+
* `sign`; a `pick` needs only `pickFile` from the util library) and the chain-locals its two arms
|
|
3024
|
+
* bind, read off the plan so the expression side and the action side cannot disagree about a name.
|
|
3025
|
+
*/
|
|
3026
|
+
export interface FileOpPlan {
|
|
3027
|
+
nodeId: string;
|
|
3028
|
+
family: 'pick' | 'upload' | 'sign';
|
|
3029
|
+
answerLocal: string;
|
|
3030
|
+
messageLocal: string;
|
|
3031
|
+
}
|
|
3032
|
+
/**
|
|
3033
|
+
* A `Variable` node carrying an authored `Value` (EXP-011 §67). The runtime's `value` input setter
|
|
3034
|
+
* runs at node creation with the parameter and schedules a store (variablenode2.ts), so the node
|
|
3035
|
+
* rewrites the variable on EVERY mount of the component that holds it — navigating back to the
|
|
3036
|
+
* page resets the variable there. The translation is therefore a mount effect in the host — never
|
|
3037
|
+
* a module-level seed, which would boot once, never reset, and run for a component nothing mounted.
|
|
3038
|
+
*/
|
|
3039
|
+
export interface VariableSeedPlan {
|
|
3040
|
+
nodeId: string;
|
|
3041
|
+
variableName: string;
|
|
3042
|
+
value: string | number | boolean;
|
|
3043
|
+
comment: string;
|
|
3044
|
+
}
|
|
3045
|
+
/** The seed as the action it prints — read off the plan so the import sweep and the emitter cannot disagree. */
|
|
3046
|
+
export declare function variableSeedAction(seed: VariableSeedPlan): HandlerAction;
|
|
3047
|
+
/**
|
|
3048
|
+
* EXP-011 §71. An `Object` node whose OWN `prop-<key>` inputs carry typed-in values. The runtime
|
|
3049
|
+
* registers any `prop-*` input on demand and queues every authored parameter at node creation
|
|
3050
|
+
* (nodescope.ts); `userInputSetter` marks the key dirty and `scheduleStore` writes it into the
|
|
3051
|
+
* record in the same pass (modelnode2.ts) — so the node rewrites those keys on EVERY mount of the
|
|
3052
|
+
* component that holds it, and a Set's Do lands over them afterwards (measured, session 94). §67's
|
|
3053
|
+
* shape one construct over: a mount effect in the host, one patch per node, never a module-level
|
|
3054
|
+
* seed (which would boot once, never reset, and run for a component nothing mounted).
|
|
3055
|
+
*/
|
|
3056
|
+
export interface ObjectSeedPlan {
|
|
3057
|
+
nodeId: string;
|
|
3058
|
+
storeName: string;
|
|
3059
|
+
/** Parameter order — the order the runtime writes `dirtyValues` in. */
|
|
3060
|
+
entries: Array<{
|
|
3061
|
+
key: string;
|
|
3062
|
+
value: string | number | boolean;
|
|
3063
|
+
}>;
|
|
3064
|
+
comment: string;
|
|
3065
|
+
}
|
|
3066
|
+
/**
|
|
3067
|
+
* P18-S116-FIELDMOUNT. A rendered text input whose Value reaches a chain the export translated (its `onChange`). The
|
|
3068
|
+
* runtime's field announces its Value as it mounts: `componentDidMount` calls `setText(startValue)`, which sends
|
|
3069
|
+
* `onTextChanged` with `outwardValueForFieldType(type, text)` and, through the output's `onChange`, Value Changed
|
|
3070
|
+
* (TextInput.tsx, text-input.ts). So every sink of the Value, and every chain on Value Changed, runs once as the field
|
|
3071
|
+
* appears, with the start value or `""`. The export runs the field's own change chain once from a mount effect, with
|
|
3072
|
+
* the announced value where the chain reads the field.
|
|
3073
|
+
*/
|
|
3074
|
+
export interface FieldMountPlan {
|
|
3075
|
+
inputId: string;
|
|
3076
|
+
/** The text the field starts from with nothing wired, as the control's boot computes it (`String(startValue ?? '')`). */
|
|
3077
|
+
start: string;
|
|
3078
|
+
/**
|
|
3079
|
+
* The wired Value's source, where a sync effect carries it. The runtime delivers it at node creation, before the
|
|
3080
|
+
* mount, so the field announces it: `undefined` is never delivered (the start value stands), `null` clears.
|
|
3081
|
+
*/
|
|
3082
|
+
source?: ValueExpr;
|
|
3083
|
+
/**
|
|
3084
|
+
* P18 session 126 (EXP-011 §93). The field and the elements above it whose Mounted a value decides: the field is not there
|
|
3085
|
+
* as the component mounts, so it announces the first time those values all say mounted (its later mounts announce only a
|
|
3086
|
+
* real change, GAM-009 R10, and the field keeps what it holds). Absent or empty, it announces as the component mounts.
|
|
3087
|
+
*/
|
|
3088
|
+
gates?: string[];
|
|
3089
|
+
comment: string;
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* P18-S128-FIELDSET and P18-S127-CLEAROUT (EXP-011 §97). A text field's Set or Clear, carried on the `state-set` of the
|
|
3093
|
+
* field's own row, so every walker that reads a state write reads this one; the emitter prints the rest.
|
|
3094
|
+
*
|
|
3095
|
+
* The runtime (`text-input.ts`, `TextInput.tsx`): **Set** writes `_internal.text`, the Value as last delivered (`undefined`
|
|
3096
|
+
* is never delivered, `null` is `''`) or `''` once a Clear has run, and **nothing while the field has focus**; **Clear**
|
|
3097
|
+
* writes `''`, focused or not. Both then call the field's own `setText`, which sends the text to every sink of the Value and
|
|
3098
|
+
* fires Value Changed, changed or not. So the exported write is followed by the field's own change chain, with the text
|
|
3099
|
+
* written where the chain reads the field, as the mount announcement prints it (P18-S116-FIELDMOUNT).
|
|
3100
|
+
*
|
|
3101
|
+
* On a Set, `expr` is the Value's source as a handler reads it (a literal where nothing is wired), and `start` the text a
|
|
3102
|
+
* Value never delivered leaves (the typed-in Value, or `''`); the emitter writes `start` for `undefined`, `''` for `null`,
|
|
3103
|
+
* else `String(value)`. On a Clear, `expr` is the literal `''`.
|
|
3104
|
+
*/
|
|
3105
|
+
export interface FieldWrite {
|
|
3106
|
+
inputId: string;
|
|
3107
|
+
port: 'set' | 'clear';
|
|
3108
|
+
start?: string;
|
|
3109
|
+
}
|
|
3110
|
+
/** The seed as the action it prints — one patch, read off the plan so the import sweep and the emitter cannot disagree. */
|
|
3111
|
+
export declare function objectSeedAction(seed: ObjectSeedPlan): HandlerAction;
|
|
3112
|
+
/**
|
|
3113
|
+
* A `Record` whose `Fetch` is unwired (EXP-011 §43, P18-S113-IDBINDREAD): the Id binds the row to the client's copy of
|
|
3114
|
+
* that record, from an effect keyed on the Id. `action` is the node's `record-fetch` with empty chains (a Done or a
|
|
3115
|
+
* Failure wired is refused, as neither fires without a Fetch); it carries the Id and the row the planner fills, and
|
|
3116
|
+
* the emitter prints the bind from it, never the read.
|
|
3117
|
+
*/
|
|
3118
|
+
export interface RecordEffectPlan {
|
|
3119
|
+
nodeId: string;
|
|
3120
|
+
action: HandlerAction;
|
|
3121
|
+
comment: string;
|
|
3122
|
+
/** The project declares a backend, so the client holds records; with none, nothing is held and the row stays unset. */
|
|
3123
|
+
held: boolean;
|
|
3124
|
+
/**
|
|
3125
|
+
* "Record properties" ticked (the default): the row follows every later write to the copy (`onModelChangedCallback`).
|
|
3126
|
+
* Unticked, the bind flags the outputs once, so the row is the copy as it was at the bind.
|
|
3127
|
+
*/
|
|
3128
|
+
follows: boolean;
|
|
3129
|
+
}
|
|
3130
|
+
export interface RepeaterPlan {
|
|
3131
|
+
nodeId: string;
|
|
3132
|
+
/** Legacy component path of the template ("/Components/PuppyCard"), or null when unset. */
|
|
3133
|
+
templatePath: string | null;
|
|
3134
|
+
/** The DbCollection2 node wired into `items`, or null when nothing statically known feeds it. */
|
|
3135
|
+
itemsQueryId: string | null;
|
|
3136
|
+
/** The named client-side array wired into `items` (Collection2.items), or null. */
|
|
3137
|
+
itemsCollectionName: string | null;
|
|
3138
|
+
/**
|
|
3139
|
+
* The identity mapping parsed from the effective mapping script (authored parameter, else the
|
|
3140
|
+
* declared port's default — the fixture's trap). **No script at all is 'template-inputs'**:
|
|
3141
|
+
* the runtime then identity-maps item properties onto same-named component inputs by itself
|
|
3142
|
+
* (foreach.tsx), so the mapping is the template's input names, resolved at emit. Null when
|
|
3143
|
+
* the script is anything beyond a static string→string `map({...})` literal; that repeater
|
|
3144
|
+
* defers to EXP-003.
|
|
3145
|
+
*/
|
|
3146
|
+
mapping: Array<{
|
|
3147
|
+
input: string;
|
|
3148
|
+
field: string;
|
|
3149
|
+
}> | 'template-inputs' | null;
|
|
3150
|
+
/**
|
|
3151
|
+
* A list-typed vocabulary source wired into `items` (CONTROLLED-STATE-TARGET §4e) — a
|
|
3152
|
+
* prop-fed or state-fed plain list. Emitted `(expr ?? []).map(…)`: `?? []` is foreach.tsx's
|
|
3153
|
+
* own "empty arrival clears the list", rows key by index (no identity column, and the
|
|
3154
|
+
* runtime re-renders on array identity change anyway — grade Q).
|
|
3155
|
+
*/
|
|
3156
|
+
itemsExpr?: ValueExpr;
|
|
3157
|
+
/**
|
|
3158
|
+
* A Static Data node whose parsed rows are hoisted to a module constant (STATIC-DATA §3).
|
|
3159
|
+
* Deliberately not `itemsExpr`: that path's contract is "no statically-known item shape, so
|
|
3160
|
+
* fields read as `any` and every mapped input is kept". Static Data's shape *is* known, so it
|
|
3161
|
+
* takes the `collection` treatment instead — derived type, `allowedFields`, a real key.
|
|
3162
|
+
*/
|
|
3163
|
+
itemsStaticId?: string;
|
|
3164
|
+
}
|
|
3165
|
+
/** EXP-011 §99. One relayed row value: the repeater, the row's output, the row's input it passes through, its state row. */
|
|
3166
|
+
export interface RowRelayPlan {
|
|
3167
|
+
repeaterId: string;
|
|
3168
|
+
output: string;
|
|
3169
|
+
input: string;
|
|
3170
|
+
stateName: string;
|
|
3171
|
+
}
|
|
3172
|
+
/**
|
|
3173
|
+
* One Static Data node hoisted to a frozen module constant (STATIC-DATA-TARGET §3).
|
|
3174
|
+
*
|
|
3175
|
+
* The node's three inputs are all `allowEditOnly` (staticdata.ts), so no wire can feed them and
|
|
3176
|
+
* the rows are knowable by construction rather than by a solver that happened to succeed. Only
|
|
3177
|
+
* nodes that pass every §4 gate get a plan; the rest defer with their reason named.
|
|
3178
|
+
*/
|
|
3179
|
+
export interface StaticDataPlan {
|
|
3180
|
+
nodeId: string;
|
|
3181
|
+
/** `PRODUCTS_DATA` — SCREAMING_SNAKE of the authored label, deduped against reserved names. */
|
|
3182
|
+
constName: string;
|
|
3183
|
+
/** `FeaturedProduct` — the row type alias. */
|
|
3184
|
+
typeName: string;
|
|
3185
|
+
/** Nested object/array aliases this row type references, declaration order (§3.1). */
|
|
3186
|
+
nestedTypes: Array<{
|
|
3187
|
+
name: string;
|
|
3188
|
+
decl: string;
|
|
3189
|
+
}>;
|
|
3190
|
+
/** The row type's own field list, source order. */
|
|
3191
|
+
fields: Array<{
|
|
3192
|
+
name: string;
|
|
3193
|
+
tsType: string;
|
|
3194
|
+
optional: boolean;
|
|
3195
|
+
}>;
|
|
3196
|
+
/** The parsed rows, verbatim — emitted as a frozen literal. */
|
|
3197
|
+
rows: Array<Record<string, unknown>>;
|
|
3198
|
+
/**
|
|
3199
|
+
* `id` when every row carries a unique, primitive, non-null one — which mirrors the runtime's
|
|
3200
|
+
* own identity notion, since `Collection.set` mints each row into a Model and treats `id` as
|
|
3201
|
+
* the record's identity rather than ordinary data (collection.ts:542). Null ⇒ key by index.
|
|
3202
|
+
*/
|
|
3203
|
+
keyField: string | null;
|
|
3204
|
+
}
|
|
3205
|
+
/**
|
|
3206
|
+
* One conditional popup render in the hosting component (POPUPS-TARGET §2, §5). Show Popup
|
|
3207
|
+
* nodes opening the same target with identical literal params share a slot — safe because a
|
|
3208
|
+
* node with any consumed close outcome defers, so shared keys never conflate observable
|
|
3209
|
+
* behaviour.
|
|
3210
|
+
*/
|
|
3211
|
+
export interface PopupSlotPlan {
|
|
3212
|
+
/** The slot's string literal (`'AboutDialog'`), from the target path's last segment. */
|
|
3213
|
+
slotKey: string;
|
|
3214
|
+
/** Legacy component path of the popup component ("/Components/AboutDialog"). */
|
|
3215
|
+
targetLegacy: string;
|
|
3216
|
+
/** Literal `popupParam-*` values, keyed by the target's input port name — props at emit. */
|
|
3217
|
+
params: Array<{
|
|
3218
|
+
input: string;
|
|
3219
|
+
value: string | number | boolean;
|
|
3220
|
+
}>;
|
|
3221
|
+
/**
|
|
3222
|
+
* HLT-014 — Show Popup's `Close On Escape`. The emitted `PopupDialog` closes on Escape unless this is
|
|
3223
|
+
* `false`, matching the runtime's default-on.
|
|
3224
|
+
*/
|
|
3225
|
+
closeOnEscape: boolean;
|
|
3226
|
+
/** HLT-014 — Show Popup's `Accessible Name`; absent means "named by the popup's first heading". */
|
|
3227
|
+
label?: string;
|
|
3228
|
+
/** HLT-014 — Show Popup's `Modal`. `false` renders the plain overlay: not a dialog (a toast). */
|
|
3229
|
+
modal: boolean;
|
|
3230
|
+
}
|
|
3231
|
+
export interface ComponentFilePlan {
|
|
3232
|
+
dir: 'pages' | 'components';
|
|
3233
|
+
/** "ThankYou" — file base name, deduplicated per directory. */
|
|
3234
|
+
fileBase: string;
|
|
3235
|
+
/** "ThankYouPage" for pages, "PuppyCard" for components. */
|
|
3236
|
+
symbol: string;
|
|
3237
|
+
}
|
|
3238
|
+
export interface ComponentPlan {
|
|
3239
|
+
path: string;
|
|
3240
|
+
legacyPath: string;
|
|
3241
|
+
role: 'page' | 'component';
|
|
3242
|
+
/** Null when nothing is emitted for this component (no visual root, or the router shell). */
|
|
3243
|
+
file: ComponentFilePlan | null;
|
|
3244
|
+
/** Why file is null, for the report. */
|
|
3245
|
+
skipReason?: string;
|
|
3246
|
+
/**
|
|
3247
|
+
* Which *kind* of skip {@link skipReason} describes — EXP-004's report leads with what worked,
|
|
3248
|
+
* and these two do not belong on the same side of that line.
|
|
3249
|
+
*
|
|
3250
|
+
* 🔴 **A discriminator rather than a read of the sentence.** There are exactly two skips: the
|
|
3251
|
+
* router shell, which the scaffold emits as `src/App.tsx` and is therefore *done*, and a
|
|
3252
|
+
* logic-only component, which is *not*. Both arrive as one `notes` string prefixed with the
|
|
3253
|
+
* component path, and a report that grouped them by matching the prose would call a working
|
|
3254
|
+
* export broken the first time either sentence was reworded. Set at the two sites that know.
|
|
3255
|
+
*/
|
|
3256
|
+
skipKind?: 'scaffolded' | 'deferred';
|
|
3257
|
+
/** The node the JSX root renders (the Page node for pages). */
|
|
3258
|
+
rootId: string | null;
|
|
3259
|
+
/** A page's sole Group child merged into the page div (TARGET-OUTPUT §2's shape). */
|
|
3260
|
+
collapsedGroupId?: string;
|
|
3261
|
+
head?: {
|
|
3262
|
+
title?: string;
|
|
3263
|
+
description?: string;
|
|
3264
|
+
};
|
|
3265
|
+
/** Root node's authored label — the component's doc comment. */
|
|
3266
|
+
docComment?: string;
|
|
3267
|
+
props: PropPlan[];
|
|
3268
|
+
/**
|
|
3269
|
+
* Props that carry the enclosing repeater's row (EXP-011 Tier 1.1) — an `Object` node in
|
|
3270
|
+
* "From repeater" mode, compiled away into the component's interface
|
|
3271
|
+
* (EXP-002-MODEL2-TARGET-OUTPUT §4). Minting order.
|
|
3272
|
+
*
|
|
3273
|
+
* 🔴 `field` is carried separately from `prop` and is **not** decoration. The prop name is
|
|
3274
|
+
* deduplicated against everything else the component declares, so an `Object` reading `mood`
|
|
3275
|
+
* in a component that already has a `mood` input becomes the prop `mood2` — and the parent
|
|
3276
|
+
* must still bind it from `item.mood`. Deriving the field from the prop name would silently
|
|
3277
|
+
* read a field no row has, exactly where the collision made it hardest to notice.
|
|
3278
|
+
*/
|
|
3279
|
+
rowProps: Array<{
|
|
3280
|
+
prop: string;
|
|
3281
|
+
field: string;
|
|
3282
|
+
/**
|
|
3283
|
+
* EXP-011 §57. Set when the row prop is a `Repeater Item`'s Item Id (field `id`, typed `string` on this
|
|
3284
|
+
* side): the parent then names the node and the port when a feed cannot supply it, rather than the
|
|
3285
|
+
* generic "no row carries this field".
|
|
3286
|
+
*/
|
|
3287
|
+
repeaterItem?: string;
|
|
3288
|
+
}>;
|
|
3289
|
+
/**
|
|
3290
|
+
* Declared signal outputs as callback props (`onWaved?: () => void`), declaration order —
|
|
3291
|
+
* the parent side reads the same list off the target's plan (COMPONENT-OUTPUTS-TARGET §2).
|
|
3292
|
+
*/
|
|
3293
|
+
outputProps: Array<{
|
|
3294
|
+
port: string;
|
|
3295
|
+
prop: string;
|
|
3296
|
+
}>;
|
|
3297
|
+
/** Render children per node, collapse applied, logic nodes filtered out. */
|
|
3298
|
+
childrenOf: Record<string, string[]>;
|
|
3299
|
+
roleOf: Record<string, RenderRole>;
|
|
3300
|
+
/**
|
|
3301
|
+
* EXP-011 §51. The `Component Children` the runtime honours (chooseChildSlot), when one exists —
|
|
3302
|
+
* the component then declares a `children` prop, and every instance passes its placed children.
|
|
3303
|
+
* Decided by the pure rule on BOTH sides so they cannot disagree; whether the marker also
|
|
3304
|
+
* RENDERS (`roleOf[childSlot] === 'slot'`) is the walk's answer — a marker below a node that did
|
|
3305
|
+
* not draw declares the prop and renders nothing, which is what the running app does.
|
|
3306
|
+
*/
|
|
3307
|
+
childSlot?: string;
|
|
3308
|
+
/**
|
|
3309
|
+
* EXP-010. The kit node definition behind each rendered `'custom'` node, plus the module it came
|
|
3310
|
+
* from. Carried on the plan rather than looked up again at emit time so there is one answer to
|
|
3311
|
+
* "which kit owns this node" — the emitted wrapper's import path is derived from it.
|
|
3312
|
+
*/
|
|
3313
|
+
customNodes: Record<string, {
|
|
3314
|
+
moduleDir: string;
|
|
3315
|
+
def: KitNodeIR;
|
|
3316
|
+
}>;
|
|
3317
|
+
/**
|
|
3318
|
+
* EXP-010. A custom node's value outputs that something in this component reads, as local state:
|
|
3319
|
+
* the wrapper calls `onXChanged`, the setter writes the row, and the sink binds to it.
|
|
3320
|
+
*
|
|
3321
|
+
* ⚠️ Local, unlike {@link instanceLifted}, and that is not an oversight. A component instance's
|
|
3322
|
+
* value output has to wait for `planProject`'s second phase because whether the *child* lifted
|
|
3323
|
+
* the port is a fact about another plan. A kit node publishes its own outputs from its own
|
|
3324
|
+
* definition, which this component already has — there is nothing to wait for.
|
|
3325
|
+
*/
|
|
3326
|
+
customLifted: Record<string, Array<{
|
|
3327
|
+
port: string;
|
|
3328
|
+
setterName: string;
|
|
3329
|
+
}>>;
|
|
3330
|
+
/**
|
|
3331
|
+
* EXP-010 AC3. Visual children of a rendered node that the export could not identify at all,
|
|
3332
|
+
* per parent id, in child order — the emitter prints a comment where each one sat.
|
|
3333
|
+
*
|
|
3334
|
+
* 🔴 **Unidentifiable, not merely undrawn**, and the difference is the whole criterion. A
|
|
3335
|
+
* `Masonry` that defers is a built-in with a reason the report can state and a slice that will
|
|
3336
|
+
* one day handle it. A node whose type is in no catalog and no loaded kit is *the author's own
|
|
3337
|
+
* node*, and before EXP-010 it left the emitted JSX with a gap and nothing in the file to say a
|
|
3338
|
+
* node had ever been there. That silence is the defect this task exists to end, and it is not
|
|
3339
|
+
* ended by a kit that loads — it is ended by the case where the kit does **not**.
|
|
3340
|
+
*/
|
|
3341
|
+
droppedChildren: Record<string, Array<{
|
|
3342
|
+
nodeId: string;
|
|
3343
|
+
type: string;
|
|
3344
|
+
reason: string;
|
|
3345
|
+
}>>;
|
|
3346
|
+
/**
|
|
3347
|
+
* Every wire this plan refused, with the endpoints kept as *values* (EXP-004's in-code markers).
|
|
3348
|
+
*
|
|
3349
|
+
* 🔴 **The emitter cannot re-derive this, and must not try.** "A wire whose sink has no binding"
|
|
3350
|
+
* looks like the same population and is not: a signal folded into a handler chain, a control's
|
|
3351
|
+
* state merged into its `onChange`, a `mounted` wire riding the render wrapper and a wire into a
|
|
3352
|
+
* collapsed group all leave no binding behind while having translated perfectly. Guessing from
|
|
3353
|
+
* absence would put `TODO(export)` markers on working code — a worse failure than the missing
|
|
3354
|
+
* marker this closes, in the one file whose job is not misleading its reader.
|
|
3355
|
+
*
|
|
3356
|
+
* So each refusal records itself here, beside the note it files, and `wireNote` is what keeps
|
|
3357
|
+
* the two from drifting: it writes the row and returns the sentence.
|
|
3358
|
+
*/
|
|
3359
|
+
droppedWires: DroppedWire[];
|
|
3360
|
+
/** nodeId → toProperty → source, for value wires landing on rendered nodes. */
|
|
3361
|
+
bindings: Record<string, Record<string, BindingSource>>;
|
|
3362
|
+
/** nodeId → source signal port → actions, for signal wires resolved to handler statements. */
|
|
3363
|
+
handlers: Record<string, Record<string, HandlerAction[]>>;
|
|
3364
|
+
/** Rendered text input id → actions its onChange performs (the wired-onTextChanged rule). */
|
|
3365
|
+
changeHandlers: Record<string, HandlerAction[]>;
|
|
3366
|
+
/** Event Receivers this component hosts as useSignal subscriptions. */
|
|
3367
|
+
receivers: ReceiverPlan[];
|
|
3368
|
+
/** P18-S126-INSIGNAL. One effect per signal input whose wires run a chain. */
|
|
3369
|
+
inputPulses: InputPulsePlan[];
|
|
3370
|
+
/** P18-S126-INSIGNAL. One count per placed component's signal input a sender here attached to. */
|
|
3371
|
+
instancePulses: InstancePulsePlan[];
|
|
3372
|
+
/** Popup slots this component renders, registration order — earned by attachment (§2). */
|
|
3373
|
+
popups: PopupSlotPlan[];
|
|
3374
|
+
/** True when a translated Close Popup attached — the component declares `onClose` (§4). */
|
|
3375
|
+
closesPopup: boolean;
|
|
3376
|
+
queries: QueryPlan[];
|
|
3377
|
+
/** Record verbs translated in this component (RECORD-VERBS-TARGET §4), compile order. */
|
|
3378
|
+
mutations: MutationPlan[];
|
|
3379
|
+
/** The user-family nodes that translated — what `src/api/session.ts` must export. */
|
|
3380
|
+
sessionCalls: SessionCallPlan[];
|
|
3381
|
+
/**
|
|
3382
|
+
* HTTP Requests translated in this component (EXP-011 Tier 1.2), compile order — what
|
|
3383
|
+
* `src/api/http.ts` must export. Earned by attachment, exactly as the record verbs are.
|
|
3384
|
+
*/
|
|
3385
|
+
httpCalls: HttpCallPlan[];
|
|
3386
|
+
/** EXP-011 §41. */
|
|
3387
|
+
cloudCalls: CloudCallPlan[];
|
|
3388
|
+
/** EXP-011 §43. */
|
|
3389
|
+
recordReads: RecordReadPlan[];
|
|
3390
|
+
/** EXP-011 §45. The files nodes that translated, compile order — earned by attachment. */
|
|
3391
|
+
fileOps: FileOpPlan[];
|
|
3392
|
+
repeaters: Record<string, RepeaterPlan>;
|
|
3393
|
+
/** Static Data nodes hoisted to module constants (STATIC-DATA-TARGET §3), resolution order. */
|
|
3394
|
+
staticData: StaticDataPlan[];
|
|
3395
|
+
/**
|
|
3396
|
+
* Re-host wrapper definitions by node id (EXP-003 §4), registered the moment a read of the
|
|
3397
|
+
* node resolves — the emit layer prints exactly the wrappers that surviving expressions
|
|
3398
|
+
* reference, so a definition nothing kept costs nothing. Insertion order is resolution order
|
|
3399
|
+
* and therefore deterministic.
|
|
3400
|
+
*/
|
|
3401
|
+
jsFunctions: Record<string, JsFunctionPlan>;
|
|
3402
|
+
/**
|
|
3403
|
+
* Nodes carrying author-written JavaScript that this component did **not** translate, and that
|
|
3404
|
+
* are not Function/Expression/Visual Function — those three are `jsFunctions`, and the emit
|
|
3405
|
+
* layer already preserves a refused one's body as a comment.
|
|
3406
|
+
*
|
|
3407
|
+
* 🔴 This is the same loss one node-family over, and it was silent. A `Map Collection` whose
|
|
3408
|
+
* Script does more than name properties is deferred with a reason and an in-code marker naming
|
|
3409
|
+
* the node — and its script text appeared **nowhere in the exported repo**, which is exactly
|
|
3410
|
+
* the case where that text is the only statement of what the developer now has to write
|
|
3411
|
+
* (EXP-004, and §20's argument for the Function case verbatim).
|
|
3412
|
+
*
|
|
3413
|
+
* Node order, so the output is deterministic.
|
|
3414
|
+
*/
|
|
3415
|
+
refusedScripts: RefusedScriptPlan[];
|
|
3416
|
+
/**
|
|
3417
|
+
* State rows (CONTROLLED-STATE-TARGET §3), registered the moment a use resolves — the emit
|
|
3418
|
+
* layer prints exactly the vars that surviving actions/expressions/effects reference.
|
|
3419
|
+
*/
|
|
3420
|
+
stateVars: StateVarPlan[];
|
|
3421
|
+
/** Sync effects (§3.4), registration order — one per translated wired control-state input. */
|
|
3422
|
+
syncEffects: SyncEffectPlan[];
|
|
3423
|
+
/** Push effects (§3.5), registration order — the lifted value outputs' child side. */
|
|
3424
|
+
pushEffects: PushEffectPlan[];
|
|
3425
|
+
/**
|
|
3426
|
+
* EXP-011 §99. A repeater's relayed row values (`itemOutput-<output>`) this component lifts: a state row per value,
|
|
3427
|
+
* written in every row's relayed-signal callback from what the parent hands that row for its input `input`, before
|
|
3428
|
+
* the signal's chain, as the runtime's `itemOutputSignalTriggered` copies the row's outputs and then sends the signal.
|
|
3429
|
+
*/
|
|
3430
|
+
rowRelays: RowRelayPlan[];
|
|
3431
|
+
/** Reactive Conditions (LOGIC-TARGET §10), compile order — one useEffect each. */
|
|
3432
|
+
branchEffects: BranchEffectPlan[];
|
|
3433
|
+
/** Value Changed effects (EXP-011 §39), compile order — one useEffect + one useRef each. */
|
|
3434
|
+
valueChangedEffects: ValueChangedEffectPlan[];
|
|
3435
|
+
/** P18-S119-TICKEDRUN. Run-wired Functions a ticked input also runs, node order — one useEffect + one useRef each. */
|
|
3436
|
+
tickedRuns: TickedRunPlan[];
|
|
3437
|
+
/** EXP-011 §43. Records whose Fetch is unwired, compile order — one useEffect keyed on the Id each. */
|
|
3438
|
+
recordEffects: RecordEffectPlan[];
|
|
3439
|
+
/** EXP-011 §67. Variables with an authored Value, node order — one mount effect each. */
|
|
3440
|
+
variableSeeds: VariableSeedPlan[];
|
|
3441
|
+
/** EXP-011 §71. Object nodes with values typed into their own property inputs, node order — one mount effect each. */
|
|
3442
|
+
objectSeeds: ObjectSeedPlan[];
|
|
3443
|
+
/** P18-S116-FIELDMOUNT. Text inputs that announce their Value as they mount, node order — one mount effect each. */
|
|
3444
|
+
fieldMounts: FieldMountPlan[];
|
|
3445
|
+
/** EXP-011 §48. CSS Definitions, node order — one module constant + one mount effect each. */
|
|
3446
|
+
styleSheets: StyleSheetPlan[];
|
|
3447
|
+
/** EXP-011 §49. The States nodes that translated, registration order. */
|
|
3448
|
+
statesMachines: StatesPlan[];
|
|
3449
|
+
/** EXP-011 §49. The Animate To Value nodes that translated, registration order. */
|
|
3450
|
+
animations: AnimationPlan[];
|
|
3451
|
+
/** EXP-011 §52. The Script nodes that translated, registration order. */
|
|
3452
|
+
scripts: ScriptPlan[];
|
|
3453
|
+
/** EXP-011 §53. The Run Tasks nodes hosted here, registration order. */
|
|
3454
|
+
runTasks: RunTasksPlan[];
|
|
3455
|
+
/** EXP-011 §53. Set when this component is a Run Tasks template whose start chain translated. */
|
|
3456
|
+
task?: TaskPlan;
|
|
3457
|
+
/** EXP-011 §57. Repeater Items whose Added chain translated — a once-on-mount effect each. */
|
|
3458
|
+
repeaterItems: RepeaterItemPlan[];
|
|
3459
|
+
/** EXP-011 §53. Why a component named as a template is not one — repeated by every host that names it. */
|
|
3460
|
+
taskRefusal?: string;
|
|
3461
|
+
/** EXP-011 §54. The On App Error boundaries hosted here, registration order. */
|
|
3462
|
+
appErrors: AppErrorPlan[];
|
|
3463
|
+
/** EXP-011 §58. */
|
|
3464
|
+
streams: StreamPlan[];
|
|
3465
|
+
/** EXP-011 §59. The Screen Resolution hooks this component reads, registration order. */
|
|
3466
|
+
screenResolutions: ScreenResolutionPlan[];
|
|
3467
|
+
/** EXP-011 §60. The Component Object record this component materializes (record mode), if any. */
|
|
3468
|
+
componentObject?: ComponentObjectRecordPlan;
|
|
3469
|
+
/** EXP-011 §60. The nearest ancestor's record this component reads or writes through the parent pair, if any. */
|
|
3470
|
+
parentObject?: ParentComponentObjectPlan;
|
|
3471
|
+
/** EXP-011 §63. The Drag nodes rendered here, registration order. */
|
|
3472
|
+
drags: DragPlan[];
|
|
3473
|
+
/** EXP-011 §61. The Component Stacks this component renders, registration order — one hook line each. */
|
|
3474
|
+
pageStacks: PageStackPlan[];
|
|
3475
|
+
/**
|
|
3476
|
+
* EXP-011 §61. Set when a Pop Component Stack attached here: the interface then declares the reserved prop
|
|
3477
|
+
* `pageStackEntry?: PageStackEntryHandle`, and every stack row that shows this component passes it.
|
|
3478
|
+
*/
|
|
3479
|
+
popsStack: boolean;
|
|
3480
|
+
/** EXP-011 §55. The `Create New Array` handles this component holds, allocation order. */
|
|
3481
|
+
mintedArrays: MintedArrayPlan[];
|
|
3482
|
+
/**
|
|
3483
|
+
* EXP-011 §54. Set when this is the router shell kept as a file for the logic beside its Router — a
|
|
3484
|
+
* null-rendering component the scaffold's `App.tsx` renders inside the router, for the app's life. The
|
|
3485
|
+
* Router itself is still `collapsed into src/App.tsx`.
|
|
3486
|
+
*/
|
|
3487
|
+
shell?: true;
|
|
3488
|
+
/**
|
|
3489
|
+
* EXP-021 slice 1 (P18 session 136). Set when this is a logic component — no node in it draws, and a component places
|
|
3490
|
+
* it — exported as a hook, `use<Name>(props)`, with the props interface a visual component has: the placement's inputs,
|
|
3491
|
+
* its signal inputs' pulses, its signal outputs' callbacks, its lifted value outputs. Its placements are called, not drawn
|
|
3492
|
+
* (`hookCalls`). The runtime keeps a component instance whether or not anything draws it, as a hook call does.
|
|
3493
|
+
*/
|
|
3494
|
+
hook?: true;
|
|
3495
|
+
/** EXP-021 slice 1. The placements of logic components in this component, in source order: one hook call each. */
|
|
3496
|
+
hookCalls: string[];
|
|
3497
|
+
/**
|
|
3498
|
+
* Value output ports this component lifts (§4d child side) — the parent side consults this
|
|
3499
|
+
* list off the target's plan, so parent and child agree by construction (the s10 rule).
|
|
3500
|
+
*/
|
|
3501
|
+
liftedOutputProps: Array<{
|
|
3502
|
+
port: string;
|
|
3503
|
+
prop: string;
|
|
3504
|
+
tsType: string;
|
|
3505
|
+
}>;
|
|
3506
|
+
/** Instance id → lifted callbacks the parent passes (`onXChanged={setX}`) (§4d parent side). */
|
|
3507
|
+
instanceLifted: Record<string, Array<{
|
|
3508
|
+
prop: string;
|
|
3509
|
+
setterName: string;
|
|
3510
|
+
}>>;
|
|
3511
|
+
/**
|
|
3512
|
+
* Parent-side lifted wires awaiting the target's plan (planProject's second phase): a
|
|
3513
|
+
* consumed instance value output binds only when the child actually lifted the port —
|
|
3514
|
+
* otherwise the parent would pass a prop the child's emitted interface does not declare.
|
|
3515
|
+
*/
|
|
3516
|
+
pendingLifted: Array<{
|
|
3517
|
+
connectionKey: string;
|
|
3518
|
+
instanceId: string;
|
|
3519
|
+
targetLegacy: string;
|
|
3520
|
+
port: string;
|
|
3521
|
+
toNodeId: string;
|
|
3522
|
+
toProperty: string;
|
|
3523
|
+
/** P18-S127-SIBLINGVALUE. The sink is another placed component's input: a lift that fails is named on its marker. */
|
|
3524
|
+
intoInstance?: true;
|
|
3525
|
+
}>;
|
|
3526
|
+
dispositions: Record<string, Disposition>;
|
|
3527
|
+
/** Dropped wires, unhandled constructs — EXP-004's report feed. Nothing silently dropped. */
|
|
3528
|
+
notes: string[];
|
|
3529
|
+
/**
|
|
3530
|
+
* EXP-013. Every node `dispositions` refused, as a row with the refused node that starves it.
|
|
3531
|
+
* Built from `dispositions` at each exit, after the sweeps — see {@link RefusedNode}.
|
|
3532
|
+
*/
|
|
3533
|
+
refusals: RefusedNode[];
|
|
3534
|
+
}
|
|
3535
|
+
export interface ProjectPlan {
|
|
3536
|
+
plans: ComponentPlan[];
|
|
3537
|
+
byLegacyPath: Map<string, ComponentPlan>;
|
|
3538
|
+
/**
|
|
3539
|
+
* Legacy component path → the scaffold's route-table row for it.
|
|
3540
|
+
*
|
|
3541
|
+
* 🔴 **The whole row, not the url string it used to hold.** A parameterised page has three
|
|
3542
|
+
* paths — the authored `/product/{id}`, the react-router pattern `/product/:id` and the
|
|
3543
|
+
* concrete url a Navigate builds — and a `Map<string, string>` can only carry one of them.
|
|
3544
|
+
* Carrying the row means the route table and the navigations into it cannot disagree.
|
|
3545
|
+
*/
|
|
3546
|
+
pageByLegacy: Map<string, ScaffoldPage>;
|
|
3547
|
+
/** Collections needing an api stub module, in first-use order. */
|
|
3548
|
+
stubCollections: string[];
|
|
3549
|
+
/** App-wide Variables, discovery order — src/stores/variables.ts when non-empty. */
|
|
3550
|
+
variables: VariablePlan[];
|
|
3551
|
+
/** Event channels, discovery order — src/events.ts when non-empty. */
|
|
3552
|
+
channels: ChannelPlan[];
|
|
3553
|
+
/** Named Global Stores, discovery order — one src/stores/<exportName>.ts each. */
|
|
3554
|
+
stores: StorePlan[];
|
|
3555
|
+
/** Named client-side arrays, discovery order — one src/collections/<exportName>.ts each. */
|
|
3556
|
+
collections: CollectionPlan[];
|
|
3557
|
+
/**
|
|
3558
|
+
* Which components a route can reach (reach.ts). Not a disposition and never a reason to emit
|
|
3559
|
+
* less — every component is still emitted. It is what lets the report say whether a deferral is
|
|
3560
|
+
* work the app is missing or a node in a component nothing renders, which over this corpus is
|
|
3561
|
+
* the difference between 85.00% and 93.38%.
|
|
3562
|
+
*/
|
|
3563
|
+
reachability: Reachability;
|
|
3564
|
+
/**
|
|
3565
|
+
* EXP-010. Node types more than one kit registers, as `type (kit that lost)`.
|
|
3566
|
+
*
|
|
3567
|
+
* 🔴 A collision, not a warning about one: `registerModule` refuses the second registration, so
|
|
3568
|
+
* the running app shows the *first* kit's node. The export follows that rule and says which kit
|
|
3569
|
+
* it therefore ignored — silently taking either one would emit an app that does not match the
|
|
3570
|
+
* one the author is looking at.
|
|
3571
|
+
*/
|
|
3572
|
+
kitDuplicates: string[];
|
|
3573
|
+
}
|
|
3574
|
+
export declare function planProject(ir: ExportIR, catalog: CatalogIndex): ProjectPlan;
|
|
3575
|
+
/**
|
|
3576
|
+
* P18-S122-DEADROW, P18-S123-UNDRAWNTRIGGER. What a component's next plan refuses before it attaches anything: chains on
|
|
3577
|
+
* an element the export never draws (`owners`), Did Mount chains on an element a value mounts (`mounts`, which the
|
|
3578
|
+
* emitter does not run), and the render reads of a Function's row that nothing the export runs ever writes (`rows`, by
|
|
3579
|
+
* the Function's id). Each maps a node id to the sentence the refusal says.
|
|
3580
|
+
*
|
|
3581
|
+
* 🔴 **A row's other readers are kept, and said.** A Function input or a Set Variable that reads a dead row reads what
|
|
3582
|
+
* HEAD's export read (nothing yet); refusing the reader would take a chain that works for every other input with it
|
|
3583
|
+
* (the older Site Builder copies' "The slug to show" reads the URL's slug, and the never-written home slug only as a
|
|
3584
|
+
* fallback). They are named in a note instead.
|
|
3585
|
+
*/
|
|
3586
|
+
/** P18 session 126 (EXP-011 §93). The words a refusal under a page's own wired Group starts with; the emitter says the same. */
|
|
3587
|
+
export declare const UNCARRIED_PAGE_GROUP = "the page's own Group,";
|
|
3588
|
+
export interface PlanExclusions {
|
|
3589
|
+
owners: Map<string, string>;
|
|
3590
|
+
mounts: Map<string, string>;
|
|
3591
|
+
rows: Map<string, string>;
|
|
3592
|
+
}
|
|
3593
|
+
/**
|
|
3594
|
+
* Why an element of a planned component is never drawn, or `null` when it may be. The emitter's own rule (`render` in
|
|
3595
|
+
* `component.ts`): an element, or an element above it, whose Mounted is bound to a statically false value, or not bound
|
|
3596
|
+
* and authored false, is removed from the tree. A Mounted bound to anything else draws it when that value says.
|
|
3597
|
+
*/
|
|
3598
|
+
export declare function undrawnReasonOf(plan: ComponentPlan, component: ComponentIR, id: string): string | null;
|
|
3599
|
+
/**
|
|
3600
|
+
* EXP-011 §99, P18-S131-ROWLESSTRIGGER. Why a repeater draws no rows in the export, or null when it draws them: its items
|
|
3601
|
+
* have no feed the export reads (a query, a named array, an expression, Static Data), or its rows are mapped by a script
|
|
3602
|
+
* (the emitter's `renderRepeater` draws a TODO marker for both). Decided after the list passes.
|
|
3603
|
+
*/
|
|
3604
|
+
export declare function rowlessReasonOf(plan: ComponentPlan, repeaterId: string): string | null;
|
|
3605
|
+
/**
|
|
3606
|
+
* The triggers of one planned component that the emitted app never runs (see {@link PlanExclusions}). Read off the
|
|
3607
|
+
* finished plan: its handlers, its bindings after the lift phase, and every action list it carries.
|
|
3608
|
+
*/
|
|
3609
|
+
export declare function unreachedTriggersOf(plan: ComponentPlan, component: ComponentIR): PlanExclusions;
|
|
3610
|
+
/**
|
|
3611
|
+
* A component's output interface, from its Component Outputs nodes' declarations — the union
|
|
3612
|
+
* across nodes, first declaration of a name winning (the runtime merges the port sets the same
|
|
3613
|
+
* way). Signal ports become callback props (COMPONENT-OUTPUTS-TARGET §2): a name that is
|
|
3614
|
+
* already an `onX` identifier is kept verbatim, anything else becomes `on` + PascalCase. A
|
|
3615
|
+
* port that cannot become a prop — no identifier material, or a collision with an input prop
|
|
3616
|
+
* or another output — fails with a reason rather than being silently renamed. Deterministic
|
|
3617
|
+
* over the ComponentIR alone, so the child's plan and every parent's plan agree.
|
|
3618
|
+
*/
|
|
3619
|
+
export interface OutputInterface {
|
|
3620
|
+
props: Array<{
|
|
3621
|
+
port: string;
|
|
3622
|
+
prop: string;
|
|
3623
|
+
}>;
|
|
3624
|
+
failed: Array<{
|
|
3625
|
+
port: string;
|
|
3626
|
+
reason: string;
|
|
3627
|
+
}>;
|
|
3628
|
+
/** Declared value-kind output ports — the lifted-state candidates (CONTROLLED-STATE §4d). */
|
|
3629
|
+
valuePorts: string[];
|
|
3630
|
+
/**
|
|
3631
|
+
* Value ports named as lifted callback props (`on` + PascalCase + `Changed` — the s10 name
|
|
3632
|
+
* source; collisions fail the port, never silently rename). Whether a port actually lifts is
|
|
3633
|
+
* the owning component's plan's to decide (its feed must resolve); the name is decided here,
|
|
3634
|
+
* off the ComponentIR alone, so the child's plan and every parent's plan agree.
|
|
3635
|
+
*/
|
|
3636
|
+
valueProps: Array<{
|
|
3637
|
+
port: string;
|
|
3638
|
+
prop: string;
|
|
3639
|
+
tsType: string;
|
|
3640
|
+
}>;
|
|
3641
|
+
}
|
|
3642
|
+
export declare function signalInputPortsOf(component: ComponentIR | undefined): Set<string>;
|
|
3643
|
+
export declare function componentOutputInterface(component: ComponentIR): OutputInterface;
|
|
3644
|
+
/** node type → the kit node that registers it, and which module folder that kit is. */
|
|
3645
|
+
export type KitIndex = Map<string, {
|
|
3646
|
+
moduleDir: string;
|
|
3647
|
+
def: KitNodeIR;
|
|
3648
|
+
}>;
|
|
3649
|
+
/**
|
|
3650
|
+
* Every node type the project's kits register (EXP-010).
|
|
3651
|
+
*
|
|
3652
|
+
* ⚠️ **First registration wins, and a duplicate is not silently discarded** — the running app has
|
|
3653
|
+
* the same collision (`registerModule` refuses the second) so the export must agree with it rather
|
|
3654
|
+
* than pick the later one and render a different node than the app does. The caller reports it.
|
|
3655
|
+
*/
|
|
3656
|
+
export declare function indexKitNodes(modules: ModuleIR[]): {
|
|
3657
|
+
index: KitIndex;
|
|
3658
|
+
duplicates: string[];
|
|
3659
|
+
};
|
|
3660
|
+
/**
|
|
3661
|
+
* EXP-013 AC4 — is losing this node losing a **pathway** rather than a feature?
|
|
3662
|
+
*
|
|
3663
|
+
* Three families, from Richard's ruling (EXP-011 §50): a backend verb (the record verbs, the
|
|
3664
|
+
* relation verbs, `Record`, `Query Records`, the user verbs, `Cloud Function`, `HTTP Request`,
|
|
3665
|
+
* the files), a navigation (`Navigate`, `Navigate To Path`, `External Link`), and `On App Error`,
|
|
3666
|
+
* whose refusal removes the app's whole error pathway. A `Set Variable` behind a refused trigger
|
|
3667
|
+
* is a missing feature; a `Cloud Function` behind one is the app no longer talking to its backend
|
|
3668
|
+
* — the verdict in the pre-flight turns on this distinction, so it lives beside the tables that
|
|
3669
|
+
* define the families rather than in a second list the report would keep.
|
|
3670
|
+
*/
|
|
3671
|
+
/** EXP-011 §58. Which of a streaming node's value outputs read undefined before anything happened — the table's answer, shared with component.ts. */
|
|
3672
|
+
export declare function streamFieldMaybeUndefined(node: StreamKind, field: string): boolean;
|
|
3673
|
+
export declare function isPathwayType(type: string): boolean;
|
|
3674
|
+
/** EXP-011 §56. Every wired expression a filter tree reads, for the walkers that follow an expression's reads. */
|
|
3675
|
+
export declare function whereExprs(where: RecordWhere | null): ValueExpr[];
|
|
3676
|
+
/**
|
|
3677
|
+
* Parses a For Each mapping script as far as "is this a static object literal of
|
|
3678
|
+
* string→string" (TARGET-OUTPUT §2). Anything cleverer returns null and defers to EXP-003.
|
|
3679
|
+
*/
|
|
3680
|
+
export declare function parseIdentityMapping(script: string): Array<{
|
|
3681
|
+
input: string;
|
|
3682
|
+
field: string;
|
|
3683
|
+
}> | null;
|
|
3684
|
+
/**
|
|
3685
|
+
* §15 — a component prop's type is what the port *declares*, and `any` when it declares nothing.
|
|
3686
|
+
*
|
|
3687
|
+
* A port type is **free text** (`AiAssistant/authoring/plan.ts`: *"names are what bind"*), so
|
|
3688
|
+
* there is no closed vocabulary to fall through to a default with. The editor's own PortEditor
|
|
3689
|
+
* panel opens a new Component Inputs port as `type: { name: '*' }` (`componentinputs.ts`), and
|
|
3690
|
+
* `*` is normatively the untyped wildcard — the Port Type Contract describes "an untyped Function
|
|
3691
|
+
* output defaulted to `*` and connected anywhere". It is two thirds of the corpus's component
|
|
3692
|
+
* ports (471 of 714).
|
|
3693
|
+
*
|
|
3694
|
+
* Mapping that to `string` was the emitter claiming a type the graph refused to make, and it is
|
|
3695
|
+
* the same disagreement §12 refused in the other direction: the declared type is a claim about
|
|
3696
|
+
* the port, not a check on what reaches it. Nothing coerces on delivery — `componentinputs.ts`
|
|
3697
|
+
* registers each output as a bare getter over `_internal.inputValues`, and the one cast on a
|
|
3698
|
+
* connection (`node.ts` `_setValueFromConnection`) is `object`/`array` → `string` only.
|
|
3699
|
+
*
|
|
3700
|
+
* `any` rather than `unknown` for the reason the `array` case already gives, and the reason the
|
|
3701
|
+
* two sibling mappers in this file — `jsOutputTsType` and `valueTsTypeOf` — both already default
|
|
3702
|
+
* to `any`: strict tsc rejects an `unknown` read at the sites that consume these values.
|
|
3703
|
+
*
|
|
3704
|
+
* ⚠️ `string` must stay an explicit case. It is 224 ports, and it reached its type through the
|
|
3705
|
+
* old default — folding it into this one would widen every genuinely-typed prop in the corpus.
|
|
3706
|
+
*/
|
|
3707
|
+
/** One row of a Component Stack's Components list, resolved (EXP-011 §61). */
|
|
3708
|
+
interface StackPageInfo {
|
|
3709
|
+
id: string;
|
|
3710
|
+
label: string;
|
|
3711
|
+
/** The `pageComp-<id>` parameter — undefined when unset (the stack then refuses). */
|
|
3712
|
+
componentLegacy: string | undefined;
|
|
3713
|
+
}
|
|
3714
|
+
/** A `Page Stack` anywhere in the project, read off the IR once (EXP-011 §61). */
|
|
3715
|
+
export interface StackInfo {
|
|
3716
|
+
nodeId: string;
|
|
3717
|
+
componentPath: string;
|
|
3718
|
+
label: string;
|
|
3719
|
+
/** The authored Name, `Main` by default — `navigation-handler.ts` keys its registry on `name || 'Main'`. */
|
|
3720
|
+
name: string;
|
|
3721
|
+
pages: StackPageInfo[];
|
|
3722
|
+
/** The authored Start Page, or the first page's id (`resetAsync`). */
|
|
3723
|
+
startPage: string;
|
|
3724
|
+
/** Why the stack does not translate, or null. Decided from the IR alone, so a pusher in any component can ask. */
|
|
3725
|
+
refusal: string | null;
|
|
3726
|
+
}
|
|
3727
|
+
export interface StackIndex {
|
|
3728
|
+
byNode: Map<string, StackInfo>;
|
|
3729
|
+
byName: Map<string, StackInfo[]>;
|
|
3730
|
+
/** The stacks listing a component (by legacy path) among their pages — the Pop's gate. */
|
|
3731
|
+
hostsOf: Map<string, StackInfo[]>;
|
|
3732
|
+
}
|
|
3733
|
+
/** The stringlist parameters of a Pop (`results`, `backActions`) — split as the runtime splits them, no trimming. */
|
|
3734
|
+
export declare function stackStringList(value: unknown): string[];
|
|
3735
|
+
/**
|
|
3736
|
+
* EXP-011 §61. Every `Page Stack` in the project with its refusal, computed once per IR — `popupTargetLegacies`'
|
|
3737
|
+
* shape, because a pusher is planned in one component and its stack sits in another, and the plan runs
|
|
3738
|
+
* component by component. Every check here is a fact the IR states outright (an authored value, a wire into
|
|
3739
|
+
* the stack, a page's existence and root), never a plan-time one.
|
|
3740
|
+
*/
|
|
3741
|
+
export declare function indexPageStacks(ir: ExportIR, catalog: CatalogIndex, kits: KitIndex): StackIndex;
|
|
3742
|
+
/**
|
|
3743
|
+
* The api module's names for one class, a pure function of the class name (RECORD-VERBS-TARGET
|
|
3744
|
+
* §4d). Queries and mutations both derive from this, so a project that reads *and* writes one
|
|
3745
|
+
* collection lands both in a single module rather than two that disagree about the type name.
|
|
3746
|
+
*/
|
|
3747
|
+
/**
|
|
3748
|
+
* A schema column's declared type as TS (EXP-002 §10a) — the one mapping, read by the collection
|
|
3749
|
+
* module's interface and by a `Record`'s column reads (EXP-011 §43), so the two cannot disagree.
|
|
3750
|
+
*/
|
|
3751
|
+
export declare function tsColumnType(columnType: string): string;
|
|
3752
|
+
export declare function collectionModuleNames(collectionName: string): {
|
|
3753
|
+
typeName: string;
|
|
3754
|
+
plural: string;
|
|
3755
|
+
moduleBase: string;
|
|
3756
|
+
fetchName: string;
|
|
3757
|
+
};
|