@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.
Files changed (49) hide show
  1. package/README.md +59 -2
  2. package/dist/analyze/appState.d.ts +296 -0
  3. package/dist/analyze/plan.d.ts +3757 -0
  4. package/dist/analyze/querySettings.d.ts +75 -0
  5. package/dist/analyze/reach.d.ts +41 -0
  6. package/dist/analyze/scriptUses.d.ts +29 -0
  7. package/dist/catalog.d.ts +129 -0
  8. package/dist/cli/args.d.ts +90 -0
  9. package/dist/cli/exitCodes.d.ts +125 -0
  10. package/dist/cli/run.d.ts +30 -0
  11. package/dist/cli.inputs.json +174 -0
  12. package/dist/cli.mjs +53746 -0
  13. package/dist/cli.mjs.map +7 -0
  14. package/dist/emit/animateLib.d.ts +43 -0
  15. package/dist/emit/component.d.ts +144 -0
  16. package/dist/emit/dateLib.d.ts +45 -0
  17. package/dist/emit/emitApp.d.ts +38 -0
  18. package/dist/emit/idLib.d.ts +68 -0
  19. package/dist/emit/kits.d.ts +85 -0
  20. package/dist/emit/mediaLib.d.ts +75 -0
  21. package/dist/emit/naming.d.ts +76 -0
  22. package/dist/emit/parseLib.d.ts +106 -0
  23. package/dist/emit/preflight.d.ts +174 -0
  24. package/dist/emit/readme.d.ts +51 -0
  25. package/dist/emit/report.d.ts +309 -0
  26. package/dist/emit/scaffold.d.ts +67 -0
  27. package/dist/emit/statesLib.d.ts +52 -0
  28. package/dist/emit/style.d.ts +165 -0
  29. package/dist/emit/utilLib.d.ts +78 -0
  30. package/dist/errorMessage.d.ts +28 -0
  31. package/dist/index.cjs +52848 -0
  32. package/dist/index.cjs.map +7 -0
  33. package/dist/index.d.ts +37 -0
  34. package/dist/index.mjs +52769 -0
  35. package/dist/index.mjs.map +7 -0
  36. package/dist/ir/types.d.ts +567 -0
  37. package/dist/ledger.cjs +971 -0
  38. package/dist/ledger.cjs.map +7 -0
  39. package/dist/ledger.d.ts +75 -0
  40. package/dist/ledger.mjs +948 -0
  41. package/dist/ledger.mjs.map +7 -0
  42. package/dist/node-catalog.json +65333 -0
  43. package/dist/parse/kitSource.d.ts +135 -0
  44. package/dist/parse/parseModules.d.ts +42 -0
  45. package/dist/parse/parseProject.d.ts +13 -0
  46. package/dist/scriptFiles.d.ts +30 -0
  47. package/dist/structurePorts.d.ts +70 -0
  48. package/dist/write/writeExport.d.ts +67 -0
  49. package/package.json +53 -4
package/README.md CHANGED
@@ -1,3 +1,60 @@
1
- # Temporary Holding Version
1
+ # `@nodegx/export`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Turns a [NodeGX](https://github.com/The-Low-Code-Foundation/NodeGX) project on disk into a React +
4
+ Vite application: project files → IR → analysis → generated source. It is the same code path the
5
+ editor's **Export React code** command runs; there is no second implementation.
6
+
7
+ Everything here is a pure function over files. Nothing opens a window, and nothing needs a display
8
+ server — which is the point: this is the half of NodeGX that a CI job or an agent can drive.
9
+
10
+ ```bash
11
+ npm i @nodegx/export
12
+ ```
13
+
14
+ Requires Node ≥ 22.
15
+
16
+ ## Use
17
+
18
+ ```js
19
+ import { emitApp, loadCatalog, parseProject, summarizePreflight, renderPreflight } from '@nodegx/export';
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+
23
+ const catalog = loadCatalog(); // ships with this package
24
+ const ir = parseProject('./my-project', catalog);
25
+ const app = emitApp(ir, catalog);
26
+
27
+ // What the export will produce, before choosing where to put it — writes nothing.
28
+ console.log(renderPreflight(summarizePreflight(app)));
29
+
30
+ for (const [file, contents] of Object.entries(app.files)) {
31
+ fs.mkdirSync(path.dirname(path.join('out', file)), { recursive: true });
32
+ fs.writeFileSync(path.join('out', file), contents);
33
+ }
34
+ ```
35
+
36
+ `app.files` is a `path → contents` map; `app.copies` lists the files that travel byte-for-byte
37
+ (fonts and other `noodl_modules` assets) rather than being generated, because a `.ttf` is not a
38
+ string. `app.notes` and `app.report` are what the export dropped or deferred.
39
+
40
+ ## What it does not do
41
+
42
+ - **It does not deploy.** Deploying the interpreted viewer needs the editor's live project model;
43
+ this package parses files.
44
+ - **It does not read the graph the editor rewrites on open.** The editor applies a set of patches
45
+ when it loads a project and this package reads the files as written. Where the two disagree, the
46
+ export follows the file. Closing that gap is tracked as HLS-003.
47
+ - **It emits an app that does not yet pass `npm run build` in every case.** See
48
+ [#24](https://github.com/The-Low-Code-Foundation/NodeGX/issues/24). (Measured 2026-10-21: 16 of the 17 shipped
49
+ templates typecheck; the one error is a known row.)
50
+ - **It does not yet carry every Function.** `nodegx export --dry-run` prints first, per node, what will and will not
51
+ translate and why; read that before anything else. A Function that keeps state on `this`, reads the clock, draws
52
+ randomness, schedules a timer or touches the browser travels as written when a Run wire drives it (2026-10-21); one that
53
+ awaits, reads `Noodl.*`, or sits in a logic component does not yet, and the preflight says so by name.
54
+
55
+ Both of those are worth knowing before putting this in a pipeline, because in a pipeline there is
56
+ nobody to notice.
57
+
58
+ ## Licence
59
+
60
+ GPL-3.0.
@@ -0,0 +1,296 @@
1
+ /**
2
+ * App-wide state discovery (EXP-002 step 5). Variables (Variable2 / Set Variable) and event
3
+ * channels (Event Sender / Event Receiver) are app-global constructs: their identity is a
4
+ * literal name parameter, not a wire — the interpreter holds every Variable as one property of
5
+ * the shared `--ndl--global-variables` record, and events travel a channel-name-keyed global
6
+ * bus. This pass walks the whole IR once and produces the registry the per-component planner
7
+ * and the stores/events emitters share.
8
+ *
9
+ * Typing is inference over statically-known writes (EXP-002-STEP5-TARGET-OUTPUT.md §1): a text
10
+ * input writes `string`; a payload key's type follows its senders' wires; a Variable read
11
+ * follows that variable's own writers. A writer whose source cannot be typed makes the whole
12
+ * variable `unknown` — and an `unknown` variable bound into rendered content defers rather than
13
+ * emitting TypeScript that does not compile. Nodes with non-literal names defer entirely: a
14
+ * computed variable or channel name is genuinely dynamic (EXP-003's territory).
15
+ */
16
+ import { ComponentIR, ExportIR, NodeIR } from '../ir/types';
17
+ export interface VariableWriter {
18
+ componentPath: string;
19
+ nodeId: string;
20
+ nodeType: string;
21
+ label?: string;
22
+ /**
23
+ * EXP-011 §67. Set when the writer is a `Variable` node carrying an authored `Value`: the
24
+ * runtime's `value` input setter runs at node creation with that parameter and schedules a
25
+ * store (variablenode2.ts), so the node writes the variable on every mount of its component.
26
+ */
27
+ seed?: string | number | boolean;
28
+ /**
29
+ * EXP-011 §71. Set when the writer is an `Object` node carrying values typed into its OWN
30
+ * `prop-<key>` inputs: the runtime registers any `prop-*` input on demand and queues every
31
+ * authored parameter into the node at creation (nodescope.ts), the setter marks the key dirty
32
+ * and `scheduleStore` writes it into the record in the same pass (modelnode2.ts) — so the node
33
+ * writes these keys on every mount of its component. Parameter order, as the runtime writes them.
34
+ */
35
+ seeds?: Array<{
36
+ key: string;
37
+ value: string | number | boolean;
38
+ }>;
39
+ }
40
+ export interface VariablePlan {
41
+ /** The authored variable name — the shared-record key. */
42
+ name: string;
43
+ /** The identifier `src/stores/variables.ts` exports. */
44
+ exportName: string;
45
+ /** 'string' when every statically-known write is string-typed; 'unknown' otherwise. */
46
+ tsType: 'string' | 'unknown';
47
+ /** The writing constructs the graph names, for the module's doc comment. */
48
+ writers: VariableWriter[];
49
+ }
50
+ export interface ChannelSender {
51
+ componentPath: string;
52
+ nodeId: string;
53
+ label?: string;
54
+ }
55
+ /** A JSON-literal type, as far as a store key's TypeScript type can be read off one. */
56
+ export type StoreKeyTsType = 'string' | 'number' | 'boolean' | 'unknown';
57
+ export interface StoreKeyPlan {
58
+ key: string;
59
+ /** From the initial-state literal when present; else inferred over the key's Set writers. */
60
+ tsType: StoreKeyTsType;
61
+ /** Present (and `required`) when a declarer's initialState carries this key. */
62
+ initial?: unknown;
63
+ required: boolean;
64
+ }
65
+ /** A sentence about one Global Store declarer, with where it sits. */
66
+ export interface StoreNote {
67
+ componentPath: string;
68
+ nodeId: string;
69
+ text: string;
70
+ }
71
+ export interface StorePlan {
72
+ /** The authored store name — 'app' when every naming node used the default. */
73
+ name: string;
74
+ /** The identifier `src/stores/<exportName>.ts` exports. */
75
+ exportName: string;
76
+ /** `MoodState`. */
77
+ interfaceName: string;
78
+ /** Initial-state keys first (transcription order), then writer-discovered keys. */
79
+ keys: StoreKeyPlan[];
80
+ /** `net.noodl.GlobalStore` nodes naming this store. */
81
+ declarers: VariableWriter[];
82
+ /** `net.noodl.GlobalStore.Set` nodes writing this store. */
83
+ writers: VariableWriter[];
84
+ /**
85
+ * EXP-011 §78. The State History, Undo / Redo, State Snapshot and Optimistic Update nodes naming this store. Each is
86
+ * handed the store module itself, so a store one of them names is emitted even when no Global Store node declares it.
87
+ */
88
+ historians?: VariableWriter[];
89
+ /** Set: the whole store defers (authored persist, unparseable initial state on every declarer). */
90
+ deferred?: string;
91
+ /**
92
+ * Merged initial states, ignored storageKeys — one per declarer, routed app-wide by `emitApp` (P18-S110-STORENOTES:
93
+ * this list had no reader, so "nothing dropped silently" did not hold for it).
94
+ */
95
+ notes: StoreNote[];
96
+ /**
97
+ * EXP-011 §47. Present when the store is a named **Object** — an `Object` node in "Specify
98
+ * explicitly" mode with a literal Id, and the `Set Object Properties` nodes naming the same
99
+ * Id — rather than a Global Store. The two are one construct in the exported app (`store()`
100
+ * in `@nodegx/core` says so in its own doc comment) and two records in the runtime, where a
101
+ * Global Store is the Model keyed `--ndl--global-store--<name>` and an Object is the Model
102
+ * keyed by its Id; `objectCollisions` is where that difference is kept honest.
103
+ */
104
+ origin?: 'object';
105
+ }
106
+ export declare const GLOBAL_STORE = "net.noodl.GlobalStore";
107
+ export declare const GLOBAL_STORE_SET = "net.noodl.GlobalStore.Set";
108
+ export declare const GLOBAL_STORE_SUBSCRIBE = "net.noodl.GlobalStore.Subscribe";
109
+ export declare function isGlobalStoreFamily(type: string): boolean;
110
+ export declare function isHistoryFamily(type: string): boolean;
111
+ /** `Object` — displayed "Object", stored as `Model2` (modelnode2.ts). */
112
+ export declare const OBJECT_TYPE = "Model2";
113
+ /** `Set Object Properties` (setmodelpropertiesnode.ts, over modelcrudbase.ts). */
114
+ export declare const SET_OBJECT_PROPERTIES_TYPE = "SetModelProperties";
115
+ /**
116
+ * EXP-011 §47. The Id an `Object` or `Set Object Properties` names **statically**, or undefined.
117
+ *
118
+ * Three facts of the runtime decide the shape: `idSource` defaults to `explicit` (an unset one
119
+ * is explicit — `modelcrudbase.ts` declares the default, and the §5.1 gate has read it so since
120
+ * Tier 1.1); in that mode the record is `Model.get(modelId)`, create-on-read, keyed by the Id
121
+ * verbatim; and a wired Id is a runtime value (a Model, a plain object, or a string from
122
+ * anywhere), which is exactly what no module can name. So: explicit or unset, an unwired
123
+ * `modelId`, a non-empty literal string — else undefined, and the caller says which.
124
+ */
125
+ export declare function objectIdOf(node: NodeIR, wiredPorts: Set<string>): string | undefined;
126
+ /**
127
+ * A `Set Object Properties`' authored property list, split as the runtime splits it. The list
128
+ * is load-bearing on the write side: `_pushInputValues` filters the keys it writes by this list
129
+ * (`validProperties`), so a wired `prop-<x>` whose `x` is not listed is silently not written.
130
+ */
131
+ /**
132
+ * EXP-011 §71. The values typed into an `Object` node's OWN `prop-<key>` inputs with nothing wired
133
+ * over them, in parameter order — what the runtime writes into the record at node creation
134
+ * (nodescope.ts queues every authored parameter; modelnode2.ts `userInputSetter` ⇒ dirty ⇒
135
+ * `scheduleStore` ⇒ `model.set`), on every mount of the holding component. The node's Properties
136
+ * list gates nothing here: `registerInputIfNeeded` registers ANY `prop-*` (measured, session 94).
137
+ * Under a wire on the same port the literal is not here (the wire's arrivals land over it, and
138
+ * plan.ts refuses the node by name); a non-primitive value is not here either — plan.ts notes it.
139
+ * One function for both files so discovery (the key's type) and the plan (the write) agree.
140
+ */
141
+ export declare function objectSeedsOf(node: NodeIR, wiredPorts: Set<string>): Array<{
142
+ key: string;
143
+ value: string | number | boolean;
144
+ }>;
145
+ export declare function setPropertiesOf(node: NodeIR): string[];
146
+ /** One translated `NewModel → CollectionInsert` chain — the write side of a named array. */
147
+ export interface CollectionInserterRef {
148
+ componentPath: string;
149
+ newModelId: string;
150
+ insertId: string;
151
+ /** The NewModel's authored label. */
152
+ label?: string;
153
+ }
154
+ export interface CollectionKeyPlan {
155
+ key: string;
156
+ /** Union over the key's statically-known insert sources; conflicting sources ⇒ 'unknown'. */
157
+ tsType: StoreKeyTsType;
158
+ }
159
+ export interface CollectionPlan {
160
+ /** The authored `collectionId` — the shared-array-table key. */
161
+ name: string;
162
+ /** The identifier `src/collections/<exportName>.ts` exports. */
163
+ exportName: string;
164
+ /** `NotesItem`. */
165
+ interfaceName: string;
166
+ /** Union of statically-known inserted property sets, first-seen order, all optional. */
167
+ keys: CollectionKeyPlan[];
168
+ /** `Collection2` nodes naming this array. */
169
+ readers: VariableWriter[];
170
+ /** Translated insert chains writing this array. */
171
+ inserters: CollectionInserterRef[];
172
+ /**
173
+ * Array mutators naming this array — EXP-011 Tier 1.1's `Clear Array`.
174
+ *
175
+ * Registered here rather than folded into {@link readers} because the doc comment on the
176
+ * emitted module is the only place an author can see who touches the array, and "named by"
177
+ * and "emptied by" are different facts. ⚠️ Registration is what makes the module exist: an
178
+ * array a `Clear Array` names and nothing else reads still needs a `src/collections/<x>.ts`
179
+ * for the emitted `.clear()` to have a subject.
180
+ */
181
+ mutators: VariableWriter[];
182
+ notes: string[];
183
+ }
184
+ /**
185
+ * The array a Collection2/CollectionInsert names: the literal `collectionId`, undefined when
186
+ * absent, empty, wired or non-literal. Unlike `storeName` there is no default — an unnamed
187
+ * array is anonymous (per-node in the runtime), which nothing static can share.
188
+ */
189
+ export declare function collectionNameOf(node: NodeIR, wiredPorts: Set<string>): string | undefined;
190
+ /** EXP-011 §55. `Create New Array` — the node whose `id` output is the only wire an Array Id port takes. */
191
+ export declare const COLLECTION_NEW_TYPE = "CollectionNew";
192
+ /**
193
+ * EXP-011 §55. Where a node's Array Id points: a literal name (a module, the named-array model) or a
194
+ * `Create New Array`'s `id` **by wire** (a handle in the minting component's state — the id is never a
195
+ * string in the emitted app; the wire is the name). One answer for every consumer of an Array Id —
196
+ * `Array`, the three mutators, the repeater feed — so the two models cannot disagree about a wire.
197
+ */
198
+ export type ArrayTarget = {
199
+ kind: 'named';
200
+ collectionName: string;
201
+ } | {
202
+ kind: 'minted';
203
+ nodeId: string;
204
+ wireKey: string;
205
+ };
206
+ export declare function arrayTargetOf(node: NodeIR, component: ComponentIR): ArrayTarget | {
207
+ defer: string;
208
+ };
209
+ /** One property of a translated insert chain: an authored literal, a wire, or (absent both) omitted. */
210
+ export interface InsertChainProperty {
211
+ key: string;
212
+ wire?: {
213
+ connectionKey: string;
214
+ fromId: string;
215
+ fromProperty: string;
216
+ };
217
+ literal?: string | number | boolean;
218
+ }
219
+ export interface InsertChain {
220
+ /** The named array, or `''` when {@link minted} is set. */
221
+ collectionName: string;
222
+ /** EXP-011 §55. The `Create New Array` whose handle the insert writes — the chain is component-local then. */
223
+ minted?: string;
224
+ insertId: string;
225
+ newModelId: string;
226
+ /** The NewModel's `properties` list in authored order, omitted keys filtered out. */
227
+ properties: InsertChainProperty[];
228
+ /** Connection keys the chain consumes: id→modifyId, done→add, and every property wire. */
229
+ consumes: string[];
230
+ }
231
+ /**
232
+ * The `NewModel → CollectionInsert` chain analysis (COLLECTIONS-TARGET §2): one `.add({...})`
233
+ * when (a) the properties are statically sourced, (b) the array id is literal, and (c) the
234
+ * created object's outputs feed exactly this insert. Shared by the registry (key discovery and
235
+ * typing) and the per-component planner (handler compilation) so the two cannot disagree.
236
+ */
237
+ export declare function insertChainOf(component: ComponentIR, insertNode: NodeIR, nodeById: Map<string, NodeIR>, wiredPorts: Set<string>): {
238
+ chain: InsertChain;
239
+ } | {
240
+ defer: string;
241
+ };
242
+ export interface ChannelPlan {
243
+ /** The authored channel name. */
244
+ name: string;
245
+ /** The identifier `src/events.ts` exports. */
246
+ exportName: string;
247
+ /** `CelebratePayload`, or null when no sender declares payload keys (the `T = void` channel). */
248
+ payloadTypeName: string | null;
249
+ /** Union of every matching sender's payload keys, first-seen order — exactly how the
250
+ * interpreter's receiver discovers its output ports. */
251
+ payload: Array<{
252
+ key: string;
253
+ tsType: 'string' | 'unknown';
254
+ }>;
255
+ senders: ChannelSender[];
256
+ }
257
+ export interface AppStateRegistry {
258
+ /** Keyed by authored name, discovery order over D1-sorted components. */
259
+ variables: Map<string, VariablePlan>;
260
+ channels: Map<string, ChannelPlan>;
261
+ stores: Map<string, StorePlan>;
262
+ collections: Map<string, CollectionPlan>;
263
+ /**
264
+ * EXP-011 §47. Object Ids that are also Global Store names. In the runtime those are two
265
+ * records (`--ndl--global-store--x` and `x`); in the exported app both would be `store('x')`,
266
+ * one registry entry — so the Object side is refused by name rather than merged into the
267
+ * Global Store's module, and the Global Store keeps translating as it did.
268
+ */
269
+ objectCollisions: Set<string>;
270
+ }
271
+ export declare function collectAppState(ir: ExportIR): AppStateRegistry;
272
+ /** The sender's `payload` stringlist parameter, split — the payload port set, statically. */
273
+ export declare function payloadKeysOf(node: NodeIR): string[];
274
+ export declare function isTextInputType(type: string): boolean;
275
+ /**
276
+ * The store a node names: the literal `storeName`, `'app'` when absent or empty (all three
277
+ * runtime nodes default exactly so, and the history family's three with them), undefined when wired or
278
+ * non-literal — genuinely dynamic.
279
+ */
280
+ export declare function storeNameOf(node: NodeIR, wiredPorts: Set<string>): string | undefined;
281
+ /** A Subscribe node's authored key list, split as the runtime splits it (blank ⇒ all keys). */
282
+ export declare function subscribeKeysOf(node: NodeIR): string[];
283
+ /**
284
+ * A declarer's initialState, as far as it is statically knowable: a JSON-object parameter, or
285
+ * JSON text that parses to a plain object (object-typed ports arrive as text when the author
286
+ * typed JSON — the runtime's own `coerceState` rule). A wire, a non-object, or unparseable
287
+ * text is 'bad'; the declarer contributes nothing and says so in the notes.
288
+ */
289
+ export declare function initialStateOf(node: NodeIR, wiredPorts: Set<string>): {
290
+ kind: 'object';
291
+ value: Record<string, unknown>;
292
+ } | {
293
+ kind: 'absent';
294
+ } | {
295
+ kind: 'bad';
296
+ };