Graph workflow
This part of the documentation has been AI generated and I haven't reviewed it yet. So please be kind if you find mistakes or inconsistencies. I'll remove this warning once I've reviewed this page.
Use the graph flavor when the dependencies between steps form a tree (or DAG) rather than a line: a node has named children, runs them concurrently, and receives a record of their outputs as its input. The dependency structure lives in the code rather than in a procedural chain.
The shared semantics — state, persistence, observability via onChanges, and structured failure handling — are described on the Workflows introduction. This page focuses on the graph-specific API.
Building a graph
A graph workflow is a subclass of GraphWorkflow that constructs its root WorkflowNode in build(...). Each node declares its children as a record: a string key plus the child node wired into that slot.
import {
GraphWorkflow,
WorkflowNode,
InMemoryWorkflowStateRepository,
WorkflowStateRepository,
} from "ontologic";
interface MyInputs {
url: string;
data: number[];
name: string;
}
class MyWorkflow extends GraphWorkflow<MyInputs, string> {
constructor(params: {
id: string;
input: MyInputs;
repository?: WorkflowStateRepository;
}) {
super({
...params,
name: "My Workflow",
repository: params.repository ?? new InMemoryWorkflowStateRepository(),
});
this.build((input) => this.#root(input));
}
#root(input: MyInputs) {
const dataSource = new WorkflowNode({
name: "Data Source",
children: {},
handler: async () => ({ data: input.data }),
});
const nameSource = new WorkflowNode({
name: "Name Source",
children: {},
handler: async () => ({ name: input.name }),
});
const summed = new WorkflowNode({
name: "Sum",
children: { source: dataSource },
handler: async ({ source }) => ({
sum: source.data.reduce((s, c) => s + c),
}),
});
return new WorkflowNode({
name: "Combine",
children: { total: summed, tag: nameSource },
handler: async ({ total, tag }) => `${tag.name} = ${total.sum}`,
});
}
}
const workflow = new MyWorkflow({
id: randomUUID(),
input: { url: "...", data: [1, 2, 3, 4, 5], name: "Sacha" },
});
const result = await workflow.execute();
The handler of Combine receives { total, tag } automatically typed as { total: { sum: number }; tag: { name: string } } — the type system follows the graph. Children of the same node run concurrently via Promise.all. A node with children: {} is a leaf: it produces a value without depending on anything below it.
Visualizing the graph
GraphWorkflow.toTree() returns an ASCII rendering of the graph rooted at the top-level node — useful for debugging or shipping into logs:
Combine
├── total: Sum
│ └── source: Data Source
└── tag: Name Source
Each line names a node; the prefix (├──, └──, │) reflects the position of that child among its siblings, and indentation shows depth.
Observability and persistence
Subscribe to step transitions before calling execute():
workflow.onChanges((event) => {
// event = { step, status: "START" | "DONE" | "FAILED", result?, error? }
});
The repository passed to the GraphWorkflow constructor is saved after execute() completes. Resumability works the same way as the step flavor: a node whose name is already a key in stepResults skips its handler and reuses the cached output.
For the full description of state, repository plumbing, and failure handling — all shared between both flavors — see the Workflows introduction.
Summary
| Concept | Purpose |
|---|---|
GraphWorkflow (subclass) | DAG-shaped workflow; constructs its root in build(...) |
WorkflowNode | Node with named children, runs them in parallel |
children: {} | Marks a leaf node — produces a value with no dependencies |
children: { key: node } | Wires node into the key slot of the handler input |
toTree() | ASCII rendering of the graph for debugging |
setContext(state) | Shares the workflow state through the tree (called by build) |
onChanges(...) | Subscribe to START / DONE / FAILED events on any node |