This document provides a high-level introduction to the @doeixd/machine library, its Type-State Programming philosophy, architectural organization, and core execution models. It covers the library's mathematical foundations, dual-API design, module structure, and how TypeScript's type system enforces state machine safety at compile-time.
Related Pages:
@doeixd/machine is a minimal, type-safe state machine library for TypeScript that leverages the type system to represent finite states as distinct types rather than string values. The library is built on formal FSM (Finite State Machine) theory and provides multiple APIs and patterns to suit different development preferences and use cases.
Key Characteristics:
Package Information:
@doeixd/machineSources: package.json1-178 README.md1-100 CLAUDE.md1-20
The fundamental paradigm of @doeixd/machine is Type-State Programming, where different states are represented as distinct TypeScript types rather than string literals or enums. This allows the compiler to enforce state validity at compile-time, making illegal states unrepresentable.
| Aspect | Traditional (String-Based) | Type-State Programming |
|---|---|---|
| State Representation | "idle", "loading", "success" | IdleMachine, LoadingMachine, SuccessMachine |
| Validation | Runtime checks (if (state === "idle")) | Compile-time (type system) |
| Transition Safety | No enforcement | Compiler prevents invalid transitions |
| Available Actions | All methods available, must check state first | Only valid methods available per state type |
| Data Access | May access undefined properties | Type system ensures properties exist |
| Bug Detection | At runtime (production) | At compile-time (development) |
The compiler prevents:
logout() on LoggedOut typeusername on LoggedOut typeSources: README.md249-467 CLAUDE.md512-533
The library implements formal FSM theory. A finite state machine is defined as a 5-tuple: M = (S, Σ, δ, s₀, F)
Formal Components:
| Component | Mathematical Symbol | Implementation in @doeixd/machine |
|---|---|---|
| States | S (finite set) | Machine<C> types and context property |
| Input Alphabet | Σ (event set) | Transition method names (increment, login, fetch) |
| Transition Function | δ : S × Σ → S | Methods returning new Machine<C> instances |
| Initial State | s₀ | Initial context passed to createMachine() |
| Accepting States | F (optional) | Not enforced, but representable via types |
Key FSM Properties:
Sources: README.md22-60 CLAUDE.md23-60
The library provides two distinct APIs serving different developer preferences and use cases. Both share the same underlying type system and principles but differ in syntax, boilerplate, and feature richness.
Sources: package.json100-155 Diagram 1, Diagram 4
Purpose: Feature-rich API with utilities for composition, decoration, and advanced patterns.
Key Exports (from src/index.ts):
createMachine<C, T>(context, transitions) - Primary factory functioncreateAsyncMachine<C, T>(context, transitions) - For async state machinesMachineBase<C> - OOP base class for Type-State machinescreateMachineFactory<C>() - Higher-order factory patternsetContext(), overrideTransitions(), extendTransitions()Context<M>, Transitions<M>, Event<M>Use When:
Sources: src/index.ts README.md469-869 CLAUDE.md85-95
Purpose: Zero-boilerplate functional API with perfect type inference and YAGNI philosophy.
Key Exports (from src/minimal.ts):
machine(context, transitions) - One-off inline machinesfactory(context) - Single-state machine templates with inferenceunion(factories) - Multi-state discriminated union machinestag(), tag.factory() - Tagged union helpersStates<T> - Ergonomic type utilityrunnable(), run() - Lifecycle and effectsUse When:
Sources: src/minimal.ts README.md1-100 Diagram 1
| Feature | Main Library | Minimal API |
|---|---|---|
| Entry Point | @doeixd/machine | @doeixd/machine/minimal |
| Boilerplate | Moderate | Zero (perfect inference) |
| Style | OOP + Functional | Pure Functional |
| Composition | Rich utilities | Manual or use withChildren |
| Async Support | createAsyncMachine() | Via runnable() pattern |
| Type Safety | Full | Full (equivalent) |
| Bundle Size Impact | Larger API surface | Smaller footprint |
Sources: README.md1-100 CLAUDE.md85-110 Diagram 1
The library is organized into focused, tree-shakeable modules accessible via subpath exports. Each module serves a specific purpose and can be imported independently.
Sources: package.json100-177 Diagram 2
| Entry Point | Purpose | Key Exports | Import Cost |
|---|---|---|---|
@doeixd/machine | Full Main library | createMachine, MachineBase, all utilities | ~26 kB |
@doeixd/machine/core | Core subset only | Machine<C> type, createMachine basic | Smaller |
@doeixd/machine/minimal | Minimal API | machine(), factory(), union(), tag() | ~8-12 kB |
@doeixd/machine/delegate | Delegation pattern | delegate(), delegateAll() | Small |
@doeixd/machine/react | React integration | useMachine(), useActor(), createMachineContext() | Depends on React |
@doeixd/machine/extract | Statechart extraction | extractMachine(), extractMachines() | ~8 kB (separate chunk) |
Key Design Decision: The extraction tools (src/extract.ts) are not exported from the main entry point. They require the heavy ts-morph dependency (~200+ kB) and are only needed at build-time, not in production. This keeps the production bundle small.
Sources: package.json100-177 CLAUDE.md574-586
Sources: CLAUDE.md80-179 Diagram 2
The library supports three distinct execution models for different use cases. Each model provides different semantics for how events are processed and state changes are managed.
Sources: Diagram 7, docs/actor.md CLAUDE.md550-570
| Aspect | Direct Usage | Actor Model | Runner Pattern |
|---|---|---|---|
| Event Handling | Immediate call | Queued (FIFO) | Cancel + replace |
| Semantics | Synchronous | All complete | Latest-wins |
| Concurrency | None | Sequential | Cancellation-based |
| State Access | Via reference | actor.getSnapshot() | runner.state |
| Observers | Manual | actor.subscribe() | Single onChange |
| Async Support | Manual | Built-in | Built-in |
| Use Cases | Simple counters | Payments, persistence | Search, tab switching |
Sources: Diagram 7, docs/actor.md src/actor.ts src/runner.ts
Implementation: Simply call transition methods on the machine instance.
Example Pattern:
Use When:
Sources: README.md72-160
createActor)Implementation: Wraps machine in actor runtime with mailbox queue (src/actor.ts).
Key Functions:
createActor(machine) - Creates actor instanceactor.send(event) - Enqueues eventactor.subscribe(listener) - Observes changesactor.getSnapshot() - Gets current stateActor.inspect(callback) - Global debuggingSemantics:
Use When:
Sources: src/actor.ts docs/actor.md Diagram 7
runMachine)Implementation: Async machine runner with cancellation (src/runner.ts).
Key Functions:
runMachine(machine, onChange) - Creates runnerrunner.dispatch(event) - Dispatches event (cancels previous)runner.state - Current context (readonly)Semantics:
AbortSignal for cancellationonChange callbackUse When:
Sources: src/runner.ts README.md565-585 Diagram 7
The library leverages TypeScript's advanced type features to provide compile-time safety. The type system is the core mechanism for enforcing state machine validity.
Sources: src/index.ts src/matcher.ts1-120 CLAUDE.md558-572
Machine<C, T> (src/index.ts):
Context<M> - Extracts context type from machine:
Transitions<M> - Extracts transition methods:
Event<M> - Generates discriminated union of events:
This automatically creates type-safe event objects like:
Sources: src/index.ts README.md906-950 CLAUDE.md558-572
The type system prevents these categories of errors at compile-time:
match.when().is()hasState() narrow types automaticallyExample from tests (test/matcher.test.ts100-112):
Sources: README.md249-467 test/matcher.test.ts1-730 src/matcher.ts1-561
The library uses pridepack as its build tool, which provides zero-config bundling for TypeScript libraries. The build outputs multiple formats for maximum compatibility.
Build Tool: pridepack (v2.6.4)
Output Formats:
dist/cjs/production/*.js and dist/cjs/development/*.jsdist/esm/production/*.js and dist/esm/development/*.jsdist/types/*.d.tsEntry Points Mapping (package.json100-155):
@doeixd/machine → dist/{cjs,esm}/{dev,prod}/index.js
@doeixd/machine/core → dist/{cjs,esm}/{dev,prod}/core.js
@doeixd/machine/extract → dist/{cjs,esm}/{dev,prod}/extract.js
@doeixd/machine/minimal → dist/{cjs,esm}/{dev,prod}/minimal.js
@doeixd/machine/delegate → dist/{cjs,esm}/{dev,prod}/delegate.js
@doeixd/machine/react → dist/{cjs,esm}/{dev,prod}/react.js
Sources: package.json96-155 CLAUDE.md568-586
| Command | Purpose | Implementation |
|---|---|---|
npm run build | Build all formats | pridepack build |
npm run watch | Development mode | pridepack watch |
npm run type-check | Type checking only | pridepack check |
npm run clean | Remove build artifacts | pridepack clean |
npm test | Run test suite | vitest run |
Sources: package.json67-81
The library is marked as side-effect free ("sideEffects": false in package.json99) to enable aggressive tree-shaking. Key strategies:
ts-morph dependency (~200+ kB) is isolated in @doeixd/machine/extract, not imported by the main bundleMachineConfig are exported type-only from main entryBundle Sizes (approximate):
@doeixd/machine): ~26 kB@doeixd/machine/minimal): ~8-12 kB@doeixd/machine/extract): ~8 kB + ts-morph (~200 kB, usually dev-only)Sources: package.json99 CLAUDE.md574-586
Sources: package.json67-81 package.json96-155 CLAUDE.md568-586
@doeixd/machine is a type-safe state machine library built on formal FSM theory and centered around Type-State Programming. Key takeaways:
For detailed API documentation, see Main Library API Reference and Minimal Library API Reference. For framework-specific guides, see Framework Integrations.
Sources: README.md1-100 CLAUDE.md1-40 package.json1-178
Refresh this wiki