Skip to content

Control Flow

Two forms. A single-step condition, or a multi-step branch with do: and an optional else:.

Terminal window
using:
- zenvara/email
- zenvara/noop
output:
ok: !bool
steps:
# Single-step condition
- if: count > 0
$notify:
invoke: email.send
with: { From: "alerts@example.com", To: "${alert}", Subject: "Data arrived", Body: "New data has arrived." }
# Multi-step branch
- if: "len(items) > 0"
do:
- log: "Processing ${= len(items)} items"
- $result:
transform: noop.invoke
with: { Value: "${items}" }
else:
- log: "Nothing to process"
- return:
ok: true

Conditions are plain expressions — no ${= ... } wrapper. if: count > 0 is correct; if: "${= count > 0}" is not.

Iterate a list or a map. Inner steps are sandboxed by default — they cannot modify the outer payload. Name the loop to collect outputs.

Terminal window
using:
- zenvara/noop
output:
batched: !obj-list
steps:
# Side-effect-only loop (sandboxed)
- for-each: $item in "${products.rows}"
do:
- log: "Row ${_index} of ${_count}: ${item.name}"
# Named loop — collects each iteration's output
- $batched:
for-each: $item in "${products.rows}"
collect:
_value: "${row.Value}"
do:
- $row:
transform: noop.invoke
with: { Value: "${item}" }
- return:
batched: "${batched.items}"

Loop variables available inside do::

VariableMeaning
$item (or your chosen name)The current element.
_index1-based position.
_countTotal number of elements.
_keyThe current key (map iteration only).

Two modes: match a value against named cases, or evaluate boolean expressions in order (first match wins).

Terminal window
using:
- environment/prod
- zenvara/http
output:
ok: !bool
steps:
# Value mode
- switch:
on: "${order.status}"
case:
paid:
- $ship:
invoke: http.post
on: warehouse
with: { Hosts: ["https://warehouse.example.com"], Url: "/shipments", Body: { orderId: "${order.id}" } }
cancelled:
- log: "Skipping cancelled order ${order.id}"
default:
- log: "Unhandled status: ${order.status}"
# Expression mode (first match wins)
- switch:
cases:
"len(items) > 1000":
- log: "Large batch — escalating"
"len(items) > 0":
- log: "Small batch"
default:
- log: "Empty batch"
- return:
ok: true
Terminal window
- stop: "no records to process" # terminates the entire flow — reason is mandatory
- for-each: $row in "${products.rows}"
do:
- if: row.qty <= 0
skip: "out-of-stock row" # ends this iteration; loop continues
- log: "Processing ${row.id}"

Use stop: "<reason>" to end the entire run without producing output (contrast with return:, which produces the typed output and ends). Use skip: "<reason>" inside a for-each:/stream: to move on to the next item — outside a loop it is a validation error. A bare stop: never runs compensation; pair it with the mapping form stop: { reason: "...", compensate: true } to roll back prior steps in a transactional flow.