Introduction
Alvyn is a production-grade event store for PostgreSQL with type-safe aggregates, event-backed snapshots, GDPR crypto-shredding, projections, transactional outbox, and schema evolution.
Alvyn
Alvyn is an event sourcing library for Node.js and PostgreSQL. It provides everything you need to build event-sourced systems with strong consistency guarantees, GDPR compliance, and full TypeScript support.
Features
- Type-safe aggregates — Define event maps and evolve handlers with full TypeScript inference via
defineAggregate - Fan-out subscriptions — Catch up and tail matching events live with resumable cursors and PostgreSQL wake-ups
- Event-backed snapshots — Cache domain-defined derived state as generated events in the same stream
- GDPR crypto-shredding — Per-entity AES-256-GCM envelope encryption with key revocation and tombstoned events
- Projections — Build read models from the global event stream with checkpoint tracking and exactly-once semantics
- Transactional outbox — At-least-once delivery to external systems, written atomically with events
- Schema evolution — Read-time upcasters that transform old event shapes without modifying stored data
- CloudEvents v1.0.2 — All events comply with the CloudEvents specification
- PostgreSQL native — Advisory locks, OCC,
FOR UPDATE SKIP LOCKED, and schema isolation
Quick Start
Installation
npm install @lox-solutions/alvyn pgSetup
import { Pool } from "pg";
import { EventStore, defineAggregate } from "@lox-solutions/alvyn";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const eventStore = new EventStore({
pool,
});
await eventStore.setup(); // idempotent — safe on every startupDefine an Aggregate
type OrderEvents = {
OrderPlaced: { customerId: string; total: number };
OrderShipped: { trackingNumber: string };
};
type OrderState = { status: "pending" | "placed" | "shipped"; total: number };
const Order = defineAggregate<OrderState, OrderEvents>()({
streamPrefix: "Order",
evolve: {
OrderPlaced: (state, event) => ({
...state,
status: "placed",
total: event.data?.total ?? 0,
}),
OrderShipped: (state) => ({
...state,
status: "shipped",
}),
},
});Load and Append
// Load aggregate state
const order = await Order.load(eventStore, "order-123");
// order.state -> { status: "placed", total: 99.99 }
// order.version -> 1
// Append events with optimistic concurrency
await Order.append(eventStore, {
entityId: "order-123",
expectedVersion: order.version,
events: [{ type: "OrderShipped", data: { trackingNumber: "TRACK-456" } }],
});Requirements
- Node.js >= 18
- PostgreSQL >= 14
pg(peer dependency)
Next Steps
- Aggregates — Full guide to defining and using aggregates
- Snapshots — Speed up expensive stream-derived calculations
- Subscriptions — Catch up and tail events for fan-out consumers
- Crypto-Shredding — GDPR-compliant PII encryption
- Projections — Build read models and use the transactional outbox
- Schema Evolution — Evolve event shapes with upcasters
- API Reference — Complete API documentation
- Database Schema — PostgreSQL table structure
- Full-stack Example — Apollo, GraphQL, and live banking updates