Skip to content

Execution

Execution<TState> is the handle returned by Dagonizer.execute() and Dagonizer.resume(). It is both an AsyncIterable (streaming per stage) and a PromiseLike (awaitable for the final result). The underlying generator runs exactly once regardless of how it is consumed.

Class: Execution<TState>

ts
import { Execution } from '@noocodex/dagonizer';

Not instantiated directly — returned by the dispatcher.


[Symbol.asyncIterator]()

Iterate stage results as they complete:

ts
const execution = dispatcher.execute('my-flow', state);
for await (const node of execution) {
  console.log(node.nodeName, node.output);
}
const result = await execution; // cached — no second run

Each yielded NodeResultInterface<TState> carries:

FieldTypeDescription
nodeNamestringName of the node that completed
outputstring | undefinedOutput name returned by the operation
skippedbooleantrue for an empty fan-out that bypassed execution
stateTStateReference to the shared state object (mutated in place)

For parallel and fan-out nodes, the iterator first yields intermediate results for each constituent node, then yields the group result.


.then(onfulfilled, onrejected)

Execution implements PromiseLikeawait it for the final ExecutionResultInterface<TState>:

ts
const result = await dispatcher.execute('my-flow', state);

If the iterator has already been consumed, the cached result is returned — the generator is not re-run.

ExecutionResultInterface<TState> carries:

FieldTypeDescription
stateTStateFinal state (same reference passed in)
cursorstring | nullNext node to run on resume; null if completed normally
executedNodesstring[]Nodes that ran (in order)
skippedNodesstring[]Nodes skipped (empty fan-out)

Consumption patterns

One-shot await:

ts
const result = await dispatcher.execute('flow', state);
if (result.cursor !== null) {
  // interrupted — checkpoint it
}

Streaming with early exit:

ts
const execution = dispatcher.execute('flow', state, { signal: ctl.signal });
for await (const node of execution) {
  if (isHeavyNode(node.nodeName)) {
    ctl.abort(new Error('pause here'));
  }
}
const result = await execution; // result.cursor holds where we stopped

Consuming both modes:

ts
const execution = dispatcher.execute('flow', state);
const nodes: string[] = [];
for await (const n of execution) nodes.push(n.nodeName);
const result = await execution; // same run, cached
console.log(nodes, result.cursor);

The iterator never throws. Cancellation and operation errors resolve to a final ExecutionResultInterface with a non-null cursor and the appropriate lifecycle.kind.

Watched over by the Order of Dagon.