Real, checked-in .nflow recipes: timeout and fallback, error fallback, parallel actions with deterministic keys, @on_error routing, dynamic dispatch, and scoped filesystem reads.
:timeout sets a deadline for one action; it is not a workflow-wide SLA or a cost cap. If the action returns an error (including a timeout), || tries the local fallback.
@description "Per-action Timeout and Local Fallback"
@assert: result.recovered == true
{ query: "SELECT * FROM huge_table" }
-> sleep:timeout=1s @{ duration_ms: 5000 }
|| { recovered: true, error: "Database took too long, serving fallback data." }The || operator runs the right side only if the left side fails. The kernel classifies the error; 4xx errors are real errors, not triggers to retry.
@description "Recover from a failed action with the fallback operator"
@assert: result == "Recovered"
fail @{ kind: "Validation", message: "User email is invalid" }
|| const @{ value: "Recovered" }The & operator fans out branches that run concurrently. Repeated action names get name#1, name#2 in source order — so downstream projections can address each one deterministically.
@description "Run independent actions concurrently"
@assert: result["const#1"] == "Data A"
@assert: result["const#2"] == "Data B"
@assert: result.noop.request == "local input"
{ request: "local input" }
-> ( const @{ value: "Data A" } & noop & const @{ value: "Data B" } )The built-in feature suite tests parallel composition with ( const @{ value: "left" } & const @{ value: "right" } ). A name used by only one branch keeps its existing key; repeated names get name#1, name#2. If a generated label is already in use, trailing # characters are added until it is unique.
Three concurrent branches whose results are projected into a single object. Duplicate action names receive deterministic keys: sleep#1, sleep#2, sleep#3.
@description "High-Throughput Parallel Processing (Scatter-Gather)"
@assert: result.a == "Data A"
@assert: result.b == "Data B"
@assert: result.c == "Data C"
(
sleep @{ duration_ms: 500, value: "Data A" }
& sleep @{ duration_ms: 500, value: "Data B" }
& sleep @{ duration_ms: 500, value: "Data C" }
)
-> {
a: .["sleep#1"].value,
b: .["sleep#2"].value,
c: .["sleep#3"].value
}Production pipelines break. @on_error routes matching failures to an action. The error.info action returns the recovered error as a structured map.
@on_error {
when error.kind == "Timeout" -> error.info
when error.kind == "Validation" -> error.info
else -> error.info
}
@assert: result.kind == "Validation"
fail @{ kind: "Validation", message: "User email is invalid" }Combine :retry=N on the failing atom with @on_error for layered resilience: the retry handles transient failures, the catch block handles classified failures that escape retry.
Dispatch tries configured member actions in order until one succeeds. The first two names are absent; const is the local final candidate.
@description "Local fallback routing with dispatch"
@assert: result == "I am the fallback!"
{ members: "not_found, broken_service, const", payload: { value: "I am the fallback!" } }
-> dispatchThis exercises fallback routing only, not recovery of an external service. The members list is local input — the dispatch action walks the list, tries each, and returns the first successful result.
Walk and read a single checked-in fixture directory. The configured root is a flow argument, not an operating-system sandbox; changing it can expand which files are read.
@description "Read only the checked-in text fixture from this example's directory"
@assert: len(result) == 1
@assert: result[0].rel_path == "sample.txt"
fs.walk:dir="examples/01_secure_pipeline/fixtures":ext="txt"
-> fs.read
-> collectControl global logging and debug pipelines effortlessly. Set LOG_LEVEL=debug or LOG_FORMAT=json as environment variables to change the output without editing the flow.
@description "Control global logging and debug pipelines effortlessly"
@assert: result.status == "done"
{ user_id: 42, action: "login" }
# Emit a structured INFO log (appears in stderr and all observability sinks)
-> log.info @{ message: "User attempting login" }
-> { ..., status: "verified" }
# Dump the current state to the console beautifully without breaking the data pipe
-> debug @{ label: "State After Verification" }
-> { status: "done" }