Skip to content

SQLite persistence ​

Use this backend for a file-backed store or when an application already works with SQLite. It uses Node's built-in node:sqlite module and passes its string location to DatabaseSync.

ts
EventStore.persistence.sqlite<TMap>(
  location?: string
): Promise<TypedEventStore<TMap>>

The optional TMap schema map is described in EventStore; it defaults to an unconstrained map.

ts
import * as EventStore from "@jolly-pixel/event-store";

using store = await EventStore.persistence.sqlite("./events.sqlite");

Passing a file path creates or opens a durable database, and creates the directories leading to it. The default location is ":memory:", which lasts only until its SQLite connection closes and touches no filesystem path.

Journal ​

For a file location, the factory sets these before running the schema:

PragmaValueEffect
auto_vacuumINCREMENTALA file created without it is converted once with VACUUM
journal_modeWALWrites go to <location>-wal; readers do not block the writer
synchronousNORMALNo fsync per commit; a power loss can drop the last commits, a process crash cannot

SQLite keeps <location>-wal and <location>-shm next to the database while a connection is open and removes them on a clean close.

The factory returns a promise because the main package entry point loads the Node-only backend when the factory is called. Importing the main entry point is safe in a browser bundle as long as the SQLite factory is not called.

Node-only entry point ​

Node.js applications can import the factory directly:

ts
import { createSqliteEventStore } from "@jolly-pixel/event-store/node";

using store = await createSqliteEventStore("./events.sqlite");

The factory owns the connection it opens and closes it with the store.

Schema initialization ​

The factory runs the schema before returning. It creates the events and tags tables and their indexes when they do not exist, so a database made before tags existed gains the tags table the next time it is opened.

tags.create checks that the event exists and inserts the tag in one statement. The schema's unique (asset_id, name) constraint turns a duplicate name into an EventTagConflictError, whichever connection created the first one.

CREATE TABLE IF NOT EXISTS does not alter an existing table. A database made with a schema that predates the unique (asset_id, event_version) constraint must be migrated by the application or recreated.

Multiple connections ​

Each insert assigns the next asset version in the INSERT statement, while the schema's unique (asset_id, event_version) constraint prevents two connections from storing the same version for one asset. If SQLite rejects the insert, writer.append returns a Result error under the shared EventStore contract.

Compaction ​

compact deletes superseded rows. With the default reclaim: true, it releases the freed pages after a deletion: with PRAGMA incremental_vacuum when the database uses incremental auto-vacuum (the factory's default), with VACUUM otherwise. Pass reclaim: false to keep the freed pages; later inserts reuse them, so the file stops growing without shrinking.

A conditional append (expectedVersion) checks the asset's newest version in the same INSERT statement that assigns the next one.