Skip to content

Available Events

HATEOAS-style event discoverability — the machine tells the consumer what it can do next. Instead of hard-coding allowed transitions in the client, each response includes the events the machine can currently accept.

Core API

Available events are computed from the current state's transition definitions. Three access points expose the same data:

php
use Tarfinlabs\EventMachine\Actor\Machine;

// 1. Via the machine (convenience proxy)
$events = $machine->availableEvents();

// 2. Via the state (core method)
$events = $machine->state->availableEvents();

// 3. Via toArray() — auto-included
$array = $machine->state->toArray();
// $array['availableEvents'] contains the same data

Each entry in the returned array is an associative array with these keys:

KeyTypeAlways presentDescription
typestringYesThe event type name (e.g., APPROVE, PROVIDE_CARD)
sourcestringYesWhere the event is handled: parent or forward
regionstringOnly in parallel statesThe active region this event belongs to

HTTP Response Format

When an endpoint returns the default response (no custom OutputBehavior), availableEvents is included automatically:

json
{
    "data": {
        "id": "01JARX5Z8KQVN...",
        "state": ["awaiting_approval"],
        "output": {
            "order_id": 42,
            "total_amount": 15000
        },
        "availableEvents": [
            { "type": "APPROVE", "source": "parent" },
            { "type": "REJECT", "source": "parent" },
            { "type": "PROVIDE_CARD", "source": "forward" }
        ]
    }
}

In parallel states, each event includes its region:

json
{
    "data": {
        "id": "01JARX5Z8KQVN...",
        "state": [
            "fulfillment.payment.pending",
            "fulfillment.shipping.preparing"
        ],
        "output": {},
        "availableEvents": [
            { "type": "PAY", "source": "parent", "region": "payment" },
            { "type": "SHIP", "source": "parent", "region": "shipping" }
        ]
    }
}

Opting Out

To exclude available_events from a specific endpoint's response, set available_events to false in the endpoint definition:

php
'SUBMIT' => [
    'available_events' => false,
],

When a custom OutputBehavior is used, availableEvents is not added to the response automatically — the output behavior has full control over the response shape. You can still call $state->availableEvents() inside your output if needed.

Source Annotations

SourceMeaning
parentDirect on-event transition defined on the current state
forwardEvent that will be forwarded to an async child machine

The source annotation helps API consumers understand the event's routing. A forward event is sent to the parent endpoint but is internally forwarded to the running child machine.

Forward-Aware Behavior

Available events are dynamic. Forward events only appear when the child machine is in a state that accepts them:

php
// Parent machine in 'processing_payment' state
// Child machine in 'awaiting_card' state
$events = $machine->availableEvents();
// → [
//     ['type' => 'CANCEL', 'source' => 'parent'],
//     ['type' => 'PROVIDE_CARD', 'source' => 'forward'],  // child accepts this
// ]

// After sending PROVIDE_CARD, child moves to 'verifying' state
$events = $machine->availableEvents();
// → [
//     ['type' => 'CANCEL', 'source' => 'parent'],
//     // PROVIDE_CARD is gone — child no longer accepts it
// ]

This ensures the API never advertises events that would be rejected. The check works by:

  1. Looking up the child machine's current state (via MachineCurrentState table or cached forward state)
  2. Checking if the child state's transition definitions include the forwarded event type
  3. Only including the event if the child can accept it

When no child machine is running (e.g., delegation hasn't started yet), forward events are excluded entirely.

Testing

TestMachine provides five assertion methods for available events:

php
use Tarfinlabs\EventMachine\Testing\TestMachine;

// Assert a specific event is available
$testMachine->assertAvailableEvent('APPROVE');

// Assert a specific event is NOT available
$testMachine->assertNotAvailableEvent('SUBMIT');

// Assert the exact set of available events (order-independent)
$testMachine->assertAvailableEvents(['APPROVE', 'CANCEL']);

// Assert a forward event is available (checks source === 'forward')
$testMachine->assertForwardAvailable('PROVIDE_CARD');

// Assert no events are available (final state, etc.)
$testMachine->assertNoAvailableEvents();

Testing Forward Availability

php
use Tarfinlabs\EventMachine\Testing\TestMachine;

// Forward event appears only when child accepts it
$testMachine = TestMachine::start(OrderMachine::class);
$testMachine->send('SUBMIT');

// Now in processing_payment — child is in awaiting_card
$testMachine->assertForwardAvailable('PROVIDE_CARD');
$testMachine->assertAvailableEvent('CANCEL');

// Send the forward event — child moves past awaiting_card
$testMachine->send('PROVIDE_CARD', ['card_number' => '4242424242424242']);

// PROVIDE_CARD should no longer be available
$testMachine->assertNotAvailableEvent('PROVIDE_CARD');

Full Testing Guide

For all available events assertion methods, see TestMachine — Available Events Assertions.

What's Excluded

The following event types are automatically excluded from available_events because they are internal and not user-sendable:

Event PatternReason
@alwaysInternal automatic transition — fires without user input
@doneChild machine completion callback
@failChild machine failure callback
@timeoutChild machine timeout callback
Internal eventsFramework-level events (e.g., xstate.init)

Only events that a consumer can actually POST to an endpoint are included in the available events list.

Released under the MIT License.