Skip to content

FlowChartExecutor

new FlowChartExecutor(chart: FlowChart)

Takes a compiled FlowChart (the return value of .build()). The executor is stateful — call .run() and then read the results.

import { FlowChartExecutor } from 'footprintjs';
const executor = new FlowChartExecutor(chart);
await executor.run({ input: { orderId: 'ORD-001', amount: 49.99 } });

Execute the flowchart. Returns a RunResult with the output value (if a .contract() mapper is defined), the final snapshot, and narrative lines.

executor.run(options?: RunOptions): Promise<RunResult>

RunOptions

FieldTypeDescription
inputunknownBusiness input — frozen, accessible via scope.$getArgs()
envExecutionEnv?Infrastructure context (signal, timeoutMs, traceId) — accessible via scope.$getEnv()
await executor.run({
input: { userId: 42 },
env: { traceId: 'req-abc123', timeoutMs: 5000 },
});

For a one-shot run without a persistent executor, chain recorders directly on the compiled chart:

import { flowChart, narrative, metrics } from 'footprintjs';
const narr = narrative();
const metr = metrics();
await chart
.recorder(narr)
.recorder(metr)
.run({ input: { orderId: 'ORD-001', amount: 49.99 } });
console.log(narr.lines().join('\n'));
console.log(`Total: ${metr.all().totalDuration}ms`);

Attach a scope recorder — fires on data operations (reads, writes, commits) during stage execution.

executor.attachRecorder(recorder: Recorder): void
import { MetricRecorder, DebugRecorder } from 'footprintjs';
executor.attachRecorder(new MetricRecorder());
executor.attachRecorder(new DebugRecorder({ verbosity: 'verbose' }));

Attach a flow recorder — fires on control-flow events (decisions, loops, forks) after each stage completes.

executor.attachFlowRecorder(recorder: FlowRecorder): void
import { ManifestFlowRecorder } from 'footprintjs';
const manifest = new ManifestFlowRecorder();
executor.attachFlowRecorder(manifest);
await executor.run();
console.log(manifest.getManifest());

Shorthand: attaches a CombinedNarrativeRecorder that implements both scope and flow recorder interfaces, producing a merged timeline.

executor.enableNarrative(): void
executor.enableNarrative();
await executor.run();
executor.getNarrative().forEach(line => console.log(line));

Returns the merged narrative as a string[] — one sentence per event.

executor.getNarrative(): string[]
Stage 1: The process began with AssessCredit.
Step 1: Write creditScore = 750
[Condition]: It evaluated Rule 0 "Strong profile": creditScore 750 gt 700 ✓ → approved.
Stage 2: Next, it moved on to Approve.
Step 1: Write decision = "Approved"

Returns structured CombinedNarrativeEntry[] with type, depth, stageName, and stageId metadata — useful for building custom UIs.

executor.getNarrativeEntries(): CombinedNarrativeEntry[]

Returns only the control-flow sentences (stage transitions, decisions, loops) — without data operation lines.

executor.getFlowNarrative(): string[]

Returns the full RuntimeSnapshot — shared state, execution tree, and recorder snapshots at every nesting depth.

executor.getSnapshot(): RuntimeSnapshot | undefined
const snapshot = executor.getSnapshot();
const finalState = snapshot?.sharedState as LoanState;
console.log(finalState.decision); // 'Approved'

Use getSubtreeSnapshot() to drill into subflows.


Protect sensitive fields from leaking into narratives and debug logs. Keys matching the policy are replaced with [REDACTED] in all recorder output.

executor.setRedactionPolicy(policy: RedactionPolicy): void
executor.setRedactionPolicy({
keys: ['password', 'ssn', 'cardNumber'],
});

→ Full guide: Observing with recorders