Quickstart: Visual Graphs to Domain Code
Pick a generator (or write your own), define your node palette, mount the ReactFlow canvas, compile the graph, run scenarios, ship — the full setup in 6 steps, in under 30 minutes.
Choose a Generator (or Implement LanguageGenerator)
| Vertical | Generator | Output |
|---|---|---|
| **3D / Game** | `NanoEngineGenerator` | React-Three-Fiber + Rapier ECS schema for Spatial scenes |
| **3D / Physics** | `PhysicsLogicGenerator` | Rapier / Cannon.js constraints for 3D scenes |
| **Mobile** | `ReactNativeGenerator` | SDUI JSON consumed by Expo for native mobile screens |
| **Web UI** | `ReactComponentGenerator` | TSX + Tailwind components for the Visual Editor + Blueprint |
| **Cloud / Infra** | `InfrastructureAsCodeGenerator` | Terraform HCL + Pulumi TS for VPC / IAM / DB provisioning |
| **IoT / Embedded** | `MicroPythonGenerator` | MicroPython payload for ESP32 / ESP8266 OTA flash |
| **Smart Home** | `SmartHomeGenerator` | HomeAssistant YAML + Matter / Zigbee device configs |
| **Industrial CNC** | `GCodeGenerator` | RS-274 G-Code for 5-axis CNC + 3D printers |
| **Industrial SCADA** | `SCADAGenerator` | SCADA HMI screens for industrial control rooms |
| **Show Control** | `ShowControlGenerator` | DMX-512 + Art-Net + OSC for theatrical / event lighting |
| **Robotics** | `MavlinkGenerator` | MAVLink mission protocol for autonomous drone fleets |
| **Twin Simulation** | `DigitalTwinGenerator` | OPC UA + AAS shells for industrial digital twins |
| **Blockchain** | `SolidityGenerator` | Solidity smart contracts (ERC-20 / ERC-721 / custom) |
| **Crypto / Privacy** | `ZeroKnowledgeGenerator` | Circom circuits + WASM artifacts for zk-SNARK proofs |
| **Quantum** | `QuantumCircuitGenerator` | OpenQASM 2.0 + Qiskit Python for quantum circuits |
| **ML / AI** | `NeuralNetworkGenerator` | PyTorch + ONNX model definitions for training pipelines |
| **Optimization** | `OptimizationGenerator` | OR-Tools CP-SAT models for VRP / scheduling / bin packing |
| **Bio / Scientific** | `NextflowGenerator` | Nextflow DSL2 pipelines for bioinformatics |
| **Audio DSP** | `AudioWorkletGenerator` | AudioWorkletProcessor JS for low-latency Web Audio |
| **Video FX** | `VideoFXGenerator` | FFmpeg filter graphs for batch video processing |
| **Video Transcode** | `FfmpegGenerator` | FFmpeg CLI command graphs for transcoding pipelines |
| **Compute Shaders** | `WebGPUComputeGenerator` | WGSL compute shaders for parallel GPU workloads |
| **Sandbox** | `WasmGenerator` | WebAssembly Text Format → `.wasm` for sandboxed execution |
| **Networking** | `WebRTCMeshGenerator` | WebRTC signaling + ICE for peer-to-peer mesh networks |
| **Multiplayer** | `MultiplayerNetcodeGenerator` | Authoritative-server + client-prediction + rollback netcode |
| **Kernel Security** | `EbpfFilterGenerator` | eBPF + XDP C-source for kernel-level firewalls |
| **ETL** | `TypeScriptETLGenerator` | Typed ETL transformer functions for the Singularity module |
| **OCR Templates** | `OcrTemplateGenerator` | Document-extraction templates for the Files module |
| **Construction** | `IfcGenerator` | IFC building information models for the Construction module |
| **Financial Filing** | `XbrlGenerator` | XBRL filings for SEC / EDGAR financial reporting |
| **Meta** | `MetaCompilerGenerator` | A generator that emits new `LanguageGenerator` classes |
| **Custom** | Your own implementation | Anything text-based — author per the `LanguageGenerator` interface |
Define Your Node Palette
Each node is a { id, type, data } triple. Edges connect nodes via named sourceHandle / targetHandle ports. There is no separate "register" step — the generator decides what node types it understands.
```typescript import type { ASTEdge, ASTNode } from "@aeion/core/compiler";
// A small NanoEngine graph: load a model, attach a pointer event, apply an impulse const nodes: ASTNode[] = [ { id: "model", type: "nanoModel", data: { url: "soda_can.glb", isDynamic: true }, }, { id: "event", type: "nanoPointerEvent", data: { eventType: "onPointerDown" }, }, { id: "impulse", type: "nanoApplyImpulse", data: { x: 0, y: 8, z: -2 } }, ];
const edges: ASTEdge[] = [ { id: "e1", source: "model", target: "event", sourceHandle: "out", targetHandle: "target", }, { id: "e2", source: "event", target: "impulse", sourceHandle: "out", targetHandle: "trigger", }, { id: "e3", source: "model", target: "impulse", sourceHandle: "out", targetHandle: "target", }, ]; ```
Type system signals live in the node's data — there is no schema validator forced by the engine. You can lay one on (Zod, custom React UI, etc.) at the editor layer if you want type-checked ports.
Persistence — graphs are plain JSON: { nodes, edges }. Save to any storage. Git-diffable, portable across tenants.
Mount the Visual Editor (@xyflow/react)
The engine doesn't ship a proprietary editor — it composes with the open-source @xyflow/react (formerly ReactFlow). Bring your own node components.
```tsx import { useState, useCallback } from "react"; import { ReactFlow, ReactFlowProvider, Controls, Background, addEdge, applyNodeChanges, applyEdgeChanges, type Node, type Edge, type Connection, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import { NanoModelNode, NanoPointerEventNode, NanoApplyImpulseNode, } from "./MyNodes"; // your own node renderers
const nodeTypes = { nanoModel: NanoModelNode, nanoPointerEvent: NanoPointerEventNode, nanoApplyImpulse: NanoApplyImpulseNode, };
export function MyGraphEditor() { const [nodes, setNodes] = useState<Node[]>(/ initial nodes /); const [edges, setEdges] = useState<Edge[]>(/ initial edges /);
return ( <ReactFlowProvider> <ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} onNodesChange={useCallback( (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), [], )} onEdgesChange={useCallback( (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), [], )} onConnect={useCallback( (params: Connection) => setEdges((eds) => addEdge(params, eds)), [], )} fitView > <Background /> <Controls /> </ReactFlow> </ReactFlowProvider> ); } ```
The Spatial module's own scene editor is built exactly this way — use it as a reference for the shape of a production-grade domain editor.
Editor concerns the engine intentionally leaves to you:
- Node palette UI / drag-and-drop catalog
- Port-type validation feedback
- Live code preview pane (call
compiler.compile()on each change) - Auto-layout, minimap, multi-select, group / collapse
The library handles graph state + rendering; the engine handles compilation. The two are decoupled by design.
Compile the Graph
The ASTCompiler constructor takes a generator; .compile() takes nodes, edges, and the output-node type (string or array).
```typescript import { ASTCompiler, NanoEngineGenerator } from "@aeion/core/compiler";
const compiler = new ASTCompiler(new NanoEngineGenerator()); const result = compiler.compile(nodes, edges, "nanoApplyImpulse");
if (result.errors.length > 0) { console.error("Compile failed:", result.errors); } else { console.log(result.code); // result.metadata — generator-specific structured output (optional) } ```
Execution model:
- The compiler finds every node matching the output type.
- From each output, it walks backward via edges, depth-first.
- Each node passes its incoming edges to the generator's
generateNode, which returns{ code, varName }. - The compiler concatenates the chunks and hands them to
finalizefor wrapping. - Errors are caught per-node — one bad node returns
code: "ERROR_VAR"and a recorded error rather than aborting.
Zero-runtime philosophy — the engine compiles to native source for the target environment (TSX, Python, Solidity, G-Code, etc.). There is no interpreter in the output; the generated code runs as fast as hand-written code on the same target.
Test Your Graph
The engine ships no built-in test runner — graphs are compiled into ordinary source, so test the source with whatever test framework that target uses (Vitest / Jest for TypeScript outputs, pytest for Python, Foundry for Solidity, etc.).
Recommended scenario pattern for visual logic:
```typescript import { describe, it, expect } from "vitest"; import { ASTCompiler, TypeScriptETLGenerator } from "@aeion/core/compiler";
const compiler = new ASTCompiler(new TypeScriptETLGenerator());
describe("My pricing graph", () => { it("compiles cleanly", () => { const { code, errors } = compiler.compile( pricingGraph.nodes, pricingGraph.edges, "output", ); expect(errors).toEqual([]); expect(code).toContain("function compiled"); });
it("produces the expected price for a wholesale order", async () => {
const { code } = compiler.compile(
pricingGraph.nodes,
pricingGraph.edges,
"output",
);
const fn = new Function("input", ${code}\nreturn compiled(input););
expect(fn({ qty: 50, segment: "wholesale", basePrice: 10 })).toEqual({
finalPrice: 280,
});
});
});
```
Anti-patterns to avoid:
- Don't unit-test the generator's
generateNodein isolation if the per-node output is glue — test the compiled program end-to-end instead. - Don't snapshot-test the raw code string for high-volatility graphs; semantics change less often than source layout.
- Don't compile inside hot UI loops without debouncing — the compiler is fast (~1-10ms for typical graphs), but graph edits fire on every keystroke, so debounce anyway.
Ship It
Once the graph compiles cleanly and the generated code passes your scenarios, deploy to your target environment.
Server-side execution:
- Persist the graph JSON in a collection (e.g.,
pricing_graphs,workflow_graphs). - On a relevant event, fetch the graph, compile, and execute the result in a sandbox (
WasmGeneratoroutput runs in a WASM runtime;TypeScriptETLGeneratoroutput runs in a BullMQ worker after type-check).
Edge / device execution:
- Compile once on save, store the artifact alongside the graph.
ReactNativeGeneratorships the SDUI JSON;MicroPythonGeneratorships the.pypayload to ESP32 devices via OTA flash.MavlinkGeneratoroutputs are uploaded to drones via a QGroundControl-compatible flight plan.
Visual-first source of truth:
- The graph is authoritative; the compiled code is a derived artifact. Re-compile on every save so the running system always matches what users see in the editor.
- Diffing graphs in PRs gives reviewers a domain-level change set ("added a 10% wholesale tier") instead of a code diff that re-emits all nodes.
Cross-vertical reuse:
- The same compiler engine drives Pricing CPQ, Blueprint workflows, Mobile Builder SDUI, Spatial NanoEngine scenes, Singularity ETL transformers, Construction BIM exports, and Smart Spaces firmware. One mental model, thirty-plus targets.
Integration Recipes
Pricing CPQ — WASM rules for offline quoting. Compile a pricing graph with WasmGenerator; ship the .wasm artifact to a sales-rep PWA so quotes work without network. Re-sync when the rep reconnects.
Blueprint workflows — TypeScript runners. Compile workflow graphs with TypeScriptETLGenerator or a Blueprint-specific generator; execute the result in a BullMQ worker pool with full retry + dead-letter handling.
Mobile Builder — server-driven UI. Compile screen graphs with ReactNativeGenerator; ship the SDUI JSON to Expo runtime. UI changes propagate without an App Store re-submission.
Spatial scenes — NanoEngine ECS schemas. Compile 3D scene graphs with NanoEngineGenerator; the React Three Fiber runtime in apps/spatial hydrates the schema into a live, interactive scene.
Smart Spaces — ESP32 firmware over-the-air. Compile IoT logic graphs with MicroPythonGenerator; flash the resulting main.py to ESP32 fleets via OTA. Field devices update without a truck roll.
Construction BIM — IFC building models. Compile architecture graphs with IfcGenerator; deliver IFC files to clients, consultants, and downstream BIM tools.
Troubleshooting
`No output node found matching: X`. The compiler couldn't find a node of the type you passed as outputNodeType. Verify the type string matches a node's .type field exactly (case-sensitive). Some generators expect an array of acceptable output types — check the generator's docs.
`Error compiling node N (type): message`. Surfaces from your generator's generateNode. The compiler captures it per-node and returns "ERROR_VAR" for that branch — fix the generator or the upstream node's data shape.
Compiled code references undefined variables. Usually a resolveInput call against a handle name that no edge targets. Either add the edge in the editor or guard with const [maybe] = resolveInput("handle"); if (!maybe) return { code: "// missing input", varName };.
Compilation slow on large graphs. Compilation time scales with node count. For very large graphs, split into smaller composing graphs (one compiled artifact calls another). Cache the compiled output until the graph JSON actually changes.
Generated code wrong but graph "looks right". The generator decides emission semantics — bugs live there, not in the engine. Add a unit test that feeds the smallest reproducing graph through your generator and asserts the chunk content.
Custom generator collides with another. Each compiler instance owns one generator. Mixing generators in one compilation isn't supported — instantiate a fresh ASTCompiler per target.
Frequently Asked Questions
No. The interface is three methods (`initialize?`, `generateNode`, `finalize`), and a typical new generator is a modest, focused piece of code. The built-in Nano Engine and TypeScript ETL generators are good canonical examples to study before writing your own.
Yes — implement `LanguageGenerator` for any text-based target. Past in-house extensions include Lua, proprietary DSLs, and bespoke YAML schemas.
LLMs emit graph JSON (constrained generation) rather than raw source. The engine compiles deterministic, syntactically-perfect output from any valid graph. This eliminates LLM syntax hallucinations entirely.
Yes — graphs are plain JSON `{ nodes, edges }`. Git-diffable. CI can run the compiler against the graph and assert no errors before merge.
Comparable to hand-written. There is no runtime interpreter — the engine emits native target source. C / Rust / WASM outputs are then optimized by their respective toolchains; TSX outputs ship to bundlers like any other component.
Yes — generators decide. `TypeScriptETLGenerator` emits `async` functions; `MultiplayerNetcodeGenerator` emits authoritative-server + rollback netcode. Each target's idioms live in its generator.
Real deployments comfortably run graphs from a handful of nodes up to several thousand. Beyond that, split into composing subgraphs that the generated runtime stitches together — the same pattern large codebases use to split one giant file into modules.
Yes — your generator emits whatever shape you need (e.g., `await aeion.api.post(...)`). The generated code receives auth + tenant context at runtime from the host application.
Not automatically — `ASTCompiler` returns `{ code, errors, metadata? }`. Use `metadata` to thread node-id → output-line mappings from your generator if you need step-through debugging in the browser.
Generated code is plain source — copy + leave. The graph JSON is portable. The interface is open and documented. There is nothing proprietary in the output beyond the conventions of the target language itself.