-
Notifications
You must be signed in to change notification settings - Fork 2
Drivers SQLite
SQLite driver — runtime-branched wrapper. SQLite is embedded (no wire protocol), so each runtime ships its own bindings:
| Runtime | Backend | Notes |
|---|---|---|
| Deno |
jsr:@db/sqlite (FFI to libsqlite) |
Auto-installed |
| Bun |
bun:sqlite (built-in) |
Zero dependency |
| Node 22+ |
node:sqlite (built-in) |
Preferred |
| Node | npm:better-sqlite3 |
Fallback when node:sqlite missing |
- File-backed and in-memory (
:memory:) databases - Native
:name:parameters (rewritten to:name; on Bun, further rewritten tobun:sqlite's$nameform — the rewrite skips string literals, quoted identifiers, and comments, sostrftime('%H:%M', …)orWHERE code = 'AB:CD'behave identically across Deno / Bun / Node) - Per-runtime native bindings auto-selected
- Transactions (commit / rollback / auto-rollback / timeout)
The driver pins the pool to a single shared handle: any pool.min /
pool.max you pass is clamped to 1 — it cannot be overridden.
Concurrent execute calls serialize on that one handle automatically, and
Capabilities.pooledConnections is false.
The single handle is a hard invariant, not just a default.
pool.min/pool.maxare forced back to1in the constructor — before the options merge that otherwise lets caller values win — sopool: { min: 2, max: 5 }still yields exactly one connection. This protects correctness: SQLite serializes writers poorly, and in':memory:'mode each extra handle would be a separate, empty database — a table created on one connection would simply be missing on another, surfacing asTABLE_NOT_FOUND. Other pool knobs (idleTimeoutSeconds,acquireTimeoutSeconds) are still honored.
import { SQLiteEngine } from '@tundralibs/drivers/sqlite';
const db = new SQLiteEngine('app', { path: './data' });
// → ./data/app/main.db (directory mode)
await db.execute({
sql: 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)',
});
await db.execute({
sql: 'INSERT INTO users (name) VALUES (:name:)',
params: { name: 'Alice' },
});
await db.disconnect();Extends SQLEngineOptions.
| Option | Type | Default | Notes |
|---|---|---|---|
path |
string |
— | Required. Directory path (or :memory:). |
readonly |
boolean |
false |
Open in read-only mode. |
create |
boolean |
true |
Create file if missing (ignored for :memory:). |
path selects one of two modes:
-
Memory mode (
path: ':memory:') — a single in-process database. -
Directory mode (
path: '<dir>') —pathis treated as a parent directory. The engine creates<dir>/<name-lowercased>/and storesmain.dbthere, so multiple named engines can share one parent directory without colliding on filenames.
SQLite has no schema object in either mode: CREATE_SCHEMA /
DROP_SCHEMA are unsupported and throw DialectUnsupportedError before
reaching this engine. An OQL schema on any other query (CREATE_TABLE,
SELECT, CREATE_INDEX, …) is instead folded into the physical identifier
as a <schema>_<name> prefix by the translator, so every "schema" lives
in the same main.db — including foreign keys that cross a schema
boundary, which are enforced like any other.
SQLiteEngine extends SQLEngine and
inherits its full OQL surface (execute, transactions, schema
lifecycle, etc.).
SQLite values are normalized via _encodeValue:
| JS | SQLite binding |
|---|---|
undefined |
null |
Date |
ISO string |
boolean |
0 or 1
|
Uint8Array |
BLOB (raw) |
| object (non-buffer) | JSON string |
string / number
|
as-is |
:memory: databases are per-handle. Each :memory: handle is its own
private in-process database, so two SQLiteEngine instances with
path: ':memory:' are completely independent — they don't share data.
The single-handle pin keeps one shared handle within a single instance
(which is exactly why pool.max is clamped to 1 — a second handle in
:memory: mode would silently read from an empty database); it is not
what isolates separate instances.
SQLite error codes (SQLITE_*) and message text are mapped to standard
EngineError.code values. The driver also extracts table / column /
constraint names from SQLite's predictable error messages so the error
meta is fully populated.
deno run --allow-all packages/drivers/engines/sqlite/soak.ts