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 setup)on :event (api)(public endpoint)├── given: (event-level setup)├── actions (no prefix)├── ? guards (conditions)├── otherwise└── expect: (assertions)on :event(internal handler)expect:(scenario 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

Machine Definition vs Running Instances ​

When you write a machine definition, you're creating a template:

flow
machine: @order     // Template definition

At runtime, many instances (aggregates) can exist from the same template:

ConceptSymbolExampleDescription
Machine (template)@name@orderThe definition in your .flow file
Instance (aggregate)@name:id@order:abc123A running entity with unique ID
Aggregate IDstringorder-abc123Auto-generated unique identifier
@order (template)@order:abc123#processing, $total: 1200@order:xyz789#paid, $total: 50@order:def456#shipped, $total: 300

Each instance:

  • Has its own independent state
  • Maintains its own context variables
  • Processes its own event stream
  • Is identified by a unique aggregate ID

See Instance Management for details on how instances are created and routed.

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 (api)
    $items adds $item
    $total increases by $item.price

Multi-Event Scenario ​

flow
scenario: complete purchase

  on :checkout from @customer (api)
    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 (api)
    ...

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 (api)

    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 ... (api)) ​

Defines a public endpoint that can be triggered from outside the system:

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

The (api) suffix 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 (api) suffix - 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 (api)
  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 (api)
    ...

  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 (api)
    $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 (api)
    ? 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 (api)     // 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.