Act Framework
Act is a modern event sourcing + CQRS + Actor Model framework for TypeScript. The core philosophy: any system distills into Actions → {State} ← Reactions.
Purpose & Philosophy
Act makes event-sourced, reactive architectures accessible and productive for TypeScript developers. It provides a minimal, functional core for modeling your domain as state machines, capturing every change as an immutable event, and reacting to those changes in a scalable, testable way.
- Simplicity — focus on state, actions, and reactions without boilerplate or code generation
- Type Safety — TypeScript and Zod for compile-time guarantees and runtime validation
- Composability — build complex workflows by composing small, testable state machines
- Auditability — every state change is an event, enabling time travel, debugging, and compliance
- Adaptability — swap storage backends, integrate external systems, scale from in-memory to production
Quick Start
import { act, state } from "@rotorsoft/act";
import { z } from "zod";
const Counter = state({ Counter: z.object({ count: z.number() }) })
.init(() => ({ count: 0 }))
.emits({ Incremented: z.object({ amount: z.number() }) })
.patch({
Incremented: ({ data }, state) => ({ count: state.count + data.amount }),
})
.on({ increment: z.object({ by: z.number() }) })
.emit((action) => ["Incremented", { amount: action.by }])
.build();
const app = act().withState(Counter).build();
const actor = { id: "user1", name: "User" };
await app.do("increment", { stream: "counter1", actor }, { by: 5 });
const snapshot = await app.load(Counter, "counter1");
console.log(snapshot.state.count); // 5
Core Concepts
Actions → State ← Reactions
- Actions — commands that represent intent to change state
- State — domain entities modeled as immutable data with event-driven transitions
- Reactions — asynchronous responses to state changes that trigger workflows and integrations
Builders
state()— define state machines with actions, events, invariants, and snapshotsprojection()— read-model updaters that react to eventsslice()— vertical feature modules grouping states, projections, and scoped reactionsact()— orchestrator that composes states, slices, and projections into an application
Port/Adapter Pattern
Infrastructure uses swappable adapters:
- Store —
InMemoryStore(default) orPostgresStorefor production - Cache —
InMemoryCache(default, LRU) or custom adapters (e.g., Redis) - Disposal —
dispose()()cleans up all registered adapters on shutdown
Event Processing
- Correlation — dynamic stream discovery via reaction resolvers
- Drain — leasing-based reaction processing with dual-frontier strategy
- Settle — debounced, non-blocking correlate→drain loop for production
Packages
| Package | Description |
|---|---|
@rotorsoft/act | Core framework |
@rotorsoft/act-pg | PostgreSQL adapter |
@rotorsoft/act-patch | Immutable deep-merge patch utility |
@rotorsoft/act-sse | Server-Sent Events for incremental state broadcast |
Requirements
- Node.js >= 22.18.0
- pnpm >= 10.32.1
FAQ
Q: Do I need to use Postgres? No. Start with the in-memory store and switch to Postgres or another backend when needed.
Q: Is Act only for DDD experts? No. Act is designed to be approachable for all TypeScript developers, with a focus on simplicity and strong typing.