flowChart() / FlowChartBuilder
flowChart()
Section titled “flowChart()”The primary factory function. Creates the root stage and returns a builder.
flowChart<State>( name: string, fn: (scope: TypedScope<State>) => Promise<void>, id?: string, inputSchema?: ZodSchema | JsonSchema, description?: string,): FlowChartBuilder<State>Parameters
| Param | Type | Description |
|---|---|---|
name | string | Stage display name (shown in narrative) |
fn | async function | Stage handler — reads/writes scope |
id | string? | Stable ID for snapshot navigation (defaults to slug of name) |
inputSchema | Zod or JSON Schema | Input validation for this stage only (rarely needed — use .contract() instead) |
description | string? | Human description included in MCP tool output |
import { flowChart } from 'footprintjs';
interface LoanState { creditScore: number; dti: number; decision?: string;}
const chart = flowChart<LoanState>('AssessCredit', async (scope) => { scope.creditScore = 720; scope.dti = 0.38;}, 'assess-credit', undefined, 'Load and validate credit application') .build();FlowChartBuilder
Section titled “FlowChartBuilder”Returned by flowChart(). All methods return the builder (or a branch builder) for chaining. Call .build() at the end.
.addFunction()
Section titled “.addFunction()”Append a sequential stage.
.addFunction( name: string, fn: (scope: TypedScope<State>) => Promise<void>, id?: string, description?: string,): FlowChartBuilder<State>const chart = flowChart<LoanState>('Load', async (scope) => { scope.creditScore = 720;}, 'load') .addFunction('Validate', async (scope) => { scope.decision = scope.creditScore >= 580 ? 'proceed' : 'reject'; }, 'validate', 'Check minimum credit score') .build();.addDeciderFunction()
Section titled “.addDeciderFunction()”Add a branching stage. The handler returns a branch ID (plain string) or a DecisionResult from decide().
.addDeciderFunction( name: string, fn: (scope: TypedScope<State>) => string | DecisionResult, id?: string, description?: string,): DeciderBuilder<State>Inside the returned DeciderBuilder, call .addFunctionBranch(), .setDefault(), then .end() to return to the parent builder.
import { decide } from 'footprintjs';
const chart = flowChart<LoanState>('Load', async (scope) => { scope.creditScore = 720; scope.dti = 0.38;}, 'load') .addDeciderFunction('ClassifyRisk', (scope) => { return decide(scope, [ { when: { creditScore: { gt: 700 }, dti: { lt: 0.43 } }, then: 'approved', label: 'Strong profile' }, { when: { creditScore: { gt: 580 } }, then: 'review', label: 'Marginal' }, ], 'rejected'); }, 'classify-risk', 'Route by credit profile') .addFunctionBranch('approved', 'Approve', async (scope) => { scope.decision = 'Approved'; }) .addFunctionBranch('review', 'Review', async (scope) => { scope.decision = 'Manual review'; }) .addFunctionBranch('rejected', 'Reject', async (scope) => { scope.decision = 'Rejected'; }) .setDefault('rejected') .end() .build();.addSelectorFunction()
Section titled “.addSelectorFunction()”Add a parallel fan-out stage. The handler returns a SelectionResult from select(). All matched branches run concurrently.
.addSelectorFunction( name: string, fn: (scope: TypedScope<State>) => SelectionResult, id?: string, description?: string,): SelectorBuilder<State>.addSubFlowChartNext()
Section titled “.addSubFlowChartNext()”Mount another compiled flowchart as a sequential subflow.
.addSubFlowChartNext( id: string, chart: FlowChart, name: string, options?: { inputMapper?: (s: State) => unknown },): FlowChartBuilder<State>const paymentChart = new FlowChartBuilder() .start('ChargeCard', async (scope: any) => { scope.txnId = 'TXN-001'; }, 'charge-card') .build();
const chart = flowChart<OrderState>('ReceiveOrder', async (scope) => { scope.amount = 99.99;}, 'receive-order') .addSubFlowChartNext('sf-payment', paymentChart, 'Payment', { inputMapper: (s) => ({ amount: s.amount }), }) .build();.addStreamingFunction()
Section titled “.addStreamingFunction()”Add a stage that emits tokens incrementally (for LLM responses, SSE, etc.).
.addStreamingFunction( name: string, fn: (scope: TypedScope<State>, handlers: StreamHandlers) => Promise<void>, id?: string,): FlowChartBuilder<State>.addStreamingFunction('GenerateResponse', async (scope, handlers) => { for await (const chunk of callLLM(scope.$getArgs())) { handlers.onChunk(chunk); } handlers.onComplete('done');}, 'generate-response').loopTo()
Section titled “.loopTo()”Create a back-edge to an earlier stage. The loop exits when scope.$break() is called inside any stage.
.loopTo(targetStageName: string): FlowChartBuilder<State>const chart = flowChart<{ attempt: number; result?: string }>('Init', async (scope) => { scope.attempt = 0;}, 'init') .addFunction('TryRequest', async (scope) => { scope.attempt += 1; const ok = await fetchWithRetry(); if (ok) { scope.result = 'success'; scope.$break(); } if (scope.attempt >= 3) { scope.result = 'failed'; scope.$break(); } }, 'try-request') .loopTo('TryRequest') .build();.contract()
Section titled “.contract()”Attach input/output schemas for validation and API generation. See Self-describing APIs.
.contract(options: { input?: ZodSchema | JsonSchema; output?: ZodSchema | JsonSchema; mapper?: (scope: TypedScope<State>) => unknown;}): FlowChartBuilder<State>.build()
Section titled “.build()”Compile the builder into an immutable FlowChart. Must be called last.
.build(): FlowChartTry it live
Section titled “Try it live”- Linear Pipeline — sequential stages
- Decider (Conditional) — branching
- Loops — loopTo + $break()
- Subflow — nested flowchart
→ Full guide: Building a flowchart