4.1 KiB
wasm-driver
Purpose
Provides createWasmDriver, the browser-side SQLite driver for @suaveplan/db-sqlite. It spawns a Web Worker running wasm-worker.ts, which loads @sqlite.org/sqlite-wasm and manages a SQLite database instance. Communication between the caller and the worker uses a message-passing protocol with sequential IDs mapped to Promise resolve/reject pairs, giving a fully async interface without blocking the main thread.
OPFS (Origin Private File System) persistence is attempted automatically when a non-:memory: filename is supplied and the worker context supports sqlite3.oo1.OpfsDb. When OPFS headers (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp) are absent, the worker falls back to in-memory storage with a console warning.
Features
createWasmDriver(options?, workerFactory?)— spawns worker, awaits ready handshake, returns{ db, close }- Injectable
workerFactoryparameter for test isolation — avoids real Worker spawning in Vitest WasmProxyDb—{ query(sql, params?), execute(sql, params?) }interface returned by the driverclose()is idempotent and drains all pending requests with rejection before terminating the worker- Worker init failure terminates the worker and throws
SqliteError(SQLITE.DRIVER_INIT_FAILED) - Worker runtime errors reject all pending promises with
SqliteError(SQLITE.DRIVER_INIT_FAILED)
Usage: Basic Example
import { createWasmDriver } from "@suaveplan/db-sqlite/drivers/wasm-driver";
const { db, close } = await createWasmDriver();
await db.execute("CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)", []);
await db.execute("INSERT INTO kv VALUES (?, ?)", ["hello", "world"]);
const rows = await db.query("SELECT * FROM kv WHERE key = ?", ["hello"]);
await close();
Usage: Advanced Example
import { createWasmDriver } from "@suaveplan/db-sqlite/drivers/wasm-driver";
// With OPFS persistence (requires COOP/COEP headers on the page)
const { db, close } = await createWasmDriver({ filename: "app.db" });
try {
await db.execute("BEGIN IMMEDIATE", []);
await db.execute("INSERT INTO users VALUES (?, ?)", [1, "Alice"]);
await db.execute("COMMIT", []);
} catch (err) {
await db.execute("ROLLBACK", []);
throw err;
} finally {
await close();
}
API Reference
createWasmDriver<TSchema>(options?, workerFactory?): Promise<WasmDriverResult<TSchema>>
Spawns the WASM worker and awaits the ready handshake before resolving.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
options.filename |
string |
undefined (→ :memory:) |
OPFS file path or omit for in-memory |
workerFactory |
() => Worker |
defaultWorkerFactory |
Injectable for testing |
Throws SqliteError(SQLITE.DRIVER_INIT_FAILED) if the worker fails to initialise.
WasmProxyDb<TSchema>
interface WasmProxyDb<TSchema> {
query(sql: string, params?: unknown[]): Promise<unknown[][]>;
execute(sql: string, params?: unknown[]): Promise<{ rowsAffected: number }>;
}
WasmDriverResult<TSchema>
interface WasmDriverResult<TSchema> {
db: WasmProxyDb<TSchema>;
close: () => Promise<void>;
}
Implementation Notes
The worker URL is constructed with new URL("./wasm-worker.js", import.meta.url) so bundlers (Vite, webpack) can resolve it as a worker entry point and emit the correct asset path. The .js extension (not .ts) is used because the compiled output is what the browser loads at runtime.
The pending-promise map (Map<id, {resolve, reject}>) ensures that each in-flight request receives exactly one response, and that a stale response for a cancelled request is silently ignored. On close(), all pending promises are rejected before worker termination to prevent callers from hanging indefinitely.
OPFS support requires Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp response headers. The worker automatically falls back to in-memory storage without these headers rather than crashing, since detecting header presence from inside a Worker is unreliable.