xstate_dart 0.1.3 copy "xstate_dart: ^0.1.3" to clipboard
xstate_dart: ^0.1.3 copied to clipboard

XState-JSON-compatible statechart interpreter in pure Dart. Hierarchical, parallel, timed (injected clock), eventless transitions.

xstate_dart #

A pure-Dart interpreter for XState-JSON statecharts — hierarchical and parallel states, guards, entry/exit/ transition actions, eventless (always) transitions, history states, snapshot save/restore, and delayed (after) transitions with time injected via an explicit tick rather than DateTime.now() or Timer, so machines stay deterministic and testable.

No Flutter dependency — usable in servers, CLIs, and Flutter apps alike. Extracted from Coyote Forge's forge_ui framework, where it drives JSON-defined workflow and UI state.

Install #

dependencies:
  xstate_dart: ^0.1.3

Quickstart #

The chart is plain XState JSON; actions and guards are Dart functions you register by name:

import 'package:xstate_dart/xstate_dart.dart';

const toggleChart = '''
{
  "id": "toggle",
  "initial": "off",
  "context": {"count": 0},
  "states": {
    "off": {"on": {"FLIP": {"target": "on", "actions": "increment"}}},
    "on": {
      "entry": "log",
      "on": {
        "FLIP": "off",
        "LOCKED_FLIP": {"target": "off", "guard": "never"}
      }
    }
  }
}
''';

void main() {
  final machine = StateMachine.fromJson(
    toggleChart,
    actions: {
      'increment': (ctx, event) => ctx['count'] = (ctx['count'] as int) + 1,
      'log': (ctx, event) => print('entered on'),
    },
    guards: {'never': (ctx, event, isActive) => false},
  );

  machine.matches('off');            // true
  final result = machine.send('FLIP');
  result.changed;                    // true
  machine.matches('on');             // true
  machine.context['count'];          // 1
  machine.send('LOCKED_FLIP').changed; // false — guard blocked it
}

send returns a StateTransitionResult telling you whether the configuration changed; activeStates exposes the full ancestor closure of the current configuration (e.g. {'player', 'player.aiming'}), and matches(path) tests any node in it.

Deterministic time: tick #

Delayed transitions never read the wall clock. You advance time explicitly, which makes timed behavior unit-testable and lets a host (a game loop, a Flutter ticker, a server scheduler) own the clock:

// "gun": {"cooling": {"after": {"50": "ready"}}}
machine.tick(const Duration(milliseconds: 16)); // still cooling
machine.tick(const Duration(milliseconds: 40)); // 56ms elapsed -> ready

Microseconds accumulate across ticks, so sub-millisecond frames don't truncate away.

Snapshots #

toSnapshotJson() serializes the live configuration, context, history, and pending timers; restoreSnapshot(json) validates the structure (configuration leaves, history values) before adopting it — a malformed or stale snapshot throws instead of corrupting the machine.

Supported XState-JSON subset #

Feature Status
Flat, compound (nested), and parallel states
on transitions with target, actions, guard
entry / exit actions
Eventless always transitions
Delayed after transitions (injected clock)
History states
context + named guard/action registration
Actors / services (invoke), spawned machines ❌ not planned — keep the interpreter pure

The interpreter is strict where XState is strict: a compound state without an initial (and no always route) throws at load, and transitions re-enter their domain correctly for external self-targets.

License #

BSD 3-Clause — see LICENSE.

0
likes
160
points
100
downloads

Documentation

API reference

Publisher

verified publishercoyoteforge.com

Weekly Downloads

XState-JSON-compatible statechart interpreter in pure Dart. Hierarchical, parallel, timed (injected clock), eventless transitions.

Repository (GitHub)
View/report issues

Topics

#statechart #state-machine #xstate #fsm

License

BSD-3-Clause (license)

More

Packages that depend on xstate_dart