Structure & Hierarchy
EventFlow files follow a clear hierarchical structure. Understanding this structure is key to writing effective flows.
Core Hierarchy
machine: (actor definition)
└── scenario: (feature/capability)
├── given: (scenario-level test setup)
├── on> :event (public/API endpoint)
│ ├── given: (event-level setup - optional)
│ ├── actions (what happens - no prefix)
│ ├── guards (conditions - ? prefix)
│ ├── otherwise (default case)
│ └── expect: (event-level assertions - optional)
├── on :event (internal event handler)
│ └── ... (same structure as above)
└── expect: (scenario-level assertions)Machine
The top-level container. A machine is an actor that can receive and emit events.
machine: @orderThe @ prefix indicates this machine is the @order actor in the system.
A machine:
- Has its own isolated state
- Owns its context (data)
- Receives events from other actors
- Emits events to other actors
Scenario
A business capability or feature consisting of one or more events. A scenario groups related event handlers together.
Single Event Scenario
scenario: add to cart
on> :add_item from @customer
$items adds $item
$total increases by $item.priceMulti-Event Scenario
scenario: complete purchase
on> :checkout from @customer
emit :payment_request to @payment
order moves to #awaiting_payment
on :payment_success from @payment
order moves to #paid
emit :reserve_stock to @inventory
on :stock_reserved from @inventory
order moves to #fulfilledA scenario is a complete business flow that may involve multiple sequential events.
Given Block
Declarative test setup that defines the initial state before events are processed. Given blocks can appear at two levels.
Scenario-Level Given
Applies to all events in the scenario:
scenario: checkout
given:
@customer is logged in as "[email protected]"
@customer has premium membership
cart contains:
| product | price |
| Laptop | 1200 |
| Mouse | 25 |
order is in #cart
$total: number is 1225
on> :checkout from @customer
...Event-Level Given
Additional setup specific to one event (runs after scenario-level given):
scenario: checkout with discount
given:
@customer is logged in
cart has items
$total: number is 1200
on> :checkout from @customer
given:
$discount_code is "SUMMER20"
$discount_amount: number is 100
? discount is valid
$total decreases by $discount_amount
order moves to #awaiting_paymentThe given: block is declarative - it describes the state, not the steps to create it.
Event Handlers
API Event Handler (on>)
Defines a public endpoint that can be triggered from outside the system:
on> :checkout from @customer
? cart is valid
emit :payment_request to @payment
order moves to #awaiting_paymentThe > prefix marks this event as externally accessible (like an API endpoint).
Internal Event Handler (on)
Defines an event handler for system-internal events only:
on :payment_success from @payment
order moves to #paid
emit :reserve_stock to @inventoryNo > prefix - this event is only triggered by other machines in the system.
The from Clause
The from clause specifies who can send the event:
// Accept from specific actor
on :payment_success from @payment
order moves to #paid
// Accept from any actor (no from clause)
on :status_update
update internal status
// Different handling based on source
on :approval from @manager
order moves to #manager_approved
on :approval from @director
order moves to #director_approvedExpect Blocks
Assertions that verify outcomes.
Event-Level Expect (Optional)
Assertions that verify the outcome after a specific event:
on> :checkout from @customer
emit :payment_request to @payment
order moves to #awaiting_payment
expect:
= order is in #awaiting_payment
= :payment_request was emitted to @paymentEvent-level expect: is optional - useful for debugging or critical checkpoints.
Scenario-Level Expect
Assertions that verify the outcome after all events complete:
scenario: complete purchase
on> :checkout from @customer
...
on :payment_success from @payment
...
on :stock_reserved from @inventory
...
expect:
= order is in #fulfilled
= @customer received :order_confirmed
= payment was processedScenario-level expect: is the primary test assertion - it's the integration test.
Complete Structure Example
machine: @order
scenario: add to cart
given:
@customer is logged in
cart is empty
on> :add_item from @customer
$items adds $item
$total increases by $item.price
expect:
= $items contains $item
expect:
= $items is not empty
scenario: complete purchase
given:
@customer is logged in
cart has items
$total: number is 1200
on> :checkout from @customer
? cart is valid
emit :payment_request to @payment
order moves to #awaiting_payment
expect:
= order is in #awaiting_payment
on :payment_success from @payment
order moves to #paid
emit :reserve_stock to @inventory
on :payment_failed from @payment
order moves to #payment_failed
emit :payment_error to @customer
on :stock_reserved from @inventory
order moves to #fulfilled
emit :order_confirmed to @customer
expect:
= order is in #fulfilled
= @customer received :order_confirmedInferred Structure
States are Inferred
You don't need to declare states upfront - they're inferred from usage:
order moves to #draft // #draft is now a known state
order moves to #submitted // #submitted is now a known state
order moves to #approved // #approved is now a known stateContext is Inferred
Context variables are also inferred:
$total becomes 150 // creates "total" (number)
$items adds "Laptop" // creates "items" (array)
$customer_id becomes "C-001" // creates "customer_id" (string)No upfront declarations needed - just use them.
Indentation Rules
EventFlow uses indentation to indicate nesting:
machine: @order // Level 0
scenario: checkout // Level 1
given: // Level 2
@customer is logged in // Level 3
on> :checkout from @customer // Level 2
? cart is valid // Level 3
order moves to #awaiting // Level 4
expect: // Level 2
= order is in #awaiting // Level 3Use consistent indentation (2 or 4 spaces recommended).