Act
Fluent event sourcing for TypeScript
Build robust, auditable, and reactive systems with composable state machines, pure functions, and zero runtime bloat.
Three steps to your first event
From install to your first persisted event in under a minute.
npm install @rotorsoft/act zod
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 }, s) => ({ count: s.count + data.amount }) })
.on({ increment: z.object({ by: z.number() }) })
.emit((action) => ["Incremented", { amount: action.by }])
.build();
const app = act().withState(Counter).build();
await app.do("increment",
{ stream: "counter1", actor: { id: "1", name: "User" } },
{ by: 1 }
);
console.log(await app.load(Counter, "counter1"));
Run your app and see the output in your terminal. Then dive into reactions, projections, and slices.
Read the full guideAdvanced composition patterns
Five primitives compose every Act application โ slices, projections, partial states, invariants, and the orchestrator.
Group states and reactions into feature modules
Each slice owns a set of partial states and their scoped reactions. Handlers receive the full IAct interface for cross-state coordination.
const TicketCreationSlice = slice()
.withState(TicketCreation) // partial state
.withState(TicketOperations) // another partial state
.on("TicketOpened")
.do(async function assign(event, _stream, app) {
const agent = assignAgent(event.stream, event.data.supportCategoryId);
await app.do("AssignTicket",
{ stream: event.stream, actor: { id: randomUUID(), name: "assign" } },
agent, event
);
})
.build();
WolfDesk uses 3 slices: Creation, Operations, and Messaging โ each a self-contained feature boundary.
Built for serious systems
Type-safe primitives and production-ready adapters, with the smallest API surface that gets the job done.
Functional Event Sourcing
Every state change is a pure function of previous state and events. Immutability and replayability by design.
Composable State Machines
Model your domain as composable, strongly-typed state machines. No classes, just functions and data.
TypeScript Native
Type safety and inference everywhere. Catch errors at compile time, not at runtime.
Reactive by Default
Reactions let you build event-driven flows and side effects with ease โ with built-in correlation, drain, and dual-frontier processing.
Production Adapters Included
Postgres for scale, SQLite for embedded, in-memory for tests. Switch between them with a single line of code.
Minimal Footprint
Minimal and dependency-light. No codegen, no runtime bloat, and a tiny bundle size.
Ready to act?
Spend a minute on the quickstart, an afternoon on the Calculator example, or a weekend on WolfDesk โ every step has a working sandbox.
