Control Flow
if: — conditional execution
Section titled “if: — conditional execution”Two forms. A single-step condition, or a multi-step branch with do: and an optional else:.
using: - zenvara/email - zenvara/noop
output: ok: !bool
steps: # Single-step condition - if: count > 0 $notify: invoke: email.send with: { From: "[email protected]", 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: trueConditions are plain expressions — no ${= ... } wrapper. if: count > 0 is correct; if: "${= count > 0}" is not.
for-each: — iteration
Section titled “for-each: — iteration”Iterate a list or a map. Inner steps are sandboxed by default — they cannot modify the outer payload. Name the loop to collect outputs.
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::
| Variable | Meaning |
|---|---|
$item (or your chosen name) | The current element. |
_index | 1-based position. |
_count | Total number of elements. |
_key | The current key (map iteration only). |
switch: — multi-branch matching
Section titled “switch: — multi-branch matching”Two modes: match a value against named cases, or evaluate boolean expressions in order (first match wins).
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: truestop: / skip: — stopping execution
Section titled “stop: / skip: — stopping execution”- 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.