UrsulaDatabase

Last updated 29 October 2026

View .md

UrsulaDatabase is the connection open() returns: a node:sqlite DatabaseSync with the members below.

UrsulaDatabase

Exported by @tonbo/sqlite-ursula and /tonbo.

class UrsulaDatabase extends DatabaseSync {
  readonly name: string;
  readonly boundPath: string;
  synced(options?: { signal?: AbortSignal }): Promise<void>;
  status(): UrsulaStatus;
  on(event: "reset", listener: ResetListener): this;
  off(event: "reset", listener: ResetListener): this;
  close(): void;
}

type ResetListener = (error: UrsulaResetError) => void;

Every DatabaseSync method works as in node:sqlite. The ones that run SQL (exec, applyChangeset, and run, get, all and iterate of the statements prepare returns) throw an UrsulaError for a failure of the extension and every other error unchanged. Get a connection only from open(): one constructed directly, as a library may do through db.constructor, is a plain DatabaseSync that classifies nothing.

MemberWhat it is
nameThe stream’s path, such as /acme/notes.
boundPathThe local database file the extension bound, as status().path reports it.
synced()Resolves once the stream holds every earlier commit. See synced().
status()The database’s state, synchronously. See Status.
on("reset", listener), off("reset", listener)Add or remove a reset listener. Another event name throws TypeError. Both return the database.
close()Closes the connection. See close().

synced()

synced(options?: { signal?: AbortSignal }): Promise<void>

Resolves once the stream holds every commit this process logged before the call, and a round trip that started after the call found this process still the owner. Await it before anything outside the process acts on what you wrote or read; gate() does that for a handler. It does not block the event loop, and it keeps the process alive while it waits.

  • Network failures never reject it: it waits. Past maxPendingBytes, writes fail with backlog_full meanwhile.
  • It rejects with the UrsulaResetError on a reset, and with signal.reason when signal fires; the commits keep replicating.
  • On a reader cache (a readOnly open in a process that does not own the stream) it resolves at once: the reader holds no commit.
  • On a database that retired it rejects with UrsulaSetupError.

What the stream then holds, and how durably, is on When a commit is durable.

Reset listeners

ResetListener is (error: UrsulaResetError) => void. When the database is reset, its pending synced() calls reject first, then each listener runs once with the reset. A listener that throws does not stop the others: its error is rethrown outside, as an uncaught exception, as an EventEmitter’s would be.

db.on("reset", (error) => {
  if (error.action === "stop") process.exit(1); // another process took it over
  // reopen or fix: stop taking work, then open() the database again
});

After a reset

Every statement of the database throws the UrsulaResetError, reads included, except reads in a read transaction it had open, which keep their snapshot; a write in it still fails. open() the database again, or stop when the action is stop. The new open need not wait for the old connection to close: it works on a copy of the local cache, which carries every commit the old one made. Close the old connection once you no longer need it.

A refused write or a reset rolls SQLite’s whole transaction back. A ROLLBACK that a transaction helper sends afterwards succeeds as a prepared statement, and through exec it throws the same UrsulaError again, so the error you handle is the package’s, not “no transaction is active”.

close()

Closes the connection, synchronously. Once a binding’s last connection closes, the commits left replicate in the background and the binding retires; a process that exits before then leaves them in the local log, and the next open sends them. To use the database again, call the package’s open(): DatabaseSync’s own open() method binds nothing.

Salvage

A local cache an open could not keep is moved into salvage/, and status().salvage lists it. When that happens and what to do with it is on Backups and salvage.