@incoqnito.io/eslib
v2.1.0
Published
A lightweight event-sourced framework.
Keywords
Readme
eslib
A lightweight, storage-agnostic event-sourcing framework for TypeScript/Node.
Goal
eslib gives you the event-sourcing primitives — documents, event chains, snapshots, projections — without locking you into a specific database. You bring a storage layer (in-memory for tests, or MongoDB out of the box) and a function that turns an event chain into a state; eslib handles the document lifecycle, reference-chain integrity, snapshotting and caching around that.
It intentionally stays small: no built-in message bus, no schema registry, no distributed transaction support. What it does provide is a clean seam between "how events are applied" and "where they are stored", so the storage backend can be swapped (or mocked in tests) without touching domain logic.
Installation
npm install @incoqnito.io/eslibMongoDB support is optional: install the mongodb driver yourself if you want to use
mongoExtension. Requires Node >= 24.6.
Quickstart
import {
ESDocumentDescriptor, EventMap, BasicInMemESDocumentStorageLayer, IESState
} from "@incoqnito.io/eslib";
// 1. describe your state shape
interface CounterState extends IESState {
data: { value: number };
}
// 2. describe how events change that state
const eventMap = new EventMap<CounterState>().register({
eventType: "increment",
eventFunction: async (state, event) => ({
...state,
data: { value: state.data.value + (event.content.by ?? 1) },
}),
});
// 3. wire up a storage layer + initial state + event handler
const descriptor = new ESDocumentDescriptor<CounterState>(
new BasicInMemESDocumentStorageLayer(),
eventMap,
async (documentID) => ({ documentID, data: { value: 0 } }),
);
// 4. create a document, add events, read state back
const doc = await descriptor.createNewDocument({ actorID: "user-1", componentID: "counter-api" });
await doc.addEvent({ eventType: "increment", actorID: "user-1", componentID: "counter-api", content: { by: 5 } });
const state = await doc.createSnapshot(false); // false = persist the snapshot
console.log(state.data.value); // 5Swap BasicInMemESDocumentStorageLayer for BasicMongoDBESDocumentStorageLayer or
EventsCollectionMongoDBESDocumentStorageLayer (from mongoExtension) to persist to MongoDB —
nothing else in the example above changes.
Core concepts
Event. The atomic unit of change (IESEvent): who did it (actorID), where it came from
(componentID), what kind it is (eventType), its payload (content), and a refEventID linking
it to the previous event in the chain. eslib checks that chain on every write and rejects an event
whose refEventID doesn't match the document's current latest event (BadReferenceError), and
rejects any event added after a closeDocument call (DocumentClosedError).
State. A snapshot of a document at some point in its event chain (IESState). You provide the
function that folds events into state — either directly (IESEventHandler) or via the built-in
EventMap, which dispatches by eventType and lets you mark event types to be ignored (the two
built-in lifecycle events, createDocument/closeDocument, are ignored by default).
Document (IESDocument). The main handle applications work with: append events, fetch the
event chain (all of it, filtered, or by category), read/create a snapshot, close the document. A
document also has a caching variant (getCachingDocument()) that keeps events/state/latestEventID
in memory across calls — cheaper for a burst of operations on the same document, at the cost of
staleness if the underlying storage changes concurrently.
Descriptor (IESDocumentDescriptor / ESDocumentDescriptor). The factory/registry for one
class of documents: creates new documents (emitting the initial createDocument event), fetches
existing ones, lists/counts/deletes them. Also has a caching variant
(getCachingDescriptor()) that keeps fetched documents in memory.
Storage layer (IESDocumentStorageLayer). The persistence seam — serialize/fetch state,
serialize/fetch events, existence/count/list queries. This is the interface you implement to
support a new backend; eslib ships three implementations (see below).
Views & router (IESView, ESStoredView, ESRouter). A lightweight alternative path for
building projections/read-models that aren't tied to a single event-sourced document: route an
event to zero or more views, each deriving its own "document" id and folding the event into its
own stored state.
Storage layers
| Class | Where events live | Notes |
| --- | --- | --- |
| BasicInMemESDocumentStorageLayer | a Map in process memory | for tests/prototyping; filtering is O(n) over all data |
| BasicMongoDBESDocumentStorageLayer | embedded array on the document | simplest Mongo setup; only suitable for small/finite event streams (the whole array is read/written together) |
| EventsCollectionMongoDBESDocumentStorageLayer | separate <collection>_events collection | scales to long event streams; needs a unique index on { documentID, eventID } on the events collection (see the test setup in src/test/mongo.test.ts) |
All three implement the same IESDocumentStorageLayer interface, so application code can target
that interface and stay portable across them.
filterDescriptor parameters throughout the storage layer API are intentionally untyped: a plain
predicate function for the in-memory layer, a MongoDB query/aggregation fragment for the Mongo
layers. They are passed straight through to the backend with no validation — never build one from
unvalidated/user-controlled input.
API overview
Everything below is exported from the package root (@incoqnito.io/eslib).
Events & state: IESEvent, IESEventStub, IESEmptyEventStub, IESConcreteEvent,
DefaultESEventType, IESState, TStateCreator, IESEventHandler.
Documents & descriptors: IESDocument, IESDocumentDescriptor, ESDocumentDescriptor.
Storage layer contract: IESDocumentStateStorageLayer, IESDocumentStorageLayer.
Errors: ESError, BadReferenceError, DocumentClosedError.
Event dispatch: EventMap, IEventDescriptor, TEventFunction.
In-memory storage: BasicInMemESDocumentStorageLayer, TInMemFilterDescriptor.
MongoDB storage: BasicMongoDBESDocumentStorageLayer, EventsCollectionMongoDBESDocumentStorageLayer,
BasicMongoDocument, EventsCollectionDocument, EventsCollectionEvent, EventsCollectionNativeAccess.
Views & routing: IESView, ESStoredView, ESRouter, IESRoutedEvent, TESViewModifier,
TESDocumentIdentifier.
Every exported symbol carries a JSDoc comment in its source file — src/main/eventSourced.ts for
the core types, src/main/eventSourcedUtils.ts for EventMap, src/main/inMemExtension.ts and
src/main/mongoExtension.ts for the storage layers.
Building & testing
npm run build # compiles src/main to dist/
npm test # runs src/test/*.test.ts via tsx + node:test, with coverageMongo tests spin up an in-memory MongoDB via mongodb-memory-server — no external database
required.
License
See LICENSE.
