statecharts

package module
v0.0.0-...-d97f8e1 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 26 Imported by: 0

README

statecharts

statecharts is a Go-first statechart runtime with precise hierarchical, parallel, history, event-queue, and invocation semantics. Charts are ordinary Go definitions. Their behavior is supplied by typed, explicitly named Go functions, so complete definitions remain deterministic, inspectable, and portable.

The runtime follows the W3C SCXML interpretation semantics, but XML is not the authoring model. XML, JSON, a visual editor, or any other syntax can be a surface over the same syntax-neutral Definition.

Installation

go get github.com/dhamidi/statecharts

Quickstart

Create a typed model, name its behavior, build a chart, and start an instance:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/dhamidi/statecharts"
)

type Door struct {
	OpenCount int
}

func main() {
	door := statecharts.New("door", func() *Door { return &Door{} }, statecharts.Version("v1"))
	recordOpen := door.Action(
		"record-open",
		func(door *Door, _ statecharts.ExecContext, _ []statecharts.Value) error {
			door.OpenCount++
			return nil
		},
	)

	chart, err := door.Build(
		statecharts.Compound("door", "closed",
			statecharts.Children(
				statecharts.Atomic("closed",
					statecharts.On("open", statecharts.Target("opened"), statecharts.Then(recordOpen.Do())),
				),
				statecharts.Atomic("opened",
					statecharts.On("close", statecharts.Target("closed")),
				),
			),
		),
	)
	if err != nil {
		log.Fatal(err)
	}

	instance, err := chart.NewInstance()
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	if err := instance.Start(ctx); err != nil {
		log.Fatal(err)
	}
	defer instance.Stop(ctx)

	if err := instance.Send(ctx, statecharts.Event{Name: "open", Type: statecharts.EventExternal}); err != nil {
		log.Fatal(err)
	}
	fmt.Println(instance.Configuration()) // [opened]
}

Each Instance owns a fresh Door from the model factory and processes events serially on one goroutine. Send returns after the event's complete macrostep, so Configuration is current immediately afterward.

Core model

Definitions and charts

The builder constructs a mutable Definition; Build validates it and compiles it with a Datamodel into an immutable Chart:

  • Atomic creates a leaf state.
  • Compound creates a state with one active child.
  • Parallel creates simultaneously active regions.
  • Final marks completion of its parent.
  • History remembers a shallow or deep prior configuration.
  • On and Eventless add transitions.
  • Target, If, Then, and AsInternal configure transitions.
  • OnEntry, OnExit, Invoke, and the executable constructors add behavior.

Charts are safe to share. Every call to Chart.NewInstance creates an isolated datamodel session and interpreter.

State and event IDs use Identifier. Dots express hierarchy in application names, for example orders.invoice-42; they do not imply a network location.

Go authoring and stable function references

New is the default authoring path. It combines a factory for ordinary Go state with a model-scoped behavior registry, qualifies short behavior names under the chart ID, and reports any registration errors from Build:

type Job struct {
	Attempts int
}

job := statecharts.New("job", func() *Job { return &Job{} }, statecharts.Version("v1"))

retry := job.Condition(
	"can-retry",
	func(job *Job, _ statecharts.ExecContext, args []statecharts.Value) (bool, error) {
		limit, ok := args[0].AsInt64()
		return ok && int64(job.Attempts) < limit, nil
	},
)

record := job.Action(
	"record-attempt",
	func(job *Job, _ statecharts.ExecContext, args []statecharts.Value) error {
		amount, _ := args[0].AsInt64()
		job.Attempts += int(amount)
		return nil
	},
)

working := statecharts.Atomic("working",
	statecharts.On("failed",
		statecharts.Target("working"),
		statecharts.If(retry.If(statecharts.GoLiteral(statecharts.Int64Value(3)))),
		statecharts.Then(record.Do(statecharts.GoLiteral(statecharts.Int64Value(1)))),
	),
)

chart, err := job.Build(working)

The definition stores the names, versions, and canonical arguments, not Go function values. Compilation resolves those references against the supplied registry. record-attempt is stored as job.record-attempt, and both behaviors use the builder's v1 version. This gives application authors a simple convention:

  • give each behavior a short, descriptive name within its chart;
  • bump Version when the chart's Go behavior changes;
  • use ActionVersion, ConditionVersion, ValueVersion, or LocationVersion to retain an older implementation while revision-pinned actors still use it. Keep every version referenced by a retained definition registered until those actors have terminated.

Registries are deliberately model scoped. There is no global concrete-type or function registry, and generated closure names never enter definitions.

NewGoModel and the package-level Build remain available when direct registry control is useful. Custom datamodels always use package-level Build(root, model).

Besides Action and Condition, a builder can register a Value producer or a readable/writable Location. References accept expression arguments, so a single registration can be reused at many definition sites without captured parameter closures. GoLiteral supplies canonical constants and GoData addresses declared definition data.

Optional ECMAScript datamodel

Applications that need runtime-editable source expressions can opt into the pure-Go ModernC QuickJS integration without changing the interpreter:

model, err := ecmascript.New(
	ecmascript.WithEvaluationTimeout(100 * time.Millisecond),
	ecmascript.WithMemoryLimit(64 << 20),
)
initial, err := ecmascript.Source("0")
condition, err := ecmascript.Source("count < 3")

chart, err := statecharts.Build(
	statecharts.Compound("root", "working", statecharts.Children(
		statecharts.Atomic("working", statecharts.Eventless(
			statecharts.If(condition), statecharts.Target("done"),
		)),
		statecharts.Final("done"),
	)),
	model,
	statecharts.WithData(statecharts.DataDefinition{ID: "count", Expr: &initial}),
)

Each instance owns an isolated VM. System bindings such as _event, _sessionid, _name, _ioprocessors, _x, and In() are refreshed for each evaluation and cannot be overwritten. Synchronously queued Promise jobs drain within the same interpreter turn; no browser, filesystem, timer, or network APIs are installed.

Declared data is the only VM state stored in snapshots. Cycles, functions, accessors, unsupported objects, and undeclared global mutations make the snapshot cache unavailable instead of being silently discarded; durable log replay remains authoritative. Exact integers outside JavaScript's safe range cross as BigInt. Decimal values that cannot round-trip through an ECMAScript Number are rejected rather than rounded. Configure finite evaluation, memory, and stack limits for runtime-edited or otherwise untrusted source.

Canonical values

Events and definition operands carry Value, a closed, deterministic value model: null, boolean, UTF-8 string, exact decimal number, list, string-keyed map, or tagged application value. It has no process-global type registration.

name, err := statecharts.StringValue("invoice-42")
if err != nil {
	// invalid UTF-8
}
payload, err := statecharts.TaggedValue("billing.invoice/v1", name)

event := statecharts.Event{
	Name: "invoice.open",
	Type: statecharts.EventExternal,
	Data: payload,
}

Use tagged values for application-level payload identities. MapValue, ListValue, ValueFromJSON, and typed As... accessors handle nested data. Numbers are exact and canonically encoded; use AsInt64 for integer consumers rather than parsing AsNumber, because canonical decimal text may use an exponent.

Instances and execution

An instance is driven with Start, Send, Configuration, Snapshot, Stop, and Wait. Registered functions receive an ExecContext, which exposes the current event, active-state checks, session metadata, logging, and controlled Raise, Send, and Cancel operations.

Raise enqueues an internal event in the current macrostep. A registered function must not call blocking methods on its own Instance; that instance's single goroutine is already executing the function.

An action or condition error becomes a tagged error.execution platform event. An outbound dispatch failure becomes error.communication. Charts can handle both with ordinary transitions, and PlatformErrorDetails extracts the stable classification and message.

Outbound effects and IOProcessor

All event delivery outside the current chart crosses an IOProcessor. Executable Send and ExecContext.Send select a processor type, target, payload, and optional delay. This is the seam for local routing, HTTP or queue transports, authorization, tracing, and test doubles; the core library does not prescribe a network topology or transport.

notify := statecharts.Send(
	"invoice.ready",
	statecharts.SendTarget("notifications"),
	statecharts.SendType("queue"),
	statecharts.SendDelay(2*time.Second),
)

instance, err := chart.NewInstance(
	statecharts.WithIOProcessor("queue", queueProcessor),
)

The default processor handles local interpreter targets. Custom processor types are explicit and isolated. Processors that implement IOProcessorDescriber can expose their type and location through ExecContext.IOProcessors.

Invoked services

Definitions declare an invocation by handler type and source. Runtime capabilities arrive later through an InvokeHandler factory:

request, err := model.Value(
	"billing.invoice.render-request", "v1",
	func(job *Job, _ statecharts.ExecContext, _ []statecharts.Value) (statecharts.Value, error) {
		return statecharts.Int64Value(int64(job.Attempts)), nil
	},
)

rendering := statecharts.Atomic("rendering",
	statecharts.Invoke(
		"renderer", "invoice-pdf",
		statecharts.WithInvokeID("render"),
		statecharts.WithInvokeContent(request.Get()),
	),
	statecharts.On("done.invoke.render", statecharts.Target("ready")),
)

instance, err := chart.NewInstance(
	statecharts.WithInvokeHandler("renderer", func() statecharts.InvokeHandler {
		return rendererHandler{Client: httpClient}
	}),
)

The definition contains only renderer, invoice-pdf, and the stable value reference. Clients, channels, credentials, and other capabilities stay in the runtime handler. A handler receives a context that is cancelled when its state exits and an InvokeIO for delivering events or receiving forwarded events. Its successful result becomes done.invoke.<id>; failures become error.communication.

Durable invoked work can implement ResumableInvokeHandler. During rehydrate, the runtime resumes active invocations through that interface; a handler that cannot resume receives an explicit communication error rather than silently pretending work is still running.

Inspecting, editing, and recompiling definitions

Go is the primary authoring surface, not the storage format. Chart.Definition returns an independently editable, normalized copy. The complete definition can be encoded, decoded, inspected, changed, and recompiled against the same application registry:

definition := chart.Definition()

wire, err := statejson.MarshalIndent(definition, "", "  ")
if err != nil {
	return err
}
if err := os.WriteFile("door.json", wire, 0o644); err != nil {
	return err
}

// Later, after an operator or deployment tool edits the file:
wire, err = os.ReadFile("door.json")
if err != nil {
	return err
}
edited, err := statejson.Unmarshal(wire)
if err != nil {
	return err
}
nextChart, err := statecharts.Compile(edited, model)
if err != nil {
	return err // structural and model-reference errors are reported here
}

The same pattern supports a running program's inspection endpoint:

func definitionHandler(chart *statecharts.Chart) http.HandlerFunc {
	return func(w http.ResponseWriter, _ *http.Request) {
		data, err := statejson.MarshalIndent(chart.Definition(), "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write(data)
	}
}

Here statejson is the optional github.com/dhamidi/statecharts/syntax/json package. JSON is one editable surface syntax, not the canonical in-memory program or revision encoding. The durable counters example serves GET /definitions/counter, validates and publishes edited complete definitions, and recompiles its own encoded definition during startup. The ai-agent example performs an equivalent encode/decode/recompile path for every chart family before returning a chart, so its behavioral tests run against transported definitions.

For standards-based interchange, the optional github.com/dhamidi/statecharts/syntax/scxml package maps the same complete Definition to and from SCXML. Expressions remain owned by the selected datamodel, so callers provide that model's text codec rather than asking the XML adapter to interpret program text:

wire, err := scxml.MarshalIndent(
	chart.Definition(),
	"  ",
	scxml.WithTextExpressionCodec(model.TextExpressionCodec()),
)
edited, err := scxml.Unmarshal(
	wire,
	scxml.WithTextExpressionCodec(model.TextExpressionCodec()),
)
nextChart, err := statecharts.Compile(edited, model)

GoModel.TextExpressionCodec stores stable function names, versions, and canonical arguments as editable expression text; it never serializes function pointers. The optional ECMAScript model's TextExpressionCodec preserves source text verbatim. Encoding fails at the exact definition path if the chosen codec cannot faithfully represent an expression. Unknown executable XML is rejected, while explicit ExtensionDefinition nodes preserve their namespace, name, and canonical payload.

Compilation creates a new immutable chart; it never mutates an existing one. actors.System.Publish provides Erlang-style deployment: existing actors stay pinned to their old chart while newly spawned actors select the new current revision. The counters example documents the complete export → edit → validate → publish → canary loop; its dashboard cards and actor-inspection endpoint make the corresponding revision pins visible.

Definitions are syntax neutral. JSON is convenient for transport today, but a different text syntax or editor only needs to produce the same Definition. Likewise, another expression model can implement Datamodel, DatamodelProgram, and DatamodelSession without changing the interpreter.

Persistence

Instance.Snapshot captures a point-in-time cache: model bytes, active configuration, history, queues, pending sends, and invocation bookkeeping. The model owns encoding of its state; GoModel uses encoding/json by default or an application-supplied GoSnapshotCodec.

Snapshots are transparent caches, not authoritative user data. The chart's revision is derived from its canonical definition and referenced function versions. When the revision no longer matches, rehydration ignores the stale snapshot and replays the event reducer from the log. Applications should not write snapshot migration code.

Chart.Revision() returns that identity as sha256:<64 lowercase hex digits>. Revision envelope version 1 hashes length-prefixed canonical definition bytes, the datamodel name, and the datamodel program's deterministic fingerprint. Canonical definition bytes include every expression/function reference and the optional WithRevisionSalt value. Runtime pointers, registry insertion order, clocks, process IDs, and compiled engine artifacts are excluded. A datamodel must return a stable, non-empty DatamodelProgram.Fingerprint.

Chart.DefinitionArtifact() packages the canonical definition, chart and datamodel identities, program fingerprint, envelope version, and revision integrity needed to recompile that exact revision after restart. The DefinitionStore and ActorStore contracts define immutable artifact storage, atomic durable actor pin plus session-start creation, terminal pin release, and atomic reference-checked deletion without exposing database concepts. storagetest.Run is a reusable conformance suite; storagetest.MemoryStore provides the same complete durable boundary for tests.

Chart.Rehydrate reconstructs a session from its Log, optionally using a compatible snapshot to skip old entries. Replay suppresses real outbound effects, then reconciles durable outbound intents and active invocations when the instance goes live.

Runtime capabilities must not be placed in snapshot state. Keep clients, channels, loggers, transports, and reply capabilities in instance options, handler factories, IOProcessors, or out-of-band registries addressed by canonical IDs.

For deterministic timer tests, use ManualClock:

clock := statecharts.NewManualClock(time.Unix(0, 0))
instance, err := chart.NewInstance(statecharts.WithClock(clock))
clock.Advance(30 * time.Second)

Actor systems

The actors package hosts many named chart instances in one process. An actor is ephemeral by default; actors.Durable() opts that spawn into logging, rehydration, checkpointing, and paging without changing its chart type.

system := actors.NewSystem(
	actors.WithNodeName("host-a"),
	actors.WithStorage(storage),
	actors.WithIdleTimeout(5*time.Minute),
	actors.WithMaxResident(1_000),
)

if err := system.Register(chart); err != nil {
	return err
}
if err := system.Spawn(ctx, "invoice-42", chart.ID(), actors.Durable()); err != nil {
	return err
}
if err := system.Tell(ctx, "invoice-42", statecharts.Event{
	Name: "open",
	Type: statecharts.EventExternal,
}); err != nil {
	return err
}

Register establishes the Go-built chart's stable identity, compiler, and first current revision. To hot-deploy a complete edited definition, publish it only after decoding the whole file:

definition, revision, ok := system.CurrentDefinition(chart.ID())
if !ok {
	return fmt.Errorf("chart is not registered")
}
_ = revision // expose this in administrative output

definition.RevisionSalt = "invoice-v2"
newRevision, err := system.Publish(ctx, definition)
if err != nil {
	return err
}

Publication compiles all expressions and handlers and stores the immutable artifact before one atomic current-pointer change. Actors already spawned stay on their pinned revision; only future actors select newRevision. Definitions returned by CurrentDefinition and Definition are independent editable copies.

For durable actors, first spawn atomically stores the actor identity, chart ID, revision, session ID, and session-start entry before initial behavior runs. Page-in and process restart load that recorded revision—even when a newer one is current—and fail before replay if its definition, datamodel implementation, named function version, invoke handler, or pending outbox processor is missing. Snapshots are only same-revision caches; a mismatch falls back to replay.

Reaching a top-level final state marks a durable actor terminal before its completion is acknowledged and releases its stored revision reference. Actor IDs are tombstones after terminal: Spawn and Tell return statecharts.ErrActorTerminal rather than silently creating a new generation. After publishing a replacement and allowing the last old actor to finish, use system.CollectDefinition(ctx, chart.ID(), oldRevision) to remove that non-current revision from storage and the compiled registry. Collection is idempotent, refuses the current revision, and reports statecharts.DefinitionReferenced while any resident, paged-out, or ephemeral non-terminal actor still uses it.

Actor IDs are hierarchical Identifier values. Routing locations use @: billing.invoice-42@host-a. The node name is routing metadata, not durable identity. Each system has an isolated storage boundary, so moving a system to a differently named host does not move or rename actor history.

Chart actions reach peers through the same IOProcessor seam:

sendReceipt, err := model.Action(
	"billing.invoice.send-receipt", "v1",
	func(_ *Invoice, ec statecharts.ExecContext, _ []statecharts.Value) error {
		ec.Send("receipt.ready", statecharts.SendOptions{
			Target: "mailer@host-b",
			Data:   statecharts.Int64Value(42),
		})
		return nil
	},
)

Local actor IDs route inside the system first. A missing local SCXML target can fall through to the configured peer processor. actors.Bridge is an in-process demonstration of this seam; real applications can supply their existing transport. The library intentionally does not define network discovery, topology, authentication, authorization, or telemetry.

Durable actors can page out after an idle timeout or under residency pressure. The next message transparently rehydrates them before delivery. System.IsResident observes residency without activating an actor, and WithResidencyObserver reports hydrating, resident, and paged out transitions. Non-durable actors remain resident because they have no history from which to reconstruct.

Each pending durable outbound send is recorded before dispatch. A processor can implement AcknowledgingIOProcessor so the intent remains pending until transport acceptance. Outcomes still arrive as ordinary events; actor charts remain responsible for domain timeout and error behavior.

SQL storage

sqllog is opt-in and works over a caller-provided database/sql connection:

db, err := sql.Open(driverName, dsn)
if err != nil {
	return err
}
storage, err := sqllog.New(db, dialect)

The optional sqllog/sqlite3 package supplies the pure-Go ModernC driver and the current SQLite dialect:

storage, err := sqlite3.Open("data/actors.db")
if err != nil {
	return err
}
defer storage.Close()

sqllog stores immutable definition artifacts and durable actor revision pins alongside the event log and snapshot cache. File-backed SQLite databases use WAL mode, configured pooled connections, and immediate write transactions so actor start and revision deletion remain linearizable across handles. Use a different database file for each actor system; one process may host many systems, but each system owns isolated durable storage.

Examples

  • examples/counters runs seven durable actors with a three-actor residency limit, a live Datastar UI, and reconnecting writer and reader load instruments. It demonstrates paging, hydration visibility, complete-definition hot publication, revision-pinned canaries, bridges, and custom IOProcessors.
  • examples/ai-agent is a multi-conversation durable workspace with ephemeral connection actors, resumable clients, streamed provider work, tool leasing, and capability registries kept outside model snapshots.
  • examples/subscriptions models durable subscription billing with parallel billing and entitlement regions, the ECMAScript datamodel, a deliberately adversarial fake payment IOProcessor, persistent idempotency and callback jobs, SSE projections, and the embedded inspector.

Standards coverage

The interpreter follows SCXML's transition selection, conflict resolution, entry/exit ordering, hierarchy, parallel regions, history, event queues, executable content, sends, cancellation, invocation, finalization, done data, and platform error behavior. scxml.html in this repository is the normative reference used by the conformance tests.

This library provides the runtime semantics and extension seams, not every ecosystem concern. Network transports and topologies, authentication and authorization, and tracing or metrics integrations belong in application IOProcessors, handlers, and wrappers.

Documentation

Overview

Package statecharts implements statechart semantics with a Go-first authoring API.

Atomic, Compound, Parallel, Final, History, On, and Eventless construct a mutable, syntax-neutral Definition. Build validates that definition and compiles it with a Datamodel into an immutable *Chart. Compile accepts the same Definition directly, so a definition obtained from Chart.Definition can be encoded, edited, decoded, and compiled through exactly the same path. Chart.Definition always returns an independently editable deep copy.

New is the default Go authoring path. It combines ordinary typed Go state with a chart-local behavior registry, qualifies short action, condition, value, and location names under the chart ID, and compiles through the same syntax-neutral Definition path as every other datamodel. Definitions store stable names and versions rather than Go function values, making them deterministic and inspectable while each running Instance still owns ordinary typed Go data. NewGoModel and package-level Build expose the underlying registry and compiler directly when an application needs them. Compile derives Chart.Revision from a versioned canonical definition, datamodel identity, and deterministic program fingerprint; snapshots and durable outbound identities are scoped to that revision. Chart.DefinitionArtifact, DefinitionStore, and ActorStore provide the durable definition and actor-revision-pin contracts needed to resolve that exact revision after process restart. Other datamodels can implement Datamodel, DatamodelProgram, and DatamodelSession without changing the interpreter.

An Instance processes events serially, selecting transitions, resolving conflicts between parallel regions, and entering and exiting states in document order. Send, Stop, Wait, and Configuration provide its lifecycle API. ExecContext supplies registered model functions with the current event, active-state predicate, session metadata, and controlled raise, send, cancel, and logging capabilities.

Communication outside an Instance goes through IOProcessor. Longer-lived services are declared with Invoke and supplied at instance creation as environment-scoped InvokeHandler factories; handler capabilities never enter a Definition or model snapshot. Logger is the separate diagnostic output seam.

Instance.Snapshot captures a disposable restoration cache. Chart.Restore reconstructs from that cache, while Chart.Rehydrate rebuilds authoritative state from a Log and uses a compatible snapshot only to skip older replay. The sqllog package provides database/sql-backed durable storage, and the optional sqllog/sqlite3 package supplies a configured SQLite driver.

Index

Constants

View Source
const (
	// DefinitionCanonicalVersion identifies the internal canonical definition
	// encoding. It must change when that encoding changes.
	DefinitionCanonicalVersion uint32 = 2

	// DefinitionCanonicalMagic separates canonical definitions from other
	// versioned byte formats when they are used as revision material.
	DefinitionCanonicalMagic = "statecharts-definition\x00"
)
View Source
const (
	// RevisionEnvelopeVersion identifies the inputs and framing used to derive
	// chart revisions. It must change whenever that material or framing changes.
	RevisionEnvelopeVersion uint32 = 1

	// RevisionEnvelopeMagic separates revision material from every other
	// versioned canonical byte format.
	RevisionEnvelopeMagic = "statecharts-revision\x00"
)
View Source
const (
	// DefaultActorMetadataPageLimit is used when ActorMetadataQuery.Limit is zero.
	DefaultActorMetadataPageLimit = 50
	// MaxActorMetadataPageLimit is the largest accepted actor directory page.
	MaxActorMetadataPageLimit = 200
)
View Source
const GoInspectionValueTag = "statecharts.go-datamodel/v1"

GoInspectionValueTag identifies the tagged Value returned by a Go datamodel session's Inspect method.

View Source
const PlatformErrorValueTag = "statecharts.error/v1"

PlatformErrorValueTag is the stable tag used for interpreter and platform errors that cross event, session, actor, or durability boundaries.

View Source
const ValueWireVersion = 1

ValueWireVersion is the current canonical Value wire-format version.

Variables

View Source
var (
	// ErrInvalidDefinitionArtifact marks corrupt, unsupported, or internally
	// inconsistent persisted definition data.
	ErrInvalidDefinitionArtifact = errors.New("statecharts: invalid definition artifact")
	// ErrDefinitionCollision marks an attempt to associate one RevisionID
	// with artifact bytes other than the bytes already stored for that ID.
	ErrDefinitionCollision = errors.New("statecharts: definition revision collision")
	// ErrDefinitionNotFound marks an operation that requires an immutable
	// definition artifact that is not present.
	ErrDefinitionNotFound = errors.New("statecharts: definition artifact not found")
)
View Source
var (
	// ErrInvalidActorMetadata marks malformed durable actor identity, pin,
	// lifecycle data, or its required session-start boundary.
	ErrInvalidActorMetadata = errors.New("statecharts: invalid actor metadata")
	// ErrActorCollision marks reuse of an actor ID with a different chart,
	// revision, session, or durability.
	ErrActorCollision = errors.New("statecharts: actor identity collision")
	// ErrActorTerminal marks an attempt to begin an actor ID whose durable
	// lifecycle has already reached terminal.
	ErrActorTerminal = errors.New("statecharts: actor is terminal")
)
View Source
var ErrInstanceStopped = errors.New("statecharts: instance stopped")

ErrInstanceStopped is returned by Send (and, internally, treated as success by Stop) when a message could not be confirmed as accepted by a live interpreter goroutine -- either the goroutine had already exited, or it exited in the narrow window between the message being buffered and the goroutine actually dequeuing it. It is distinct from Err(): the instance's terminal error may well be nil (a clean stop), but that must never be confused with "this particular message was processed".

View Source
var ErrInvalidSnapshot = errors.New("statecharts: invalid snapshot")

ErrInvalidSnapshot marks malformed or incompatible snapshot cache data.

View Source
var ErrOutboundCollision = errors.New("statecharts: outbound delivery ID collision")

ErrOutboundCollision marks two different durable send intents carrying the same DeliveryID. A durable store must reject the second intent rather than silently treating it as an idempotent retry of the first.

Functions

func LoggingTimerFiredDetailsHook

func LoggingTimerFiredDetailsHook(log Log, sessionID SessionID, clock Clock) func(Identifier, Identifier, Identifier, Event) error

LoggingTimerFiredDetailsHook returns a callback for WithTimerFiredDetailsHook. It persists the original target and I/O processor type as well as the event, and uses clock for the entry's logical timestamp.

func LoggingTimerFiredHook

func LoggingTimerFiredHook(log Log, sessionID SessionID, clocks ...Clock) func(Identifier, Event) error

LoggingTimerFiredHook returns a callback for WithTimerFiredHook that appends a KindTimerFired entry to log before the event is allowed to apply, satisfying the write-ahead requirement for timer-fired events -- the one kind of inbound message with no explicit Instance.Send call site for an application to log itself. Explicit application Sends must separately call log.Append(ctx, LogEntry{Kind: KindExternalEvent, ...}) before calling Instance.Send; that ordinary call site needs no hook.

Types

type AcknowledgingIOProcessor

type AcknowledgingIOProcessor interface {
	IOProcessor
	SendWithAck(ctx context.Context, req SendRequest, complete func(error)) error
}

AcknowledgingIOProcessor reports stable asynchronous acceptance. complete must be called exactly once after the destination has accepted (or rejected) the request; returning an error reports a synchronous failure.

type ActiveInvoke

type ActiveInvoke struct {
	State        Identifier `json:"state"`            // the state that owns this invocation
	DefinitionID Identifier `json:"definition_id"`    // stable declaration identity in the pinned chart definition
	ID           Identifier `json:"id"`               // the invocation's own id, exactly as assigned when it started
	Type         Identifier `json:"type,omitempty"`   // evaluated handler type selected when this invocation began
	Source       string     `json:"source,omitempty"` // evaluated source selected when this invocation began
}

ActiveInvoke records one <invoke> that was active in Configuration when a Snapshot was taken.

type ActorBeginResult

type ActorBeginResult uint8

ActorBeginResult describes an idempotent atomic actor start.

const (
	ActorStarted ActorBeginResult = iota + 1
	ActorAlreadyActive
)

type ActorLifecycle

type ActorLifecycle string

ActorLifecycle is the durable lifecycle of a started actor. Absence from an ActorStore means never started; no never-started metadata row is persisted.

const (
	ActorLifecycleActive   ActorLifecycle = "active"
	ActorLifecycleTerminal ActorLifecycle = "terminal"
)

type ActorMetadata

type ActorMetadata struct {
	ActorID    Identifier
	ChartID    Identifier
	Revision   RevisionID
	SessionID  SessionID
	Durable    bool
	Lifecycle  ActorLifecycle
	StartedAt  time.Time
	TerminalAt time.Time
}

ActorMetadata is the authoritative durable revision pin for one actor ID. Ephemeral actors retain their Chart directly and are not written to an ActorStore; Durable remains explicit so identity retries cannot silently cross the durable/ephemeral boundary.

func (ActorMetadata) Validate

func (m ActorMetadata) Validate() error

Validate checks active or terminal persisted metadata. BeginActor accepts only ActorLifecycleActive values.

type ActorMetadataCursor

type ActorMetadataCursor string

ActorMetadataCursor is an opaque, storage-independent position in the ActorID ordering. Callers should only retain and return cursor values from a previous ActorMetadataPage.

func ActorMetadataCursorFor

func ActorMetadataCursorFor(actorID Identifier) ActorMetadataCursor

ActorMetadataCursorFor returns the opaque cursor following actorID.

type ActorMetadataPage

type ActorMetadataPage struct {
	Actors []ActorMetadata
	Next   ActorMetadataCursor
}

ActorMetadataPage owns its Actors slice and contains Next only when another matching actor exists.

type ActorMetadataQuery

type ActorMetadataQuery struct {
	After         ActorMetadataCursor
	Limit         int
	ActorIDPrefix string
	ChartID       Identifier
	Revision      RevisionID
	Lifecycle     ActorLifecycle
}

ActorMetadataQuery selects durable actor metadata. An empty Lifecycle selects both active and terminal actors, making the default suitable for a complete directory traversal. After is exclusive. Limit zero uses DefaultActorMetadataPageLimit; negative or oversized limits are rejected.

func (ActorMetadataQuery) Validate

func (q ActorMetadataQuery) Validate() (normalized ActorMetadataQuery, after Identifier, err error)

Validate normalizes the limit and decodes the exclusive ActorID cursor. Storage implementations use this method to ensure identical validation.

type ActorStore

type ActorStore interface {
	// BeginActor atomically stores metadata and appends exactly one
	// KindSessionStarted entry at metadata.StartedAt before any initial chart
	// behavior may run. It requires a valid matching DefinitionArtifact.
	// Exact identity retries return the originally stored metadata and
	// ActorAlreadyActive without another log entry. Different pins return
	// ErrActorCollision; terminal IDs return ErrActorTerminal. Reusing a
	// SessionID or beginning against a pre-populated session log also returns
	// ErrActorCollision.
	BeginActor(ctx context.Context, metadata ActorMetadata) (ActorMetadata, ActorBeginResult, error)

	// GetActor returns authoritative metadata regardless of residency or
	// terminal state. Absence means the actor was never started. Corrupt actor
	// metadata or a missing/inconsistent session-start record returns
	// ErrInvalidActorMetadata before replay can begin.
	GetActor(ctx context.Context, actorID Identifier) (ActorMetadata, bool, error)

	// QueryActors returns durable metadata in ActorID ascending order without
	// activating actors. Filters are combined, and the cursor is exclusive.
	QueryActors(ctx context.Context, query ActorMetadataQuery) (ActorMetadataPage, error)

	// MarkActorTerminal atomically releases actorID's revision reference. It
	// is idempotent and preserves the first terminal timestamp.
	MarkActorTerminal(ctx context.Context, actorID Identifier, terminalAt time.Time) (ActorMetadata, ActorTerminalResult, error)

	// ReferencedRevisions returns the sorted, deduplicated revisions pinned by
	// non-terminal durable actors.
	ReferencedRevisions(ctx context.Context) ([]RevisionID, error)
}

ActorStore persists durable actor revision pins and their lifecycle. The same concrete storage object must also implement Log and DefinitionStore so BeginActor and DeleteDefinitionIfUnreferenced can provide their documented cross-record atomicity. A SessionID is owned by exactly one ActorID. Its first log entry is the actor's sole KindSessionStarted record, at Seq 1 and the stored StartedAt; every actor read verifies that boundary before returning metadata.

type ActorTerminalResult

type ActorTerminalResult uint8

ActorTerminalResult describes an idempotent terminal transition.

const (
	ActorMarkedTerminal ActorTerminalResult = iota + 1
	ActorAlreadyTerminal
	ActorNotFound
)

type AssignDefinition

type AssignDefinition struct {
	Location Expression `json:"location"`
	Expr     Expression `json:"expr"`
}

AssignDefinition evaluates Expr and stores it at the datamodel Location.

type AuthoringOption

type AuthoringOption func(*authoringConfig)

AuthoringOption configures the default Go authoring path created by New.

func Version

func Version(version string) AuthoringOption

Version sets the default version for registered behavior and supplies the chart revision salt. Bump it when Go behavior changes without changing the chart structure. The default is "v1".

type BuildOption

type BuildOption func(*Definition)

BuildOption configures the mutable Definition assembled by Build before it is validated and compiled. Options must store only definition data.

func WithData

func WithData(data ...DataDefinition) BuildOption

WithData appends document-level data declarations.

func WithDataBinding

func WithDataBinding(binding DataBinding) BuildOption

WithDataBinding selects early or late initialization for data declarations.

func WithName

func WithName(name string) BuildOption

WithName sets the SCXML document name exposed as _name. It is independent of the root state's structural ID.

func WithRevisionSalt

func WithRevisionSalt(salt string) BuildOption

WithRevisionSalt adds explicit application-controlled revision material. Change it when registered behavior changes without a corresponding function version change.

type Builder

type Builder[D any] struct {
	// contains filtered or unexported fields
}

Builder is the concise, default authoring surface for Go applications. It owns one model-local behavior registry, qualifies short behavior names with the chart ID, and reports registration errors when Build is called.

Use the package-level Build with an explicit Datamodel instead when authoring for another datamodel or when direct registry control is required.

func New

func New[D any](id Identifier, factory func() *D, options ...AuthoringOption) *Builder[D]

New starts a Go-authored chart definition. id is the stable chart identity; behavior registered as "publish" is stored as "<id>.publish" in the canonical Definition. The returned Builder is intended for configuration by one goroutine before Build.

func (*Builder[D]) Action

func (b *Builder[D]) Action(name string, action GoAction[D]) GoActionRef

Action registers a named action at the Builder's default Version.

func (*Builder[D]) ActionVersion

func (b *Builder[D]) ActionVersion(name string, version string, action GoAction[D]) GoActionRef

ActionVersion registers a retained implementation at an explicit version. This is useful when old, revision-pinned actors may still resolve it.

func (*Builder[D]) Build

func (b *Builder[D]) Build(root StateDefinition, options ...BuildOption) (*Chart, error)

Build compiles root through the ordinary Definition compiler. The Builder's ID becomes the chart ID and its Version becomes the default revision salt; an explicit WithRevisionSalt option may override that salt.

func (*Builder[D]) Condition

func (b *Builder[D]) Condition(name string, condition GoCondition[D]) GoConditionRef

Condition registers a named condition at the Builder's default Version.

func (*Builder[D]) ConditionVersion

func (b *Builder[D]) ConditionVersion(name string, version string, condition GoCondition[D]) GoConditionRef

ConditionVersion registers a retained condition implementation at an explicit version.

func (*Builder[D]) Location

func (b *Builder[D]) Location(name string, get GoValue[D], set GoLocation[D]) GoLocationRef

Location registers a named readable/writable location at the Builder's default Version.

func (*Builder[D]) LocationVersion

func (b *Builder[D]) LocationVersion(name string, version string, get GoValue[D], set GoLocation[D]) GoLocationRef

LocationVersion registers a retained location implementation at an explicit version.

func (*Builder[D]) Value

func (b *Builder[D]) Value(name string, value GoValue[D]) GoValueRef

Value registers a named value producer at the Builder's default Version.

func (*Builder[D]) ValueVersion

func (b *Builder[D]) ValueVersion(name string, version string, value GoValue[D]) GoValueRef

ValueVersion registers a retained value implementation at an explicit version.

type CallDefinition

type CallDefinition struct {
	Function FunctionRef `json:"function"`
}

CallDefinition invokes one named, versioned host behavior.

type CancelDefinition

type CancelDefinition struct {
	SendID     Identifier  `json:"sendID,omitempty"`
	SendIDExpr *Expression `json:"sendIDExpr,omitempty"`
}

CancelDefinition cancels a delayed send selected by either a static ID or a datamodel expression.

type Chart

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

Chart is an immutable, validated, indexed chart definition produced by Build. It is safe for concurrent use by multiple Instances.

func Build

func Build(root StateDefinition, model Datamodel, opts ...BuildOption) (*Chart, error)

Build assembles a canonical Go-authored Definition and sends it through the same compiler used by decoded or edited definitions.

func Compile

func Compile(definition Definition, model Datamodel) (*Chart, error)

Compile validates and normalizes definition, compiles all model-owned expressions, and lowers it to an immutable Chart.

func (*Chart) Datamodel

func (c *Chart) Datamodel() Datamodel

Datamodel returns the compiler used to build this chart. Actor registries retain it to compile complete replacement definitions and resolve retained artifacts without modifying an existing Chart.

func (*Chart) DatamodelProgram

func (c *Chart) DatamodelProgram() DatamodelProgram

DatamodelProgram returns the immutable model program shared by this chart's instances.

func (*Chart) Definition

func (c *Chart) Definition() Definition

Definition returns an independently editable copy of the normalized definition used to compile c.

func (*Chart) DefinitionArtifact

func (c *Chart) DefinitionArtifact() DefinitionArtifact

DefinitionArtifact returns an immutable-storage representation of c that can be recompiled after a process restart. Returned byte slices are owned by the caller.

func (*Chart) ID

func (c *Chart) ID() Identifier

ID returns the stable Definition identity used for publication and actor kinds. It is independent of the root state's structural ID.

func (*Chart) Name

func (c *Chart) Name() string

Name returns the SCXML document's name attribute, or empty if omitted.

func (*Chart) NewInstance

func (c *Chart) NewInstance(opts ...Option) (*Instance, error)

NewInstance constructs an Instance with a fresh session from the chart's DatamodelProgram. The interpreter goroutine is not started until Start is called.

func (*Chart) Prepare

func (c *Chart) Prepare(opts ...Option) error

Prepare validates this chart's statically declared runtime requirements against opts without creating a datamodel session or running behavior. Dynamic invoke type expressions are deliberately resolved at runtime.

func (*Chart) Rehydrate

func (c *Chart) Rehydrate(ctx context.Context, log Log, snapshots SnapshotStore, sessionID SessionID, realIO IOProcessor, opts ...Option) (*Instance, error)

Rehydrate reconstructs a running Instance for sessionID: it loads the latest Checkpoint if one exists, then replays every subsequent Log entry. Real I/O, Logger calls, invocation starts, and live timers are suppressed until replay catches up. Each entry's Timestamp supplies logical time for delayed sends created while applying it, and a KindTimerFired entry consumes the corresponding pending send by SendID without logging the fire again. Finally, remaining timers are activated against the configured Clock -- including synchronously applying overdue sends -- and active invocations are reconciled before the returned Instance is considered live. Rehydrate reconstructs a running Instance using fresh sessions from the chart's DatamodelProgram. Snapshot model state is decoded into one fresh session; an incompatible cache is discarded before replay starts with another fresh session.

func (*Chart) Restore

func (c *Chart) Restore(snap Snapshot, opts ...Option) (*Instance, error)

Restore reconstructs a paused Instance from snap with a fresh session from the chart's DatamodelProgram. The session owns decoding the opaque model snapshot bytes. The returned instance is not started.

func (*Chart) Revision

func (c *Chart) Revision() RevisionID

Revision returns the deterministic identity of this compiled definition and datamodel program.

func (*Chart) States

func (c *Chart) States() []Identifier

States returns every state's ID in document order.

type Checkpoint

type Checkpoint struct {
	Snapshot Snapshot
	Seq      uint64
}

Checkpoint pairs a Snapshot with the Log sequence number it reflects. Seq == 0 means the Snapshot was taken independent of any Log.

type ChooseBranchDefinition

type ChooseBranchDefinition struct {
	Condition Expression        `json:"condition"`
	Actions   []ExecutableBlock `json:"actions,omitempty"`
}

ChooseBranchDefinition is one condition and its ordered action blocks.

type ChooseDefinition

type ChooseDefinition struct {
	Branches []ChooseBranchDefinition `json:"branches"`
	Else     []ExecutableBlock        `json:"else,omitempty"`
}

ChooseDefinition evaluates branches in order and executes the first true branch, or Else when none match.

type Clock

type Clock interface {
	Now() time.Time
	AfterFunc(d time.Duration, f func()) (stop func() bool)
}

Clock abstracts time so delayed-<send> timers can be replaced with compute-only bookkeeping during log replay (see Rehydrate), instead of arming real OS timers for events that already fired historically.

func NewRealClock

func NewRealClock() Clock

NewRealClock returns the default Clock, backed by the real wall clock and time.AfterFunc.

type CompiledExpression

type CompiledExpression any

CompiledExpression is an opaque immutable handle produced by a DatamodelProgram. The interpreter passes it back to that program's sessions without inspecting its concrete representation.

type DataBinding

type DataBinding string

DataBinding controls when declarations receive their initial values. The zero value means DataBindingEarly.

const (
	// DataBindingEarly initializes every declaration when a session starts.
	DataBindingEarly DataBinding = "early"
	// DataBindingLate initializes a declaration immediately before its owning
	// state is entered for the first time.
	DataBindingLate DataBinding = "late"
)

type DataDefinition

type DataDefinition struct {
	ID      Identifier  `json:"id"`
	Source  string      `json:"source,omitempty"`
	Expr    *Expression `json:"expr,omitempty"`
	Content *Expression `json:"content,omitempty"`
}

DataDefinition declares one model-owned data item. Source, Expr, and Content are mutually exclusive initialization modes; all three may be absent to declare an initially empty item. Declarations retain their order.

type Datamodel

type Datamodel interface {
	Name() Identifier
	Compile(definition *Definition) (DatamodelProgram, error)
}

Datamodel compiles syntax-neutral definitions for one model implementation. Compile must not mutate definition. It returns an immutable program only after every model-owned expression has been validated and compiled.

type DatamodelProgram

type DatamodelProgram interface {
	// Fingerprint returns deterministic implementation identity used as chart
	// revision material. It must be non-empty and restart-stable for equivalent
	// programs. Callers must treat the returned bytes as immutable.
	Fingerprint() []byte
	// ResolveExpression returns the immutable handle compiled for expression.
	// It rejects expressions that were not part of the compiled Definition.
	ResolveExpression(Expression) (CompiledExpression, error)
	// ResolveFunction returns the immutable executable handle compiled for a
	// named host-function reference in the Definition.
	ResolveFunction(FunctionRef) (CompiledExpression, error)
	// ResolveDataLocation returns an assignable handle for a declared data ID.
	// This lets the syntax-neutral chart compiler implement ordered early and
	// late initialization and foreach bindings without knowing the model.
	ResolveDataLocation(Identifier) (CompiledExpression, error)
	// NewSession creates fresh mutable model state owned by one Instance.
	NewSession(SessionOptions) (DatamodelSession, error)
}

DatamodelProgram is immutable compiled model state shared safely by every instance of one chart revision.

type DatamodelSession

type DatamodelSession interface {
	EvaluateBoolean(ExecContext, CompiledExpression) (bool, error)
	EvaluateValue(ExecContext, CompiledExpression) (Value, error)
	Assign(ExecContext, CompiledExpression, Value) error
	Execute(ExecContext, CompiledExpression) error
	ForEach(ExecContext, CompiledExpression, IterationBindings, func() error) error

	// Inspect returns an independently owned canonical representation of the
	// application-owned model state.
	Inspect() (Value, error)

	// EncodeSnapshot returns opaque, model-owned cache bytes.
	EncodeSnapshot() ([]byte, error)
	// DecodeSnapshot atomically replaces this fresh session's restorable model
	// state. A failed session is closed and discarded rather than reused.
	DecodeSnapshot([]byte) error
	// Close releases model resources. Instance calls it exactly once.
	Close() error
}

DatamodelSession owns exactly one Instance's mutable model state and any model runtime resources. Methods return ordinary Go errors; only the interpreter translates them into statechart error events and abort rules. Implementations are single-owner and need not be safe for concurrent use.

type Definition

type Definition struct {
	ID           Identifier       `json:"id"`
	Name         string           `json:"name,omitempty"`
	Datamodel    Identifier       `json:"datamodel"`
	RevisionSalt string           `json:"revisionSalt,omitempty"`
	DataBinding  DataBinding      `json:"dataBinding,omitempty"`
	Data         []DataDefinition `json:"data,omitempty"`
	Root         StateDefinition  `json:"root"`
}

Definition is a complete, syntax-neutral statechart program. It is mutable authoring/inspection data, unlike the immutable compiled Chart. Definition contains no callbacks, runtime pointers, compiled indexes, or syntax bytes.

func (Definition) CanonicalBytes

func (d Definition) CanonicalBytes() ([]byte, error)

CanonicalBytes returns deterministic, versioned bytes suitable for chart revision material. The encoding is internal: authoring surfaces should translate to and from Definition rather than expose this format.

The canonical form resolves generated state IDs and default transition types on a clone. It does not mutate d.

func (Definition) Clone

func (d Definition) Clone() Definition

Clone returns a deep, independently editable copy.

func (Definition) Validate

func (d Definition) Validate() error

Validate performs syntax-neutral structural validation. Datamodel-specific expression checking belongs to Datamodel compilation, not this method. Validation assigns generated IDs only on a private clone.

type DefinitionArtifact

type DefinitionArtifact struct {
	Revision                RevisionID
	RevisionEnvelopeVersion uint32
	ChartID                 Identifier // stable Definition.ID, independent of the root state's structural ID
	Datamodel               Identifier
	CanonicalDefinition     []byte
	ProgramFingerprint      []byte
}

DefinitionArtifact is the deterministic data required to recompile one Chart revision after a process restart. Go functions are represented only by the named/versioned references in CanonicalDefinition; the process must supply their implementations through the selected Datamodel.

func (DefinitionArtifact) Clone

Clone returns a deep copy whose byte slices are independently owned.

func (DefinitionArtifact) Compile

func (a DefinitionArtifact) Compile(model Datamodel) (*Chart, error)

Compile validates a, resolves its references through model, and verifies that recompilation produces the exact persisted program fingerprint and revision. Applications must do this before replaying a pinned actor.

func (DefinitionArtifact) Definition

func (a DefinitionArtifact) Definition() (Definition, error)

Definition decodes and validates an independently editable definition.

func (DefinitionArtifact) Equal

Equal reports whether every persisted field is byte-for-byte equal.

func (DefinitionArtifact) Validate

func (a DefinitionArtifact) Validate() error

Validate checks canonical encoding, identity fields, envelope version, and revision integrity.

type DefinitionDeleteResult

type DefinitionDeleteResult uint8

DefinitionDeleteResult describes an atomic reference-check-and-delete.

const (
	DefinitionDeleted DefinitionDeleteResult = iota + 1
	DefinitionReferenced
	DefinitionNotFound
)

type DefinitionError

type DefinitionError struct {
	Path string
	Err  error
}

DefinitionError reports a structural definition error at a stable public traversal path.

func (*DefinitionError) Error

func (e *DefinitionError) Error() string

func (*DefinitionError) Unwrap

func (e *DefinitionError) Unwrap() error

type DefinitionPutResult

type DefinitionPutResult uint8

DefinitionPutResult describes an idempotent immutable artifact write.

const (
	DefinitionStored DefinitionPutResult = iota + 1
	DefinitionUnchanged
)

type DefinitionStore

type DefinitionStore interface {
	// PutDefinition validates artifact and stores it when its revision is
	// absent. An exact retry returns DefinitionUnchanged. If the revision is
	// already associated with any different field or byte, it returns
	// ErrDefinitionCollision without changing the stored artifact.
	PutDefinition(ctx context.Context, artifact DefinitionArtifact) (DefinitionPutResult, error)

	// GetDefinition returns a validated, independently owned artifact. Corrupt
	// stored data returns ErrInvalidDefinitionArtifact rather than found=true.
	GetDefinition(ctx context.Context, revision RevisionID) (artifact DefinitionArtifact, found bool, err error)

	// DeleteDefinitionIfUnreferenced atomically checks non-terminal actor
	// references and deletes revision only when none exist. It must linearize
	// with BeginActor on the same storage implementation. Publication state is
	// intentionally separate; callers must not request deletion of a current
	// revision.
	DeleteDefinitionIfUnreferenced(ctx context.Context, revision RevisionID) (DefinitionDeleteResult, error)
}

DefinitionStore persists immutable, content-addressed chart definitions. Implementations must copy caller-owned bytes on writes and returns.

type DeliveryID

type DeliveryID string

DeliveryID is an opaque, stable identity for one external delivery. Applications must not interpret it; durable transports use it for inbox deduplication and as an idempotency key.

type Dispatcher

type Dispatcher interface {
	Deliver(ctx context.Context, ev Event) error
}

Dispatcher lets an IOProcessor deliver events back into whichever Instance attached it -- fired invoke results, responses, or messages from other sessions. Instance implements Dispatcher.

type DoneDataDefinition

type DoneDataDefinition struct {
	Params  []ParamDefinition `json:"params,omitempty"`
	Content *Expression       `json:"content,omitempty"`
}

DoneDataDefinition computes the payload of a final state's done event. Params and Content are mutually exclusive; Content is one whole payload.

type DurableLog

type DurableLog interface {
	Log
	// AppendIngress atomically deduplicates deliveryID and appends entry. When
	// the implementation also stores ActorMetadata for entry.SessionID, it
	// must linearize with MarkActorTerminal and return ErrActorTerminal instead
	// of appending after that actor is terminal.
	AppendIngress(ctx context.Context, entry LogEntry, deliveryID DeliveryID) (seq uint64, appended bool, err error)
	StoreOutbound(ctx context.Context, message OutboundMessage) error
	ResolveOutbound(ctx context.Context, sessionID SessionID, deliveryID DeliveryID, result OutboundResult) error
	Outbounds(ctx context.Context, sessionID SessionID) ([]OutboundMessage, error)
}

DurableLog combines the WAL inbox/dedup and durable outbox boundary.

type EncodedEvent

type EncodedEvent struct {
	Name       Identifier `json:"name"`
	Type       EventType  `json:"type"`
	SendID     Identifier `json:"send_id,omitempty"`
	Origin     Identifier `json:"origin,omitempty"`
	OriginType Identifier `json:"origin_type,omitempty"`
	InvokeID   Identifier `json:"invoke_id,omitempty"`
	DeliveryID DeliveryID `json:"delivery_id,omitempty"`
	Data       []byte     `json:"data"`
}

EncodedEvent is the flat, JSON/SQL-safe encoding of an Event, used by Snapshot's JSON envelope and durable stores. Data is always the canonical Value marshal representation, including for null.

func EncodeEvent

func EncodeEvent(ev Event) (EncodedEvent, error)

EncodeEvent flattens ev using Value's canonical, versioned bytes.

type EntryKind

type EntryKind string

EntryKind distinguishes durable inputs and the session-start boundary. Everything else a chart does (<raise>, <cancel>, history recording, new transitions) is pure, deterministic recomputation given the initial configuration and these entries (see Rehydrate in replay.go).

const (
	// KindSessionStarted is written before a durable actor is started for the
	// first time. It is a no-op during replay, but proves initial executable
	// content may already have run -- particularly an initial <invoke> -- even
	// when the process crashed before the actor received its first message.
	// Its Timestamp also anchors initial delayed sends to the original start
	// time instead of the recovery time.
	KindSessionStarted EntryKind = "session_started"

	// KindExternalEvent records an explicit application call, Instance.Send.
	KindExternalEvent EntryKind = "external_event"

	// KindTimerFired records a delayed <send>'s timer elapsing -- this
	// originates outside the deterministic microstep computation (a real
	// wall-clock timer) and must be replayed as data, never re-derived by
	// letting a real timer re-elapse.
	KindTimerFired EntryKind = "timer_fired"
)

type Event

type Event struct {
	Name       Identifier
	Type       EventType
	Data       Value
	SendID     Identifier
	Origin     Identifier
	OriginType Identifier
	InvokeID   Identifier
	DeliveryID DeliveryID
}

Event is a message flowing through a chart's internal or external queue. It mirrors the mandatory fields of an SCXML event (5.10.1). Data is the canonical payload representation shared by datamodel, actor, process, and durability boundaries. Its zero value is canonical null.

func DecodeEvent

func DecodeEvent(enc EncodedEvent) (Event, error)

DecodeEvent reconstructs an Event from canonical Value bytes.

type EventType

type EventType uint8

EventType classifies where an Event originated, per SCXML 5.10.1.

const (
	// EventExternal events arrived via the blocking external queue (an
	// application Send, or a message from another session/processor).
	EventExternal EventType = iota
	// EventInternal events were raised via <raise> or sent to "#_internal".
	EventInternal
	// EventPlatform events are placed by the interpreter itself, e.g.
	// error.execution, error.communication, done.state.<id>.
	EventPlatform
)

func (EventType) String

func (t EventType) String() string

String implements fmt.Stringer.

type ExecContext

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

ExecContext exposes the runtime context supplied to datamodel operations.

func (ExecContext) Cancel

func (ec ExecContext) Cancel(sendID Identifier)

Cancel best-effort cancels every pending delayed Send with the author-given sendID, mirroring <cancel>. An unknown or already-fired sendID is a no-op.

func (ExecContext) Event

func (ec ExecContext) Event() (Event, bool)

Event returns the event currently being processed, if any. Per SCXML 5.10.1, _event is unbound before the first event is processed.

func (ExecContext) IOProcessorLocation

func (ec ExecContext) IOProcessorLocation(typ Identifier) (location Location, ok bool)

IOProcessorLocation returns the Location advertised for typ among IOProcessors(), the common case of wanting one specific processor's own address (e.g. to embed in outgoing event data so a peer can reply) -- callers don't need to write that lookup loop themselves. ok is false if no processor of that Type is advertised, in which case location is the zero Location.

func (ExecContext) IOProcessors

func (ec ExecContext) IOProcessors() []IOProcessorInfo

IOProcessors reports every Event I/O Processor available to this session, per SCXML 5.10's _ioprocessors -- specifically, the ones with an address another session could use to reach this one (see IOProcessorDescriber). It is empty/nil if the configured IOProcessor has nothing to advertise.

func (ExecContext) In

func (ec ExecContext) In(id Identifier) bool

In implements the SCXML In() predicate: reports whether id is a member of the current configuration.

func (ExecContext) Log

func (ec ExecContext) Log(label string, data Value)

Log records one diagnostic entry, mirroring <log>: label names it, data carries canonical data. It is routed to whichever Logger the owning Instance was configured with (see WithLogger), and is a silent no-op if none was. Unlike Send, Log never produces an event another transition can match against, and unlike Raise, it never touches either event queue -- it exists purely for a human or a log aggregator to read.

func (ExecContext) Name

func (ec ExecContext) Name() string

Name returns the SCXML document name, bound for this session's entire lifetime, per SCXML 5.10's _name.

func (ExecContext) PlatformVariables

func (ec ExecContext) PlatformVariables() map[string]any

PlatformVariables returns a fresh binding map for SCXML's protected _x system variable. Opaque values are shared, but the map itself is not.

func (ExecContext) Raise

func (ec ExecContext) Raise(ev Event)

Raise enqueues ev on the internal queue, as <raise> would. ev.Type is always overwritten to EventInternal.

func (ExecContext) Send

func (ec ExecContext) Send(name Identifier, opts SendOptions)

Send schedules delivery of an event, mirroring <send>: immediately (if opts.Delay is zero) or after opts.Delay elapses. Dispatch failures are never returned here -- per SCXML, invalid/unsupported requests become error.execution and delivery failures become error.communication on the internal queue, rather than Go errors propagated to the caller.

func (ExecContext) SessionID

func (ec ExecContext) SessionID() string

SessionID returns this session's id, bound for its entire lifetime, per SCXML 5.10's _sessionid. See WithSessionID and WithIDGenerator (instance.go) for how it is minted or supplied.

type Executable

type Executable struct {
	Kind      ExecutableKind       `json:"kind"`
	Raise     *RaiseDefinition     `json:"raise,omitempty"`
	Send      *SendDefinition      `json:"send,omitempty"`
	Cancel    *CancelDefinition    `json:"cancel,omitempty"`
	Log       *LogDefinition       `json:"log,omitempty"`
	Assign    *AssignDefinition    `json:"assign,omitempty"`
	Choose    *ChooseDefinition    `json:"choose,omitempty"`
	ForEach   *ForEachDefinition   `json:"foreach,omitempty"`
	Script    *ScriptDefinition    `json:"script,omitempty"`
	Call      *CallDefinition      `json:"call,omitempty"`
	Extension *ExtensionDefinition `json:"extension,omitempty"`
}

Executable is a closed tagged union. Exactly one payload pointer must be present and it must match Kind. Constructors below create that outer union; Definition.Validate validates both the union and its payload.

func CancelSend

func CancelSend(sendID Identifier) Executable

CancelSend creates executable content that cancels a delayed send by ID.

func LogValue

func LogValue(label string, expression Expression) Executable

LogValue creates diagnostic logging executable content.

func NewAssignExecutable

func NewAssignExecutable(value AssignDefinition) Executable

NewAssignExecutable returns an assignment executable with a valid outer union.

func NewCallExecutable

func NewCallExecutable(value CallDefinition) Executable

NewCallExecutable returns a host-function call executable with a valid outer union.

func NewCancelExecutable

func NewCancelExecutable(value CancelDefinition) Executable

NewCancelExecutable returns a cancel executable with a valid outer union.

func NewChooseExecutable

func NewChooseExecutable(value ChooseDefinition) Executable

NewChooseExecutable returns a conditional executable with a valid outer union.

func NewExtensionExecutable

func NewExtensionExecutable(value ExtensionDefinition) Executable

NewExtensionExecutable returns an extension executable with a valid outer union.

func NewForEachExecutable

func NewForEachExecutable(value ForEachDefinition) Executable

NewForEachExecutable returns an iteration executable with a valid outer union.

func NewLogExecutable

func NewLogExecutable(value LogDefinition) Executable

NewLogExecutable returns a log executable with a valid outer union.

func NewRaiseExecutable

func NewRaiseExecutable(value RaiseDefinition) Executable

NewRaiseExecutable returns a raise executable with a valid outer union.

func NewScriptExecutable

func NewScriptExecutable(value ScriptDefinition) Executable

NewScriptExecutable returns a model-script executable with a valid outer union.

func NewSendExecutable

func NewSendExecutable(value SendDefinition) Executable

NewSendExecutable returns a send executable with a valid outer union.

func Raise

func Raise(event Identifier, data ...Expression) Executable

Raise creates executable content that raises an internal event. An optional expression computes its payload.

func Send

func Send(event Identifier, options ...SendOption) Executable

Send creates serializable event-delivery executable content.

type ExecutableBlock

type ExecutableBlock []Executable

ExecutableBlock is one ordered executable-content block. Blocks remain distinct because an evaluation error aborts only its containing block.

type ExecutableKind

type ExecutableKind string

ExecutableKind identifies one first-class executable-content node.

const (
	ExecutableRaise     ExecutableKind = "raise"
	ExecutableSend      ExecutableKind = "send"
	ExecutableCancel    ExecutableKind = "cancel"
	ExecutableLog       ExecutableKind = "log"
	ExecutableAssign    ExecutableKind = "assign"
	ExecutableChoose    ExecutableKind = "choose"
	ExecutableForEach   ExecutableKind = "foreach"
	ExecutableScript    ExecutableKind = "script"
	ExecutableCall      ExecutableKind = "call"
	ExecutableExtension ExecutableKind = "extension"
)

type Expression

type Expression struct {
	Kind Identifier `json:"kind"`
	Data Value      `json:"data"`
}

Expression is syntax-neutral, datamodel-owned program data. Kind selects an expression form understood by a Datamodel; Data contains that form's canonical operands or source. Structural validation checks only that Kind and Data are well formed. A datamodel validates their meaning at compile time.

func GoData

func GoData(id Identifier) Expression

GoData returns a readable and writable expression for a declared data ID.

func GoLiteral

func GoLiteral(v Value) Expression

GoLiteral returns a canonical literal expression.

func (Expression) Clone

func (e Expression) Clone() Expression

Clone returns an independently editable expression.

type ExtensionDefinition

type ExtensionDefinition struct {
	Namespace string `json:"namespace"`
	Name      string `json:"name"`
	Data      Value  `json:"data"`
}

ExtensionDefinition preserves executable content owned by an extension. Namespace and Name identify the extension; Data is its canonical payload.

type ForEachDefinition

type ForEachDefinition struct {
	Array   Expression        `json:"array"`
	Item    Identifier        `json:"item"`
	Index   Identifier        `json:"index,omitempty"`
	Actions []ExecutableBlock `json:"actions,omitempty"`
}

ForEachDefinition iterates Array with datamodel-owned Item and optional Index bindings.

type FunctionRef

type FunctionRef struct {
	Name    Identifier   `json:"name"`
	Version string       `json:"version"`
	Args    []Expression `json:"args,omitempty"`
}

FunctionRef identifies host behavior without storing a Go function. Name and Version are stable definition data; Args are datamodel expressions evaluated according to the operation that owns the reference.

type GoAction

type GoAction[D any] func(*D, ExecContext, []Value) error

GoAction is a typed host action used by both script expressions and named call nodes.

type GoActionRef

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

GoActionRef creates serializable script expressions and call references.

func (GoActionRef) Call

func (r GoActionRef) Call(args ...Expression) Executable

Call returns this registered action as an explicit named call node.

func (GoActionRef) Do

func (r GoActionRef) Do(args ...Expression) Executable

Do returns this registered action as a serializable executable node.

func (GoActionRef) Expression

func (r GoActionRef) Expression(args ...Expression) Expression

func (GoActionRef) Function

func (r GoActionRef) Function(args ...Expression) FunctionRef

type GoCondition

type GoCondition[D any] func(*D, ExecContext, []Value) (bool, error)

GoCondition is a typed host condition. Args are the evaluated expressions attached to the reference at its use site.

type GoConditionRef

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

GoConditionRef creates serializable condition expressions without repeating the registered behavior's name or version.

func (GoConditionRef) Expression

func (r GoConditionRef) Expression(args ...Expression) Expression

func (GoConditionRef) If

func (r GoConditionRef) If(args ...Expression) Expression

If returns this registered condition as a serializable expression.

type GoInspectionCodec

type GoInspectionCodec[D any] struct {
	Encode func(*D) (Value, error)
}

GoInspectionCodec converts application-owned D into canonical inspector data. It is independent of the opaque snapshot codec. Implementations must not mutate D; the returned Value is cloned before it leaves the session.

type GoLocation

type GoLocation[D any] func(*D, ExecContext, Value, []Value) error

GoLocation writes a canonical value to a typed host location.

type GoLocationRef

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

GoLocationRef creates serializable readable and writable location expressions.

func (GoLocationRef) At

func (r GoLocationRef) At(args ...Expression) Expression

At returns this registered readable/writable location as a serializable expression.

func (GoLocationRef) Expression

func (r GoLocationRef) Expression(args ...Expression) Expression

type GoModel

type GoModel[D any] struct {
	// contains filtered or unexported fields
}

GoModel is the default datamodel. It combines a typed D factory with a model-local registry of stable, named host behavior.

func NewGoModel

func NewGoModel[D any](factory func() *D, options ...GoModelOption[D]) *GoModel[D]

NewGoModel creates an independently scoped Go datamodel registry.

func (*GoModel[D]) Action

func (m *GoModel[D]) Action(n Identifier, v string, fn GoAction[D]) (GoActionRef, error)

Action is an alias for Script; its handle supports both Script expressions and Call references.

func (*GoModel[D]) Compile

func (m *GoModel[D]) Compile(def *Definition) (DatamodelProgram, error)

func (*GoModel[D]) Condition

func (m *GoModel[D]) Condition(n Identifier, v string, fn GoCondition[D]) (GoConditionRef, error)

Condition registers one condition implementation and returns its reusable reference.

func (*GoModel[D]) Location

func (m *GoModel[D]) Location(n Identifier, v string, get GoValue[D], set GoLocation[D]) (GoLocationRef, error)

Location registers a reusable readable and writable host location.

func (*GoModel[D]) Name

func (*GoModel[D]) Name() Identifier

func (*GoModel[D]) Script

func (m *GoModel[D]) Script(n Identifier, v string, fn GoAction[D]) (GoActionRef, error)

Script registers one action implementation and returns its reusable reference.

func (*GoModel[D]) TextExpressionCodec

func (*GoModel[D]) TextExpressionCodec() TextExpressionCodec

TextExpressionCodec returns the default text codec for this GoModel.

func (*GoModel[D]) Value

func (m *GoModel[D]) Value(n Identifier, v string, fn GoValue[D]) (GoValueRef, error)

Value registers one value producer and returns its reusable reference.

type GoModelOption

type GoModelOption[D any] func(*GoModel[D])

GoModelOption configures a GoModel.

func WithGoInspectionCodec

func WithGoInspectionCodec[D any](codec GoInspectionCodec[D]) GoModelOption[D]

WithGoInspectionCodec replaces the default JSON-shaped conversion for D.

func WithGoSnapshotCodec

func WithGoSnapshotCodec[D any](codec GoSnapshotCodec[D]) GoModelOption[D]

WithGoSnapshotCodec replaces the default encoding/json codec for D.

type GoSnapshotCodec

type GoSnapshotCodec[D any] struct {
	Encode func(*D) ([]byte, error)
	Decode func([]byte) (*D, error)
}

GoSnapshotCodec controls the D portion of a session snapshot. Decode must construct a replacement value. The default is encoding/json. Applications whose D has a non-deterministic MarshalJSON must provide a deterministic codec because snapshot and revision reproducibility otherwise cannot be guaranteed.

type GoTextExpressionCodec

type GoTextExpressionCodec struct{}

GoTextExpressionCodec represents GoModel expressions as deterministic JSON text. The text contains stable registered function names and versions, never Go function pointers. It can therefore be inspected, edited, and resolved again against the same model-local registry during compilation.

func (GoTextExpressionCodec) FormatExpression

func (GoTextExpressionCodec) FormatExpression(_ TextExpressionRole, expression Expression) (string, error)

FormatExpression encodes one GoModel expression without changing its canonical Value representation.

func (GoTextExpressionCodec) ParseExpression

func (GoTextExpressionCodec) ParseExpression(_ TextExpressionRole, text string) (Expression, error)

ParseExpression decodes one GoModel expression and rejects trailing input.

type GoValue

type GoValue[D any] func(*D, ExecContext, []Value) (Value, error)

GoValue is a typed host value producer or location reader.

type GoValueRef

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

GoValueRef creates serializable value expressions.

func (GoValueRef) Expression

func (r GoValueRef) Expression(args ...Expression) Expression

func (GoValueRef) Get

func (r GoValueRef) Get(args ...Expression) Expression

Get returns this registered value computation as a serializable expression.

type HistoryKind

type HistoryKind uint8

HistoryKind distinguishes shallow from deep history, per SCXML "history" element semantics.

const (
	Shallow HistoryKind = iota
	Deep
)

func (HistoryKind) String

func (k HistoryKind) String() string

String implements fmt.Stringer.

type IDGenerator

type IDGenerator interface {
	NewID() SessionID
}

IDGenerator mints a fresh id, such as an Instance's SCXML 5.10 _sessionid. Swapping it for a deterministic stand-in, the way Clock abstracts time, lets a test assert against a specific id instead of only checking that some unpredictable text was assigned.

type IDGeneratorFunc

type IDGeneratorFunc func() SessionID

IDGeneratorFunc adapts a plain func into an IDGenerator, mirroring http.HandlerFunc.

func (IDGeneratorFunc) NewID

func (f IDGeneratorFunc) NewID() SessionID

NewID calls f.

type IDLocationFunc

type IDLocationFunc func(ExecContext, Identifier) error

IDLocationFunc is the Go datamodel profile's equivalent of SCXML's idlocation expression. It synchronously assigns a generated send or invoke ID while that element is being evaluated. Returning an error or panicking produces error.execution and aborts the element.

type IOProcessor

type IOProcessor interface {
	// Attach is called once, before Start, giving the processor a way to
	// deliver events back in later via Dispatcher.
	Attach(d Dispatcher)

	// Send dispatches req immediately. It is called synchronously, in-line,
	// while executing a microstep's action list, to preserve SCXML's
	// lock-step ordering of executable content -- implementations MUST
	// return quickly (hand off to their own goroutine and return) rather
	// than blocking on real delivery.
	Send(ctx context.Context, req SendRequest) error
}

IOProcessor is the seam every real side effect goes through: anything that reaches outside the process, or otherwise isn't pure and deterministic, is routed through here, so the interpreter core stays pure everywhere else. A replay-safe caller can swap in a suppressing implementation (see NoopIOProcessor and Rehydrate) without the interpreter noticing any difference.

var NoopIOProcessor IOProcessor = noopIOProcessor{}

NoopIOProcessor suppresses all outbound dispatch while reporting success. Replaying Instances use it so real-world effects are never repeated when reconstructing state from a Log (see replay.go).

type IOProcessorDescriber

type IOProcessorDescriber interface {
	IOProcessors() []IOProcessorInfo
}

IOProcessorDescriber is implemented by an IOProcessor that has an address to advertise for the current session -- one another session could use to reach it. An IOProcessor with no transport of its own (NoopIOProcessor, LocalIOProcessor) simply doesn't implement this: ExecContext.IOProcessors reports no entries for it rather than guessing at an address.

type IOProcessorInfo

type IOProcessorInfo struct {
	Type     Identifier
	Location Location
}

IOProcessorInfo describes one Event I/O Processor available to the current session, per SCXML 5.10's _ioprocessors: Type is the same value an action would put in SendOptions.Type / that arrives as SendRequest.Type to select this processor, and Location is the address a *different* session must set as its own SendOptions.Target in order to reach this session through that processor.

type Identifier

type Identifier string

Identifier is a dot-segmented, case-sensitive name. It is used uniformly for state IDs, event names, event descriptors (the "event" attribute of a transition), and SCXML send targets (including the special "#_..." forms).

const (
	ErrEventExecution     Identifier = "error.execution"
	ErrEventCommunication Identifier = "error.communication"
)

Platform event names placed on the internal queue by the interpreter itself, per SCXML 5.10.2 / C.1. These are ordinary event names, not Go errors: a chart reacts to them with transitions exactly like any other event.

const SCXMLEventProcessor Identifier = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"

SCXMLEventProcessor is the mandatory SCXML Event I/O Processor type and the default for a send that does not specify a type.

const SCXMLEventProcessorAlias Identifier = "scxml"

SCXMLEventProcessorAlias is the short SCXML processor name and the value required for Event.OriginType by Appendix C.1.

const SCXMLInvokeType Identifier = "http://www.w3.org/TR/scxml/"

SCXMLInvokeType is the standard handler type used when an invocation does not declare a type. Environments may bind it to a child-chart resolver.

func NewIdentifier

func NewIdentifier(s string) (Identifier, error)

NewIdentifier validates s and returns it as an Identifier. Use this for untrusted input (e.g. values read back from a database or external message); identifiers built from Go source literals in chart definitions can be used directly via the defined string type without calling this.

func PlatformErrorDetails

func PlatformErrorDetails(value Value) (classification Identifier, message string, ok bool)

PlatformErrorDetails extracts a canonical platform error.

func (Identifier) Compare

func (id Identifier) Compare(other Identifier) int

Compare gives a total, deterministic lexicographic order over Identifier values, for diagnostics, sorted dumps, and deterministic map-key iteration. It is NOT SCXML "document order" -- document order is an explicit sequence number assigned by Build, tracking declaration order in the Go builder calls, and must never be derived by sorting Identifiers.

func (Identifier) MarshalText

func (id Identifier) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (Identifier) Matches

func (id Identifier) Matches(eventName Identifier) bool

Matches reports whether id, used as an event descriptor, matches the event name eventName, per SCXML 3.12.1: a descriptor matches if it is an exact match or a token-prefix match of the event name. A bare "*" matches any event name; a trailing ".*" or "." is sugar with the same effect as the bare token-prefix it leaves behind once trimmed.

func (*Identifier) Scan

func (id *Identifier) Scan(src any) error

Scan implements database/sql.Scanner.

func (Identifier) Segments

func (id Identifier) Segments() []string

Segments splits id on ".".

func (Identifier) String

func (id Identifier) String() string

String implements fmt.Stringer.

func (*Identifier) UnmarshalText

func (id *Identifier) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

func (Identifier) Value

func (id Identifier) Value() (driver.Value, error)

Value implements database/sql/driver.Valuer.

type Instance

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

Instance is one running chart: a single goroutine driving the interpreter algorithm, exposed through plain method calls. No channel type ever appears in this public surface -- Send and Stop internally hand a request to the actor's own goroutine and wait for it to be accepted, but that plumbing is entirely unexported.

func (*Instance) Checkpoint

func (in *Instance) Checkpoint(ctx context.Context, save func(Snapshot) error) error

Checkpoint calls save with a stable Snapshot on the Instance's own goroutine. No event or timer can be applied until save returns, allowing a durable runtime to pair the Snapshot atomically with its current Log sequence. A successful save stops the Instance at that exact boundary; a failed save leaves it running so checkpointing can be retried. Because save runs on the actor goroutine, it must not call back into this Instance.

func (*Instance) Configuration

func (in *Instance) Configuration() []Identifier

Configuration returns the active state IDs as of the last completed macrostep.

func (*Instance) Deliver

func (in *Instance) Deliver(ctx context.Context, ev Event) error

Deliver implements Dispatcher, letting an attached IOProcessor feed events back into this Instance exactly as any other caller would via Send.

func (*Instance) Done

func (in *Instance) Done() <-chan struct{}

Done returns a channel that's closed once the interpreter goroutine has exited -- a top-level final state was reached, Stop was called, or a fatal error occurred -- the same condition Wait blocks on. Unlike Wait, Done never blocks by itself: it's meant for a non-blocking check (a select against it with a default case) or as one case among several in a larger select, e.g. an embedder deciding whether an Instance that has stopped on its own is now safe to discard.

func (*Instance) Err

func (in *Instance) Err() error

Err returns the sticky terminal error, or nil while the instance is healthy (including while it is still running).

func (*Instance) HasActiveInvokes

func (in *Instance) HasActiveInvokes(ctx context.Context) (bool, error)

HasActiveInvokes reports whether at least one <invoke> currently belongs to this Instance's active configuration -- true for as long as its invoking state remains entered, regardless of whether the invoked service itself has already finished (SCXML 6.4.2: cancellation on state exit, not on the service's own completion, is what ends an invocation). Snapshot and Rehydrate cannot capture or restart a real invocation (ADR 0010), so the actors package uses this to avoid paging out (and later Rehydrating) an actor while one is running -- see System's eviction paths.

func (*Instance) ID

func (in *Instance) ID() SessionID

ID returns this Instance's session id (SCXML 5.10's _sessionid). In order of precedence, it is an explicit WithSessionID, an id recorded in the Snapshot Restore was called with, or one minted by the configured IDGenerator. It is stable for the lifetime of the Instance.

func (*Instance) Inspect

func (in *Instance) Inspect(ctx context.Context) (InstanceInspection, error)

Inspect returns a stable live view without invoking snapshot codecs.

func (*Instance) Result

func (in *Instance) Result() (Value, error)

Result returns the data produced by the top-level final state. It is available after the Instance has completed naturally; a stopped or still running Instance has no final result.

func (*Instance) Send

func (in *Instance) Send(ctx context.Context, ev Event) error

Send places ev on the external queue, regardless of its incoming Type, and blocks until the resulting macrostep(s) have been fully processed and Configuration() reflects them, honoring ctx.

func (*Instance) Snapshot

func (in *Instance) Snapshot(ctx context.Context) (Snapshot, error)

Snapshot captures this Instance's current state (safely, by running on the interpreter's own goroutine), suitable for persisting and later passing to Restore.

func (*Instance) Start

func (in *Instance) Start(ctx context.Context) error

Start spawns the interpreter goroutine, enters the chart's initial configuration, and drains to the first stable point before returning. ctx bounds only this handshake, not the goroutine's subsequent lifetime.

func (*Instance) Stop

func (in *Instance) Stop(ctx context.Context) error

Stop is modeled as SCXML's own cancellation: a message on the same ingress path as any Send, giving it the same FIFO ordering guarantee relative to in-flight Sends that a second, separate channel could not. Stopping an already-stopped Instance is not an error.

func (*Instance) Wait

func (in *Instance) Wait(ctx context.Context) error

Wait blocks until the interpreter goroutine has exited (a top-level final state was reached, Stop was called, or a fatal error occurred), honoring ctx, and returns the terminal error (nil on clean exit).

type InstanceInspection

type InstanceInspection struct {
	SessionID     SessionID
	Revision      RevisionID
	Running       bool
	Configuration []Identifier
	HistoryValue  map[Identifier][]Identifier
	Datamodel     Value
	InternalQueue []Event
	ExternalQueue []Event
	PendingSends  []PendingSend
	ActiveInvokes []ActiveInvoke
}

InstanceInspection is an independently owned, point-in-time view captured at a stable macrostep boundary on an Instance's goroutine.

type InvokeDefinition

type InvokeDefinition struct {
	DefinitionID Identifier        `json:"definitionId,omitempty"`
	ID           Identifier        `json:"id,omitempty"`
	IDLocation   *Expression       `json:"idLocation,omitempty"`
	Type         string            `json:"type,omitempty"`
	TypeExpr     *Expression       `json:"typeExpr,omitempty"`
	Src          string            `json:"src,omitempty"`
	SrcExpr      *Expression       `json:"srcExpr,omitempty"`
	Params       []ParamDefinition `json:"params,omitempty"`
	Content      *Expression       `json:"content,omitempty"`
	AutoForward  bool              `json:"autoForward,omitempty"`
	Finalize     []ExecutableBlock `json:"finalize,omitempty"`
}

InvokeDefinition contains invocation declaration data only. Src is a static source while SrcExpr is a datamodel expression; preserving both forms avoids making static source identity model-dependent. Content is one whole payload and is not a synthetic parameter map.

type InvokeHandler

type InvokeHandler interface {
	Start(context.Context, InvokeRequest, InvokeIO) (Value, error)
}

InvokeHandler implements one environment-provided invocation type. The runtime cancels ctx when the owning state exits and owns event delivery, completion, and error classification around the handler call.

type InvokeHandlerFactory

type InvokeHandlerFactory func() InvokeHandler

InvokeHandlerFactory creates isolated mutable handler state for one start or resume. Registrations are Instance- or System-scoped, never global.

func InvokeChartHandler

func InvokeChartHandler(chart *Chart, baseIO IOProcessor) InvokeHandlerFactory

InvokeChartHandler returns an environment-scoped handler factory for the standard child-chart invocation type. The child chart and fallback transport remain runtime capabilities; only the invocation source key belongs in a serializable InvokeDefinition. Child input should be modeled explicitly by the child definition rather than by replacing its model.

type InvokeHandlerFunc

type InvokeHandlerFunc func(context.Context, InvokeRequest, InvokeIO) (Value, error)

InvokeHandlerFunc adapts a function to InvokeHandler.

func (InvokeHandlerFunc) Start

func (fn InvokeHandlerFunc) Start(ctx context.Context, request InvokeRequest, io InvokeIO) (Value, error)

type InvokeIO

type InvokeIO struct {
	// Deliver posts ev to the invoking chart's external event queue,
	// tagged with this invocation's InvokeID, exactly as SCXML 6.4
	// requires of events an invoked service returns. Safe to call from any
	// goroutine at any time, including after the invocation has been
	// cancelled (a no-op once cancelled, per SCXML 6.4.3: a cancelled
	// invocation's events "MUST NOT" reach the invoking session).
	Deliver func(Event)

	// Incoming delivers events sent to this invocation from the chart --
	// via Send(name, SendOptions{Target: "#_<invokeid>"}), SCXML 6.4.4's
	// addressing form for talking back to a running invocation. Always
	// non-nil; a service that never expects inbound traffic simply never
	// reads from it.
	Incoming <-chan Event
}

InvokeIO is an invoked service's only channel back into (and, for services that accept forwarded events, in from) the chart that invoked it -- the <invoke> analogue of IOProcessor for the rest of the world.

type InvokeOption

type InvokeOption func(*InvokeDefinition)

InvokeOption appends serializable authoring data to an invocation.

func WithAutoForward

func WithAutoForward() InvokeOption

WithAutoForward forwards each external event to the active invocation.

func WithFinalize

func WithFinalize(actions ...Executable) InvokeOption

WithFinalize attaches executable blocks run before processing an event returned by this invocation. Each call creates one independent block.

func WithInvokeContent

func WithInvokeContent(content Expression) InvokeOption

WithInvokeContent sets one whole invocation input expression.

func WithInvokeDefinitionID

func WithInvokeDefinitionID(id Identifier) InvokeOption

WithInvokeDefinitionID sets the stable declaration identity used by snapshots and hot-deployment revision pinning.

func WithInvokeID

func WithInvokeID(id Identifier) InvokeOption

WithInvokeID sets an explicit invoke ID, e.g. for targeting it later via SendOptions{Target: "#_" + id}. Left unset, an ID unique within the session is generated when the invocation actually starts.

func WithInvokeIDLocation

func WithInvokeIDLocation(location Expression) InvokeOption

WithInvokeIDLocation stores a generated runtime ID in a model location.

func WithInvokeParams

func WithInvokeParams(params ...ParamDefinition) InvokeOption

WithInvokeParams sets named invocation input expressions.

func WithInvokeSourceExpression

func WithInvokeSourceExpression(expression Expression) InvokeOption

WithInvokeSourceExpression selects a handler source from the datamodel.

func WithInvokeTypeExpression

func WithInvokeTypeExpression(expression Expression) InvokeOption

WithInvokeTypeExpression selects a handler type from the datamodel.

type InvokeRequest

type InvokeRequest struct {
	DefinitionID Identifier
	ID           Identifier
	Type         Identifier
	Source       string
	Data         Value
}

InvokeRequest is the fully evaluated, immutable input to one invocation. DefinitionID identifies the declaration across definition edits; ID is the runtime address visible to events and #_<invokeid> sends.

type IterationBindings

type IterationBindings struct {
	Item  CompiledExpression
	Index CompiledExpression
}

IterationBindings identifies model-owned locations receiving the current item and optional index during ForEach.

type LocalIOProcessor

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

LocalIOProcessor is the default IOProcessor for a single, non-distributed Instance: it has no transport for genuinely external targets and reports that honestly, rather than silently discarding. Internal/self dispatch never reaches an IOProcessor at all (see SendRequest), so this type has nothing to do for that traffic either.

func NewLocalIOProcessor

func NewLocalIOProcessor() *LocalIOProcessor

NewLocalIOProcessor returns the default IOProcessor.

func (*LocalIOProcessor) Attach

func (p *LocalIOProcessor) Attach(d Dispatcher)

func (*LocalIOProcessor) IOProcessors

func (p *LocalIOProcessor) IOProcessors() []IOProcessorInfo

IOProcessors advertises this session's mandatory SCXML processor address.

func (*LocalIOProcessor) Send

func (p *LocalIOProcessor) Send(ctx context.Context, req SendRequest) error

type Location

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

Location is the address an IOProcessor advertises for the current session (IOProcessorInfo.Location) -- what a different session sets as its own SendOptions.Target to reach this one, per SCXML 5.10's _ioprocessors. It wraps a parsed net/url.URL, so a real network address (scheme, host, path) round-trips with genuine structure a real transport can inspect, while an opaque in-process token -- an actor's own dotted name, say -- is exactly as easy to construct as it always was: net/url does not require a scheme or host, so "locator-1" parses just as validly as "https://example.com/svc", and both round-trip through String() unchanged.

func LocationFromIdentifier

func LocationFromIdentifier(id Identifier) Location

LocationFromIdentifier converts id directly to a Location. It never fails: id's own construction (NewIdentifier) already restricts it to characters that are always a valid URL path segment with no escaping needed, so parsing id's text can never produce an error here.

func NewLocation

func NewLocation(s string) (Location, error)

NewLocation parses s as a URL and returns the result. The only strings it rejects are genuinely malformed ones (bad percent-encoding, a stray control character) -- the absence of a scheme or host is not malformed, it is simply an opaque, schemeless Location.

func (Location) String

func (loc Location) String() string

String implements fmt.Stringer, rendering loc back to the same text form a SendOptions.Target expects.

func (Location) URL

func (loc Location) URL() *url.URL

URL returns the *url.URL backing loc, for a caller that wants to inspect scheme/host/path directly -- e.g. a real network IOProcessor deciding how to dial the address a peer advertised.

type Log

type Log interface {
	// Append durably records entry, assigning the next Seq for
	// entry.SessionID (entry.Seq is ignored on input), and returns the
	// assigned Seq. Callers must Append before applying entry's effects
	// (write-ahead): a crash between Append and full processing just means
	// replay reprocesses that entry from the last stable configuration,
	// which is safe since processing is deterministic and replay's
	// IOProcessor is suppressed.
	Append(ctx context.Context, entry LogEntry) (seq uint64, err error)

	// Read streams entries for sessionID in ascending Seq order, starting
	// at from (inclusive). Iteration stops as soon as the consumer stops
	// ranging, or on the first yielded error.
	Read(ctx context.Context, sessionID SessionID, from uint64) iter.Seq2[LogEntry, error]

	// LastSeq returns the highest Seq recorded for sessionID, or 0 if none.
	LastSeq(ctx context.Context, sessionID SessionID) (uint64, error)
}

Log is the primary persistence mechanism for a chart: recording every inbound message lets a session's state be reconstructed exactly by replaying them, rather than by directly serializing the datamodel.

type LogDefinition

type LogDefinition struct {
	Label     string      `json:"label,omitempty"`
	LabelExpr *Expression `json:"labelExpr,omitempty"`
	Expr      *Expression `json:"expr,omitempty"`
}

LogDefinition records one diagnostic value. Label and LabelExpr are mutually exclusive; Expr may be omitted to log canonical null.

type LogEntry

type LogEntry struct {
	SessionID SessionID
	Seq       uint64
	Kind      EntryKind

	// Timestamp is the entry's logical append time (UTC), obtained from the
	// same configured Clock that drives the live Instance. It is used as
	// "now" during replay when recomputing a new pending send's FireAt
	// (FireAt = Timestamp + delay) -- replay never consults the real clock.
	Timestamp time.Time

	Event Event // the inbound event (for KindTimerFired, the event that fired)

	SendID Identifier // KindTimerFired only
	Target Identifier // KindTimerFired only
	Type   Identifier // KindTimerFired only; original I/O processor selector
}

LogEntry is one recorded message. SessionID+Seq is monotonically increasing and gapless, assigned by Log.Append.

type Logger

type Logger interface {
	// Log records one diagnostic entry. It is called synchronously, inline,
	// from the interpreter's own goroutine, and is not expected to hand off
	// to a goroutine of its own the way IOProcessor.Send is -- a Logger call
	// is a local, in-process write, not real I/O.
	Log(label string, data Value)
}

Logger is where ExecContext.Log's output goes: a chart's diagnostic output, distinct from any event traffic or persisted Log entry. label and data mirror <log>'s own label-plus-expr shape.

var NoopLogger Logger = noopLogger{}

NoopLogger discards every call. It is the default Logger for an Instance built with no WithLogger option, so a chart that never calls ExecContext.Log behaves identically whether or not WithLogger was ever configured.

type MacrostepObserver

type MacrostepObserver interface {
	Enabled() bool
	ObserveMacrostep(MacrostepTrace)
}

MacrostepObserver receives diagnostic descriptions of completed live macrosteps. Enabled and ObserveMacrostep execute on the Instance's actor goroutine and must return promptly. Panics are recovered and ignored.

type MacrostepObserverFunc

type MacrostepObserverFunc func(MacrostepTrace)

MacrostepObserverFunc adapts a function to an always-enabled observer.

func (MacrostepObserverFunc) Enabled

func (f MacrostepObserverFunc) Enabled() bool

func (MacrostepObserverFunc) ObserveMacrostep

func (f MacrostepObserverFunc) ObserveMacrostep(trace MacrostepTrace)

type MacrostepTrace

type MacrostepTrace struct {
	Sequence      uint64
	Timestamp     time.Time
	Trigger       *Event
	Before        []Identifier
	Microsteps    []MicrostepTrace
	After         []Identifier
	Terminal      bool
	TerminalError string
}

MacrostepTrace describes one completed macrostep. All values in a delivered trace are independently owned and may be mutated by the observer. Sequence starts at one for each live Instance activation and is unrelated to any durable log sequence. Timestamp is read from the configured Clock and normalized to UTC.

func (MacrostepTrace) Clone

func (trace MacrostepTrace) Clone() MacrostepTrace

Clone returns an independently owned copy of the trace, including all events, values, and slices.

type ManualClock

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

ManualClock is a Clock for tests: time only advances when Advance is called, and pending callbacks fire synchronously, in fire-time order, as part of that call instead of on a real timer goroutine.

func NewManualClock

func NewManualClock(start time.Time) *ManualClock

NewManualClock returns a ManualClock starting at start.

func (*ManualClock) Advance

func (c *ManualClock) Advance(d time.Duration)

Advance moves the clock forward by d, then synchronously fires (in fire-time order) any timers now due.

func (*ManualClock) AfterFunc

func (c *ManualClock) AfterFunc(d time.Duration, f func()) func() bool

func (*ManualClock) Now

func (c *ManualClock) Now() time.Time

type ManualIDGenerator

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

ManualIDGenerator is an IDGenerator for tests. Each call to NewID returns the next value from a sequential counter ("id-1", "id-2", ...) instead of random text, so a test can assert against a specific, reproducible id rather than merely checking that one was assigned.

func (*ManualIDGenerator) NewID

func (g *ManualIDGenerator) NewID() SessionID

NewID returns the next sequential id, starting at "id-1".

type MicrostepTrace

type MicrostepTrace struct {
	Trigger     *Event
	Transitions []TransitionRef
	Exited      []Identifier
	Entered     []Identifier
}

MicrostepTrace describes one selected transition set and its state changes.

type Option

type Option func(*instanceConfig)

Option configures an Instance built by New or Restore.

func WithClock

func WithClock(clk Clock) Option

WithClock sets the Clock used for delayed-send timers. Defaults to the real wall clock.

func WithCompletionHook

func WithCompletionHook(fn func() error) Option

WithCompletionHook registers a callback run exactly once when the Instance reaches a top-level final state. It runs synchronously on the interpreter goroutine after exit processing and before Send, Start, or Done can report completion. Returning an error makes completion fail and records that error as the Instance's terminal error. Stop, Checkpoint, and fatal interpreter exits do not invoke the hook.

func WithDeliveryNamespace

func WithDeliveryNamespace(namespace string) Option

WithDeliveryNamespace sets a stable external-delivery namespace. It is primarily intended for durable runtimes; ordinary instances receive a unique incarnation namespace automatically.

func WithIDGenerator

func WithIDGenerator(g IDGenerator) Option

WithIDGenerator sets the IDGenerator used to mint this Instance's session id (SCXML 5.10's _sessionid) when no explicit WithSessionID is given. Defaults to IDGeneratorFunc(rand.Text) (crypto/rand.Text). Tests that need a reproducible id instead of random text can pass a ManualIDGenerator.

func WithIOProcessor

func WithIOProcessor(typ Identifier, p IOProcessor) Option

WithIOProcessor sets the IOProcessor used for genuinely external dispatch. Defaults to LocalIOProcessor, which reports unreachable targets instead of silently discarding them.

func WithInboxSize

func WithInboxSize(n int) Option

WithInboxSize sets the buffer size of the actor's ingress channel, i.e. how many in-flight Send/Stop calls can be accepted before callers start experiencing backpressure. Defaults to 1.

func WithIngressHook

func WithIngressHook(fn func(Event) error) Option

WithIngressHook registers a callback run on the Instance's goroutine immediately before every event accepted through Send or Deliver is applied. Returning an error rejects the event and terminates the Instance: a durable runtime cannot safely continue after its write-ahead record failed. Rehydrate suppresses this hook while replaying entries already in the Log, then enables it for new live arrivals.

func WithInvokeHandler

func WithInvokeHandler(typ Identifier, factory InvokeHandlerFactory) Option

WithInvokeHandler binds one declarative invocation type in this Instance's environment. The factory is called once per live start or resume; static requirements are validated before a datamodel session is created.

func WithLogger

func WithLogger(l Logger) Option

WithLogger sets the Logger that ExecContext.Log calls are routed to. Defaults to NoopLogger.

func WithMacrostepObserver

func WithMacrostepObserver(observer MacrostepObserver) Option

WithMacrostepObserver installs a diagnostic observer for completed live macrosteps. Its Enabled method is consulted at the start of every macrostep, so an observer may cheaply toggle collection after an Instance has started.

func WithPlatformVariables

func WithPlatformVariables(values map[string]any) Option

WithPlatformVariables supplies the opaque capabilities exposed through SCXML's protected _x system-variable root. The binding map is copied; callers must provide the option again to Restore or Rehydrate because platform capabilities are runtime configuration, not snapshot data.

func WithSessionID

func WithSessionID(id SessionID) Option

WithSessionID pins this Instance's session id (SCXML 5.10's _sessionid) to an id the caller already has -- e.g. from a Log -- instead of minting a fresh one. It takes priority over both the configured IDGenerator and, for Restore, any id recorded in the Snapshot being restored from.

func WithTimerFiredDetailsHook

func WithTimerFiredDetailsHook(fn func(sendID, target, typ Identifier, ev Event) error) Option

WithTimerFiredDetailsHook is WithTimerFiredHook's metadata-preserving counterpart. In addition to the generated send ID and event, fn receives the original send target and I/O processor type so a durable log can reconstruct the dispatch even if its pending-send record is unavailable.

func WithTimerFiredHook

func WithTimerFiredHook(fn func(sendID Identifier, ev Event) error) Option

WithTimerFiredHook registers a callback invoked synchronously, on the interpreter's own goroutine, immediately before a fired delayed-send's event is applied. It exists so a Log implementation can satisfy the write-ahead requirement (record the message, then let it be applied) for the one kind of inbound message that has no explicit Instance.Send call site for an application to hook itself -- see log.go, LoggingTimerFiredHook. A non-nil returned error is treated as this Instance's fatal terminal error (surfaced via Err()/Wait()) rather than silently letting the event through.

type OutboundMessage

type OutboundMessage struct {
	SessionID  SessionID
	DeliveryID DeliveryID
	Request    SendRequest
	Status     OutboundStatus
	Result     OutboundResult
}

OutboundMessage is one stable external send intent.

type OutboundResult

type OutboundResult struct {
	Error     string
	Execution bool
	// Synchronous distinguishes an outcome returned by Send (and therefore
	// reproduced during replay) from a callback/recovery failure, which is
	// reported as a separate inbound platform event.
	Synchronous bool
}

OutboundResult is the recorded synchronous/asynchronous outcome.

type OutboundStatus

type OutboundStatus string

OutboundStatus is the durable lifecycle of an external send intent.

const (
	OutboundPending  OutboundStatus = "pending"
	OutboundResolved OutboundStatus = "resolved"
)

type ParamDefinition

type ParamDefinition struct {
	Name     Identifier  `json:"name"`
	Expr     *Expression `json:"expr,omitempty"`
	Location *Expression `json:"location,omitempty"`
}

ParamDefinition binds one named invocation or done-data parameter. Exactly one of Expr and Location must be present. Location lets the selected datamodel read a value from one of its own locations without exposing that representation to the interpreter.

type PendingSend

type PendingSend struct {
	SendID Identifier
	Target Identifier
	Type   Identifier
	Event  Event
	FireAt time.Time
}

PendingSend describes one delayed <send> that has not yet fired or been cancelled. FireAt is absolute so a restored Instance can re-arm it against its configured Clock, firing it during Start if it is already overdue.

type RaiseDefinition

type RaiseDefinition struct {
	Event     Identifier  `json:"event,omitempty"`
	EventExpr *Expression `json:"eventExpr,omitempty"`
	Data      *Expression `json:"data,omitempty"`
}

RaiseDefinition raises an internal event. Exactly one of Event and EventExpr is required. Data, when present, computes its canonical payload.

type ReplayAwareIOProcessor

type ReplayAwareIOProcessor interface {
	IOProcessor
	ReplaySend(ctx context.Context, req SendRequest) error
	Recover(ctx context.Context) error
}

ReplayAwareIOProcessor lets a durable processor reproduce the recorded synchronous result of a send while deterministic replay is in progress, and hand persisted work back to its transport once replay has caught up. Implementations must not perform external I/O from ReplaySend.

type ResumableInvokeHandler

type ResumableInvokeHandler interface {
	InvokeHandler
	Resume(context.Context, InvokeRequest, InvokeIO) (Value, error)
}

ResumableInvokeHandler can reattach to an invocation that was active when a durable process stopped. A fresh handler is created for every resume.

type RevisionID

type RevisionID string

RevisionID is the deterministic identity of one compiled Chart revision. Version 1 IDs are "sha256:" followed by 64 lowercase hexadecimal digits.

type ScriptDefinition

type ScriptDefinition struct {
	Expr Expression `json:"expr"`
}

ScriptDefinition executes one datamodel-owned script expression.

type SendDefinition

type SendDefinition struct {
	Event      Identifier        `json:"event,omitempty"`
	EventExpr  *Expression       `json:"eventExpr,omitempty"`
	Target     string            `json:"target,omitempty"`
	TargetExpr *Expression       `json:"targetExpr,omitempty"`
	Type       string            `json:"type,omitempty"`
	TypeExpr   *Expression       `json:"typeExpr,omitempty"`
	ID         Identifier        `json:"id,omitempty"`
	IDLocation *Expression       `json:"idLocation,omitempty"`
	Delay      string            `json:"delay,omitempty"`
	DelayExpr  *Expression       `json:"delayExpr,omitempty"`
	Params     []ParamDefinition `json:"params,omitempty"`
	Content    *Expression       `json:"content,omitempty"`
}

SendDefinition describes one send operation. Static and expression forms of each field are mutually exclusive. Content is one whole payload and is mutually exclusive with Params.

type SendExecutionError

type SendExecutionError interface {
	error
	SendExecutionError()
}

SendExecutionError marks an IOProcessor.Send failure as an invalid or unsupported target/type rather than a delivery failure. The interpreter turns marked errors into error.execution; all other Send errors become error.communication.

type SendOption

type SendOption func(*SendDefinition)

SendOption configures serializable send executable content.

func SendContent

func SendContent(content Expression) SendOption

SendContent sets the whole event payload expression.

func SendDelay

func SendDelay(delay time.Duration) SendOption

SendDelay delays delivery by a static duration.

func SendID

func SendID(id Identifier) SendOption

SendID sets the author-visible send ID.

func SendIDLocation

func SendIDLocation(location Expression) SendOption

SendIDLocation stores a generated send ID at a model-owned location.

func SendParams

func SendParams(params ...ParamDefinition) SendOption

SendParams sets named event payload expressions.

func SendTarget

func SendTarget(target string) SendOption

SendTarget sets a static send target.

func SendType

func SendType(typ string) SendOption

SendType sets the static I/O processor type.

type SendOptions

type SendOptions struct {
	SendID     Identifier     // author-visible ID; empty uses an unexposed execution ID
	IDLocation IDLocationFunc // assigns a generated, author-visible ID; mutually exclusive with SendID
	Target     Identifier     // "" = own external queue (default); "#_internal"; or an external target
	Type       Identifier     // IOProcessor selector, meaningful for external targets only
	Data       Value
	Delay      time.Duration // 0 = dispatch immediately
}

SendOptions configures a scheduled event, mirroring <send>'s attributes.

type SendRequest

type SendRequest struct {
	DeliveryID  DeliveryID
	SendID      Identifier // execution ID, including an implementation-generated ID
	EventSendID Identifier // author-specified ID exposed as _event.sendid; empty if none
	Target      Identifier
	Type        Identifier
	Event       Identifier
	Data        Value
}

SendRequest is a request to dispatch an event to a genuinely external target (Target is neither "" nor "#_internal" -- those two are handled directly by the interpreter core, never reaching an IOProcessor, since they aren't real I/O). By the time an IOProcessor sees a SendRequest, any <send delay="..."> has already elapsed: delay-timer bookkeeping belongs to the interpreter core, so Send is always an immediate dispatch request.

type SessionID

type SessionID string

SessionID identifies one running session -- SCXML 5.10's _sessionid: an Instance's identity for logging, snapshotting, and Rehydrate. Unlike Identifier, it is an opaque token: it has no dot-segment structure, is never matched as an event descriptor, and is never split into segments.

func (SessionID) Compare

func (id SessionID) Compare(other SessionID) int

Compare gives a total, deterministic lexicographic order over SessionID values, for diagnostics, sorted dumps, and deterministic map-key iteration.

func (SessionID) MarshalText

func (id SessionID) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (*SessionID) Scan

func (id *SessionID) Scan(src any) error

Scan implements database/sql.Scanner.

func (SessionID) String

func (id SessionID) String() string

String implements fmt.Stringer.

func (*SessionID) UnmarshalText

func (id *SessionID) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

func (SessionID) Value

func (id SessionID) Value() (driver.Value, error)

Value implements database/sql/driver.Valuer.

type SessionOptions

type SessionOptions struct{}

SessionOptions configures creation of one datamodel session. Interpreter environment such as the current event, configuration, identity, and platform values is deliberately supplied per operation through ExecContext rather than cached here.

type Snapshot

type Snapshot struct {
	Version           int
	Revision          RevisionID
	Datamodel         []byte
	ID                SessionID // SCXML 5.10's _sessionid for this session; Restore preserves it unless WithSessionID overrides it
	Configuration     []Identifier
	HistoryValue      map[Identifier][]Identifier
	InternalQueue     []Event
	ExternalQueue     []Event
	Running           bool
	SendSeq           int // high-water mark for auto-generated send.<n> IDs
	InvokeSeq         int // high-water mark for auto-generated <state>.invoke<n> IDs
	DispatchSeq       uint64
	DeliveryNamespace string
	PendingSends      []PendingSend
	ActiveInvokes     []ActiveInvoke
	InitializedData   []Identifier
}

Snapshot is a point-in-time cache of a running chart's datamodel and interpreter state: the session id, active configuration, recorded history, both event queues, running flag, outstanding delayed sends, and active invocation bookkeeping. Datamodel bytes are an opaque cache owned by the instance's DatamodelSession.

Snapshot is a derivable checkpoint, not an independent source of truth: it captures exactly what replaying a Log up to some point would produce, and exists purely so a cold start doesn't need to replay from the beginning every time (see Checkpoint, Rehydrate in replay.go).

func (Snapshot) MarshalJSON

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

MarshalJSON implements json.Marshaler.

func (*Snapshot) UnmarshalJSON

func (s *Snapshot) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler.

type SnapshotStore

type SnapshotStore interface {
	Save(ctx context.Context, sessionID SessionID, cp Checkpoint) error
	Load(ctx context.Context, sessionID SessionID) (Checkpoint, bool, error)
}

SnapshotStore persists Checkpoints, letting Rehydrate skip replaying a Log from the very beginning.

type StateDefinition

type StateDefinition struct {
	ID          StateDefinitionID      `json:"id"`
	Kind        StateKind              `json:"kind"`
	History     HistoryKind            `json:"history,omitempty"`
	Initial     *TransitionDefinition  `json:"initial,omitempty"`
	OnEntry     []ExecutableBlock      `json:"onEntry,omitempty"`
	OnExit      []ExecutableBlock      `json:"onExit,omitempty"`
	Transitions []TransitionDefinition `json:"transitions,omitempty"`
	Invokes     []InvokeDefinition     `json:"invokes,omitempty"`
	Data        []DataDefinition       `json:"data,omitempty"`
	DoneData    *DoneDataDefinition    `json:"doneData,omitempty"`
	Children    []StateDefinition      `json:"children,omitempty"`
}

StateDefinition describes one state-tree node in document order.

func Atomic

func Atomic(id Identifier, opts ...StateOption) StateDefinition

Atomic declares a leaf state with no children.

func Compound

func Compound(id Identifier, initial Identifier, opts ...StateOption) StateDefinition

Compound declares a state with children. initial is the first target of its default transition; WithInitial may add targets and executable content. If initial is empty and WithInitial is absent, the first child is the default, as required by SCXML.

func Final

func Final(id Identifier, opts ...StateOption) StateDefinition

Final declares a final state.

func History

func History(id Identifier, kind HistoryKind, defaultTarget Identifier, opts ...TransitionOption) StateDefinition

History declares a history pseudostate belonging to whichever compound or parallel state contains it. defaultTarget is entered when the history has never recorded a configuration (i.e. on first entry to the parent). opts may add targets and executable content to that default transition.

func Parallel

func Parallel(id Identifier, opts ...StateOption) StateDefinition

Parallel declares a state whose children are all simultaneously active (each child is one region).

type StateDefinitionID

type StateDefinitionID struct {
	Value     Identifier `json:"value,omitempty"`
	Generated bool       `json:"generated,omitempty"`
}

StateDefinitionID preserves whether a state's ID was written explicitly or generated by the builder. A generated ID may have an empty Value before validation/canonicalization; normalization assigns it deterministically on a private clone and never mutates caller-owned input.

type StateKind

type StateKind uint8

StateKind is the kind of node in a chart's state tree.

const (
	KindAtomic StateKind = iota
	KindCompound
	KindParallel
	KindFinal
	KindHistory
)

func (StateKind) String

func (k StateKind) String() string

String implements fmt.Stringer.

type StateOption

type StateOption func(*StateDefinition)

StateOption appends serializable authoring data to a state definition.

func Children

func Children(children ...StateDefinition) StateOption

Children attaches child states, in document order.

func Eventless

func Eventless(opts ...TransitionOption) StateOption

Eventless attaches an eventless (automatic) transition, evaluated whenever no event is required for it to fire -- only its Cond gates it.

func Invoke

func Invoke(typ, source string, opts ...InvokeOption) StateOption

Invoke attaches a declarative external service to a state. Runtime behavior is supplied separately through WithInvokeHandler.

func On

func On(events string, opts ...TransitionOption) StateOption

On attaches a transition matching the space-separated event descriptors.

func OnEntry

func OnEntry(actions ...Executable) StateOption

OnEntry attaches executable content run when the state is entered.

func OnExit

func OnExit(actions ...Executable) StateOption

OnExit attaches executable content run when the state is exited.

func WithDone

func WithDone(content Expression) StateOption

WithDone sets a final state's whole result payload expression.

func WithDoneParams

func WithDoneParams(params ...ParamDefinition) StateOption

WithDoneParams sets a final state's named result payload expressions.

func WithInitial

func WithInitial(opts ...TransitionOption) StateOption

WithInitial configures a compound state's eventless, unconditional default transition. The initial argument passed to Compound remains its first target; pass an empty initial when Target supplies every target.

type TextExpressionCodec

type TextExpressionCodec interface {
	ParseExpression(role TextExpressionRole, text string) (Expression, error)
	FormatExpression(role TextExpressionRole, expression Expression) (string, error)
}

TextExpressionCodec is the optional bridge between a datamodel's canonical Expression values and expression text in a surface syntax. It is owned by the datamodel rather than by Definition or any particular syntax package.

type TextExpressionRole

type TextExpressionRole string

TextExpressionRole describes how a textual surface syntax will use an expression. Datamodel codecs may use the role to parse context-sensitive expression forms; models with one source language may ignore it.

const (
	TextExpressionValue     TextExpressionRole = "value"
	TextExpressionCondition TextExpressionRole = "condition"
	TextExpressionLocation  TextExpressionRole = "location"
	TextExpressionScript    TextExpressionRole = "script"
	TextExpressionEvent     TextExpressionRole = "event"
	TextExpressionTarget    TextExpressionRole = "target"
	TextExpressionType      TextExpressionRole = "type"
	TextExpressionSendID    TextExpressionRole = "send-id"
	TextExpressionDelay     TextExpressionRole = "delay"
	TextExpressionLabel     TextExpressionRole = "label"
	TextExpressionArray     TextExpressionRole = "array"
)

type TransitionDefinition

type TransitionDefinition struct {
	Events    []Identifier      `json:"events,omitempty"`
	Targets   []Identifier      `json:"targets,omitempty"`
	Type      TransitionType    `json:"type,omitempty"`
	Condition *Expression       `json:"condition,omitempty"`
	Actions   []ExecutableBlock `json:"actions,omitempty"`
}

TransitionDefinition preserves event, target, condition, executable-block, and transition-type ordering. Empty Events means eventless; empty Targets means targetless.

type TransitionOption

type TransitionOption func(*TransitionDefinition)

TransitionOption appends serializable authoring data to a transition.

func AsInternal

func AsInternal() TransitionOption

AsInternal marks the transition as SCXML transition type="internal".

func If

func If(condition Expression) TransitionOption

If sets the transition's guard condition.

func Target

func Target(ids ...Identifier) TransitionOption

Target sets the transition's target state(s) (more than one for a transition that enters multiple parallel regions).

func Then

func Then(actions ...Executable) TransitionOption

Then attaches executable content run when the transition fires.

type TransitionRef

type TransitionRef struct {
	Source Identifier
	Index  int
}

TransitionRef identifies a transition in the pinned immutable definition. Index is zero-based within Source's StateDefinition.Transitions.

type TransitionType

type TransitionType string

TransitionType describes SCXML external versus internal transition entry semantics. The zero value means TransitionExternal.

const (
	TransitionExternal TransitionType = "external"
	TransitionInternal TransitionType = "internal"
)

type Value

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

Value is syntax-neutral data that can cross datamodel, actor, process, and durability boundaries. Its variants are null, boolean, UTF-8 string, exact decimal number, ordered list, string-keyed map, and a tagged application value whose payload is another Value.

Value is immutable through its public API: constructors copy collection inputs, collection accessors return copies, and Clone makes a deep copy. The zero value is the canonical null value.

Numbers are finite decimal values, not float64 values. NumberValue accepts RFC 8259 JSON number syntax and stores a canonical coefficient/exponent representation without imposing an integer size or silently rounding.

func BoolValue

func BoolValue(value bool) Value

BoolValue returns a canonical boolean Value.

func Float64Value

func Float64Value(value float64) (Value, error)

Float64Value returns a canonical number for the exact decimal spelling Go uses to round-trip value. NaN and infinities are rejected because they are not canonical data numbers.

func Int64Value

func Int64Value(value int64) Value

Int64Value returns an exact canonical number for value.

func ListValue

func ListValue(values []Value) Value

ListValue returns an ordered list and deeply copies values.

func MapValue

func MapValue(values map[string]Value) (Value, error)

MapValue returns a string-keyed map and deeply copies values. It rejects invalid UTF-8 keys. Map order is not semantically significant; canonical encodings sort keys.

func NullValue

func NullValue() Value

NullValue returns the canonical null Value. It is equal to a zero Value.

func NumberValue

func NumberValue(value string) (Value, error)

NumberValue parses an RFC 8259 JSON number without converting it through a floating-point type. Mathematically equal spellings have the same canonical representation: insignificant zeroes and a negative sign on zero are removed, and a base-10 exponent is used when necessary.

func PlatformErrorValue

func PlatformErrorValue(classification Identifier, err error) Value

PlatformErrorValue converts err into canonical data with a stable classification and message. Invalid UTF-8 is replaced deterministically so an error can never make a durable event unencodable.

func StringValue

func StringValue(value string) (Value, error)

StringValue returns a canonical string Value. It rejects invalid UTF-8 at construction rather than creating a Value that cannot be persisted.

func TaggedValue

func TaggedValue(tag string, payload Value) (Value, error)

TaggedValue returns an application value identified by tag. A tag is a stable application-defined type identifier; it must be non-empty UTF-8. The canonical payload is deeply copied and needs no concrete-type registry to decode.

func Uint64Value

func Uint64Value(value uint64) Value

Uint64Value returns an exact canonical number for value.

func ValueFromJSON

func ValueFromJSON(input any) (Value, error)

ValueFromJSON converts a JSON-shaped Go value to a canonical Value without reflection. It accepts nil, bool, string, json.Number, finite float types, integer types, []any, and map[string]any. Use json.Decoder.UseNumber before this conversion when decoding JSON whose integer precision must be kept.

func ValueFromWire

func ValueFromWire(wire ValueWire) (Value, error)

ValueFromWire validates wire and returns an immutable Value. Unknown versions, unknown kinds, malformed union combinations, invalid numbers, invalid UTF-8, and malformed tagged values are rejected.

func (Value) AsBool

func (v Value) AsBool() (bool, bool)

AsBool returns the boolean and true when v is a boolean Value.

func (Value) AsInt64

func (v Value) AsInt64() (int64, bool)

AsInt64 returns the exact signed integer represented by v. It accepts the canonical exponent form used for integers with trailing zeroes (for example, Int64Value(10) is stored as "1e1") and rejects fractional and out-of-range numbers.

func (Value) AsList

func (v Value) AsList() ([]Value, bool)

AsList returns a deep copy of the list and true when v is a list Value.

func (Value) AsMap

func (v Value) AsMap() (map[string]Value, bool)

AsMap returns a deep copy of the map and true when v is a map Value.

func (Value) AsNumber

func (v Value) AsNumber() (string, bool)

AsNumber returns the exact canonical decimal spelling and true when v is a number Value. The spelling is either "0", a signed coefficient, or a signed coefficient followed by "e" and a signed base-10 exponent.

func (Value) AsString

func (v Value) AsString() (string, bool)

AsString returns the string and true when v is a string Value.

func (Value) AsTagged

func (v Value) AsTagged() (tag string, payload Value, ok bool)

AsTagged returns the tag, a deep copy of the payload, and true when v is a tagged application Value.

func (Value) Clone

func (v Value) Clone() Value

Clone returns a deeply independent Value.

func (Value) Equal

func (v Value) Equal(other Value) bool

Equal reports whether v and other contain the same canonical data.

func (Value) JSONValue

func (v Value) JSONValue() (any, error)

JSONValue converts v to an independent JSON-shaped Go value. Numbers are returned as json.Number to retain precision. Their canonical spelling may use exponent form even when mathematically integral, so json.Number.Int64 does not necessarily succeed. Tagged values are rejected because untagged JSON cannot preserve their application tag.

func (Value) Kind

func (v Value) Kind() ValueKind

Kind returns the Value variant. A zero Value has ValueKindNull.

func (Value) MarshalBinary

func (v Value) MarshalBinary() ([]byte, error)

MarshalBinary implements encoding.BinaryMarshaler using the deterministic, versioned ValueWire representation.

func (Value) MarshalJSON

func (v Value) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler using the deterministic, versioned ValueWire representation. This is the canonical Value wire JSON, not the untagged JSON-shaped form returned by JSONValue.

func (Value) MarshalText

func (v Value) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler using the deterministic, versioned ValueWire representation.

func (*Value) UnmarshalBinary

func (v *Value) UnmarshalBinary(data []byte) error

UnmarshalBinary implements encoding.BinaryUnmarshaler using the deterministic, versioned ValueWire representation.

func (*Value) UnmarshalJSON

func (v *Value) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler using the deterministic, versioned ValueWire representation.

func (*Value) UnmarshalText

func (v *Value) UnmarshalText(data []byte) error

UnmarshalText implements encoding.TextUnmarshaler using the deterministic, versioned ValueWire representation.

func (Value) Wire

func (v Value) Wire() (ValueWire, error)

Wire returns an independent, validated wire representation of v.

type ValueKind

type ValueKind string

ValueKind identifies one of the closed set of canonical Value variants.

const (
	// ValueKindNull is the null variant. It is also the kind of a zero Value.
	ValueKindNull ValueKind = "null"
	// ValueKindBool is the boolean variant.
	ValueKindBool ValueKind = "bool"
	// ValueKindString is the UTF-8 string variant.
	ValueKindString ValueKind = "string"
	// ValueKindNumber is the arbitrary-precision decimal number variant.
	ValueKindNumber ValueKind = "number"
	// ValueKindList is the ordered list variant.
	ValueKindList ValueKind = "list"
	// ValueKindMap is the string-keyed map variant.
	ValueKindMap ValueKind = "map"
	// ValueKindTagged is an application-tagged canonical payload.
	ValueKindTagged ValueKind = "tagged"
)

type ValueWire

type ValueWire struct {
	Version int                   `json:"version"`
	Kind    ValueKind             `json:"kind"`
	Bool    *bool                 `json:"bool,omitempty"`
	String  *string               `json:"string,omitempty"`
	Number  *string               `json:"number,omitempty"`
	List    *[]ValueWire          `json:"list,omitempty"`
	Map     *map[string]ValueWire `json:"map,omitempty"`
	Tag     *string               `json:"tag,omitempty"`
	Payload *ValueWire            `json:"payload,omitempty"`
}

ValueWire is the documented wire union for Value. Exactly one variant field is present according to Kind; null has no variant field, while tagged has both Tag and Payload. Version must be ValueWireVersion.

The JSON field names are the canonical text and binary representation used by Value's marshal methods. JSON object keys in Map are sorted by encoding/json, so the resulting bytes are deterministic and suitable as revision material. The binary representation deliberately uses the same versioned UTF-8 bytes as the text representation. Unmarshal into Value when decoding bytes; ValueFromWire is the validation gate for a ValueWire built or decoded separately.

type WriterLogger

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

WriterLogger is a Logger that writes one line per call to an underlying io.Writer. It is safe for concurrent use by multiple goroutines: calls to Log are serialized, so multiple Instances or actors sharing one WriterLogger never interleave partial writes.

func NewWriterLogger

func NewWriterLogger(w io.Writer) *WriterLogger

NewWriterLogger returns a WriterLogger that writes to w.

func (*WriterLogger) Log

func (l *WriterLogger) Log(label string, data Value)

Log writes label and data as a single line to the underlying io.Writer.

Directories

Path Synopsis
Package actors builds virtual actors on top of statecharts.Chart, statecharts.Instance, statecharts.IOProcessor, and statecharts.Log: named, addressable instances that can be spawned durable so a process restart resumes them exactly where they left off, and that page into and out of memory automatically as they go idle or as the system comes under resident-actor pressure.
Package actors builds virtual actors on top of statecharts.Chart, statecharts.Instance, statecharts.IOProcessor, and statecharts.Log: named, addressable instances that can be spawned durable so a process restart resumes them exactly where they left off, and that page into and out of memory automatically as they go idle or as the system comes under resident-actor pressure.
datamodel
ecmascript
Package ecmascript provides an optional ECMAScript datamodel backed by the pure-Go modernc QuickJS port.
Package ecmascript provides an optional ECMAScript datamodel backed by the pure-Go modernc QuickJS port.
Package inspector provides a transport-neutral, non-activating view of actor systems.
Package inspector provides a transport-neutral, non-activating view of actor systems.
http
Package inspectorhttp adapts an inspector.Service to a versioned HTTP API.
Package inspectorhttp adapts an inspector.Service to a versioned HTTP API.
Package sqllog provides database/sql-backed durable statechart storage.
Package sqllog provides database/sql-backed durable statechart storage.
sqlite3
Package sqlite3 opens SQLite-backed sqllog storage using the pure-Go modernc.org/sqlite driver.
Package sqlite3 opens SQLite-backed sqllog storage using the pure-Go modernc.org/sqlite driver.
Package storagetest provides a reusable storage conformance suite and an in-memory implementation for statecharts storage implementations.
Package storagetest provides a reusable storage conformance suite and an in-memory implementation for statecharts storage implementations.
syntax
json
Package json encodes and decodes statecharts.Definition as a strict, human-editable JSON surface syntax.
Package json encodes and decodes statecharts.Definition as a strict, human-editable JSON surface syntax.
scxml
Package scxml encodes and decodes statecharts.Definition using the W3C State Chart XML surface syntax.
Package scxml encodes and decodes statecharts.Definition using the W3C State Chart XML surface syntax.

Jump to

Keyboard shortcuts

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