Skip to content

    Adapters02

    SQLite

    A file, memory, or Turso through one adapter built on libSQL.

    @bounda-dev/sqlite stores everything in SQLite through libSQL. The same adapter opens a local file, an in-memory database or a remote libSQL server such as Turso.

    TypeScript
    import { sqlite } from "@bounda-dev/sqlite";
    sqlite({ path: "./data/app.db" });
    sqlite({ memory: true });
    sqlite({ url: "libsql://my-app-me.turso.io", authToken: process.env.TURSO_AUTH_TOKEN });
    Option Default Meaning
    path Path of the database file, created if missing
    memory: true An in-memory database that lives as long as the app
    url, authToken A libSQL URL (libsql://, https:// or file:) and its token
    tablePrefix bounda_ Put in front of every table Bounda creates
    • position in the events table is INTEGER PRIMARY KEY AUTOINCREMENT. SQLite has a single writer, so the global order matches commit order without extra locking.
    • An append runs in BEGIN IMMEDIATE … COMMIT: the stream version is checked and the events are written inside one transaction. A stale version rolls back and the command is retried with fresh state.
    • Claims for handlers and scheduled commands are single statements with RETURNING, so two instances of your app on the same file never run the same handler for the same event at once.
    • Booleans are stored as 0/1, dates as ISO-8601 text, JSON fields as text. You never see that: rows come back typed from your fields.

    Within one process, on a file or in memory, the adapter runs its statements, and those a query sends through client.raw, one after another around its write transactions, so they never wait on a lock of their own. A file is opened in WAL mode, so other processes keep reading while one writes, and a statement waits up to five seconds for another process’s write instead of failing at once. A file not in WAL mode yet, such as one just restored from a backup, is switched by whichever process opens it first; others starting at the same moment try again for those same five seconds rather than fail. That is enough for a web process, a worker and bounda rebuild on one file; for heavier contention, or for more than one machine, use PostgreSQL.

    The SQL itself is not specific to libSQL. The stores, the schema and the read models live in @bounda-dev/core/adapter/sqlite, and createSqliteAdapter builds a complete adapter from any SQLite connection a host brings: this package brings libSQL, and the same code runs inside a Cloudflare Durable Object.

    createTestApp defaults to the in-memory adapter from @bounda-dev/core/memory, which runs no SQL. Pass sqlite({ memory: true }) when a test needs the real thing, for example to exercise a query written in SQL: see against a real database.