Skip to content

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.

flow
machine: @order

The @ 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

flow
scenario: add to cart

  on> :add_item from @customer
    $items adds $item
    $total increases by $item.price

Multi-Event Scenario

flow
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 #fulfilled

A 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:

flow
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):

flow
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_payment

The 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:

flow
on> :checkout from @customer
  ? cart is valid
    emit :payment_request to @payment
    order moves to #awaiting_payment

The > prefix marks this event as externally accessible (like an API endpoint).

Internal Event Handler (on)

Defines an event handler for system-internal events only:

flow
on :payment_success from @payment
  order moves to #paid
  emit :reserve_stock to @inventory

No > prefix - this event is only triggered by other machines in the system.

The from Clause

The from clause specifies who can send the event:

flow
// 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_approved

Expect Blocks

Assertions that verify outcomes.

Event-Level Expect (Optional)

Assertions that verify the outcome after a specific event:

flow
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 @payment

Event-level expect: is optional - useful for debugging or critical checkpoints.

Scenario-Level Expect

Assertions that verify the outcome after all events complete:

flow
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 processed

Scenario-level expect: is the primary test assertion - it's the integration test.

Complete Structure Example

flow
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_confirmed

Inferred Structure

States are Inferred

You don't need to declare states upfront - they're inferred from usage:

flow
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 state

Context is Inferred

Context variables are also inferred:

flow
$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:

flow
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 3

Use consistent indentation (2 or 4 spaces recommended).

Released under the MIT License.