Event sourcing in TypeScript and Node.js
Chronicle is an open-source (MIT) event-sourcing database and processing runtime, and @cratis/chronicle is its idiomatic TypeScript client for Node.js. It provides a type-safe API built on decorators — @eventType, @readModel, @reactor, @reducer, @projection, @constraint, and more — together with value objects and a fluent client that goes from ChronicleClient to an event store to an event log you append to. It supports transactions, jobs, webhooks, compliance/PII, and OpenTelemetry, and builds on the @cratis/chronicle.contracts gRPC contracts package.
With event sourcing, every state change in your application is captured as an immutable event rather than an update in place. Chronicle stores those events, and your read models, reactors, and projections are derived from them — so your TypeScript services get a full history to audit, replay, and build new views from.
The event store itself is not tied to Node.js: Chronicle’s kernel exposes a language-agnostic gRPC/protobuf boundary, and the same store is reachable from the .NET, Kotlin/Java, and Elixir clients as well. Storage is pluggable — MongoDB by default, with PostgreSQL, SQL Server, SQLite, and in-memory providers implemented in the kernel.
Install
Section titled “Install”npm install @cratis/chronicle reflect-metadataThe client relies on TypeScript decorators and runtime type metadata. Your project needs:
reflect-metadata, imported once at the top of your entry point;"experimentalDecorators": trueand"emitDecoratorMetadata": trueintsconfig.json;- an ES module project (
"type": "module"inpackage.json, a matchingmodulesetting), since the package is published as ESM and the example below uses top-levelawait.
The Console sample’s tsconfig.json is a working reference.
A taste
Section titled “A taste”Define an event with a decorator, connect, and append — from the client repository’s quick example:
import 'reflect-metadata';import { ChronicleClient, ChronicleOptions, eventType } from '@cratis/chronicle';
@eventType()class EmployeeHired { constructor(readonly firstName: string, readonly lastName: string) {}}
const client = new ChronicleClient(ChronicleOptions.development());const store = await client.getEventStore('MyStore');const result = await store.eventLog.append('employee-123', new EmployeeHired('Jane', 'Doe'));console.log(`Appended at sequence number ${result.sequenceNumber.value}`);client.dispose();ChronicleOptions.development() connects to a local development kernel. Get started with Chronicle shows how to run one, and the TypeScript client docs cover connection strings for anything else.
Where to go next
Section titled “Where to go next”Looking for another language? See Chronicle in your language for the .NET, Kotlin/Java, and Elixir clients.