Aller au contenu principal

Graph workflow

attention

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

ConceptPurpose
GraphWorkflow (subclass)DAG-shaped workflow; constructs its root in build(...)
WorkflowNodeNode 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