Skip to main content
Version: Current

Act Framework

The event-sourcing framework for TypeScript.

Most business apps can be modeled with just three primitives: Actions β†’ {State} ← Reactions. Act wires them together with Zod schemas, an immutable event log, and a built-in pipeline that turns reactions into observable workflows. Drop in Postgres for production, SQLite for embedded, or run in-memory for tests; no external message broker required.

What you get​

  • 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​

  1. Actions β€” commands that represent intent to change state
  2. State β€” domain entities modeled as immutable data with event-driven transitions
  3. Reactions β€” asynchronous responses to state changes that trigger workflows and integrations

Builders​

  • state() β€” define state machines with actions, events, invariants, and snapshots
  • projection() β€” read-model updaters that react to events (with optional .batch() for high-throughput replay)
  • slice() β€” vertical feature modules grouping states, projections, and scoped reactions
  • act() β€” orchestrator that composes states, slices, and projections into an application

Port/Adapter Pattern​

Infrastructure uses swappable adapters injected via log(), store(), and cache() port functions:

  • Logger β€” ConsoleLogger (default) or PinoLogger (@rotorsoft/act-pino)
  • Store β€” InMemoryStore (default), PostgresStore (@rotorsoft/act-pg), or SqliteStore (@rotorsoft/act-sqlite)
  • 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
  • Time-travel β€” load() accepts an asOf filter to reconstruct historical state
  • Close the books β€” app.close() archives, tombstones, or restarts streams

Deliberate choices & non-goals​

Coming from Axon, Marten, MassTransit, or Temporal? Some patterns those frameworks ship as named primitives β€” a compensation stack, an upcaster chain, an exactly-once projection checkpoint β€” are deliberately absent here, replaced by simpler shapes on machinery Act already has. Deliberate choices & non-goals explains each call, how to reach the same outcome, and the one pattern that genuinely is still missing.

Packages​

Core​

PackageDescription
@rotorsoft/actThe framework
@rotorsoft/act-pgPostgreSQL store adapter
@rotorsoft/act-sqliteSQLite (libSQL) store adapter
@rotorsoft/act-patchImmutable deep-merge patch utility

Supporting​

PackageDescription
@rotorsoft/act-httpHTTP integrations β€” webhooks, auto-generated APIs, and the canonical sse subpath (@rotorsoft/act-http/sse) for incremental state broadcast
@rotorsoft/act-sseDeprecated β€” thin re-export shim over @rotorsoft/act-http/sse, kept only for migration and scheduled for removal
@rotorsoft/act-pinoPino logger adapter
@rotorsoft/act-otelPrometheus metrics bridge over the lifecycle events
@rotorsoft/act-diagramSVG diagram generator

Requirements​

  • Node.js >= 22.18.0
  • pnpm >= 10.32.1

Stability and support​

Act follows SemVer. The Stability Charter is the published contract for what each version protects and how the project is supported:

  • What semver covers β€” the builder API, IAct interface, Store/Cache/Logger adapter contracts, lifecycle events, and public type exports. Internals, performance characteristics, and log formats are explicitly out of scope.
  • Support window β€” the latest major is actively maintained; the previous major gets security and critical-fix maintenance for at least 6 months after the next major ships. These windows have been in effect since the 1.0 release.
  • Deprecation policy β€” public surface is deprecated in a minor release and removed no earlier than the next major. Events on disk are never broken; they evolve through versioned event names (Foo β†’ Foo_v2), with a startup advisory enumerating deprecated versions.
  • Security fixes β€” report privately via a GitHub security advisory; fixes land on the active major and any previous major still inside its maintenance window.

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.

License​

MIT