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

@bimetal/store-sqlite

v0.39.0

Published

SQLite EventStore implementation for @bimetal/event-sourcing — file-based, zero-config persistence

Readme

@bimetal/store-sqlite

SQLite-backed EventStore implementation for @bimetal/event-sourcing — file-based, zero-config native event sourcing. Use with any domain package (@bimetal/calendar-data, @bimetal/table-data, …). Built on better-sqlite3.

It is one of the storage adapters covered by the storage-parity test suite (In-Memory / SQLite / EventStoreDB produce bit-identical results), so it is a drop-in replacement for the in-memory store.

Installation

npm install @bimetal/store-sqlite

Usage

import { createSqliteEventStore } from '@bimetal/store-sqlite';
import { createCalendarStore } from '@bimetal/calendar-data';

const eventStore = createSqliteEventStore({ path: './events.db' });
const store = createCalendarStore({ eventStore });
await store.hydrate();

Snapshots (createSqliteSnapshotStore)

Without a persistent snapshot store, every process start replays the stream from its first event and holds the whole log in memory afterwards. createSqliteSnapshotStore keeps the most recent snapshot per stream in its own snapshots table — point it at the same file as the event store:

import { createSqliteEventStore, createSqliteSnapshotStore } from '@bimetal/store-sqlite';
import { createCalendarStore } from '@bimetal/calendar-data';

const eventStore = createSqliteEventStore('./events.db');
const snapshotStore = createSqliteSnapshotStore('./events.db');
const store = createCalendarStore({ eventStore, snapshotStore, snapshotEveryN: 500 });
await store.hydrate(); // loads snapshot + the events after it — not the whole log
  • One row per stream, last save wins — the same contract as createInMemorySnapshotStore, pinned by runSnapshotStoreConformance (@bimetal/event-sourcing/conformance).
  • Written like an append: save runs in a BEGIN IMMEDIATE transaction — the write lock is held before the upsert runs (checked from a second connection in the tests).
  • Format lock travels along: eventSchemaVersion is stored and returned unchanged; hydrate() rejects a snapshot from a newer schema (UnknownEventSchemaVersionError).
  • Other projection version → full replay: hydrate() ignores a stored snapshot whose projectionVersion differs and folds the full stream. The stale row stays until the next snapshot is written (snapshotEveryN or rebuild()), so until then each start replays fully.
  • Opens its own connection. ':memory:' is a private database, not shared with the event store.
  • The aggregate state is stored as JSON (maps as entry lists). It must be JSON-safe — which it is whenever it was folded from events read back out of SQLite.

Measured effect

scripts/heap-messung.mjs builds a log of 20,000 calendar events in one stream (2,000 entries × 1 create + 9 updates), writes one snapshot at its end, then hydrates in a fresh Node process each run (--expose-gc, heapUsed after full GC before opening and after hydrate, median of 3). Measured 2026-10-03, Node v22.23.3:

| | heap after hydrate | hydrate time | events held | |---|---|---|---| | without snapshot | 13.2 MiB | 852 ms | 20,000 | | with snapshot | 7.5 MiB | 75 ms | 0 |

Log payload 1.2 MiB, snapshot row 4.3 MiB. What remains with the snapshot is the aggregate state itself — including the per-entity undo stacks, which is why the snapshot row is larger than the log payload in this workload. The saving is the log the store no longer loads at start: after hydrate it holds only the events after the snapshot, not the whole stream. Numbers are for this workload.

What this does not bound: only the memory at start is measured and bounded. In a running process the held log keeps growing with every event appended since the hydrate — a snapshot written via snapshotEveryN does not trim it (tracked as bimetal-316). A long-running process still needs a restart to shed that log. Rerun the measurement:

npm run build -w @bimetal/store-sqlite
node packages/storage/store-sqlite/scripts/heap-messung.mjs 2000 9 3

Options (SqliteStoreOptions)

| Option | Purpose | |--------|---------| | path | Path to the SQLite database file. Use ':memory:' for an in-memory database. | | wal? | Enable WAL mode for better concurrent read performance. Default true. |

Testing

npm test --workspace @bimetal/store-sqlite

The parity suite (__tests__/storage-parity.test.ts) runs the same append/read/subscribe sequence against In-Memory, SQLite and EventStoreDB and asserts identical results — the guarantee that makes the adapter interchangeable.