Initial Commit
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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>`
|
||||
|
||||
```ts
|
||||
interface WasmProxyDb<TSchema> {
|
||||
query(sql: string, params?: unknown[]): Promise<unknown[][]>;
|
||||
execute(sql: string, params?: unknown[]): Promise<{ rowsAffected: number }>;
|
||||
}
|
||||
```
|
||||
|
||||
### `WasmDriverResult<TSchema>`
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user