Act Framework
Welcome to the Act Framework documentation! Act is a modern, event-sourced framework for building scalable, maintainable, and auditable applications in TypeScript. Act is inspired by the best ideas from Domain-Driven Design (DDD), Command Query Responsibility Segregation (CQRS), and Event Sourcing, but is designed to be simple, composable, and highly type-safe.
π― Purpose & Philosophyβ
Act aims to make event-sourced, reactive architectures accessible and productive for all 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 the essentialsβstate, actions, and reactionsβwithout boilerplate or code generation.
- Type Safety: Leverage TypeScript and Zod for compile-time guarantees and runtime validation.
- Composability: Build complex workflows by composing small, testable state machines and reactions.
- Auditability: Every state change is an event, enabling time travel, debugging, and compliance.
- Adaptability: Easily swap storage backends, integrate with external systems, and scale from in-memory to production databases.
π Why Act?β
- Event Sourcing Made Easy: Model your domain as a series of state transitions, with every change captured as an event.
- Functional State Machines: Define state, actions, and events as pure functionsβno classes or decorators required.
- Reactive by Default: Build workflows and integrations by reacting to events, not just commands.
- Production-Ready: Includes adapters for in-memory and Postgres event stores, with robust resource management.
- Minimal Footprint: No codegen, no runtime bloat, and a tiny bundle size.
ποΈ Core Conceptsβ
Actions β State β Reactionsβ
Act follows a simple but powerful pattern:
- Actions: Commands that represent intent to change state (e.g., user input, API calls).
- State: The current state of your domain objects, modeled as immutable data.
- Reactions: Responses to state changes that can trigger additional actions, side effects, or integrations.
This pattern enables you to build complex, event-driven systems while maintaining clarity, testability, and auditability.
π Quick Startβ
Here's a simple example to get you started:
import { act, state, z } from "@rotorsoft/act";
// Define a counter state machine
const Counter = state("Counter", z.object({ count: z.number() }))
.init(() => ({ count: 0 }))
.emits({ Incremented: z.object({ amount: z.number() }) })
.patch({
Incremented: (event, state) => ({ count: state.count + event.amount }),
})
.on("increment", z.object({ by: z.number() }))
.emit((action) => ["Incremented", { amount: action.by }])
.build();
// Create an application
const app = act().with(Counter).build();
// Use the application
const actor = { id: "user1", name: "User" };
await app.do("increment", { stream: "counter1", actor }, { by: 5 });
const state = await app.load(Counter, "counter1");
console.log(state.state); // { count: 5 }
β FAQβ
Q: Do I need to use Postgres?
A: No. You can start with the in-memory store and switch to Postgres or another backend as needed.
Q: Is Act only for DDD experts?
A: No. Act is designed to be approachable for all TypeScript developers, with a focus on simplicity and strong typing.
π Licenseβ
This documentation is part of the Act Framework project, licensed under the MIT License.
For more information, visit the Act Framework GitHub repository.