Skip to content
markdown
# Graph & Flow Export

Two plugins let you serialise a flow's structure to JSON for debugging, visualisation, or documentation generation.

| Plugin            | Works on          | Returns                              |
| ----------------- | ----------------- | ------------------------------------ |
| `withExportGraph` | Graph flows only  | `GraphExport` (nodes + edges)        |
| `withExportFlow`  | Any `FlowBuilder` | `FlowExport` (flow + optional graph) |

Load `withExportFlow` last — it overrides `.exportGraph()` with the richer unified shape.

---

## `withExportGraph`

Exports the raw nodes and edges declared via `addNode` / `addEdge` **before** `.compile()` is called. The call is non-destructive — `.compile()` can still be chained after.

### Setup

```typescript
import { FlowBuilder } from "flowneer";
import { withGraph, withExportGraph } from "flowneer/plugins/graph";

const AppFlow = FlowBuilder.extend([withGraph, withExportGraph]);
```

### Usage

```typescript
const result = new AppFlow<State>()
  .addNode("fetch", fetchData, { retries: 3 })
  .addNode("transform", transformData)
  .addNode("save", saveData)
  .addEdge("fetch", "transform")
  .addEdge("transform", "save")
  .addEdge("transform", "fetch", (s) => s.needsRetry) // conditional back-edge
  .exportGraph(); // ← non-destructive

console.log(JSON.stringify(result, null, 2));
```

```json
{
  "format": "json",
  "nodes": [
    { "name": "fetch", "options": { "retries": 3 } },
    { "name": "transform" },
    { "name": "save" }
  ],
  "edges": [
    { "from": "fetch", "to": "transform", "conditional": false },
    { "from": "transform", "to": "save", "conditional": false },
    { "from": "transform", "to": "fetch", "conditional": true }
  ]
}
```

### `GraphExport` type

```typescript
interface GraphExport {
  format: "json";
  nodes: GraphNodeExport[];
  edges: GraphEdgeExport[];
}

interface GraphNodeExport {
  name: string;
  options?: {
    retries?: number | string; // "<dynamic>" when a function
    delaySec?: number | string;
    timeoutMs?: number | string;
  };
}

interface GraphEdgeExport {
  from: string;
  to: string;
  conditional: boolean; // true when the edge has a runtime guard
}
```

### Notes

- Options with value `0` (the default) are omitted from the output.
- Dynamic option values (functions) are serialised as `"<dynamic>"`.
- Calling `.exportGraph()` on a builder with no nodes throws an error.
- The `"mermaid"` format is reserved for a future release.

---

## `withExportFlow`

Exports **any** `FlowBuilder` — sequential, loop, batch, parallel, branch — into a structured node/edge graph. When loaded alongside `withGraph`, also includes the raw graph store in a separate `graph` section.

Loading `withExportFlow` after `withExportGraph` is fine — it overrides `.exportGraph()` at runtime.

### Setup

```typescript
import { FlowBuilder } from "flowneer";
import { withExportFlow } from "flowneer/plugins/graph";
// Optionally also load withGraph + withExportGraph for the combined output:
// const AppFlow = FlowBuilder.extend([withGraph, withExportGraph, withExportFlow]);
const AppFlow = FlowBuilder.extend([withExportFlow]); // load last
```

### Usage — sequential flow

```typescript
const result = new AppFlow<State>()
  .startWith(loadData)
  .then(validate)
  .then(save)
  .exportGraph();
```

```json
{
  "format": "json",
  "flow": {
    "nodes": [
      { "id": "fn_0", "type": "fn", "label": "loadData" },
      { "id": "fn_1", "type": "fn", "label": "validate" },
      { "id": "fn_2", "type": "fn", "label": "save" }
    ],
    "edges": [
      { "from": "fn_0", "to": "fn_1", "kind": "sequential" },
      { "from": "fn_1", "to": "fn_2", "kind": "sequential" }
    ]
  }
}
```

### Usage — flow with complex steps

```typescript
const result = new AppFlow<State>()
  .startWith(init)
  .loop(
    (s) => !s.done,
    (b) => b.startWith(process).then(check),
  )
  .parallel([workerA, workerB, workerC])
  .exportGraph();
```

Loop and parallel bodies get nested `id` paths (e.g. `"loop_1:body:fn_0"`) so the full structure is unambiguous.

### Usage — graph + flow combined

When `withGraph`, `withExportGraph`, and `withExportFlow` are all loaded:

```typescript
const result = new AppFlow<State>()
  .addNode("a", stepA)
  .addNode("b", stepB)
  .addEdge("a", "b")
  .exportGraph(); // before compile() — includes both sections
```

```json
{
  "format": "json",
  "flow": { "nodes": [], "edges": [] },
  "graph": {
    "nodes": [{ "name": "a" }, { "name": "b" }],
    "edges": [{ "from": "a", "to": "b", "conditional": false }]
  }
}
```

After `.compile()` the flow section is populated; the graph section is still present.

### `FlowExport` type

```typescript
interface FlowExport {
  format: "json";
  flow: {
    nodes: FlowNodeExport[];
    edges: FlowEdgeExport[];
  };
  graph?: {
    // present only when a graph store is attached
    nodes: GraphNodeExport[];
    edges: GraphEdgeExport[];
  };
}

interface FlowNodeExport {
  id: string; // e.g. "fn_0", "loop_2", "loop_2:body:fn_1"
  type: "fn" | "branch" | "loop" | "batch" | "parallel" | "anchor";
  label: string; // function name, anchor name, or "anonymous"
  options?: {
    retries?: number | string;
    delaySec?: number | string;
    timeoutMs?: number | string;
  };
  meta?: Record<string, unknown>; // branch keys, parallel count, etc.
}

interface FlowEdgeExport {
  from: string;
  to: string;
  kind:
    | "sequential"
    | "branch-arm"
    | "loop-body"
    | "loop-back"
    | "parallel-fan-out"
    | "batch-body";
  label?: string; // branch key or parallel index
}
```

### Node `id` conventions

| Step type  | `id` pattern                    |
| ---------- | ------------------------------- |
| `fn`       | `fn_<index>`                    |
| `branch`   | `branch_<index>`                |
| branch arm | `branch_<index>:arm:<key>`      |
| `loop`     | `loop_<index>`                  |
| loop body  | `loop_<index>:body:<child-id>`  |
| `batch`    | `batch_<index>`                 |
| batch body | `batch_<index>:body:<child-id>` |
| `parallel` | `parallel_<index>`              |
| `anchor`   | `anchor:<name>`                 |

---

## See also

- [Graph Composition](./overview.md) — `addNode`, `addEdge`, `compile()`