Skip to content

Flow Execution

Every school operation (enroll student, schedule class, create grade) is a footprintjs flow — a pipeline of stages that executes with automatic tracing.

enrollmentFlow:
Validate-Input ──→ Prepare-Context ──→ Enroll-Student ──→ Link-Grade
│ │ │ │
▼ ▼ ▼ ▼
Check required Resolve family Call repository Assign to
fields exist linkage createStudent() grade/level

In code:

function createEnrollmentFlow(repo: SchoolRepository, t?: TermResolver): FlowChart {
return flowChart("Validate-Input", async (scope) => {
const input = scope.getValue("input");
if (!input.firstName) throw new Error("First name required");
scope.setValue("validatedInput", input);
}, "validate-input", undefined, "Validate that required enrollment fields are present")
.addFunction("Prepare-Context", async (scope) => {
// Resolve family linkage
}, "prepare-context", "Resolve family linkage based on familyId")
.addFunction("Enroll-Student", async (scope) => {
const student = await repo.createStudent(scope.getValue("validatedInput"));
scope.setValue("createdStudent", student);
}, "enroll-student", "Create student record in repository")
.addFunction("Link-Grade", async (scope) => {
// Assign to grade if specified
}, "link-grade", "Assign student to grade if specified")
.build();
}
FlowStagesDecision PointsDescription
enrollmentFlow40Validate → Prepare → Enroll → Link
attendanceFlow3+1 deciderValidate → Create → Decide(mark/skip)
schedulingFlow3+1 deciderValidate → Check → Decide(create/conflict)
availabilityFlow20Validate → Check
gradeFlow20Validate → Create
sectionFlow20Validate → Create
feesFlow20Validate → Calculate
operationsFlow2+1 selectorRoute → Select(enroll/attend/schedule)

When a flow runs, footprintjs automatically captures:

Narrative:
"Step 1: Validate-Input — Validate that required enrollment fields are present
Read input: {firstName: "Alice", lastName: "Smith"}
Wrote validatedInput: {firstName: "Alice", lastName: "Smith"}
Step 2: Enroll-Student — Create student record in repository
Read validatedInput: {firstName: "Alice", ...}
Wrote createdStudent: {id: 42, firstName: "Alice", ...}"
Metrics:
validate-input: 2ms
enroll-student: 15ms
Snapshot:
Full state tree of every stage's reads and writes

Some flows use decide() to branch:

// schedulingFlow — conflict detection
.addDeciderFunction("Conflict-Decision", (scope) => {
const conflicts = scope.getValue("conflicts");
return conflicts.length === 0 ? "no-conflict" : "has-conflict";
}, "conflict-decision", "Route based on conflict detection result")
.addFunctionBranch("no-conflict", "Create-Entry", async (scope) => {
// Create the schedule entry
})
.addFunctionBranch("has-conflict", "Report-Conflict", async (scope) => {
// Return conflict details
})
.end()

The decision is recorded in the narrative:

Decision Conflict-Decision: chose "no-conflict"
ComponentUses footprintjs?Why
8 flow filesYESBuild and run pipelines
serviceComposerYESFlowChartExecutor, ManifestRecorder
modules/NoPure config objects
profiles/NoPure config objects
strategies/NoPure functions (call repo directly)
terminology/NoPure data
types.tsNoPure TypeScript interfaces

Key insight: footprintjs is used in only ~10% of the codebase. Everything else is pure configuration and functions.