Skip to main content

Flow

Type-safe state machines for TypeScript with statecharts, compile-time validated transitions, guards, effects, and HexDI integration.

Overview

Flow is a state machine runtime that provides maximum type safety, full type inference, zero type casts, and seamless integration with the @hex-di ecosystem for managing complex UI state transitions. Built on the principles of statecharts, Flow enables you to model complex application behavior declaratively while maintaining complete type safety.

Features

  • Branded State Types: State types with unique symbol branding for nominal typing, compile-time validation, and type-safe narrowing
  • Branded Event Types: Event types with conditional payloads and full type inference for send() calls
  • Effects as Data: Effect descriptors (InvokeEffect, SpawnEffect, EmitEffect, DelayEffect, etc.) are pure data structures, not side effects
  • Activity System: Long-running processes with AbortSignal cancellation, EventSink for emitting events, and proper lifecycle management
  • HexDI Integration: FlowAdapter pattern for seamless container integration, DIEffectExecutor for port resolution, scoped lifetime by default
  • DevTools Integration: FlowCollector for transition tracing with zero overhead when disabled
  • Serialization Support: Persist and restore machine state with version migrations and context validation
  • Testing Utilities: Comprehensive testing package with mock executors, virtual clocks, and assertions
  • React Integration: Type-safe React hooks with concurrent mode support

Installation

pnpm add @hex-di/flow

For testing support:

pnpm add -D @hex-di/flow-testing

For React integration:

pnpm add @hex-di/flow-react

Quick Start

Here's a simple example of a traffic light state machine:

import { defineMachine, createMachineRunner, Effect } from "@hex-di/flow";

// Define the machine
const trafficLightMachine = defineMachine({
id: "traffic-light",
initial: "red",
states: {
red: {
entry: [Effect.log("Red light")],
on: {
TIMER: {
target: "green",
effects: [Effect.delay(3000)],
},
},
},
green: {
entry: [Effect.log("Green light")],
on: {
TIMER: {
target: "yellow",
effects: [Effect.delay(5000)],
},
},
},
yellow: {
entry: [Effect.log("Yellow light")],
on: {
TIMER: {
target: "red",
effects: [Effect.delay(2000)],
},
},
},
},
});

// Create and run the machine
const runner = createMachineRunner(trafficLightMachine);

// Send events
runner.send({ type: "TIMER" });

// Query state
console.log(runner.state()); // 'green'

// Subscribe to transitions
const unsubscribe = runner.subscribe(snapshot => {
console.log(`Transitioned to: ${snapshot.state}`);
});

// Clean up
runner.dispose();

What's Next