Visualization
Exporters live in a separate entry point and have zero impact on the core engine. Import from flowyd/visualization:
import { MermaidExporter, JsonGraphExporter } from 'flowyd/visualization';Both exporters are stateless — pass a definition and an optional snapshot.
MermaidExporter
MermaidExporter.export(
definition: WorkflowDefinition,
snapshot?: InstanceSnapshot,
): stringReturns a stateDiagram-v2 string. Paste into any Mermaid-compatible renderer — GitHub markdown, Mermaid Live Editor, Obsidian, Notion.
// Static diagram (no live state)
const diagram = MermaidExporter.export(workflow.getDefinition());
// With live status overlay — colours states by current status
const diagram = MermaidExporter.export(workflow.getDefinition(), inst.getSnapshot());State kind indicators
| Kind | Notation |
|---|---|
step | Plain label |
fork | state id <<fork>> (rendered as UML synchronisation bar) |
join | state id <<join>> (rendered as UML synchronisation bar) |
wait | Label suffix ⤴ |
Live status overlay
When a snapshot is provided, states are annotated with CSS class directives:
class pending_approval active
class legal_review completed
class payment_processing waitingClasses emitted: active, waiting, completed. Style in your renderer:
.active {
fill: #2196f3;
}
.waiting {
fill: #ff9800;
}
.completed {
fill: #4caf50;
}JsonGraphExporter
JsonGraphExporter.export(
definition: WorkflowDefinition,
snapshot?: InstanceSnapshot,
): JsonGraphReturns a plain JSON-serialisable object for use with D3.js, React Flow, Cytoscape.js, or any graph renderer.
JsonGraph
interface JsonGraph {
name: string;
nodes: JsonGraphNode[];
edges: JsonGraphEdge[];
meta: {
initialStateId: string;
terminalStateIds: string[];
actionNames: string[];
instance?: {
instanceId: string;
version: number;
isTerminal: boolean;
};
};
}JsonGraphNode
interface JsonGraphNode {
id: string;
kind: 'step' | 'fork' | 'join' | 'wait';
label: string;
isInitial: boolean;
isTerminal: boolean;
status?: StateStatus; // present only when a snapshot is provided
targets?: string[]; // ForkState only
join?: {
requires: string[];
mode: string | number;
}; // JoinState only
externalName?: string; // WaitState only
}JsonGraphEdge
interface JsonGraphEdge {
id: string;
from: string;
to: string;
kind: 'transition' | 'fork-target' | 'join-requires';
action?: string; // present only when kind === 'transition'
hasGuard?: boolean; // present only when kind === 'transition'
}Edges carry a kind discriminant. 'transition' edges are user-defined action-triggered transitions and carry action + hasGuard. 'fork-target' and 'join-requires' are structural edges — they make the fork fan-out and join fan-in topology visible without explicit transitions, and carry neither action nor hasGuard. Filter to kind === 'transition' before reading action/hasGuard:
const guarded = graph.edges
.filter((e) => e.kind === 'transition' && e.hasGuard)
.map((e) => e.action);hasGuard lets dashboards highlight which transitions require authorization.
Getting the definition
Both exporters require a WorkflowDefinition from:
const definition = workflow.getDefinition();The definition is the immutable compiled representation produced by WorkflowBuilder.build(). It is safe to store and reuse.