@camcima/finita
v2.2.0
Published
A full-featured finite state machine library for TypeScript
Maintainers
Readme
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.
Features
- 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
- Process Merging -- combine state collections with optional name prefixing
- Graph Visualization -- build graph data structures for rendering with GraphViz or other tools
- Setup Helper -- fluent API for building state machines from configuration
- Zero Dependencies -- no runtime dependencies
Installation
npm install @camcima/finitaQuick Start
stateDiagram-v2
[*] --> draft
draft --> published : publish
published --> archived : archive
archived --> draft : reopenimport { State, Transition, Process, Statemachine } from "@camcima/finita";
// Define states
const draft = new State("draft");
const published = new State("published");
const archived = new State("archived");
// Define transitions
draft.addTransition(new Transition(published, "publish"));
published.addTransition(new Transition(archived, "archive"));
archived.addTransition(new Transition(draft, "reopen"));
// Create process and state machine
const process = new Process("article-workflow", draft);
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.Typed Usage
Use the TSubject generic parameter for type-safe access to your domain object -- no more casts:
import {
State,
Transition,
Process,
Statemachine,
CallbackCondition,
} from "@camcima/finita";
interface Order {
id: number;
total: number;
}
const pending = new State("pending");
const approved = new State("approved");
// subject is typed as Order -- no cast needed
const canApprove = new CallbackCondition<Order>(
"canApprove",
(order) => order.total <= 1000,
);
pending.addTransition(new Transition(approved, "review", canApprove));
const process = new Process("order", pending);
const sm = new Statemachine<Order>({ id: 1, total: 500 }, process);
const order = sm.getSubject(); // typed as Order
console.log(order.total); // 500Concepts
A 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 changeDocumentation
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
- Utilities -- SetupHelper, StateCollectionMerger
- Graph -- GraphBuilder
- Errors -- WrongEventForStateError, LockCanNotBeAcquiredError, DuplicateStateError
- Interfaces -- All TypeScript interfaces
Examples
A complete working example (order processing with prepayment and postpayment workflows) is available in the finita-example repository.
Architecture
src/
index.ts # Barrel export
MaybePromise.ts # MaybePromise<T> = T | Promise<T> utility type
Event.ts # Event implementation
State.ts # State implementation
Transition.ts # Transition implementation
StateCollection.ts # Named collection of states
Process.ts # Process (workflow definition)
Statemachine.ts # Runtime state machine
Dispatcher.ts # Deferred event dispatcher
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
util/ # SetupHelper, StateCollectionMerger
graph/ # Graph visualization builder
error/ # Custom error classesSecurity
CI
- 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)
Local (via Lefthook)
- Gitleaks -- scans staged files for secrets on every commit (auto-skipped if not installed)
Manual checks
npm run security:audit # npm dependency audit
npm run security:secrets # Scan full repo for secrets (requires gitleaks)Install Gitleaks: https://github.com/gitleaks/gitleaks#installing
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Type check
npm run lint
# Build
npm run buildLicense
MIT
