ESC
Type to search...
S
Soli Docs

State Machines

Implement state machines for managing complex workflows and business logic in your application.

Workflow Automation

State machines provide a structured way to model workflows with discrete states and transitions. They help prevent invalid state transitions and make complex business logic more maintainable.

1 Creating a State Machine

Use the create_state_machine() function to create a new state machine instance:

basic_workflow.sl
let states = ["pending", "confirmed", "processing", "shipped", "delivered", "cancelled"];
let transitions = [
    {"event": "confirm", "from": "pending", "to": "confirmed"},
    {"event": "process", "from": "confirmed", "to": "processing"},
    {"event": "ship", "from": "processing", "to": "shipped"},
    {"event": "deliver", "from": "shipped", "to": "delivered"},
    {"event": "cancel", "from": "pending", "to": "cancelled"}
];

let order = create_state_machine("pending", states, transitions);

print(order.current_state());  # "pending"

Parameters

Parameter Type Description
initial_state String The starting state (must be in the states array)
states Array Array of valid state names
transitions Array Array of transition definitions

Transition Definition

Key Description
event Name of the event/method to create
from Source state(s) - can be a string or array of strings
to Target state
if / guard Optional guard condition field name

2 Instance Methods

current_state()

Returns the current state as a string.

print(order.current_state());  # "pending"

is(state)

Check if the state machine is in a specific state.

if (order.is("pending")) {
    print("Order is waiting for confirmation");
}

is_in([states])

Check if the state machine is in any of the given states.

if (order.is_in(["shipped", "delivered"])) {
    print("Order is on its way or delivered");
}

set(key, value) & get(key)

Store and retrieve custom data in the state machine context.

order.set("customer_id", 12345);
order.set("total", 99.99);
order.set("items", ["Product A", "Product B"]);

let customer_id = order.get("customer_id");
let total = order.get("total");

history()

Get the state transition history.

let hist = order.history();
print(hist);

last_transition()

Returns information about the last transition as a hash.

order.confirm();
let last = order.last_transition();
# {from => pending, to => confirmed, event => confirm}

print(last["from"]);   # "pending"
print(last["to"]);     # "confirmed"
print(last["event"]);  # "confirm"

can(event)

Checks if an event is available from the current state.

if (order.can("ship")) {
    print("Order can be shipped");
}
# Returns: true or false

available_events()

Returns an array of events available from the current state.

let events = order.available_events();
# ["confirm", "cancel"] from pending state

3 Automatic Transition Methods

When you define transitions, Soli automatically creates methods for each event:

# From the transitions array above
order.confirm();   # Transitions from "pending" to "confirmed"
order.process();   # Transitions from "confirmed" to "processing"
order.ship();      # Transitions from "processing" to "shipped"
order.deliver();   # Transitions from "shipped" to "delivered"
order.cancel();    # Transitions from "pending" to "cancelled"
Invalid transitions are rejected

Calling an event from an invalid state will raise an error with a helpful message.

order.ship();
# Error: Cannot transition 'ship' from state 'pending'. Valid states: processing

4 Guard Conditions

Guard conditions allow you to conditionally enable transitions based on context values:

guard_example.sl
let transitions = [
    {"event": "confirm", "from": "pending", "to": "confirmed"},
    {"event": "ship", "from": "processing", "to": "shipped", "if": "can_ship"},
    {"event": "deliver", "from": "shipped", "to": "delivered", "guard": "is_deliverable"},
    {"event": "cancel", "from": "pending", "to": "cancelled"}
];

let order = create_state_machine("pending", states, transitions);

# Set guard conditions
order.set("can_ship", true);
order.set("is_deliverable", true);

# Transitions will fail if guard is false
order.set("can_ship", false);
order.ship();
# Error: Guard condition 'can_ship' is false
Note

The if and guard keys are aliases and work identically. They specify a context field that must be true (or undefined) for the transition to proceed.

5 Example: Order Processing Workflow

class OrderWorkflow
    def create_order(items: Array, total: Float)
        let states = ["pending", "confirmed", "processing", "shipped", "delivered", "cancelled"];
        let transitions = [
            {"event": "confirm", "from": "pending", "to": "confirmed"},
            {"event": "process", "from": "confirmed", "to": "processing"},
            {"event": "ship", "from": "processing", "to": "shipped"},
            {"event": "deliver", "from": "shipped", "to": "delivered"},
            {"event": "cancel", "from": "pending", "to": "cancelled"}
        ];

        let order = create_state_machine("pending", states, transitions);
        order.set("items", items);
        order.set("total", total);
        order.set("created_at", clock());

        order
    end

    def process_order(order: Any)
        if (order.is("pending"))
            order.confirm();
        end

        if (order.is("confirmed"))
            order.process();
        end

        order
    end
end

# Usage
let workflow = new OrderWorkflow();
let order = workflow.create_order(["Widget A", "Widget B"], 149.99);
workflow.process_order(order);

6 Example: Payment State Machine

let payment_states = ["pending", "authorized", "captured", "failed", "refunded"];
let payment_transitions = [
    {"event": "authorize", "from": "pending", "to": "authorized"},
    {"event": "capture", "from": "authorized", "to": "captured"},
    {"event": "fail", "from": ["pending", "authorized"], "to": "failed"},
    {"event": "refund", "from": ["captured", "failed"], "to": "refunded"},
    {"event": "retry", "from": "failed", "to": "pending"}
];

let payment = create_state_machine("pending", payment_states, payment_transitions);
payment.set("amount", 99.99);
payment.set("currency", "USD");

payment.authorize();
print("Payment status: " + payment.current_state());  # "authorized"

payment.capture();
print("Payment status: " + payment.current_state());  # "captured"

7 Database Persistence

State machines can be persisted to the database for long-running workflows:

# Save state to database
let state_data = {
    "machine_type": "Order",
    "machine_id": order.get("id"),
    "current_state": order.current_state(),
    "context": {
        "total": order.get("total"),
        "items": order.get("items")
    }
};
Order.update(order.get("id"), state_data);

# Load state from database
let saved_data = Order.find(order.get("id"));
let loaded_order = create_state_machine(
    saved_data["current_state"],
    ["pending", "confirmed", "processing", "shipped", "delivered"],
    [...]
);
loaded_order.set("total", saved_data["context"]["total"]);
loaded_order.set("items", saved_data["context"]["items"]);

8 Best Practices

Define Clear States

Use descriptive state names that reflect business states clearly.

Validate Transitions

Let the state machine reject invalid transitions automatically.

Use Context for Data

Store related data in the state machine context for easy access.

One Machine Per Workflow

Use separate state machines for independent workflows.

API Reference

create_state_machine(initial_state, states, transitions)

Creates a new state machine instance.

Parameter Type Description
initial_state String Starting state
states Array List of valid state names
transitions Array Transition definitions with event, from, to keys

Instance Methods

Method Description
current_state() Returns current state as string
is(state) Returns true if in given state
is_in([states]) Returns true if in any of given states
set(key, value) Stores data in context
get(key) Retrieves data from context
history() Returns transition history
last_transition() Returns info about last transition
can(event) Returns true if event is available
available_events() Returns array of available events

9 Interactive Demo

Try out the state machine interactive demo to see how transitions work in real-time:

Open State Machine Demo