statekit

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 29, 2026 License: MIT Imports: 12 Imported by: 0

README

Statekit

Go Reference Go Report Card CI

Go-native statechart execution engine with XState JSON compatibility for visualization.

Define and execute statecharts in Go — visualize them with XState tooling.

Features

  • Fluent Builder API — Type-safe machine construction with Go generics
  • Hierarchical States — Compound/nested states with event bubbling
  • History States — Shallow and deep history for resuming previous states
  • Delayed Transitions — Timer-based automatic transitions
  • Parallel States — Orthogonal regions active simultaneously
  • Reflection DSL — Define machines using struct tags
  • Guards & Actions — Conditional transitions and side effects
  • Native Visualization — Interactive HTML simulator, Mermaid diagrams, and TUI
  • Build-time Validation — Catch configuration errors before runtime
  • Testing Utilities — Assertions, recorders, and helpers for testing machines
  • Prometheus Metrics — Production monitoring with metrics and health checks
  • Static Analysis — Lint rules for detecting structural issues
  • MCP Server — Create, manage, and visualize machines via Model Context Protocol
  • Zero Dependencies — Pure Go, no external runtime dependencies (core library)

Installation

go get github.com/felixgeelhaar/statekit

Requires Go 1.24 or later.

Quick Start

package main

import (
    "fmt"
    "github.com/felixgeelhaar/statekit"
)

func main() {
    machine, _ := statekit.NewMachine[struct{}]("traffic").
        WithInitial("green").
        State("green").On("TIMER").Target("yellow").Done().
        State("yellow").On("TIMER").Target("red").Done().
        State("red").On("TIMER").Target("green").Done().
        Build()

    interp := statekit.NewInterpreter(machine)
    interp.Start()

    fmt.Println(interp.State().Value) // "green"
    interp.Send(statekit.Event{Type: "TIMER"})
    fmt.Println(interp.State().Value) // "yellow"
}

Hierarchical States

Nested states with event bubbling and proper entry/exit ordering:

machine, _ := statekit.NewMachine[struct{}]("editor").
    WithInitial("editing").
    State("editing").
        WithInitial("idle").
        On("SAVE").Target("saved").End().  // Parent handles SAVE
        State("idle").On("TYPE").Target("dirty").End().End().
        State("dirty").On("CLEAR").Target("idle").End().End().
    Done().
    State("saved").Final().Done().
    Build()

interp := statekit.NewInterpreter(machine)
interp.Start()

fmt.Println(interp.State().Value)     // "idle"
fmt.Println(interp.Matches("editing")) // true
interp.Send(statekit.Event{Type: "SAVE"}) // Bubbles to parent
fmt.Println(interp.State().Value)     // "saved"

History States

Remember and restore previous states:

machine, _ := statekit.NewMachine[struct{}]("player").
    WithInitial("playing").
    State("playing").
        WithInitial("track1").
        On("PAUSE").Target("paused").End().
        History("hist").Shallow().Default("track1").End().
        State("track1").On("NEXT").Target("track2").End().End().
        State("track2").On("NEXT").Target("track3").End().End().
        State("track3").End().
    Done().
    State("paused").
        On("PLAY").Target("hist").  // Resume last track
    Done().
    Build()
  • Shallow history — Remembers immediate child state
  • Deep history — Remembers exact leaf state

Delayed Transitions

Timer-based automatic transitions:

machine, _ := statekit.NewMachine[struct{}]("loading").
    WithInitial("loading").
    State("loading").
        After(5*time.Second).Target("timeout").
        On("LOADED").Target("ready").
    Done().
    State("timeout").Done().
    State("ready").Done().
    Build()

interp := statekit.NewInterpreter(machine)
interp.Start()
// Timer starts automatically, canceled if LOADED received
defer interp.Stop() // Always clean up timers

Parallel States

Multiple regions active simultaneously:

machine, _ := statekit.NewMachine[struct{}]("editor").
    WithInitial("active").
    State("active").Parallel().
        Region("bold").WithInitial("off").
            State("off").On("TOGGLE_BOLD").Target("on").EndState().
            State("on").On("TOGGLE_BOLD").Target("off").EndState().
        EndRegion().
        Region("italic").WithInitial("off").
            State("off").On("TOGGLE_ITALIC").Target("on").EndState().
            State("on").On("TOGGLE_ITALIC").Target("off").EndState().
        EndRegion().
    Done().
    Build()

interp := statekit.NewInterpreter(machine)
interp.Start()
interp.Send(statekit.Event{Type: "TOGGLE_BOLD"})
// bold: on, italic: off (independent regions)

Reflection DSL

Define machines using struct tags:

type OrderMachine struct {
    statekit.MachineDef `id:"order" initial:"pending"`
    Pending   statekit.StateNode `on:"SUBMIT->processing:hasItems"`
    Processing statekit.StateNode `on:"COMPLETE->shipped"`
    Shipped   statekit.FinalNode
}

type OrderContext struct {
    Items []string
}

registry := statekit.NewActionRegistry[OrderContext]().
    WithGuard("hasItems", func(ctx OrderContext, e statekit.Event) bool {
        return len(ctx.Items) > 0
    })

machine, _ := statekit.FromStruct[OrderMachine, OrderContext](registry)

Guards & Actions

Conditional transitions and side effects:

type Context struct{ Count int }

machine, _ := statekit.NewMachine[Context]("counter").
    WithInitial("idle").
    WithContext(Context{Count: 0}).
    WithAction("increment", func(ctx *Context, e statekit.Event) {
        ctx.Count++
    }).
    WithGuard("hasCount", func(ctx Context, e statekit.Event) bool {
        return ctx.Count > 0
    }).
    State("idle").
        OnEntry("increment").
        On("NEXT").Target("done").Guard("hasCount").
    Done().
    State("done").Final().Done().
    Build()

Visualization

Statekit provides a native viz command to visualize state machines from Go source code or JSON.

Try the Live Visualizer to paste your JSON and interact with it.

# Interactive HTML simulation
statekit viz --go-package ./examples/order_workflow --format html -o machine.html

# Mermaid diagram
statekit viz --go-package ./examples/order_workflow --format mermaid

It supports multiple output formats:

  • HTML: Interactive simulator with Cytoscape graph.
  • Mermaid: Markdown-friendly state diagrams.
  • ASCII: Terminal box diagrams.
  • TUI: Interactive terminal UI.

To export JSON programmatically:

import "github.com/felixgeelhaar/statekit/export"

exporter := export.NewNativeExporter(machine)
jsonStr, _ := exporter.ExportJSONIndent("", "  ")
fmt.Println(jsonStr)

MCP Server

Statekit includes a built-in Model Context Protocol server for AI-assisted state machine development. Create, manage, and visualize machines directly from Claude Code or any MCP host.

# Add to your MCP configuration
go install github.com/felixgeelhaar/statekit/cmd/statekit-mcp@latest
{
  "mcpServers": {
    "statekit": {
      "command": "statekit-mcp"
    }
  }
}

Available tools:

Tool Description
create_machine Create a machine from a Native JSON definition
list_machines List all running machine instances
get_state Get current state, done status, and state path
send_event Send an event to trigger a transition
get_context Get the machine's context data
visualize_machine Get visualization data with interactive MCP App
validate_machine Validate a definition using lint rules
export_machine Export as JSON, Mermaid, or ASCII

The visualize_machine tool includes an interactive Vue.js + Cytoscape.js visualizer that MCP Apps hosts render inline.

Additional Packages

Package Description
mcp MCP server for AI-assisted state machine management
statetest Testing utilities: assertions, recorders, helpers
debug Runtime inspection and state graph analysis
metrics Prometheus metrics for monitoring
health Kubernetes liveness/readiness probes
lint Static analysis for detecting structural issues
export XState JSON exporter
generate Go code generation from XState JSON
http HTTP handlers and middleware
otel OpenTelemetry tracing

Examples

See the examples directory:

Example Description
traffic_light Simple FSM with cyclic transitions
pedestrian_light Hierarchical states with event bubbling
order_workflow Reflection DSL for business workflows
incident_lifecycle Complex IT incident management

API Reference

See the full API documentation on pkg.go.dev.

Core Types
// Machine construction
statekit.NewMachine[C](id string) *MachineBuilder[C]
statekit.FromStruct[M, C](registry) (*MachineConfig[C], error)

// Runtime
statekit.NewInterpreter[C](machine) *Interpreter[C]
    .Start()                      // Enter initial state
    .Send(event Event)            // Process event
    .Stop()                       // Cancel timers, cleanup
    .State() State[C]             // Current state
    .Matches(id StateID) bool     // Check state or ancestor
    .Done() bool                  // In final state?

Design Philosophy

  • Go-first Execution — Explicit, deterministic, testable
  • Statecharts over FSMs — Hierarchy enables complex behavior
  • Visualization as a Feature — XState compatibility for free tooling
  • Small Surface Area — Fewer features, better guarantees

Documentation

Contributing

Contributions are welcome! Please read our Contributing Guide.

License

MIT © Felix Geelhaar

Documentation

Overview

Package statekit provides a Go-native statechart execution engine.

Define and execute statecharts in Go — visualize them with built-in tools.

Features

  • Fluent Builder API
  • Hierarchical States (Compound/Nested)
  • History States (Shallow and Deep)
  • Delayed Transitions (Timers)
  • Parallel States (Orthogonal Regions)
  • Reflection DSL (Struct tags)
  • Native Visualization (HTML simulator, Mermaid, ASCII, TUI)

Quick Start

machine, _ := statekit.NewMachine[struct{}]("traffic").
	WithInitial("green").
	State("green").On("TIMER").Target("yellow").Done().
	State("yellow").On("TIMER").Target("red").Done().
	State("red").On("TIMER").Target("green").Done().
	Build()

interp := statekit.NewInterpreter(machine)
interp.Start()

interp.Send(statekit.Event{Type: "TIMER"})

Visualization

Export your machine to Statekit Native JSON for visualization:

exporter := export.NewNativeExporter(machine)
jsonStr, _ := exporter.ExportJSONIndent("", "  ")
fmt.Println(jsonStr)

Or use the CLI:

statekit viz --go-package . --format html -o machine.html

Package statekit provides event sourcing support for state machine persistence.

Example
package main

import (
	"fmt"

	"github.com/felixgeelhaar/statekit"
)

func main() {
	// Create a simple traffic light state machine
	machine, _ := statekit.NewMachine[struct{}]("traffic").
		WithInitial("green").
		State("green").On("TIMER").Target("yellow").Done().
		State("yellow").On("TIMER").Target("red").Done().
		State("red").On("TIMER").Target("green").Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()

	fmt.Println(interp.State().Value)
	interp.Send(statekit.Event{Type: "TIMER"})
	fmt.Println(interp.State().Value)

}
Output:

green
yellow

Index

Examples

Constants

View Source
const (
	ErrCodeSnapshotMachineMismatch = "SNAPSHOT_MACHINE_MISMATCH"
	ErrCodeSnapshotInvalidState    = "SNAPSHOT_INVALID_STATE"
	ErrCodeSnapshotVersionMismatch = "SNAPSHOT_VERSION_MISMATCH"
)

Error codes for snapshot operations

View Source
const (
	StateTypeAtomic   = ir.StateTypeAtomic
	StateTypeCompound = ir.StateTypeCompound
	StateTypeFinal    = ir.StateTypeFinal
	StateTypeHistory  = ir.StateTypeHistory  // v2.0
	StateTypeParallel = ir.StateTypeParallel // v2.0

	HistoryTypeShallow = ir.HistoryTypeShallow // v2.0
	HistoryTypeDeep    = ir.HistoryTypeDeep    // v2.0
)

Re-export constants

Variables

View Source
var ErrActorAlreadyExists = errors.New("actor already exists")

ErrActorAlreadyExists is returned when spawning an actor with a duplicate ID

View Source
var ErrActorNotFound = errors.New("actor not found")

ErrActorNotFound is returned when an actor ID doesn't exist in the registry

View Source
var ErrActorStopped = errors.New("actor stopped")

ErrActorStopped is returned when sending to a stopped actor

View Source
var ErrLockHeld = errors.New("lock held by another node")

ErrLockHeld is returned when attempting to acquire a lock held by another node.

View Source
var ErrLockLost = errors.New("lock lost")

ErrLockLost is returned when a lock is lost unexpectedly.

View Source
var ErrNoParent = errors.New("no parent actor")

ErrNoParent is returned when SendParent is called on a root interpreter

Functions

func FromStruct

func FromStruct[M any, C any](registry *ActionRegistry[C]) (*ir.MachineConfig[C], error)

FromStruct builds a MachineConfig from a struct definition using the reflection DSL.

The struct M must embed MachineDef and define states using StateNode, CompoundNode, or FinalNode marker types with appropriate struct tags.

Actions and guards referenced in tags must be registered in the provided ActionRegistry.

Example:

type MyMachine struct {
    statekit.MachineDef `id:"example" initial:"idle"`
    Idle    statekit.StateNode `on:"START->running:canStart" entry:"logStart"`
    Running statekit.StateNode `on:"STOP->idle"`
}

registry := statekit.NewActionRegistry[MyContext]().
    WithAction("logStart", func(ctx *MyContext, e statekit.Event) { ... }).
    WithGuard("canStart", func(ctx MyContext, e statekit.Event) bool { ... })

machine, err := statekit.FromStruct[MyMachine, MyContext](registry)

func FromStructWithContext

func FromStructWithContext[M any, C any](registry *ActionRegistry[C], ctx C) (*ir.MachineConfig[C], error)

FromStructWithContext builds a MachineConfig with an initial context value.

Types

type Action

type Action[C any] func(ctx *C, event Event)

Action is a side-effect function executed during transitions. It receives a pointer to the context for modification and the triggering event.

type ActionRegistry

type ActionRegistry[C any] struct {
	// contains filtered or unexported fields
}

ActionRegistry holds action and guard function implementations that are referenced by name in the reflection DSL.

ActionRegistry is not safe for concurrent use. It should be fully configured before calling FromStruct or FromStructWithContext.

func NewActionRegistry

func NewActionRegistry[C any]() *ActionRegistry[C]

NewActionRegistry creates a new empty action registry.

func (*ActionRegistry[C]) WithAction

func (r *ActionRegistry[C]) WithAction(name ActionType, action Action[C]) *ActionRegistry[C]

WithAction registers an action function by name. Returns the registry for method chaining.

func (*ActionRegistry[C]) WithGuard

func (r *ActionRegistry[C]) WithGuard(name GuardType, guard Guard[C]) *ActionRegistry[C]

WithGuard registers a guard function by name. Returns the registry for method chaining.

type ActionType

type ActionType = ir.ActionType

ActionType identifies a named action

type ActorID added in v0.8.0

type ActorID string

ActorID uniquely identifies a spawned child actor

type ActorMetadata added in v0.14.0

type ActorMetadata struct {
	// ID is the actor's unique identifier
	ID ActorID `json:"id"`

	// SpawnedInState is the state that spawned this actor
	SpawnedInState StateID `json:"spawned_in_state"`

	// Supervision is the supervision strategy for this actor
	Supervision SupervisionStrategy `json:"supervision"`

	// AutoForward lists event types that were auto-forwarded to this actor
	AutoForward []EventType `json:"auto_forward,omitempty"`
}

ActorMetadata captures information about a spawned actor for persistence (v0.14). This is a metadata-only snapshot - the actor's internal state is not captured. When restoring, actors must be manually respawned by the application.

type ActorRef added in v0.8.0

type ActorRef struct {
	// contains filtered or unexported fields
}

ActorRef provides a handle to communicate with a spawned child actor. It is safe to use concurrently from multiple goroutines.

func Spawn added in v0.8.0

func Spawn[ParentC, ChildC any](
	parent *Interpreter[ParentC],
	id ActorID,
	childMachine *ir.MachineConfig[ChildC],
	opts ...SpawnOption,
) (*ActorRef, error)

Spawn creates and starts a child actor from a machine configuration. The actor is associated with the current state and will be stopped when that state is exited (state-scoped lifecycle).

Options can configure supervision strategy, auto-forwarding, and completion handlers.

func SpawnWithContext added in v0.8.0

func SpawnWithContext[ParentC, ChildC any](
	parent *Interpreter[ParentC],
	id ActorID,
	childMachine *ir.MachineConfig[ChildC],
	initContext func(ParentC) ChildC,
	opts ...SpawnOption,
) (*ActorRef, error)

SpawnWithContext is like Spawn but allows initializing the child context from the parent context.

func (*ActorRef) Done added in v0.8.0

func (a *ActorRef) Done() <-chan struct{}

Done returns a channel that's closed when the actor stops. Use this to wait for actor completion.

func (*ActorRef) ID added in v0.8.0

func (a *ActorRef) ID() ActorID

ID returns the actor's unique identifier

func (*ActorRef) Send added in v0.8.0

func (a *ActorRef) Send(e Event) error

Send sends an event to the child actor. Returns ErrActorStopped if the actor has been stopped.

func (*ActorRef) Stop added in v0.8.0

func (a *ActorRef) Stop()

Stop gracefully stops the child actor. Safe to call multiple times.

type ClusterMembership added in v0.8.0

type ClusterMembership interface {
	// Join registers this node with the cluster.
	Join(ctx context.Context, node ClusterNode) error

	// Leave removes this node from the cluster.
	Leave(ctx context.Context) error

	// Members returns all known cluster members.
	Members(ctx context.Context) ([]ClusterNode, error)

	// Watch returns a channel that receives membership changes.
	Watch(ctx context.Context) (<-chan MembershipEvent, error)
}

ClusterMembership provides cluster membership management. Implementations can use etcd, consul, or a custom gossip protocol.

type ClusterNode added in v0.8.0

type ClusterNode struct {
	ID       string
	Address  string
	JoinedAt time.Time
	LastSeen time.Time
	Metadata map[string]string
}

ClusterNode represents a node in the cluster.

type CompoundNode

type CompoundNode struct{}

CompoundNode is a marker type for defining compound (nested) states.

Use struct tags to configure the compound state:

  • initial:"childState" - Required initial child state
  • on:"EVENT->target" - Parent-level transitions
  • entry:"action" - Parent entry actions
  • exit:"action" - Parent exit actions

Child states are defined as fields within the struct that embeds CompoundNode.

Example:

type ActiveState struct {
    statekit.CompoundNode `initial:"idle" on:"RESET->done"`
    Idle    statekit.StateNode `on:"START->working"`
    Working statekit.StateNode `on:"STOP->idle"`
}

type ConsistentHashRouter added in v0.8.0

type ConsistentHashRouter struct {
	// Replicas is the number of virtual nodes per physical node.
	Replicas int
}

ConsistentHashRouter routes streams using consistent hashing.

func NewConsistentHashRouter added in v0.8.0

func NewConsistentHashRouter(replicas int) *ConsistentHashRouter

NewConsistentHashRouter creates a new consistent hash router.

func (*ConsistentHashRouter) IsLocal added in v0.8.0

func (r *ConsistentHashRouter) IsLocal(streamID string, members []ClusterNode, localNodeID string) bool

IsLocal returns true if this node should handle the stream.

func (*ConsistentHashRouter) RouteStream added in v0.8.0

func (r *ConsistentHashRouter) RouteStream(streamID string, members []ClusterNode) string

RouteStream returns the node that should handle the stream using consistent hashing.

type DistributedInterpreter added in v0.8.0

type DistributedInterpreter[C any] struct {
	// contains filtered or unexported fields
}

DistributedInterpreter wraps a PersistentInterpreter with distributed locking. It ensures only one node processes events for a stream at a time.

func NewDistributedInterpreter added in v0.8.0

func NewDistributedInterpreter[C any](
	ctx context.Context,
	streamID string,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	streamLock StreamLock,
	opts ...DistributedInterpreterOption[C],
) (*DistributedInterpreter[C], error)

NewDistributedInterpreter creates a new distributed interpreter. It acquires a lock for the stream before hydrating state.

func (*DistributedInterpreter[C]) Commit added in v0.8.0

func (di *DistributedInterpreter[C]) Commit(ctx context.Context) (int, error)

Commit persists uncommitted events.

func (*DistributedInterpreter[C]) Context added in v0.8.0

func (di *DistributedInterpreter[C]) Context() C

Context returns the current context.

func (*DistributedInterpreter[C]) Done added in v0.8.0

func (di *DistributedInterpreter[C]) Done() bool

Done returns true if in a final state.

func (*DistributedInterpreter[C]) ForceSnapshot added in v0.8.0

func (di *DistributedInterpreter[C]) ForceSnapshot(ctx context.Context) error

ForceSnapshot creates a snapshot.

func (*DistributedInterpreter[C]) LockHeld added in v0.8.0

func (di *DistributedInterpreter[C]) LockHeld() bool

LockHeld returns true if the lock is still held.

func (*DistributedInterpreter[C]) LockLost added in v0.8.0

func (di *DistributedInterpreter[C]) LockLost() <-chan struct{}

LockLost returns a channel that's closed when the lock is lost.

func (*DistributedInterpreter[C]) Matches added in v0.8.0

func (di *DistributedInterpreter[C]) Matches(stateID StateID) bool

Matches checks if current state matches or is descendant of given state.

func (*DistributedInterpreter[C]) Send added in v0.8.0

func (di *DistributedInterpreter[C]) Send(event Event) error

Send processes an event if the lock is still held.

func (*DistributedInterpreter[C]) SendAll added in v0.8.0

func (di *DistributedInterpreter[C]) SendAll(events []Event) error

SendAll processes multiple events.

func (*DistributedInterpreter[C]) State added in v0.8.0

func (di *DistributedInterpreter[C]) State() State[C]

State returns the current state.

func (*DistributedInterpreter[C]) Stop added in v0.8.0

func (di *DistributedInterpreter[C]) Stop(ctx context.Context) error

Stop releases the lock and stops the interpreter.

func (*DistributedInterpreter[C]) StreamID added in v0.8.0

func (di *DistributedInterpreter[C]) StreamID() string

StreamID returns the stream identifier.

func (*DistributedInterpreter[C]) UncommittedCount added in v0.8.0

func (di *DistributedInterpreter[C]) UncommittedCount() int

UncommittedCount returns the number of uncommitted events.

func (*DistributedInterpreter[C]) Version added in v0.8.0

func (di *DistributedInterpreter[C]) Version() int

Version returns the current stream version.

type DistributedInterpreterOption added in v0.8.0

type DistributedInterpreterOption[C any] func(*DistributedInterpreter[C])

DistributedInterpreterOption configures a DistributedInterpreter.

func WithDistributedSnapshotConfig added in v0.8.0

func WithDistributedSnapshotConfig[C any](config SnapshotConfig) DistributedInterpreterOption[C]

WithDistributedSnapshotConfig sets the snapshot configuration.

func WithDistributedSnapshotStore added in v0.8.0

func WithDistributedSnapshotStore[C any](store SnapshotStore[C]) DistributedInterpreterOption[C]

WithDistributedSnapshotStore sets the snapshot store.

func WithLockTTL added in v0.8.0

func WithLockTTL[C any](ttl time.Duration) DistributedInterpreterOption[C]

WithLockTTL sets the lock TTL and renewal interval. Defaults: TTL=30s, renewInterval=10s (renew at 1/3 of TTL).

type ErrConcurrencyConflict added in v0.8.0

type ErrConcurrencyConflict struct {
	StreamID        string
	ExpectedVersion int
	ActualVersion   int
}

ErrConcurrencyConflict is returned when optimistic concurrency check fails.

func (*ErrConcurrencyConflict) Error added in v0.8.0

func (e *ErrConcurrencyConflict) Error() string

type Event

type Event = ir.Event

Event represents a runtime event with optional payload

type EventStore added in v0.8.0

type EventStore interface {
	// AppendEvents atomically appends events to a stream.
	// Returns ErrConcurrencyConflict if expectedVersion doesn't match.
	AppendEvents(ctx context.Context, streamID string, expectedVersion int, events []PersistedEvent) error

	// LoadEvents loads all events for a stream from a specific version.
	// Pass fromVersion=0 to load all events.
	LoadEvents(ctx context.Context, streamID string, fromVersion int) ([]PersistedEvent, error)

	// GetStreamVersion returns the current version of a stream.
	// Returns 0 if the stream doesn't exist.
	GetStreamVersion(ctx context.Context, streamID string) (int, error)
}

EventStore defines the interface for event persistence. Implementations handle storage of events for event sourcing.

type EventType

type EventType = ir.EventType

EventType is a named event identifier

type FinalNode

type FinalNode struct{}

FinalNode is a marker type for defining final states.

Final states indicate the machine has completed. They typically have no outgoing transitions.

Example:

Completed statekit.FinalNode

type Guard

type Guard[C any] func(ctx C, event Event) bool

Guard is a predicate that determines if a transition should occur. It receives the current context (by value) and the triggering event.

type GuardType

type GuardType = ir.GuardType

GuardType identifies a named guard

type HistoryBuilder added in v0.4.0

type HistoryBuilder[C any] struct {
	// contains filtered or unexported fields
}

HistoryBuilder provides a fluent API for constructing history states

func (*HistoryBuilder[C]) Deep added in v0.4.0

func (b *HistoryBuilder[C]) Deep() *HistoryBuilder[C]

Deep sets the history type to deep (remembers full leaf path)

func (*HistoryBuilder[C]) Default added in v0.4.0

func (b *HistoryBuilder[C]) Default(target StateID) *HistoryBuilder[C]

Default sets the default target state if no history is recorded

func (*HistoryBuilder[C]) End added in v0.4.0

func (b *HistoryBuilder[C]) End() *StateBuilder[C]

End completes the history state definition and returns to the parent StateBuilder

func (*HistoryBuilder[C]) Shallow added in v0.4.0

func (b *HistoryBuilder[C]) Shallow() *HistoryBuilder[C]

Shallow sets the history type to shallow (remembers immediate child)

type HistoryType added in v0.4.0

type HistoryType = ir.HistoryType

HistoryType specifies how history states remember previous states (v2.0)

type Interpreter

type Interpreter[C any] struct {
	// contains filtered or unexported fields
}

Interpreter is the statechart runtime that processes events and manages state

func NewInterpreter

func NewInterpreter[C any](machine *ir.MachineConfig[C]) *Interpreter[C]

NewInterpreter creates a new interpreter for the given machine configuration

func ReplayEvents added in v0.8.0

func ReplayEvents[C any](
	ctx context.Context,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	streamID string,
) (*Interpreter[C], error)

ReplayEvents replays events from the store to a new interpreter. Useful for debugging or creating a read model.

func ReplayToVersion added in v0.8.0

func ReplayToVersion[C any](
	ctx context.Context,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	streamID string,
	targetVersion int,
) (*Interpreter[C], error)

ReplayToVersion replays events up to a specific version.

func (*Interpreter[C]) Done

func (i *Interpreter[C]) Done() bool

Done returns true if the machine is in a final state

func (*Interpreter[C]) GetActor added in v0.8.0

func (i *Interpreter[C]) GetActor(id ActorID) *ActorRef

GetActor returns the ActorRef for the given ID, or nil if not found

func (*Interpreter[C]) Matches

func (i *Interpreter[C]) Matches(id StateID) bool

Matches checks if the current state matches the given state ID For hierarchical states, returns true if current state equals id or is a descendant of id For parallel states, also checks all active region states

Example
package main

import (
	"fmt"

	"github.com/felixgeelhaar/statekit"
)

func main() {
	machine, _ := statekit.NewMachine[struct{}]("nested").
		WithInitial("parent").
		State("parent").
		WithInitial("child").
		State("child").End().
		Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()

	// Current state is "child"
	fmt.Println(interp.State().Value)
	// Matches both the current state and its ancestors
	fmt.Println(interp.Matches("child"))
	fmt.Println(interp.Matches("parent"))

}
Output:

child
true
true

func (*Interpreter[C]) Restore added in v0.5.0

func (i *Interpreter[C]) Restore(s Snapshot[C]) error

Restore restores the interpreter to the state captured in the snapshot. The machine configuration must match (same machine ID). Returns an error if the snapshot is incompatible.

func (*Interpreter[C]) Send

func (i *Interpreter[C]) Send(event Event)

Send processes an event and potentially transitions to a new state

func (*Interpreter[C]) SendParent added in v0.8.0

func (i *Interpreter[C]) SendParent(event Event) error

SendParent sends an event to the parent interpreter. Returns ErrNoParent if this is a root interpreter (no parent).

func (*Interpreter[C]) SendTo added in v0.8.0

func (i *Interpreter[C]) SendTo(id ActorID, event Event) error

SendTo sends an event to a specific child actor by ID

func (*Interpreter[C]) Snapshot added in v0.5.0

func (i *Interpreter[C]) Snapshot() Snapshot[C]

Snapshot creates a snapshot of the current interpreter state. The snapshot captures all information needed to restore the interpreter to this exact state later.

Note on actors: Actor metadata is captured but actors are NOT automatically restored. The SpawnedActors field contains metadata about what actors were running, allowing the application to manually respawn them if needed.

func (*Interpreter[C]) Start

func (i *Interpreter[C]) Start()

Start initializes the interpreter and enters the initial state

func (*Interpreter[C]) State

func (i *Interpreter[C]) State() State[C]

State returns the current state of the interpreter

func (*Interpreter[C]) Stop added in v0.4.0

func (i *Interpreter[C]) Stop()

Stop cancels all active timers, services, invoked machines, and actors, then stops the interpreter

func (*Interpreter[C]) UpdateContext

func (i *Interpreter[C]) UpdateContext(fn func(ctx *C))

UpdateContext allows updating the context with a function

func (*Interpreter[C]) Use added in v0.14.0

func (i *Interpreter[C]) Use(p plugin.Plugin[C])

Use registers a plugin with the interpreter. Plugins receive callbacks during interpreter execution. Multiple plugins can be registered; they are called in registration order.

type InvokeBuilder added in v0.5.0

type InvokeBuilder[C any] struct {
	// contains filtered or unexported fields
}

InvokeBuilder provides a fluent API for constructing service invocations (v3.0)

func (*InvokeBuilder[C]) Done added in v0.5.0

func (b *InvokeBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the machine builder

func (*InvokeBuilder[C]) End added in v0.5.0

func (b *InvokeBuilder[C]) End() *StateBuilder[C]

End completes the invocation definition and returns to the StateBuilder

func (*InvokeBuilder[C]) ID added in v0.5.0

func (b *InvokeBuilder[C]) ID(id string) *InvokeBuilder[C]

ID sets a custom ID for this invocation

func (*InvokeBuilder[C]) OnDone added in v0.5.0

func (b *InvokeBuilder[C]) OnDone(target StateID) *InvokeBuilder[C]

OnDone sets the transition target when the service completes successfully

func (*InvokeBuilder[C]) OnDoneAction added in v0.5.0

func (b *InvokeBuilder[C]) OnDoneAction(action ActionType) *InvokeBuilder[C]

OnDoneAction sets an action to execute when the service completes successfully

func (*InvokeBuilder[C]) OnError added in v0.5.0

func (b *InvokeBuilder[C]) OnError(target StateID) *InvokeBuilder[C]

OnError sets the transition target when the service fails

func (*InvokeBuilder[C]) OnErrorAction added in v0.5.0

func (b *InvokeBuilder[C]) OnErrorAction(action ActionType) *InvokeBuilder[C]

OnErrorAction sets an action to execute when the service fails

type Lock added in v0.8.0

type Lock interface {
	// Renew extends the lock's TTL.
	Renew(ctx context.Context, ttl time.Duration) error

	// Release releases the lock.
	Release(ctx context.Context) error

	// Done returns a channel that's closed when the lock is lost.
	Done() <-chan struct{}
}

Lock represents a held distributed lock.

type MachineBuilder

type MachineBuilder[C any] struct {
	// contains filtered or unexported fields
}

MachineBuilder provides a fluent API for constructing state machines

func NewMachine

func NewMachine[C any](id string) *MachineBuilder[C]

NewMachine creates a new MachineBuilder with the given ID

Example (Hierarchical)
package main

import (
	"fmt"

	"github.com/felixgeelhaar/statekit"
)

func main() {
	// Hierarchical state machine with nested states
	machine, _ := statekit.NewMachine[struct{}]("editor").
		WithInitial("editing").
		State("editing").
		WithInitial("idle").
		On("SAVE").Target("saved").End(). // Parent handles SAVE for all children
		State("idle").
		On("TYPE").Target("dirty").
		End().
		End().
		State("dirty").
		On("CLEAR").Target("idle").
		End().
		End().
		Done().
		State("saved").Final().Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()

	fmt.Println(interp.State().Value)      // Starts in leaf state
	fmt.Println(interp.Matches("editing")) // Matches parent
	interp.Send(statekit.Event{Type: "TYPE"})
	fmt.Println(interp.State().Value)
	interp.Send(statekit.Event{Type: "SAVE"}) // Bubbles up to parent
	fmt.Println(interp.State().Value)

}
Output:

idle
true
dirty
saved
Example (WithAction)
package main

import (
	"fmt"

	"github.com/felixgeelhaar/statekit"
)

func main() {
	type Context struct {
		Count int
	}

	machine, _ := statekit.NewMachine[Context]("counter").
		WithInitial("idle").
		WithAction("increment", func(ctx *Context, e statekit.Event) {
			ctx.Count++
		}).
		State("idle").
		OnEntry("increment").
		On("COUNT").Target("idle").
		Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()

	fmt.Println(interp.State().Context.Count) // Entry action runs on Start
	interp.Send(statekit.Event{Type: "COUNT"})
	fmt.Println(interp.State().Context.Count) // Entry action runs again on self-transition

}
Output:

1
2
Example (WithGuard)
package main

import (
	"fmt"

	"github.com/felixgeelhaar/statekit"
)

func main() {
	type Context struct {
		Approved bool
	}

	machine, _ := statekit.NewMachine[Context]("approval").
		WithInitial("pending").
		WithContext(Context{Approved: true}).
		WithGuard("isApproved", func(ctx Context, e statekit.Event) bool {
			return ctx.Approved
		}).
		State("pending").
		On("SUBMIT").Target("approved").Guard("isApproved").
		Done().
		State("approved").Final().Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()
	interp.Send(statekit.Event{Type: "SUBMIT"})

	fmt.Println(interp.State().Value)
}
Output:

approved

func (*MachineBuilder[C]) Build

func (b *MachineBuilder[C]) Build() (*ir.MachineConfig[C], error)

Build constructs the final MachineConfig from the builder

func (*MachineBuilder[C]) State

func (b *MachineBuilder[C]) State(id StateID) *StateBuilder[C]

State starts building a new state with the given ID

func (*MachineBuilder[C]) WithAction

func (b *MachineBuilder[C]) WithAction(name ActionType, action Action[C]) *MachineBuilder[C]

WithAction registers a named action

func (*MachineBuilder[C]) WithChildMachine added in v0.14.0

func (b *MachineBuilder[C]) WithChildMachine(name string, factory ir.ChildMachineFactory[C]) *MachineBuilder[C]

WithChildMachine registers a child machine factory for machine composition (v0.14). The factory creates a child interpreter when the machine is invoked.

func (*MachineBuilder[C]) WithContext

func (b *MachineBuilder[C]) WithContext(ctx C) *MachineBuilder[C]

WithContext sets the initial context value

func (*MachineBuilder[C]) WithGuard

func (b *MachineBuilder[C]) WithGuard(name GuardType, guard Guard[C]) *MachineBuilder[C]

WithGuard registers a named guard

func (*MachineBuilder[C]) WithInitial

func (b *MachineBuilder[C]) WithInitial(initial StateID) *MachineBuilder[C]

WithInitial sets the initial state ID

func (*MachineBuilder[C]) WithService added in v0.5.0

func (b *MachineBuilder[C]) WithService(name ServiceType, service Service[C]) *MachineBuilder[C]

WithService registers a named service (v3.0)

type MachineConfig

type MachineConfig[C any] = ir.MachineConfig[C]

MachineConfig is the immutable internal representation of a statechart

type MachineDef

type MachineDef struct{}

MachineDef is a marker type that must be embedded in a struct to define a state machine using the reflection DSL.

Use struct tags to configure the machine:

  • id:"machineId" - Required machine identifier
  • initial:"stateName" - Required initial state name

Example:

type MyMachine struct {
    statekit.MachineDef `id:"myMachine" initial:"idle"`
    Idle    statekit.StateNode `on:"START->running"`
    Running statekit.StateNode `on:"STOP->idle"`
}

type MachineInvokeBuilder added in v0.14.0

type MachineInvokeBuilder[C any] struct {
	// contains filtered or unexported fields
}

MachineInvokeBuilder provides a fluent API for constructing child machine invocations (v0.14)

func (*MachineInvokeBuilder[C]) AutoForward added in v0.14.0

func (b *MachineInvokeBuilder[C]) AutoForward(events ...EventType) *MachineInvokeBuilder[C]

AutoForward adds event types that should be auto-forwarded to the child machine

func (*MachineInvokeBuilder[C]) Done added in v0.14.0

func (b *MachineInvokeBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the machine builder

func (*MachineInvokeBuilder[C]) End added in v0.14.0

func (b *MachineInvokeBuilder[C]) End() *StateBuilder[C]

End completes the machine invocation definition and returns to the StateBuilder

func (*MachineInvokeBuilder[C]) ID added in v0.14.0

ID sets a custom ID for this machine invocation

func (*MachineInvokeBuilder[C]) OnDone added in v0.14.0

func (b *MachineInvokeBuilder[C]) OnDone(target StateID) *MachineInvokeBuilder[C]

OnDone sets the transition target when the child machine reaches a final state

func (*MachineInvokeBuilder[C]) OnDoneAction added in v0.14.0

func (b *MachineInvokeBuilder[C]) OnDoneAction(action ActionType) *MachineInvokeBuilder[C]

OnDoneAction sets an action to execute when the child machine completes

func (*MachineInvokeBuilder[C]) OnError added in v0.14.0

func (b *MachineInvokeBuilder[C]) OnError(target StateID) *MachineInvokeBuilder[C]

OnError sets the transition target when the child machine encounters an error

func (*MachineInvokeBuilder[C]) OnErrorAction added in v0.14.0

func (b *MachineInvokeBuilder[C]) OnErrorAction(action ActionType) *MachineInvokeBuilder[C]

OnErrorAction sets an action to execute when the child machine fails

type MachineSnapshot added in v0.8.0

type MachineSnapshot[C any] struct {
	// Current state value
	StateValue ir.StateID `json:"state_value"`

	// Full state path for hierarchical states
	StatePath []ir.StateID `json:"state_path,omitempty"`

	// Machine context
	Context C `json:"context"`

	// History memory for shallow history states
	HistoryShallow map[ir.StateID]ir.StateID `json:"history_shallow,omitempty"`

	// History memory for deep history states
	HistoryDeep map[ir.StateID]ir.StateID `json:"history_deep,omitempty"`

	// Parallel region states
	ActiveInParallel map[ir.StateID]ir.StateID `json:"active_in_parallel,omitempty"`

	// Current parallel state (if any)
	CurrentParallel ir.StateID `json:"current_parallel,omitempty"`

	// Timestamp when snapshot was taken
	Timestamp time.Time `json:"timestamp"`
}

MachineSnapshot captures the complete state of an interpreter for persistence.

type MembershipEvent added in v0.8.0

type MembershipEvent struct {
	Type MembershipEventType
	Node ClusterNode
}

MembershipEvent represents a cluster membership change.

type MembershipEventType added in v0.8.0

type MembershipEventType int

MembershipEventType indicates the type of membership change.

const (
	// MemberJoined indicates a new member joined.
	MemberJoined MembershipEventType = iota
	// MemberLeft indicates a member left gracefully.
	MemberLeft
	// MemberFailed indicates a member failed (no heartbeat).
	MemberFailed
)

type MemoryEventStore added in v0.8.0

type MemoryEventStore struct {
	// contains filtered or unexported fields
}

MemoryEventStore is an in-memory implementation of EventStore for testing.

func NewMemoryEventStore added in v0.8.0

func NewMemoryEventStore() *MemoryEventStore

NewMemoryEventStore creates a new in-memory event store.

func (*MemoryEventStore) AppendEvents added in v0.8.0

func (s *MemoryEventStore) AppendEvents(ctx context.Context, streamID string, expectedVersion int, events []PersistedEvent) error

AppendEvents atomically appends events to a stream.

func (*MemoryEventStore) GetStreamVersion added in v0.8.0

func (s *MemoryEventStore) GetStreamVersion(ctx context.Context, streamID string) (int, error)

GetStreamVersion returns the current version of a stream.

func (*MemoryEventStore) LoadEvents added in v0.8.0

func (s *MemoryEventStore) LoadEvents(ctx context.Context, streamID string, fromVersion int) ([]PersistedEvent, error)

LoadEvents loads all events for a stream from a specific version.

type MemorySnapshotStore added in v0.8.0

type MemorySnapshotStore[C any] struct {
	// contains filtered or unexported fields
}

MemorySnapshotStore is an in-memory implementation of SnapshotStore for testing.

func NewMemorySnapshotStore added in v0.8.0

func NewMemorySnapshotStore[C any]() *MemorySnapshotStore[C]

NewMemorySnapshotStore creates a new in-memory snapshot store.

func (*MemorySnapshotStore[C]) LoadSnapshot added in v0.8.0

func (s *MemorySnapshotStore[C]) LoadSnapshot(ctx context.Context, streamID string, maxVersion int) (*MachineSnapshot[C], int, error)

LoadSnapshot loads the latest snapshot at or before maxVersion.

func (*MemorySnapshotStore[C]) SaveSnapshot added in v0.8.0

func (s *MemorySnapshotStore[C]) SaveSnapshot(ctx context.Context, streamID string, version int, snapshot *MachineSnapshot[C]) error

SaveSnapshot saves a state snapshot at the given version.

type MemoryStreamLock added in v0.8.0

type MemoryStreamLock struct {
	// contains filtered or unexported fields
}

MemoryStreamLock is an in-memory implementation of StreamLock for testing. In production, use Redis, etcd, or PostgreSQL advisory locks.

func NewMemoryStreamLock added in v0.8.0

func NewMemoryStreamLock() *MemoryStreamLock

NewMemoryStreamLock creates a new in-memory stream lock.

func (*MemoryStreamLock) Acquire added in v0.8.0

func (s *MemoryStreamLock) Acquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)

Acquire blocks until the lock is acquired or context is cancelled.

func (*MemoryStreamLock) TryAcquire added in v0.8.0

func (s *MemoryStreamLock) TryAcquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)

TryAcquire attempts to acquire a lock without blocking.

type PendingTimer added in v0.5.0

type PendingTimer struct {
	// StateID is the state that owns this delayed transition
	StateID StateID `json:"state_id"`

	// TransitionIndex identifies which transition in the state
	TransitionIndex int `json:"transition_index"`

	// Target is the destination state
	Target StateID `json:"target"`

	// Remaining is how much time is left until the timer fires
	Remaining time.Duration `json:"remaining_ns"`
}

PendingTimer represents an active delayed transition that hasn't fired yet.

type PersistedEvent added in v0.8.0

type PersistedEvent struct {
	// ID is a unique identifier for this event (e.g., UUID)
	ID string `json:"id"`

	// StreamID identifies the aggregate/stream this event belongs to
	StreamID string `json:"stream_id"`

	// Type is the event type that triggered the transition
	Type EventType `json:"type"`

	// Version is the stream version (monotonically increasing per stream)
	Version int `json:"version"`

	// Timestamp when the event was recorded
	Timestamp time.Time `json:"timestamp"`

	// Payload contains serialized event data
	Payload json.RawMessage `json:"payload,omitempty"`

	// Metadata holds additional context (correlation ID, user ID, etc.)
	Metadata map[string]any `json:"metadata,omitempty"`

	// StateAfter is the state after processing this event
	StateAfter ir.StateID `json:"state_after"`
}

PersistedEvent represents a stored event in the event store.

type PersistentInterpreter added in v0.8.0

type PersistentInterpreter[C any] struct {
	// contains filtered or unexported fields
}

PersistentInterpreter wraps an Interpreter with event sourcing capabilities. All state transitions are persisted to an EventStore and can be replayed.

func NewPersistentInterpreter added in v0.8.0

func NewPersistentInterpreter[C any](
	ctx context.Context,
	streamID string,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	opts ...PersistentInterpreterOption[C],
) (*PersistentInterpreter[C], error)

NewPersistentInterpreter creates a new persistent interpreter. It automatically hydrates state from the event store if events exist.

func (*PersistentInterpreter[C]) Commit added in v0.8.0

func (pi *PersistentInterpreter[C]) Commit(ctx context.Context) (int, error)

Commit persists all uncommitted events to the event store. Returns the number of events committed.

func (*PersistentInterpreter[C]) Context added in v0.8.0

func (pi *PersistentInterpreter[C]) Context() C

Context returns a copy of the current context.

func (*PersistentInterpreter[C]) Done added in v0.8.0

func (pi *PersistentInterpreter[C]) Done() bool

Done returns true if the interpreter is in a final state.

func (*PersistentInterpreter[C]) ForceSnapshot added in v0.8.0

func (pi *PersistentInterpreter[C]) ForceSnapshot(ctx context.Context) error

ForceSnapshot creates a snapshot regardless of the configured strategy.

func (*PersistentInterpreter[C]) Matches added in v0.8.0

func (pi *PersistentInterpreter[C]) Matches(stateID StateID) bool

Matches checks if the current state matches or is a descendant of the given state.

func (*PersistentInterpreter[C]) Rollback added in v0.8.0

func (pi *PersistentInterpreter[C]) Rollback()

Rollback discards all uncommitted events.

func (*PersistentInterpreter[C]) Send added in v0.8.0

func (pi *PersistentInterpreter[C]) Send(event Event)

Send processes an event and records it for persistence. Events are recorded if they match a transition (including self-transitions). Events that don't match any transition are not recorded. Call Commit() to persist uncommitted events.

func (*PersistentInterpreter[C]) SendAll added in v0.8.0

func (pi *PersistentInterpreter[C]) SendAll(events []Event)

SendAll processes multiple events.

func (*PersistentInterpreter[C]) State added in v0.8.0

func (pi *PersistentInterpreter[C]) State() State[C]

State returns the current state.

func (*PersistentInterpreter[C]) Stop added in v0.8.0

func (pi *PersistentInterpreter[C]) Stop()

Stop stops the underlying interpreter.

func (*PersistentInterpreter[C]) StreamID added in v0.8.0

func (pi *PersistentInterpreter[C]) StreamID() string

StreamID returns the stream identifier.

func (*PersistentInterpreter[C]) UncommittedCount added in v0.8.0

func (pi *PersistentInterpreter[C]) UncommittedCount() int

UncommittedCount returns the number of uncommitted events.

func (*PersistentInterpreter[C]) Version added in v0.8.0

func (pi *PersistentInterpreter[C]) Version() int

Version returns the current stream version.

type PersistentInterpreterOption added in v0.8.0

type PersistentInterpreterOption[C any] func(*PersistentInterpreter[C])

PersistentInterpreterOption configures a PersistentInterpreter.

func WithSnapshotConfig added in v0.8.0

func WithSnapshotConfig[C any](config SnapshotConfig) PersistentInterpreterOption[C]

WithSnapshotConfig sets the snapshot configuration.

func WithSnapshotStore added in v0.8.0

func WithSnapshotStore[C any](store SnapshotStore[C]) PersistentInterpreterOption[C]

WithSnapshotStore sets the snapshot store for the interpreter.

type RegionBuilder added in v0.4.0

type RegionBuilder[C any] struct {
	// contains filtered or unexported fields
}

RegionBuilder provides a fluent API for constructing parallel regions (v2.0)

func (*RegionBuilder[C]) EndRegion added in v0.4.0

func (b *RegionBuilder[C]) EndRegion() *StateBuilder[C]

EndRegion completes the region and returns to the parent parallel state

func (*RegionBuilder[C]) State added in v0.4.0

func (b *RegionBuilder[C]) State(id StateID) *StateBuilder[C]

State starts building a state within this region

func (*RegionBuilder[C]) WithInitial added in v0.4.0

func (b *RegionBuilder[C]) WithInitial(initial StateID) *RegionBuilder[C]

WithInitial sets the initial state for this region

type RestoreError added in v0.5.0

type RestoreError struct {
	Code    string
	Message string
}

RestoreError represents an error during snapshot restoration.

func (*RestoreError) Error added in v0.5.0

func (e *RestoreError) Error() string

type Service added in v0.5.0

type Service[C any] = ir.Service[C]

Service is an async operation invoked when entering a state. It runs in a goroutine and can send events back to the machine. The service should respect the context for cancellation.

type ServiceContext added in v0.5.0

type ServiceContext[C any] = ir.ServiceContext[C]

ServiceContext provides the execution context for a service (v3.0)

type ServiceType added in v0.5.0

type ServiceType = ir.ServiceType

ServiceType identifies a named service (v3.0)

type Snapshot added in v0.5.0

type Snapshot[C any] struct {
	// MachineID identifies which machine type this snapshot belongs to
	MachineID string `json:"machine_id"`

	// Version is the machine version for compatibility checking
	Version string `json:"version,omitempty"`

	// CurrentState is the active state ID (leaf state, or parallel state ID)
	CurrentState StateID `json:"current_state"`

	// Context is the user-defined context data
	Context C `json:"context"`

	// ShallowHistory maps compound state IDs to their last active child
	ShallowHistory map[StateID]StateID `json:"shallow_history,omitempty"`

	// DeepHistory maps compound state IDs to their last active leaf
	DeepHistory map[StateID]StateID `json:"deep_history,omitempty"`

	// ActiveInParallel maps region IDs to their current leaf states
	// Only populated when snapshot was taken from a parallel state
	ActiveInParallel map[StateID]StateID `json:"active_in_parallel,omitempty"`

	// CurrentParallel holds the parallel state ID if currently in a parallel state
	CurrentParallel StateID `json:"current_parallel,omitempty"`

	// PendingTimers captures active delayed transitions
	PendingTimers []PendingTimer `json:"pending_timers,omitempty"`

	// SpawnedActors captures metadata about spawned child actors (v0.14)
	// Note: Actors are NOT automatically restored. This metadata allows
	// the application to manually respawn actors if needed.
	SpawnedActors []ActorMetadata `json:"spawned_actors,omitempty"`

	// CreatedAt is when the snapshot was taken
	CreatedAt time.Time `json:"created_at"`
}

Snapshot captures the complete state of an interpreter for persistence. It can be serialized to JSON and later used to restore an interpreter to the exact same state.

func (Snapshot[C]) MarshalJSON added in v0.5.0

func (s Snapshot[C]) MarshalJSON() ([]byte, error)

MarshalJSON serializes the snapshot to JSON.

func (*Snapshot[C]) UnmarshalJSON added in v0.5.0

func (s *Snapshot[C]) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes the snapshot from JSON.

type SnapshotConfig added in v0.8.0

type SnapshotConfig struct {
	// Strategy determines when to create snapshots
	Strategy SnapshotStrategy

	// Interval is the number of events between snapshots (for SnapshotByInterval)
	Interval int

	// TimeInterval is the duration between snapshots (for SnapshotByTime)
	TimeInterval time.Duration
}

SnapshotConfig configures snapshot behavior.

type SnapshotStore added in v0.8.0

type SnapshotStore[C any] interface {
	// SaveSnapshot saves a state snapshot at the given version.
	SaveSnapshot(ctx context.Context, streamID string, version int, snapshot *MachineSnapshot[C]) error

	// LoadSnapshot loads the latest snapshot at or before maxVersion.
	// Returns nil if no snapshot exists.
	LoadSnapshot(ctx context.Context, streamID string, maxVersion int) (*MachineSnapshot[C], int, error)
}

SnapshotStore defines the interface for state snapshot persistence. Snapshots enable faster state reconstruction by avoiding full event replay.

type SnapshotStrategy added in v0.8.0

type SnapshotStrategy int

SnapshotStrategy determines when snapshots are created.

const (
	// SnapshotNever disables automatic snapshots
	SnapshotNever SnapshotStrategy = iota

	// SnapshotByInterval creates snapshots every N events
	SnapshotByInterval

	// SnapshotOnFinal creates a snapshot when reaching a final state
	SnapshotOnFinal

	// SnapshotByTime creates snapshots after a time interval
	SnapshotByTime
)

type SpawnOption added in v0.8.0

type SpawnOption func(*spawnOptions)

SpawnOption configures actor spawning behavior

func WithAutoForward added in v0.8.0

func WithAutoForward(events ...EventType) SpawnOption

WithAutoForward configures events to automatically forward to the child

func WithOnDone added in v0.8.0

func WithOnDone(target StateID) SpawnOption

WithOnDone sets the target state when the child reaches a final state

func WithOnError added in v0.8.0

func WithOnError(target StateID) SpawnOption

WithOnError sets the target state when the child encounters an error

func WithSupervision added in v0.8.0

func WithSupervision(s SupervisionStrategy) SpawnOption

WithSupervision sets the supervision strategy for the spawned actor

type State

type State[C any] struct {
	Value   StateID // Current state ID (leaf state, or parallel state when in parallel)
	Context C       // Current context

	// Parallel state tracking (v2.0)
	// When inside a parallel state, maps region ID to its current leaf state
	// Empty when not in a parallel state
	ActiveInParallel map[StateID]StateID
}

State represents the current runtime state of an interpreter

func (State[C]) Matches

func (s State[C]) Matches(id StateID) bool

Matches checks if the current state matches the given state ID For parallel states, also checks if any region's current state matches

type StateBuilder

type StateBuilder[C any] struct {
	// contains filtered or unexported fields
}

StateBuilder provides a fluent API for constructing states

func (*StateBuilder[C]) After added in v0.4.0

func (b *StateBuilder[C]) After(d time.Duration) *TransitionBuilder[C]

After starts building a delayed transition that triggers automatically after the specified duration (v2.0)

func (*StateBuilder[C]) Done

func (b *StateBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the parent builder For nested states, returns to the parent StateBuilder For root states, returns to the MachineBuilder

func (*StateBuilder[C]) End

func (b *StateBuilder[C]) End() *StateBuilder[C]

End completes a nested state and returns to the parent StateBuilder Use this instead of Done() when building nested states

func (*StateBuilder[C]) EndState added in v0.4.0

func (b *StateBuilder[C]) EndState() *RegionBuilder[C]

EndState completes a state within a region and returns to the RegionBuilder (v2.0) Use this instead of End() when building states inside parallel regions

func (*StateBuilder[C]) Final

func (b *StateBuilder[C]) Final() *StateBuilder[C]

Final marks this state as a final state

func (*StateBuilder[C]) History added in v0.4.0

func (b *StateBuilder[C]) History(id StateID) *HistoryBuilder[C]

History starts building a history state within this compound state (v2.0) History states remember the last active child and transition back to it

func (*StateBuilder[C]) Invoke added in v0.5.0

func (b *StateBuilder[C]) Invoke(src ServiceType) *InvokeBuilder[C]

Invoke starts building a service invocation for this state (v3.0) The service is started when entering the state and cancelled when exiting

func (*StateBuilder[C]) InvokeMachine added in v0.14.0

func (b *StateBuilder[C]) InvokeMachine(machineRef string) *MachineInvokeBuilder[C]

InvokeMachine starts building a child machine invocation for this state (v0.14). The child machine is spawned when entering the state and stopped when exiting. The machineRef must match a name registered with WithChildMachine.

func (*StateBuilder[C]) On

func (b *StateBuilder[C]) On(event EventType) *TransitionBuilder[C]

On starts building a new transition triggered by the given event

func (*StateBuilder[C]) OnEntry

func (b *StateBuilder[C]) OnEntry(action ActionType) *StateBuilder[C]

OnEntry adds an entry action to the state

func (*StateBuilder[C]) OnExit

func (b *StateBuilder[C]) OnExit(action ActionType) *StateBuilder[C]

OnExit adds an exit action to the state

func (*StateBuilder[C]) Parallel added in v0.4.0

func (b *StateBuilder[C]) Parallel() *StateBuilder[C]

Parallel marks this state as a parallel state (v2.0) Use Region() to add orthogonal regions that execute simultaneously

func (*StateBuilder[C]) Region added in v0.4.0

func (b *StateBuilder[C]) Region(id StateID) *RegionBuilder[C]

Region starts building a new region within this parallel state (v2.0)

func (*StateBuilder[C]) State

func (b *StateBuilder[C]) State(id StateID) *StateBuilder[C]

State starts building a nested child state

func (*StateBuilder[C]) WithInitial

func (b *StateBuilder[C]) WithInitial(initial StateID) *StateBuilder[C]

WithInitial sets the initial child state for a compound state

type StateID

type StateID = ir.StateID

StateID uniquely identifies a state within a machine

type StateNode

type StateNode struct{}

StateNode is a marker type for defining atomic states in the reflection DSL.

Use struct tags to configure the state:

  • on:"EVENT->target" - Define a transition (can specify multiple with comma)
  • on:"EVENT->target:guard" - Transition with guard condition
  • on:"EVENT->target/action1;action2" - Transition with actions
  • on:"EVENT->target/action:guard" - Transition with action and guard
  • entry:"action1,action2" - Entry actions
  • exit:"action1,action2" - Exit actions

Example:

Idle statekit.StateNode `on:"START->running:canStart" entry:"logIdle"`

type StateType

type StateType = ir.StateType

StateType represents the kind of state node

type StreamLock added in v0.8.0

type StreamLock interface {
	// Acquire attempts to acquire a lock for the given stream.
	// Returns a Lock handle if successful, or an error if the lock is held by another node.
	// The lock should automatically expire after ttl if not renewed.
	Acquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)

	// TryAcquire attempts to acquire a lock without blocking.
	// Returns ErrLockHeld if the lock is already held by another node.
	TryAcquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)
}

StreamLock provides distributed locking for state machine streams. Implementations can use Redis, etcd, PostgreSQL advisory locks, etc.

type StreamRouter added in v0.8.0

type StreamRouter interface {
	// RouteStream returns the node ID that should handle the stream.
	RouteStream(streamID string, members []ClusterNode) string

	// IsLocal returns true if this node should handle the stream.
	IsLocal(streamID string, members []ClusterNode, localNodeID string) bool
}

StreamRouter determines which node should handle a given stream. This enables consistent hashing for stream distribution.

type SupervisionStrategy added in v0.8.0

type SupervisionStrategy int

SupervisionStrategy defines how parent handles child actor errors

const (
	// SupervisionEscalate bubbles the error to the parent via xstate.error.actor.<id> event
	SupervisionEscalate SupervisionStrategy = iota
	// SupervisionRecover logs the error and continues without stopping the child
	SupervisionRecover
	// SupervisionRestart stops the child and restarts it with initial state
	SupervisionRestart
	// SupervisionStop stops the child silently without generating an error event
	SupervisionStop
)

type TransitionBuilder

type TransitionBuilder[C any] struct {
	// contains filtered or unexported fields
}

TransitionBuilder provides a fluent API for constructing transitions

func (*TransitionBuilder[C]) After added in v0.4.0

func (b *TransitionBuilder[C]) After(d time.Duration) *TransitionBuilder[C]

After starts a new delayed transition on the same state (chainable) (v2.0)

func (*TransitionBuilder[C]) Do

func (b *TransitionBuilder[C]) Do(action ActionType) *TransitionBuilder[C]

Do adds an action to be executed during the transition

func (*TransitionBuilder[C]) Done

func (b *TransitionBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the machine builder

func (*TransitionBuilder[C]) End

func (b *TransitionBuilder[C]) End() *StateBuilder[C]

End completes the transition and returns to the parent StateBuilder Use this instead of Done() when building transitions in nested states

func (*TransitionBuilder[C]) EndState added in v0.4.0

func (b *TransitionBuilder[C]) EndState() *RegionBuilder[C]

EndState completes the transition and returns to the RegionBuilder (v2.0) Use this when building transitions in states inside parallel regions

func (*TransitionBuilder[C]) Guard

func (b *TransitionBuilder[C]) Guard(guard GuardType) *TransitionBuilder[C]

Guard sets the guard condition for the transition

func (*TransitionBuilder[C]) On

func (b *TransitionBuilder[C]) On(event EventType) *TransitionBuilder[C]

On starts a new transition on the same state (chainable)

func (*TransitionBuilder[C]) Target

func (b *TransitionBuilder[C]) Target(target StateID) *TransitionBuilder[C]

Target sets the target state for the transition

Directories

Path Synopsis
cmd
statekit command
Package main provides the statekit CLI tool for visualizing state machines.
Package main provides the statekit CLI tool for visualizing state machines.
statekit-mcp command
statekit/commands
Package commands provides CLI commands for the statekit tool.
Package commands provides CLI commands for the statekit tool.
Package debug provides utilities for debugging and inspecting statekit state machines.
Package debug provides utilities for debugging and inspecting statekit state machines.
examples
actor_supervisor command
Package main demonstrates the actor model features of statekit.
Package main demonstrates the actor model features of statekit.
form_wizard command
Package main demonstrates history states with a multi-step form wizard.
Package main demonstrates history states with a multi-step form wizard.
game_save command
Package main demonstrates persistence with snapshots for a game save system.
Package main demonstrates persistence with snapshots for a game save system.
incident_lifecycle command
Package main demonstrates an SRE incident lifecycle using hierarchical states.
Package main demonstrates an SRE incident lifecycle using hierarchical states.
logging_plugin command
Package main demonstrates the plugin system for extending interpreter behavior.
Package main demonstrates the plugin system for extending interpreter behavior.
order_workflow command
Package main demonstrates an e-commerce order workflow using the reflection DSL.
Package main demonstrates an e-commerce order workflow using the reflection DSL.
pedestrian_light
Package pedestrianlight implements a pedestrian crossing signal with hierarchical states.
Package pedestrianlight implements a pedestrian crossing signal with hierarchical states.
session_timeout command
Package main demonstrates delayed (timed) transitions with a session timeout.
Package main demonstrates delayed (timed) transitions with a session timeout.
text_editor command
Package main demonstrates parallel (orthogonal) states with a text editor.
Package main demonstrates parallel (orthogonal) states with a text editor.
Package generate provides Go code generation from Statekit Native JSON definitions.
Package generate provides Go code generation from Statekit Native JSON definitions.
Package health provides liveness and readiness probes for statekit state machines.
Package health provides liveness and readiness probes for statekit state machines.
Package http provides HTTP middleware and handlers for statekit state machines.
Package http provides HTTP middleware and handlers for statekit state machines.
internal
ir
parser
Package parser provides reflection-based parsing for struct-defined state machines.
Package parser provides reflection-based parsing for struct-defined state machines.
Package lint provides static analysis for statekit state machines.
Package lint provides static analysis for statekit state machines.
Package mcp provides an MCP (Model Context Protocol) server for creating, managing, and visualizing statekit state machines.
Package mcp provides an MCP (Model Context Protocol) server for creating, managing, and visualizing statekit state machines.
Package metrics provides Prometheus metrics integration for statekit state machines.
Package metrics provides Prometheus metrics integration for statekit state machines.
Package otel provides OpenTelemetry tracing integration for statekit state machines.
Package otel provides OpenTelemetry tracing integration for statekit state machines.
Package plugin provides a hook system for extending interpreter behavior.
Package plugin provides a hook system for extending interpreter behavior.
Package statetest provides utilities for testing statekit state machines.
Package statetest provides utilities for testing statekit state machines.
viz
Package viz provides visualization models and renderers for state machines.
Package viz provides visualization models and renderers for state machines.
ascii
Package ascii provides ASCII/Unicode box diagram rendering for state machines.
Package ascii provides ASCII/Unicode box diagram rendering for state machines.
goparser
Package goparser provides Go source code parsing to extract state machine definitions.
Package goparser provides Go source code parsing to extract state machine definitions.
mermaid
Package mermaid provides Mermaid stateDiagram-v2 rendering for state machines.
Package mermaid provides Mermaid stateDiagram-v2 rendering for state machines.
tui
Package tui provides an interactive terminal UI for visualizing state machines.
Package tui provides an interactive terminal UI for visualizing state machines.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL