Skip to content

Getting started

Terminal window
go get github.com/stablekernel/crucible/state

The kernel depends only on the Go standard library.

A machine is generic over three types you define: S (state), E (event), and C (context, your domain entity). Here is a toy turnstile, forged and fired from scratch.

package main
import (
"context"
"fmt"
"github.com/stablekernel/crucible/state"
)
type Turnstile struct{ Coins int } // C
func main() {
// Forge a builder, declare states + transitions, then Quench to freeze
// the definition into an immutable *Machine. Quench panics on misconfig.
// State and event identifiers here are plain strings, so ForgeFor fixes
// S and E to string and leaves only the context type to spell.
m := state.ForgeFor[Turnstile]("turnstile").
Initial("Locked").
Transition("Locked").On("Coin").GoTo("Unlocked").
Transition("Unlocked").On("Push").GoTo("Locked").
Quench()
// Cast an instance around an entity value.
inst := m.Cast(Turnstile{})
// Fire advances the instance and returns a FireResult. It performs NO IO.
// NewState is the next state, Effects is data for the caller to dispatch.
res := inst.Fire(context.Background(), "Coin")
fmt.Println(res.NewState) // Unlocked
res = inst.Fire(context.Background(), "Push")
fmt.Println(res.NewState) // Locked
}

That toy machine looks like this:

stateDiagram-v2
    [*] --> Locked
    Locked --> Unlocked: Coin
    Unlocked --> Locked: Push

The same primitives scale to hierarchical and parallel statecharts. Here is the food-delivery order machine that ships as a worked example. Note the nested Active region with parallel fulfillment and watchdog sub-machines, guarded transitions, and an after(...) timer:

stateDiagram-v2
    [*] --> Placed
    state Active {
        [*] --> Active_Fulfillment__Cooking
        Active_Fulfillment__Cooking
        Active_Fulfillment__AwaitingCourier
        Active_Fulfillment__EnRoute
        Active_Fulfillment__AwaitingCourier --> Active_Fulfillment__EnRoute: PickedUp
        Active_Fulfillment__Cooking --> Active_Fulfillment__AwaitingCourier: PlatedUp
        --
        [*] --> Active_Watchdog__OnTime
        Active_Watchdog__OnTime
        Active_Watchdog__Overdue
        Active_Watchdog__Overdue --> [*]
        Active_Watchdog__OnTime --> Active_Watchdog__Overdue: after(30m0s) SLABreached
    }
    Delivered --> [*]
    Canceled --> [*]
    Rejected --> [*]
    Active --> Refunding: Cancel
    Active --> Settling: DroppedOff
    Authorizing --> Active: Authorized [or(generousOrder,and(subtotal ge 5000,priority in ["fast","express"]))]
    Authorizing --> Rejected: Declined
    Placed --> Authorizing: Submit
    Refunding --> Canceled: Refunded
    Settling --> Delivered

Next: read the foundry vocabulary to learn exactly what each lifecycle verb does.