Skip to main content
v1.x ยท TypeScript-first event sourcing

Act

Fluent event sourcing for TypeScript

Build robust, auditable, and reactive systems with composable state machines, pure functions, and zero runtime bloat.

Actions โ†’ State โ† ReactionsPostgres ยท SQLite ยท In-MemoryZod-typed

Three steps to your first event

From install to your first persisted event in under a minute.

1Install
npm install @rotorsoft/act zod
2Define a state and run an action
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"));
3Run & explore

Run your app and see the output in your terminal. Then dive into reactions, projections, and slices.

Read the full guide

Advanced 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.