Introduction
TypeScript and PostgreSQL event history for agent workflows and domain applications, with typed aggregates, replay, projections, snapshots, and an outbox.
Alvyn is a TypeScript library for event history on PostgreSQL. Record meaningful facts, rebuild application state, and inspect how a workflow reached its current state. AI agents are an approachable starting point; orders, approvals, billing, and other domain applications use the same primitives. You do not need CQRS knowledge or an all-at-once rewrite.
Use Alvyn in an application
npm install alvyn pgYou supply and close the connection pool. This snippet records a goal and a known result; it does not call a model or execute a tool. See the Agent History, Replay & Audit playbook for model integration boundaries.
import { Pool } from "pg";
import { EventStore, defineAggregate } from "alvyn";
type AgentEvents = {
GoalReceived: { goal: string };
RunCompleted: { output: string };
};
type AgentState = { goal: string | null; output: string | null };
const AgentRun = defineAggregate<AgentState, AgentEvents>()({
streamPrefix: "AgentRun",
evolve: {
GoalReceived: (_state, event) => ({
goal: event.data?.goal ?? null,
output: null,
}),
RunCompleted: (state, event) => ({
goal: state?.goal ?? null,
output: event.data?.output ?? null,
}),
},
});
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
try {
const store = new EventStore({ pool });
await store.setup();
const runId = crypto.randomUUID();
const created = await AgentRun.append(store, {
entityId: runId,
expectedVersion: -1,
events: [
{ type: "GoalReceived", data: { goal: "Find the returns policy" } },
],
});
await AgentRun.append(store, {
entityId: runId,
expectedVersion: created.toVersion,
events: [
{
type: "RunCompleted",
data: { output: "Returns accepted for 30 days." },
},
],
});
console.log((await AgentRun.load(store, runId)).state);
} finally {
await pool.end();
}The first evolve handler receives null at runtime and must initialize the whole state. An empty aggregate loads with state: null and version: 0; use expectedVersion: -1 to explicitly create a new stream. Later appends use the loaded version or the previous append's toVersion. Define a handler for every event in the event map.
Building blocks
- Typed aggregates group related facts into streams and rebuild current state.
- Historical replay uses
loadorloadFromplus your reducer over an explicit event prefix; current aggregate load is not a historical query. - Snapshots cache expensive derived state as separate events.
- Subscriptions and projections support resumable consumers and query-oriented read models.
- Transactional outbox records delivery work atomically with events; delivery is at least once and external effects need idempotency.
- Schema evolution applies read-time upcasters to older event shapes.
- Encryption and key revocation support privacy workflows when explicitly configured.
Replay reproduces reducer state from retained, readable events, not a deterministic model rerun. Only instrumented actions are recorded, not hidden thoughts. Event history is neither automatic regulatory compliance nor tamper-proof evidence. Encryption does not automatically cover snapshots, projections, logs, backups, or provider copies; deletion can make original state unreconstructable.
Requirements
- Node.js matching the package engines:
^22.18.0 || ^24.11.0 || >=26.0.0. - PostgreSQL 14 or later for persistence.
pg8 as a peer dependency.
Next steps
- Aggregates — event maps and domain state.
- Aggregate design — stream boundaries and concurrency.
- Snapshots — faster derived-state reads.
- Subscriptions — catch-up and live event delivery.
- Transactional outbox — at-least-once downstream delivery.
- Projections — read models and checkpoints.
- Data privacy — encryption and deletion boundaries.
- Schema evolution — handling older payloads.
- API reference — supported methods and types.