Files
suaveplan/packages/persistence/database/db-sqlite/src/drivers/wasm-driver.md
2026-05-27 23:47:53 +01:00

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 workerFactory parameter for test isolation — avoids real Worker spawning in Vitest
  • WasmProxyDb — { query(sql, params?), execute(sql, params?) } interface returned by the driver
  • close() 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.