Skip to content

Querying and Observers

Checking before firing

.can_send("event") returns true if the event would cause a transition from the current state, without changing anything. Use it to enable or disable UI buttons, validate inputs before confirming, or write tests that assert protocol invariants.

Two-state traffic light: the can_send response flips as the current state changes.

03-can-send.zolo
Playground
// Feature: `.can_send(event)` — check applicability without firing
// Syntax: returns true if the event would cause a transition from
// the current state, false otherwise.
// When to use: enable/disable UI buttons, validate input before
// committing, write tests that assert protocol invariants.

machine Light {
  state Red, Green
  initial Red
  Red -> Green on go
  Green -> Red on stop
}

let l = Light.new()

// In Red, "go" is valid but "stop" is not.
print(l.can_send("go"))  // expected: true
print(l.can_send("stop"))  // expected: false

l.send("go")

// In Green, the answer flips.
print(l.can_send("go"))  // expected: false
print(l.can_send("stop"))  // expected: true

Observing all transitions

.on_transition(|from, to, event| { ... }) registers an observer that fires after each successful transition — in registration order when there are multiple. It is the right place for cross-cutting concerns: audit logs, metrics, replay and debugging, without polluting the machine definition.

Counter A → B → C with an observer that prints each step as from -> to via event.

04-on-transition.zolo
Playground
// Feature: `.on_transition(callback)` — observe every transition
// Syntax: register a closure receiving `(from, to, event)`. Fires
// after each successful transition, in registration order.
// When to use: audit logs, metrics, replay/journaling, debugging
// — all the cross-cutting concerns that don't belong inside the
// machine definition.

machine Counter {
  state A, B, C
  initial A
  A -> B on next
  B -> C on next
  C -> A on reset
}

let c = Counter.new()

c.on_transition(|from, to, event| {
  print("{from} -> {to} via {event}")
})

c.send("next")

// expected: A -> B via next

c.send("next")

// expected: B -> C via next

c.send("reset")
// expected: C -> A via reset

Challenge

Register two observers on the same counter: the first prints the transition, the second accumulates a count. Verify that both fire on every send.

enespt-br