@aigentive/wire-core
Canonical schema, validation, normalization, layout, and pure editors. Zero React; runs in browser, Node, edge functions.
Install
The core package is React-free and dependency-light. Most apps don’t install it directly — @aigentive/wire-react re-exports the symbols you usually need. Install standalone when you want server-side rendering, validation in a CLI/Lambda, or schema reuse outside the React tree.
npm install @aigentive/wire-coreParsing & schema
The Zod schema is the source of truth. Call parseWireDiagram at any boundary (network input, file load) and you get a typed WireDiagram back — or a precise error pointing at the bad field.
| Export | Signature |
|---|---|
| parseWireDiagram | parseWireDiagram(input: unknown): WireDiagramValidate + parse using the canonical Zod schema. Throws on schema errors. |
| safeParseWireDiagram | safeParseWireDiagram(input: unknown): SafeParseReturnTypeNon-throwing variant — returns Zod's success/error shape. |
| WireDiagramSchema | z.ZodType<WireDiagram>Top-level Zod schema. Use directly when you need to compose with other schemas. |
| NodeSchema | z.ZodType<WireNode>Discriminated union over all 12 node kinds. |
| AINodeSchema · TriggerNodeSchema · … | z.ZodType<WireNode<kind>>Kind-specific schemas for narrow validation. |
import { parseWireDiagram, safeParseWireDiagram } from "@aigentive/wire-core";
const diagram = parseWireDiagram(JSON.parse(buffer)); // throws on bad input
const result = safeParseWireDiagram(maybe);
if (!result.success) console.error(result.error.issues);Validation
validate goes beyond the schema — it checks references between nodes (orphan from, missing parent, branch names that don’t exist on the source condition) and returns issues with stable codes.
| Export | Signature |
|---|---|
| validate | validate(input: unknown): ValidationResultStructural + reference validation. Returns `{ valid, issues: ValidationIssue[] }` with codes + repair hints. |
| ValidationIssue | { code: string; severity: "error" | "warning"; path: string; message: string; hint?: string }Individual issue shape. Codes are listed in Concepts → Validation. |
import { validate } from "@aigentive/wire-core";
const { valid, issues } = validate(diagram);
if (!valid) {
for (const issue of issues) {
console.error(`[${issue.severity}] ${issue.code} @ ${issue.path}: ${issue.message}`);
if (issue.hint) console.error(` → ${issue.hint}`);
}
}Normalization & layout
normalize turns shorthand from / after references into a flat list of explicit edges plus a node index keyed by id.layoutDiagram runs the dagre layout and hands back a position + size per node.
| Export | Signature |
|---|---|
| normalize | normalize(diagram: WireDiagram): { resolvedEdges, nodeIndex }Resolves `from` / `after` shorthand into explicit edges and indexes nodes by id. |
| layoutDiagram | layoutDiagram(diagram, opts?: { rankSep?, nodeSep?, engine? }): LayoutResultRuns dagre layout; returns `{x, y, width, height}` per node id. |
Rendering & export
Two pure functions cover the static surfaces. Both take a WireDiagram and return a string — no DOM, no canvas engine, no peer deps.
| Export | Signature |
|---|---|
| renderToSvg | renderToSvg(diagram, opts?: RenderSvgOptions): stringServer-renderable SVG string. Self-contained, no external CSS. |
| toMermaid | toMermaid(diagram): stringExport as Mermaid `flowchart` syntax — paste into any Mermaid renderer. |
RenderSvgOptions
| Option | Type | Purpose |
|---|---|---|
| padding | number | Padding around the diagram bounds (px). Default 24. |
| background | string | Background color. Default #ffffff. Pass `transparent` to omit the background rect. |
| toneColors | Partial<Record<Tone, ToneColor>> | Override the per-tone color map (default, success, warning, error, info, ai). |
| titleWrapChars | number | Wrap node titles at this character count. Default 24. |
| nodeStyle | NodeStyle | Diagram-level default node style. Per-node `style` overrides this. |
| edgeStyle | EdgeStyle | Diagram-level default edge style. Per-edge `style` overrides this. |
| edgeLabelStyle | EdgeLabelStyle | Diagram-level default edge label style. |
| edgeRouting | "bezier" | "smoothstep" | "step" | "straight" | Diagram-level default routing. Per-edge `routing` overrides this. |
import { renderToSvg, toMermaid } from "@aigentive/wire-core";
const svg = renderToSvg(diagram, { padding: 32, background: "transparent" });
const mermaid = toMermaid(diagram);
await fs.writeFile("flow.svg", svg, "utf8");
await fs.writeFile("flow.mmd", mermaid, "utf8");Pure editors
Every editor returns a new diagram — never mutates. The same functions back the React provider, the MCP server, and the CLI. Use them anywhere you need to author a diagram outside the editor shell (build-time generation, migrations, agent loops).
| Export | Signature |
|---|---|
| emptyDiagram | emptyDiagram(opts?: { id?, title?, layout? }): WireDiagramConstruct a blank, schema-valid diagram. |
| addNode | addNode(diagram, node): { diagram, node }Append a node. Returns the new diagram and the resolved node (with auto id when omitted). |
| updateNode | updateNode(diagram, id, patch): WireDiagramPatch fields on a node by id. |
| removeNode | removeNode(diagram, id): WireDiagramRemove a node and prune incoming refs. |
| connect | connect(diagram, edge): { diagram, edge }Add an explicit edge with stable id, optional handles, label, branch, routing, and style. |
| disconnect | disconnect(diagram, opts: { from, to, branch? }): WireDiagramRemove a connection by `from` reference (branch-aware sweep when branch omitted). |
| addNote | addNote(diagram, note): { diagram, note }Add an annotation; supports `attachedTo` for visual association. |
| setLayout | setLayout(diagram, layout): WireDiagramChange layout direction (LR / TB / RL / BT) without touching nodes. |
import { emptyDiagram, addNode, connect } from "@aigentive/wire-core";
let d = emptyDiagram({ layout: "LR", title: "Support agent" });
d = addNode(d, { kind: "trigger", title: "Webhook", id: "in" }).diagram;
d = addNode(d, { kind: "ai", title: "Plan", from: "in", model: "gpt-5.4-mini" }).diagram;
d = addNode(d, { kind: "action", title: "Reply", from: "plan", tone: "success" }).diagram;Utilities
| Export | Signature |
|---|---|
| generateNodeId | generateNodeId(opts: { kind, title?, existing }): stringProduce a unique slug-like id from kind + title, avoiding collisions in `existing`. |
| slugify | slugify(input: string): stringLowercase + dash-collapsed slug used for ids and resource names. |
| splitFromRef | splitFromRef(ref: string): { id, branch? }Parse the `"id"` or `"id.branch"` shorthand used by `from` / `after`. |