A full-featured finite state machine (FSM) library for TypeScript.
This library is a TypeScript port of metabor/statemachine, the PHP state machine library created by Oliver Tischlinger. The original library was the engine behind Bob -- the backend component of the Alice & Bob retail system that Rocket Internet used to power its e-commerce ventures worldwide. The author of this TypeScript port and Oliver worked together at Rocket Internet, where the state machine proved itself at scale across multiple global operations.
- Type-Safe Subjects -- generic
TSubjectparameter gives you typed access to the domain object, withunknowndefault for backward compatibility - Async-First -- all conditions, observers, and mutexes support async operations via
MaybePromise<T>return types - States, Transitions, Events -- define complex workflows declaratively
- Conditions (Guards) -- control when transitions are allowed, with composable AND/OR/NOT logic
- Observers (Commands) -- execute side effects when events fire or states change
- Automatic Transitions -- transitions that fire when a condition becomes true, without an explicit event
- Transition Selectors -- pluggable strategies for resolving ambiguous transitions (score-based, weight-based)
- Mutex/Locking -- concurrency control with pluggable lock adapters
- Factory Pattern -- create pre-configured state machines from subject objects
- ProcessBuilder -- fluent, validated API for building frozen, immutable state graphs
- Graph Visualization -- build graph data structures for rendering with GraphViz or other tools
- Zero Dependencies -- no runtime dependencies
pnpm add @camcima/finitastateDiagram-v2
[*] --> draft
draft --> published : publish
published --> archived : archive
archived --> draft : reopen
import { ProcessBuilder, Statemachine } from "@camcima/finita";
// Define the process
const process = new ProcessBuilder("article-workflow")
.addState("draft", { initial: true })
.addState("published")
.addState("archived")
.addTransition("draft", "published", { event: "publish" })
.addTransition("published", "archived", { event: "archive" })
.addTransition("archived", "draft", { event: "reopen" })
.build();
// Create the state machine
const article = { title: "Hello World" };
const sm = new Statemachine(article, process);
console.log(sm.getCurrentState().getName()); // 'draft'
await sm.triggerEvent("publish");
console.log(sm.getCurrentState().getName()); // 'published'
await sm.triggerEvent("archive");
console.log(sm.getCurrentState().getName()); // 'archived'
// Note: use top-level await (supported in ES modules) or wrap in an async function.Use the TSubject generic parameter for type-safe access to your domain object -- no more casts:
import {
ProcessBuilder,
Statemachine,
CallbackCondition,
} from "@camcima/finita";
interface Order {
id: number;
total: number;
}
// subject is typed as Order -- no cast needed
const canApprove = new CallbackCondition<Order>(
"canApprove",
(order) => order.total <= 1000,
);
const process = new ProcessBuilder<Order>("order")
.addState("pending", { initial: true })
.addState("approved")
.addTransition("pending", "approved", {
event: "review",
condition: canApprove,
})
.build();
const sm = new Statemachine<Order>({ id: 1, total: 500 }, process);
const order = sm.getSubject(); // typed as Order
console.log(order.total); // 500A state machine consists of:
| Concept | Description |
|---|---|
| State | A named node in the workflow graph. Holds transitions, events, and metadata. |
| Transition | A directed edge from one state to another, optionally guarded by a condition and triggered by an event. |
| Event | A named trigger attached to a state. When invoked, it fires observers (commands) and initiates transitions. |
| Condition | A guard that determines whether a transition is active. |
| Process | A named collection of states that defines a complete workflow, starting from an initial state. |
| Statemachine | The runtime orchestrator that manages the current state, triggers events, checks conditions, and notifies observers. |
| Observer | A callback that reacts to events or state changes. |
classDiagram
direction LR
Process *-- State : contains
State *-- Transition : has outgoing
State *-- Event : has named
Transition --> State : targets
Transition --> Condition : guarded by
Event --> Observer : notifies
Statemachine --> Process : uses
Statemachine --> State : tracks current
Statemachine --> Observer : notifies on change
Detailed documentation for every component:
- Core -- State, Transition, Event, Process, Statemachine, Dispatcher, StateCollection
- Conditions -- Tautology, Contradiction, CallbackCondition, Timeout, AndComposite, OrComposite, Not
- Observers -- CallbackObserver, StatefulStatusChanger, OnEnterObserver, TransitionLogger
- Filters -- ActiveTransitionFilter, FilterStateByEvent, FilterStateByTransition, FilterStateByFinalState, FilterTransitionByEvent
- Selectors -- OneOrNoneActiveTransition, ScoreTransition, WeightTransition
- Mutex -- NullMutex, LockAdapterMutex, MutexFactory
- Factory -- Factory, SingleProcessDetector, AbstractNamedProcessDetector, StatefulStateNameDetector
- Graph -- GraphBuilder
- Errors -- WrongEventForStateError, LockCanNotBeAcquiredError, DuplicateStateError, ProcessFinalizedError, GraphValidationError, DuplicateTransitionError
- Interfaces -- All TypeScript interfaces
- Migration Guide -- Upgrading from v2 to v3
A complete working example (order processing with prepayment and postpayment workflows) is available in the finita-example repository.
src/
index.ts # Barrel export
MaybePromise.ts # MaybePromise<T> = T | Promise<T> utility type
ProcessBuilder.ts # Fluent builder for frozen graph construction
Event.ts # Event implementation
State.ts # State implementation (frozen; use ProcessBuilder)
Transition.ts # Transition implementation (frozen; use ProcessBuilder)
Process.ts # Process (workflow definition; use ProcessBuilder)
Statemachine.ts # Runtime state machine
interfaces/ # All TypeScript interfaces
condition/ # Condition (guard) implementations
observer/ # Observer implementations
filter/ # State and transition filters
selector/ # Transition selection strategies
mutex/ # Locking implementations
factory/ # State machine factory pattern
internal/ # Internal helpers (OperationQueue, InternalConstruction)
graph/ # Graph visualization builder
error/ # Custom error classes
- CodeQL -- static analysis for security vulnerabilities (push, PR, weekly)
- OSV-Scanner -- dependency vulnerability scanning against the OSV database (push, PR, weekly)
- Dependabot -- automated PRs for dependency and GitHub Actions updates (weekly)
- Gitleaks -- scans staged files for secrets on every commit (auto-skipped if not installed)
pnpm run security:audit # dependency audit
pnpm run security:secrets # Scan full repo for secrets (requires gitleaks)Install Gitleaks: https://github.com/gitleaks/gitleaks#installing
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests in watch mode
pnpm run test:watch
# Type check
pnpm run lint
# Build
pnpm run buildMIT