npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@incoqnito.io/eslib

v2.1.0

Published

A lightweight event-sourced framework.

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/eslib

MongoDB 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); // 5

Swap 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 coverage

Mongo tests spin up an in-memory MongoDB via mongodb-memory-server — no external database required.

License

See LICENSE.