first commit
This commit is contained in:
110
docs/persistence/database/db-sqlite/README.md
Normal file
110
docs/persistence/database/db-sqlite/README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# @suaveplan/db-sqlite - Universal SQLite adapter for Suaveplan: bun:sqlite on the Bun server, @sqlite.org/sqlite-wasm in the browser, unified behind a Drizzle-ORM surface with LRU prepared-statement cache, forward-only migration runner, OTel instrumentation, and structured SQLITE.* error codes.
|
||||
|
||||
## Summary
|
||||
|
||||
Universal SQLite adapter for Suaveplan. Runs on `bun:sqlite` on the Bun server and `@sqlite.org/sqlite-wasm` (Web Worker) in the browser, unified behind a single `SqliteAdapter` that implements `DatabaseAdapter` from `@suaveplan/db`. Includes an LRU prepared-statement cache (128 entries by default), a forward-only ledger-tracked `MigrationRunner` with dry-run support, OpenTelemetry span instrumentation, structured `SQLITE_*` error codes, and a `./unsafe` subpath for raw queries.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [adapter](./src/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> - adapter.wasm — Browser-runtime entry point for the……
|
||||
- [adapter.bun](./src/adapter/adapter.bun.md) — Bun-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a bundler or the Bun run…
|
||||
- [adapter](./src/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> - adapter.wasm — Browser-runtime entry point for the……
|
||||
- [adapter.wasm](./src/adapter/adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [conformance](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [conformance-suite](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [bun-driver](./src/drivers/bun-driver.md) — Provides `createBunDriver`, the server-side SQLite driver for `@suaveplan/db-sqlite`. It dynamically imports `bun:sqlit…
|
||||
- [wasm-driver](./src/drivers/wasm-driver.md) — Provides `createWasmDriver`, the browser-side SQLite driver for `@suaveplan/db-sqlite`. It spawns a Web Worker running…
|
||||
- [wasm-worker](./src/drivers/wasm-worker.md) — Web Worker implementation for the WASM SQLite driver. This module runs inside a dedicated worker thread created by `cre…
|
||||
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - adapter.wasm — Browser-runtime entry point for the……
|
||||
- [adapter.bun](./src/adapter/adapter.bun.md) — Bun-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a bundler or the Bun run…
|
||||
- [adapter](./src/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- adapter.wasm — Browser-runtime entry point for the……
|
||||
- [adapter.wasm](./src/adapter/adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance-suite](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [bun-driver](./src/drivers/bun-driver.md) — Provides `createBunDriver`, the server-side SQLite driver for `@suaveplan/db-sqlite`. It dynamically imports `bun:sqlit…
|
||||
- [wasm-driver](./src/drivers/wasm-driver.md) — Provides `createWasmDriver`, the browser-side SQLite driver for `@suaveplan/db-sqlite`. It spawns a Web Worker running…
|
||||
- [wasm-worker](./src/drivers/wasm-worker.md) — Web Worker implementation for the WASM SQLite driver. This module runs inside a dedicated worker thread created by `cre…
|
||||
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_- adapter.wasm — Browser-runtime entry point for the……
|
||||
|
||||
- [adapter.bun](./src/adapter/adapter.bun.md) — Bun-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a bundler or the Bun run…
|
||||
- [adapter](./src/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- adapter.wasm — Browser-runtime entry point for the……
|
||||
- [adapter.wasm](./src/adapter/adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance-suite](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [bun-driver](./src/drivers/bun-driver.md) — Provides `createBunDriver`, the server-side SQLite driver for `@suaveplan/db-sqlite`. It dynamically imports `bun:sqlit…
|
||||
- [wasm-driver](./src/drivers/wasm-driver.md) — Provides `createWasmDriver`, the browser-side SQLite driver for `@suaveplan/db-sqlite`. It spawns a Web Worker running…
|
||||
- [wasm-worker](./src/drivers/wasm-worker.md) — Web Worker implementation for the WASM SQLite driver. This module runs inside a dedicated worker thread created by `cre…
|
||||
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_- adapter.wasm — Browser-runtime entry point for the…
|
||||
|
||||
- [adapter.bun](./src/adapter/adapter.bun.md) — Bun-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a bundler or the Bun run…
|
||||
- [adapter](./src/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- adapter.wasm — Browser-runtime entry point for the…
|
||||
- [adapter.wasm](./src/adapter/adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance-suite](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [bun-driver](./src/drivers/bun-driver.md) — Provides `createBunDriver`, the server-side SQLite driver for `@suaveplan/db-sqlite`. It dynamically imports `bun:sqlit…
|
||||
- [wasm-driver](./src/drivers/wasm-driver.md) — Provides `createWasmDriver`, the browser-side SQLite driver for `@suaveplan/db-sqlite`. It spawns a Web Worker running…
|
||||
- [wasm-worker](./src/drivers/wasm-worker.md) — Web Worker implementation for the WASM SQLite driver. This module runs inside a dedicated worker thread created by `cre…
|
||||
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_- [adapter.bun](./src/adapter/adapter.bun.md) — Bun-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a bundler or the Bun run…
|
||||
- [adapter](./src/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- [adapter.wasm](./src/adapter/adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_ - [cache](./src/cache/cache.md) — <!-- begin:auto-docs --> _(no submodules)_- [conformance](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_ - [conformance-suite](./src/conformance/conformance-suite.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_ - [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [bun-driver](./src/drivers/bun-driver.md) — Provides `createBunDriver`, the server-side SQLite driver for `@suaveplan/db-sqlite`. It dynamically imports `bun:sqlit…
|
||||
- [wasm-driver](./src/drivers/wasm-driver.md) — Provides `createWasmDriver`, the browser-side SQLite driver for `@suaveplan/db-sqlite`. It spawns a Web Worker running…
|
||||
- [wasm-worker](./src/drivers/wasm-worker.md) — Web Worker implementation for the WASM SQLite driver. This module runs inside a dedicated worker thread created by `cre…
|
||||
- [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_ - [errors](./src/errors/errors.md) — <!-- begin:auto-docs --> _(no submodules)_- [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_ - [migration](./src/migration/migration.md) — <!-- begin:auto-docs --> _(no submodules)_- [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_ - [otel](./src/otel/otel.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ - [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_ - [unsafe](./src/unsafe/unsafe.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/db-sqlite
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { createSqliteAdapter } from "@suaveplan/db-sqlite/adapter";
|
||||
|
||||
const db = await createSqliteAdapter({ url: ":memory:" });
|
||||
|
||||
await db.exec(`
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await db.run("INSERT INTO users (id, email) VALUES (?, ?)", [
|
||||
"user-123",
|
||||
"alice@example.com",
|
||||
]);
|
||||
|
||||
const rows = await db.all<{ id: string; email: string }>(
|
||||
"SELECT id, email FROM users WHERE id = ?",
|
||||
["user-123"],
|
||||
);
|
||||
|
||||
await db.close();
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [Adapter (universal)](./src/adapter/adapter.md) — `SqliteAdapter`, `createSqliteAdapter`, runtime selection
|
||||
- [Adapter (Bun)](./src/adapter/adapter.bun.md) — `bun:sqlite` driver binding
|
||||
- [Adapter (WASM)](./src/adapter/adapter.wasm.md) — browser/Worker `@sqlite.org/sqlite-wasm` binding
|
||||
- [Cache](./src/cache/cache.md) — `PreparedStatementCache` (LRU)
|
||||
- [Connection](./src/connection/connection.md) — connection helpers and singleton management
|
||||
- [Bun driver](./src/drivers/bun-driver.md) — low-level Bun SQLite driver
|
||||
- [WASM driver](./src/drivers/wasm-driver.md) — low-level WASM SQLite driver
|
||||
- [WASM worker](./src/drivers/wasm-worker.md) — Web Worker host for WASM driver
|
||||
- [Errors](./src/errors/errors.md) — `SqliteError`, `SQLITE_*` codes
|
||||
- [Migration](./src/migration/migration.md) — `MigrationRunner`, forward-only migration ledger
|
||||
- [OTel](./src/otel/otel.md) — span and metric instrumentation
|
||||
- [Schemas](./src/schemas/schemas.md) — Zod-validated config types
|
||||
- [Conformance](./src/conformance/conformance-suite.md) — shared `DatabaseAdapter` conformance suite
|
||||
- [Unsafe](./src/unsafe/unsafe.md) — `./unsafe` subpath for raw queries
|
||||
@@ -0,0 +1,61 @@
|
||||
# adapter.bun
|
||||
|
||||
## Purpose
|
||||
|
||||
Bun-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a bundler or the Bun runtime resolves `import { SqliteAdapter } from "@suaveplan/db-sqlite/adapter"`, this file is selected via the `"bun"` condition in the package's `exports` field. It re-exports `SqliteAdapter` and the `SqliteAdapterOptions` type so consumers see a single, stable surface regardless of which environment condition matched.
|
||||
|
||||
The separation between adapter entry points prevents browser bundles from pulling in `bun:sqlite` references, and prevents Bun server bundles from pulling in WASM worker code. Each entry is a thin barrel that resolves at bundle time.
|
||||
|
||||
## Features
|
||||
|
||||
- Conditional export target for the `"bun"` condition in `package.json`
|
||||
- Re-exports `SqliteAdapter` from `./adapter.js`
|
||||
- Re-exports `SqliteAdapterOptions` type from `../schemas/index.js`
|
||||
- Zero runtime overhead — pure re-export barrel
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Resolved automatically by Bun via conditional exports
|
||||
import { SqliteAdapter } from "@suaveplan/db-sqlite/adapter";
|
||||
|
||||
const db = new SqliteAdapter();
|
||||
await db.connect();
|
||||
const rows = await db.query("SELECT 1 AS n");
|
||||
await db.disconnect();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { SqliteAdapter } from "@suaveplan/db-sqlite/adapter";
|
||||
import type { SqliteAdapterOptions } from "@suaveplan/db-sqlite/adapter";
|
||||
|
||||
const opts: SqliteAdapterOptions = {
|
||||
filename: "/var/data/app.db",
|
||||
readonly: false,
|
||||
};
|
||||
|
||||
const adapter = new SqliteAdapter(opts);
|
||||
await adapter.connect();
|
||||
|
||||
await adapter.migrate({ migrationsFolder: "./migrations" });
|
||||
|
||||
await adapter.transaction(async (tx) => {
|
||||
await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
||||
});
|
||||
|
||||
await adapter.disconnect();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
This file does not export any symbols directly — it is a conditional-export barrel. All types and values are documented in [`adapter.md`](./adapter.md) (`SqliteAdapter`) and [`../schemas/schemas.md`](../schemas/schemas.md) (`SqliteAdapterOptions`).
|
||||
|
||||
**Conditional export condition:** `"bun"` — selected when the consumer runs under the Bun runtime or when a bundler sets the `bun` condition (e.g. via `resolve.conditions: ["bun"]` in Vite).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The file intentionally contains no logic. Any change to the Bun-specific adapter behaviour lives in `adapter.ts`, not here. This design keeps the conditional export system orthogonal to implementation: adding a new runtime condition (e.g. `"deno"`) only requires adding a new entry-point barrel and a new `exports` condition, with no changes to the core adapter code.
|
||||
|
||||
Vite excludes this file from the default ESM browser bundle because the `"browser"` condition resolves to `adapter.wasm.ts` instead.
|
||||
113
docs/persistence/database/db-sqlite/src/adapter/adapter.md
Normal file
113
docs/persistence/database/db-sqlite/src/adapter/adapter.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# adapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs --> - adapter.wasm — Browser-runtime entry point for the……
|
||||
- [adapter.wasm](./adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- adapter.wasm — Browser-runtime entry point for the……
|
||||
|
||||
- [adapter.wasm](./adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
|
||||
- adapter.wasm — Browser-runtime entry point for the……
|
||||
|
||||
- [adapter.wasm](./adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
|
||||
- adapter.wasm — Browser-runtime entry point for the…
|
||||
|
||||
- [adapter.wasm](./adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
- [adapter.wasm](./adapter.wasm.md) — Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler re…
|
||||
|
||||
## Purpose
|
||||
|
||||
`SqliteAdapter` is the main `DatabaseAdapter` implementation for `@suaveplan/db-sqlite`. It implements the full `DatabaseAdapter` contract from `@suaveplan/db`, backing it with a `bun:sqlite` database instance, an LRU `PreparedStatementCache`, a `MigrationRunner`, and OTel span instrumentation on every public operation.
|
||||
|
||||
The adapter is designed for the Bun server runtime only. Attempting to construct it outside Bun throws `SqliteError(SQLITE.ENVIRONMENT_ERROR)` immediately. Browser callers should use `createWasmDriver` directly via the `./adapter` conditional export, which resolves to `adapter.wasm.ts` in browser bundles.
|
||||
|
||||
## Features
|
||||
|
||||
- Implements `DatabaseAdapter` from `@suaveplan/db` — fully substitutable wherever `DatabaseLike` is expected
|
||||
- Idempotent `connect()` — concurrent callers share one in-flight connection promise
|
||||
- `PreparedStatementCache` (LRU, default 128 entries) reused across `query()` calls
|
||||
- `transaction()` uses `BEGIN IMMEDIATE` to prevent deadlocks on concurrent writes; auto-rollback on failure
|
||||
- `migrate()` delegates to `MigrationRunner` and invalidates the cache on success
|
||||
- `AbortSignal` respected on every public method — throws `SqliteError(SQLITE.*)` when aborted
|
||||
- All operations wrapped in `withQuerySpan` for OTel tracing and latency metrics
|
||||
- `sideEffects: false` — safe for tree-shaking
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SqliteAdapter } from "@suaveplan/db-sqlite";
|
||||
|
||||
const adapter = new SqliteAdapter({ filename: ":memory:" });
|
||||
await adapter.connect();
|
||||
|
||||
const rows = await adapter.query<{ id: number }>(
|
||||
"SELECT id FROM users WHERE active = ?",
|
||||
[1]
|
||||
);
|
||||
await adapter.disconnect();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { SqliteAdapter } from "@suaveplan/db-sqlite";
|
||||
|
||||
const adapter = new SqliteAdapter({ filename: "/data/app.db" });
|
||||
await adapter.connect();
|
||||
|
||||
// Transactional insert
|
||||
await adapter.transaction(async (tx) => {
|
||||
await tx.execute("INSERT INTO accounts VALUES (?, ?)", [1, "Alice"]);
|
||||
await tx.execute("INSERT INTO accounts VALUES (?, ?)", [2, "Bob"]);
|
||||
});
|
||||
|
||||
// Run migrations
|
||||
const result = await adapter.migrate({
|
||||
migrationsFolder: "./db/migrations",
|
||||
});
|
||||
console.log(`Applied ${result.applied} migration(s)`);
|
||||
|
||||
await adapter.disconnect();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new SqliteAdapter(options?, schema?)`
|
||||
|
||||
Validates options, throws `SqliteError(SQLITE.ENVIRONMENT_ERROR)` outside Bun.
|
||||
|
||||
### `connect(config?, opts?): Promise<void>`
|
||||
|
||||
Opens the SQLite file or `:memory:` database. Idempotent under concurrent calls — only one connection is opened.
|
||||
|
||||
### `disconnect(opts?): Promise<void>`
|
||||
|
||||
Closes the database, invalidates the prepared-statement cache, and resets connection state.
|
||||
|
||||
### `query<T>(sql, params?, opts?): Promise<T[]>`
|
||||
|
||||
Executes a `SELECT` query. Uses the prepared-statement cache for repeated SQL strings.
|
||||
|
||||
### `execute(sql, params?, opts?): Promise<{ rowsAffected: number }>`
|
||||
|
||||
Executes a `INSERT`/`UPDATE`/`DELETE` statement. Returns affected row count.
|
||||
|
||||
### `transaction<T>(fn, opts?): Promise<T>`
|
||||
|
||||
Runs `fn` inside a `BEGIN IMMEDIATE` transaction. Commits on success, rolls back on failure.
|
||||
|
||||
### `migrate(config?, opts?): Promise<MigrationResult>`
|
||||
|
||||
Delegates to `MigrationRunner` and invalidates the prepared-statement cache on success.
|
||||
|
||||
### `isConnected: boolean`
|
||||
|
||||
`true` after `connect()` and before `disconnect()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `_connectingPromise` field serialises concurrent `connect()` calls — the second caller awaits the same in-flight promise rather than opening a second connection. This is safe because `bun:sqlite` uses synchronous I/O under the covers and does not support concurrent connections to the same file.
|
||||
|
||||
`transaction()` casts `this` to `DatabaseLike` when passing it to `fn`. This is sound because `SqliteAdapter` implements all `DatabaseLike` methods. The cast avoids exposing internal `_sqlite` state to transaction callbacks while keeping the type constraint tight.
|
||||
@@ -0,0 +1,64 @@
|
||||
# adapter.wasm
|
||||
|
||||
## Purpose
|
||||
|
||||
Browser-runtime entry point for the `./adapter` conditional export in `@suaveplan/db-sqlite`. When a browser bundler resolves `import { createWasmDriver } from "@suaveplan/db-sqlite/adapter"`, this file is selected via the `"browser"` condition in the package's `exports` field. It re-exports the WASM-backed driver factory and its related types, giving browser consumers a clean surface that never references `bun:sqlite` or any Node.js-only API.
|
||||
|
||||
The separation between Bun and WASM adapter entry points keeps tree-shaking effective: a Bun server bundle never pulls in `@sqlite.org/sqlite-wasm` or the Web Worker, and a browser bundle never pulls in `bun:sqlite`. Each entry point is resolved at build time by the bundler's condition matching.
|
||||
|
||||
## Features
|
||||
|
||||
- Conditional export target for the `"browser"` condition in `package.json`
|
||||
- Re-exports `createWasmDriver`, `WasmDriverResult`, and `WasmProxyDb` from the WASM driver
|
||||
- Re-exports `WasmDriverOptions` type from the shared schemas
|
||||
- Zero runtime overhead — pure re-export barrel
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Resolved automatically by the browser bundler via conditional exports
|
||||
import { createWasmDriver } from "@suaveplan/db-sqlite/adapter";
|
||||
|
||||
const { db, close } = await createWasmDriver();
|
||||
const rows = await db.query("SELECT 1 AS n");
|
||||
await close();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createWasmDriver } from "@suaveplan/db-sqlite/adapter";
|
||||
import type { WasmDriverOptions } from "@suaveplan/db-sqlite/adapter";
|
||||
|
||||
const opts: WasmDriverOptions = {
|
||||
filename: "app.db", // OPFS-backed persistent storage
|
||||
};
|
||||
|
||||
const { db, close } = await createWasmDriver(opts);
|
||||
|
||||
await db.execute(
|
||||
"CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
||||
);
|
||||
await db.execute("INSERT INTO notes (body) VALUES (?)", ["Hello from the browser"]);
|
||||
|
||||
const rows = await db.query<{ id: number; body: string }>(
|
||||
"SELECT * FROM notes ORDER BY id"
|
||||
);
|
||||
console.log(rows);
|
||||
|
||||
await close();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
This file does not export any symbols directly — it is a conditional-export barrel. All types and values are documented in [`../drivers/wasm-driver.md`](../drivers/wasm-driver.md).
|
||||
|
||||
**Conditional export condition:** `"browser"` — selected when a bundler uses browser-targeting conditions (e.g. Vite's default, Webpack's `target: "web"`).
|
||||
|
||||
**Default fallback:** When neither `"bun"` nor `"browser"` conditions are active (e.g. vanilla Node.js `require()`), the `exports.default` condition falls back to the Bun adapter entry point. The WASM path is never the fallback to avoid loading 1.5 MB of WASM in server-side contexts.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`WasmDriverOptions` is imported from `../schemas/index.js` rather than from `wasm-driver.js` because the Zod schemas live in the shared schemas module — keeping the driver files free of Zod as a direct import. This pattern is consistent across all driver options types in this package.
|
||||
|
||||
The WASM worker bundle (`wasm-worker.js`) is inlined as an asset URL by Vite at build time using `new URL("./wasm-worker.js", import.meta.url)`. The worker receives messages for `init`, `query`, `execute`, and `close`, and responds synchronously from SQLite's in-memory or OPFS-backed database.
|
||||
88
docs/persistence/database/db-sqlite/src/cache/cache.md
vendored
Normal file
88
docs/persistence/database/db-sqlite/src/cache/cache.md
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
# cache
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`PreparedStatementCache` is an LRU-bounded cache for `bun:sqlite` prepared statement objects, keyed by SQL text. Reusing prepared statements avoids the overhead of repeated SQL parsing and optimisation — a significant win for hot query paths. The cache is bounded to prevent unbounded memory growth in applications that generate many distinct SQL strings.
|
||||
|
||||
The cache integrates with OpenTelemetry to surface hit/miss/eviction counters at `sqlite.cache.hits`, `sqlite.cache.misses`, and `sqlite.cache.evictions`. These are no-ops when no OTel provider is registered, keeping the module safe for use in zero-dependency environments.
|
||||
|
||||
## Features
|
||||
|
||||
- LRU eviction when `size >= maxSize` (default `128`)
|
||||
- `get(sql)` — returns cached statement or `undefined`; updates LRU order
|
||||
- `set(sql, stmt)` — inserts or updates; evicts LRU entry when at capacity, calling `stmt.finalize()` on the evicted statement
|
||||
- `invalidate()` — clears all entries and calls `finalize()` on each held statement; called by `SqliteAdapter.migrate()` to prevent stale plan reuse after schema changes
|
||||
- `size` / `maxSize` read-only getters
|
||||
- OTel counters: `sqlite.cache.hits`, `sqlite.cache.misses`, `sqlite.cache.evictions`
|
||||
- `static resetInstruments()` — clears the OTel lazy singleton for test isolation
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { PreparedStatementCache } from "@suaveplan/db-sqlite/cache/cache";
|
||||
|
||||
const cache = new PreparedStatementCache();
|
||||
const stmt = sqlite.prepare("SELECT * FROM users WHERE id = ?");
|
||||
cache.set("SELECT * FROM users WHERE id = ?", stmt);
|
||||
|
||||
const hit = cache.get("SELECT * FROM users WHERE id = ?");
|
||||
// hit === stmt
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { PreparedStatementCache } from "@suaveplan/db-sqlite/cache/cache";
|
||||
|
||||
// Small cache for testing eviction
|
||||
const cache = new PreparedStatementCache(3);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const sql = `SELECT ${i} FROM t`;
|
||||
cache.set(sql, { finalize: () => undefined }); // mock statement
|
||||
}
|
||||
|
||||
console.log(cache.size); // 3 — first entry evicted
|
||||
|
||||
cache.invalidate();
|
||||
console.log(cache.size); // 0 — all entries cleared
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new PreparedStatementCache(maxSize?: number)`
|
||||
|
||||
Constructs a new cache. `maxSize` defaults to `128`.
|
||||
|
||||
### `get(sql: string): Statement | undefined`
|
||||
|
||||
Returns the cached statement for `sql`, or `undefined` on miss. Increments `sqlite.cache.hits` or `sqlite.cache.misses`. Updates LRU order on hit.
|
||||
|
||||
### `set(sql: string, stmt: Statement): void`
|
||||
|
||||
Inserts or updates the cache entry for `sql`. On capacity overflow, evicts the least-recently-used entry, calling `stmt.finalize()` on it, and increments `sqlite.cache.evictions`.
|
||||
|
||||
### `invalidate(): void`
|
||||
|
||||
Clears all entries. Calls `stmt.finalize()` on each held statement before clearing. Called automatically by `SqliteAdapter.migrate()`.
|
||||
|
||||
### `size: number`
|
||||
|
||||
Current number of cached entries.
|
||||
|
||||
### `maxSize: number`
|
||||
|
||||
Maximum capacity as set at construction.
|
||||
|
||||
### `static resetInstruments(): void`
|
||||
|
||||
Clears the lazy OTel instruments singleton. Use in tests to ensure each test suite starts with a fresh counter baseline.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
LRU ordering is maintained with a parallel `string[]` array (`#lruOrder`) rather than a doubly-linked list. This is simpler and sufficient for cache sizes up to a few hundred entries — `Array.indexOf` and `splice` on an array of at most 128 strings add negligible overhead compared to a SQLite query round-trip. For much larger caches a linked-list implementation would be preferable.
|
||||
|
||||
`finalize()` is called defensively with a try/catch because `bun:sqlite` statements may already be finalised if the underlying database was closed before the cache was cleared.
|
||||
@@ -0,0 +1,64 @@
|
||||
# conformance-suite
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides `runConformanceSuite`, a driver-agnostic test factory that accepts a `DriverFactory` callback and executes a standard suite of CRUD, transaction, and rollback assertions against any SQLite driver. Running the same suite against both the Bun driver and a WASM driver ensures that both runtimes satisfy the same behavioural contract, catching driver-specific regressions before they reach production.
|
||||
|
||||
The suite is intentionally minimal — it tests the lowest-level SQL operations that the adapter layer depends on. Higher-level tests (prepared-statement cache behaviour, migration runner idempotency, OTel counter increments) live in their respective unit test files.
|
||||
|
||||
## Features
|
||||
|
||||
- `runConformanceSuite(name, factory)` — registers a Vitest `describe` block named `${name} conformance`
|
||||
- Tests: table creation, SELECT, INSERT with parameters, transaction commit, transaction rollback, UPDATE, DELETE
|
||||
- `DriverFactory` interface — a single `create()` method returning `{ sqlite, close }`
|
||||
- Runs under the Bun driver via `bun-conformance.test.ts`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { runConformanceSuite } from "@suaveplan/db-sqlite/src/conformance/conformance-suite";
|
||||
import { createBunDriver } from "@suaveplan/db-sqlite/drivers/bun-driver";
|
||||
|
||||
runConformanceSuite("Bun driver", {
|
||||
async create() {
|
||||
return createBunDriver({ filename: ":memory:" });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { runConformanceSuite } from "@suaveplan/db-sqlite/src/conformance/conformance-suite";
|
||||
|
||||
// Custom driver shim for testing
|
||||
runConformanceSuite("Custom driver", {
|
||||
async create() {
|
||||
const driver = await MyCustomDriver.open(":memory:");
|
||||
return { sqlite: driver.raw, close: async () => driver.close() };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `DriverFactory`
|
||||
|
||||
```ts
|
||||
interface DriverFactory {
|
||||
create(): Promise<{ sqlite: any; close: () => Promise<void> }>;
|
||||
}
|
||||
```
|
||||
|
||||
### `runConformanceSuite(name: string, factory: DriverFactory): void`
|
||||
|
||||
Registers a Vitest `describe` block with 5 test cases covering the core SQLite contract.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The conformance suite uses raw `sqlite.run()` / `sqlite.query().all()` calls rather than going through `SqliteAdapter` or Drizzle. This keeps the suite focused on driver-level semantics rather than adapter-layer concerns, making it useful for validating new driver implementations independently of the adapter code.
|
||||
|
||||
Each test creates fresh tables with unique names to avoid interference between tests within the same suite run.
|
||||
@@ -0,0 +1,83 @@
|
||||
# connection
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides thin singleton-management wrappers for `SqliteAdapter`, built on top of the `@suaveplan/db` singleton helpers. These functions let callers share a single connected `SqliteAdapter` instance across an application without manually tracking the singleton lifecycle or passing the adapter through every layer of the call stack.
|
||||
|
||||
The helpers default the singleton name to `"default"` so simple applications need only call `getDefaultSqlite()` with no arguments. More complex applications with multiple databases use explicit names.
|
||||
|
||||
## Features
|
||||
|
||||
- `getDefaultSqlite(options?, config?, name?)` — creates-or-returns a connected `SqliteAdapter` singleton
|
||||
- `closeSqlite(name?)` — disconnects and removes the named singleton
|
||||
- `closeAllSqlite()` — disconnects and removes all `SqliteAdapter` singletons
|
||||
- `hasSqlite(name?)` — returns `true` if the named singleton exists and is connected
|
||||
- Default name: `"default"` — single-database apps never need to supply a name
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { getDefaultSqlite } from "@suaveplan/db-sqlite/connection/connection";
|
||||
|
||||
const adapter = await getDefaultSqlite();
|
||||
const rows = await adapter.query("SELECT * FROM users");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
closeAllSqlite,
|
||||
closeSqlite,
|
||||
getDefaultSqlite,
|
||||
hasSqlite,
|
||||
} from "@suaveplan/db-sqlite/connection/connection";
|
||||
|
||||
// Primary database
|
||||
const primary = await getDefaultSqlite(
|
||||
{ filename: "/data/app.db" },
|
||||
undefined,
|
||||
"primary"
|
||||
);
|
||||
|
||||
// Analytics read-only shard
|
||||
const analytics = await getDefaultSqlite(
|
||||
{ filename: "/data/analytics.db", readonly: true },
|
||||
undefined,
|
||||
"analytics"
|
||||
);
|
||||
|
||||
console.log(hasSqlite("primary")); // true
|
||||
console.log(hasSqlite("analytics")); // true
|
||||
|
||||
await closeSqlite("analytics");
|
||||
console.log(hasSqlite("analytics")); // false
|
||||
|
||||
// Shutdown — closes all remaining singletons
|
||||
await closeAllSqlite();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getDefaultSqlite(options?, config?, name?): Promise<SqliteAdapter>`
|
||||
|
||||
Creates a new `SqliteAdapter` using the given `options`, calls `connect(config)` on it, caches it under `name`, and returns it. Subsequent calls with the same `name` return the cached instance without reconnecting.
|
||||
|
||||
### `closeSqlite(name?): Promise<void>`
|
||||
|
||||
Disconnects and removes the singleton named `name`. No-op if no singleton by that name exists.
|
||||
|
||||
### `closeAllSqlite(): Promise<void>`
|
||||
|
||||
Disconnects and removes all singletons. Throws `AggregateError` if any disconnect fails.
|
||||
|
||||
### `hasSqlite(name?): boolean`
|
||||
|
||||
Returns `true` if a singleton exists under `name`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
These functions delegate entirely to `@suaveplan/db`'s `getSingleton`, `closeSingleton`, `closeAllSingletons`, and `hasSingleton`. The `SqliteAdapter`-specific names (`getDefaultSqlite`, `closeSqlite`, etc.) exist for discoverability — callers working with SQLite need not know the generic singleton API. The return type is narrowed from `DatabaseAdapter` to `SqliteAdapter` via a cast so callers have access to SQLite-specific properties.
|
||||
@@ -0,0 +1,81 @@
|
||||
# bun-driver
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides `createBunDriver`, the server-side SQLite driver for `@suaveplan/db-sqlite`. It dynamically imports `bun:sqlite` (a built-in Bun module) and returns a thin wrapper holding the raw `Database` instance plus an idempotent `close()` function. The `isBunEnvironment()` guard ensures the driver is never accidentally called in a browser context — doing so raises `SQLITE.ENVIRONMENT_ERROR` immediately rather than surfacing a confusing module-not-found error at import time.
|
||||
|
||||
The dynamic `import("bun:sqlite")` pattern keeps the module evaluatable in non-Bun environments (so bundlers can tree-shake it out and browser builds do not fail), while still providing full type information from `@types/bun`.
|
||||
|
||||
## Features
|
||||
|
||||
- `createBunDriver(options?)` — opens an in-memory (default) or file-backed SQLite database via `bun:sqlite`
|
||||
- `isBunEnvironment()` — detects Bun runtime via `process.versions.bun`
|
||||
- `BunDriverResult` — `{ sqlite, close }` shape; `close()` is idempotent (safe to call multiple times)
|
||||
- Throws `SqliteError(SQLITE.ENVIRONMENT_ERROR)` when called outside Bun
|
||||
- Throws `SqliteError(SQLITE.DRIVER_INIT_FAILED)` if `Database` constructor throws (e.g. bad path, locked file)
|
||||
- `readonly` option forwarded to `bun:sqlite` `Database` constructor
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createBunDriver } from "@suaveplan/db-sqlite/drivers/bun-driver";
|
||||
|
||||
const { sqlite, close } = await createBunDriver();
|
||||
// sqlite is a bun:sqlite Database instance — run raw SQL or pass to Drizzle
|
||||
await close();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createBunDriver, isBunEnvironment } from "@suaveplan/db-sqlite/drivers/bun-driver";
|
||||
|
||||
if (!isBunEnvironment()) {
|
||||
throw new Error("Server-side only path");
|
||||
}
|
||||
|
||||
const { sqlite, close } = await createBunDriver({
|
||||
filename: "/data/app.db",
|
||||
readonly: false,
|
||||
});
|
||||
|
||||
try {
|
||||
sqlite.run("CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)");
|
||||
sqlite.run("INSERT OR REPLACE INTO kv VALUES (?, ?)", ["hello", "world"]);
|
||||
} finally {
|
||||
await close(); // second call is a no-op
|
||||
await close(); // safe
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `isBunEnvironment(): boolean`
|
||||
|
||||
Returns `true` when `process.versions.bun` is defined. Safe to call in any environment.
|
||||
|
||||
### `createBunDriver(options?: BunDriverOptions): Promise<BunDriverResult>`
|
||||
|
||||
Opens a SQLite database. Throws `SqliteError(SQLITE.ENVIRONMENT_ERROR)` outside Bun, `SqliteError(SQLITE.DRIVER_INIT_FAILED)` on open failure.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `options.filename` | `string` | `":memory:"` | File path or `":memory:"` |
|
||||
| `options.readonly` | `boolean` | `false` | Open in read-only mode |
|
||||
|
||||
### `BunDriverResult`
|
||||
|
||||
```ts
|
||||
interface BunDriverResult {
|
||||
sqlite: Database; // bun:sqlite Database instance
|
||||
close: () => Promise<void>; // idempotent — safe to call multiple times
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`close()` sets a `closed` flag so repeated calls are no-ops rather than throwing "database already closed". This is intentional: callers cleaning up in `finally` blocks should not have to track whether they already closed.
|
||||
|
||||
The `bun:sqlite` import is deferred via `await import("bun:sqlite")` inside the function body so that bundlers processing non-Bun targets (e.g. a Vite browser build) can externalize or ignore the module without causing a build error at parse time. The `isBunEnvironment()` guard before the import provides an early exit in the rare case this driver is invoked in a test environment that stubs the process object.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,81 @@
|
||||
# wasm-worker
|
||||
|
||||
## Purpose
|
||||
|
||||
Web Worker implementation for the WASM SQLite driver. This module runs inside a dedicated worker thread created by `createWasmDriver` and handles all direct SQLite operations using `@sqlite.org/sqlite-wasm`. It communicates with the main thread via a simple message-passing protocol, keeping the WASM heap and SQLite connection completely isolated from the main thread's event loop.
|
||||
|
||||
Running SQLite operations in a worker prevents blocking the UI thread during expensive queries and migrations, and satisfies browser restrictions around synchronous WASM I/O on the main thread (required for OPFS-backed databases via `sqlite3_vfs`).
|
||||
|
||||
## Features
|
||||
|
||||
- Loads `@sqlite.org/sqlite-wasm` lazily on `init` message
|
||||
- Supports `init`, `query`, `execute`, `begin_tx`, `commit_tx`, `rollback_tx`, and `close` message types
|
||||
- Sequential request IDs for reliable request-response correlation
|
||||
- Returns structured `{ id, ok, result }` or `{ id, ok: false, error }` responses
|
||||
- Opens either an in-memory database (default) or a named database when `filename` is provided
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
This module is not imported directly. It is instantiated by `createWasmDriver` as a Web Worker:
|
||||
|
||||
```ts
|
||||
// Worker is created internally by the driver
|
||||
import { createWasmDriver } from "@suaveplan/db-sqlite/adapter";
|
||||
|
||||
const { db, close } = await createWasmDriver();
|
||||
await db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY)");
|
||||
const rows = await db.query("SELECT * FROM t");
|
||||
await close();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
For testing purposes, the worker's message protocol can be exercised directly by creating a mock worker that implements the same interface. The `createWasmDriver` function accepts an injectable `workerFactory` parameter for this:
|
||||
|
||||
```ts
|
||||
import { createWasmDriver } from "@suaveplan/db-sqlite";
|
||||
|
||||
function mockWorkerFactory(): Worker {
|
||||
let onmessage: ((ev: MessageEvent) => void) | null = null;
|
||||
const post = (data: unknown) => { if (onmessage) onmessage({ data } as MessageEvent); };
|
||||
|
||||
return {
|
||||
set onmessage(fn) { onmessage = fn; },
|
||||
postMessage(msg: unknown) {
|
||||
const m = msg as { type: string; id: number };
|
||||
if (m.type === "init") post({ id: m.id, ok: true, result: { type: "ready" } });
|
||||
if (m.type === "query") post({ id: m.id, ok: true, result: [] });
|
||||
},
|
||||
terminate() {},
|
||||
} as unknown as Worker;
|
||||
}
|
||||
|
||||
const { db, close } = await createWasmDriver(undefined, mockWorkerFactory);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Message Protocol (inbound → worker)
|
||||
|
||||
| `type` | Extra fields | Description |
|
||||
|---------------|----------------------------|-----------------------------------------|
|
||||
| `init` | `filename?: string` | Load WASM, open database, reply ready |
|
||||
| `query` | `sql: string`, `params?` | Run `all()`, reply with rows array |
|
||||
| `execute` | `sql: string`, `params?` | Run `run()`, reply with `rowsAffected` |
|
||||
| `begin_tx` | — | `BEGIN IMMEDIATE` |
|
||||
| `commit_tx` | — | `COMMIT` |
|
||||
| `rollback_tx` | — | `ROLLBACK` |
|
||||
| `close` | — | Close database, terminate worker |
|
||||
|
||||
### Message Protocol (outbound ← worker)
|
||||
|
||||
All responses carry `{ id: number, ok: boolean }`.
|
||||
|
||||
- Success: `{ id, ok: true, result: <payload> }`
|
||||
- Failure: `{ id, ok: false, error: "<message>" }`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `/// <reference lib="webworker" />` triple-slash directive at the top of the file provides TypeScript access to `self`, `MessageEvent`, `postMessage`, and other Web Worker globals without requiring the file to be compiled with `"lib": ["WebWorker"]` globally — that would pollute the non-worker files.
|
||||
|
||||
The module uses the `@sqlite.org/sqlite-wasm` default export which is a zero-argument async factory: `const sqlite3 = await initSqlite()`. Previous versions of this package accepted a configuration argument; the current API does not.
|
||||
93
docs/persistence/database/db-sqlite/src/errors/errors.md
Normal file
93
docs/persistence/database/db-sqlite/src/errors/errors.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# errors
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the structured error hierarchy for `@suaveplan/db-sqlite`. All runtime failures — connection problems, query errors, migration failures, environment mismatches — are reported as `SqliteError` instances carrying a stable `SQLITE.*` code from the `SQLITE_CODES` registry. This makes error handling programmatic and refactor-safe: callers switch on `err.code` rather than matching message strings.
|
||||
|
||||
The companion `mapSqliteCode` helper normalises raw error values thrown by `bun:sqlite` (which surfaces result-code strings like `SQLITE_BUSY` or `SQLITE_CONSTRAINT`) into the same stable code namespace, so adapters never leak driver-specific strings into calling code.
|
||||
|
||||
## Features
|
||||
|
||||
- `SQLITE_CODES` registry — 10 stable `SQLITE.*` code strings defined via `defineCodes`
|
||||
- `SqliteError extends SuaveplanError<SqliteCode>` — carries `code`, `message`, `cause`, and full `SuaveplanError` wire-format serialisation
|
||||
- `mapSqliteCode(err)` — maps raw `bun:sqlite` result codes and message substrings to stable `SqliteCode` values; falls back to `SQLITE.QUERY_FAILED`
|
||||
- Type alias `SqliteCode` — union of all valid code strings
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SqliteError, SQLITE_CODES } from "@suaveplan/db-sqlite/errors/errors";
|
||||
|
||||
throw new SqliteError(
|
||||
SQLITE_CODES.NOT_CONNECTED,
|
||||
"Cannot query: adapter is not connected"
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { mapSqliteCode, SqliteError, SQLITE_CODES } from "@suaveplan/db-sqlite/errors/errors";
|
||||
|
||||
async function safeQuery(db: unknown, sql: string): Promise<unknown[]> {
|
||||
try {
|
||||
// ... execute query
|
||||
return [];
|
||||
} catch (raw) {
|
||||
const code = mapSqliteCode(raw);
|
||||
if (code === SQLITE_CODES.BUSY) {
|
||||
// caller can retry on BUSY
|
||||
throw new SqliteError(code, "Database is busy — retry after a moment", { cause: raw instanceof Error ? raw : undefined });
|
||||
}
|
||||
throw new SqliteError(code, `Query failed: ${raw instanceof Error ? raw.message : String(raw)}`, {
|
||||
cause: raw instanceof Error ? raw : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SQLITE_CODES`
|
||||
|
||||
Registry object produced by `defineCodes("SQLITE", [...])`. Keys and values:
|
||||
|
||||
| Key | Value |
|
||||
|-----|-------|
|
||||
| `NOT_CONNECTED` | `"SQLITE.NOT_CONNECTED"` |
|
||||
| `QUERY_FAILED` | `"SQLITE.QUERY_FAILED"` |
|
||||
| `EXECUTE_FAILED` | `"SQLITE.EXECUTE_FAILED"` |
|
||||
| `TRANSACTION_FAILED` | `"SQLITE.TRANSACTION_FAILED"` |
|
||||
| `MIGRATION_FAILED` | `"SQLITE.MIGRATION_FAILED"` |
|
||||
| `ENVIRONMENT_ERROR` | `"SQLITE.ENVIRONMENT_ERROR"` |
|
||||
| `BUSY` | `"SQLITE.BUSY"` |
|
||||
| `CONSTRAINT` | `"SQLITE.CONSTRAINT"` |
|
||||
| `CORRUPT` | `"SQLITE.CORRUPT"` |
|
||||
| `DRIVER_INIT_FAILED` | `"SQLITE.DRIVER_INIT_FAILED"` |
|
||||
|
||||
### `SqliteError`
|
||||
|
||||
```ts
|
||||
class SqliteError extends SuaveplanError<SqliteCode> {
|
||||
constructor(code: SqliteCode, message: string, options?: SuaveplanErrorOptions): SqliteError
|
||||
}
|
||||
```
|
||||
|
||||
Inherits `.code`, `.message`, `.cause`, `.name` from `SuaveplanError`. Fully serialisable to the `@suaveplan/error` wire format.
|
||||
|
||||
### `mapSqliteCode(err: unknown): SqliteCode`
|
||||
|
||||
Maps a raw thrown value from `bun:sqlite` to a stable `SqliteCode`. Checks `err.message` for known result-code strings (`SQLITE_BUSY`, `SQLITE_LOCKED`, `SQLITE_CONSTRAINT`, `SQLITE_CORRUPT`, `SQLITE_NOTADB`). Returns `SQLITE.QUERY_FAILED` as the safe default when no pattern matches.
|
||||
|
||||
### `SqliteCode`
|
||||
|
||||
Type alias: `(typeof SQLITE_CODES)[keyof typeof SQLITE_CODES]` — the union of all ten code strings.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `noUselessConstructor` biome rule is suppressed on `SqliteError` because `SuaveplanError` uses a `protected` constructor; the subclass constructor exists solely to expose a `public` entry point — without it TypeScript raises TS2674.
|
||||
|
||||
`mapSqliteCode` checks `err instanceof SqliteError` first so re-thrown errors are not re-wrapped. The result-code detection loop converts both the error message and the raw value to upper-case before matching, handling both `"SQLITE_BUSY: database is locked"` messages and bare result-code strings.
|
||||
@@ -0,0 +1,84 @@
|
||||
# migration
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`MigrationRunner` applies forward-only SQL migration files to a SQLite database. It maintains a ledger table (`__sqlite_migrations` by default) that records which migration files have been applied, making runs idempotent — re-running the same set of migration files produces the same database state regardless of how many times it is called.
|
||||
|
||||
Each migration is executed inside a `BEGIN IMMEDIATE` transaction. A failure mid-migration triggers an automatic `ROLLBACK`, ensuring the database is never left in a partially-applied state. A `dryRun()` mode reads and validates files but makes no database changes, letting callers preview what would be applied.
|
||||
|
||||
## Features
|
||||
|
||||
- Forward-only, filename-ordered migration application (`.sql` files sorted lexicographically)
|
||||
- Idempotent: skips already-applied migrations via ledger table lookup
|
||||
- Transactional: each migration runs in `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK`
|
||||
- Dry-run mode: previews pending migrations without modifying the database
|
||||
- Injectable `Clock` for deterministic `applied_at` timestamps in tests
|
||||
- Configurable `migrationsFolder`, `migrationsTable`, and `dryRun` via `MigrationRunnerOptionsSchema`
|
||||
- Throws `SqliteError(SQLITE.MIGRATION_FAILED)` with original cause on any failure
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createBunDriver } from "@suaveplan/db-sqlite/drivers/bun-driver";
|
||||
import { MigrationRunner } from "@suaveplan/db-sqlite/migration/migration";
|
||||
|
||||
const { sqlite, close } = await createBunDriver({ filename: "app.db" });
|
||||
try {
|
||||
const runner = new MigrationRunner(sqlite, { migrationsFolder: "./db/migrations" });
|
||||
const result = await runner.run();
|
||||
console.log(`Applied ${result.applied} migration(s):`, result.migrations);
|
||||
} finally {
|
||||
await close();
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { MigrationRunner } from "@suaveplan/db-sqlite/migration/migration";
|
||||
|
||||
// Dry-run to preview without applying
|
||||
const runner = new MigrationRunner(sqlite, {
|
||||
migrationsFolder: "./db/migrations",
|
||||
migrationsTable: "custom_ledger",
|
||||
});
|
||||
|
||||
const preview = await runner.dryRun();
|
||||
console.log("Would apply:", preview.migrations);
|
||||
|
||||
if (preview.applied > 0) {
|
||||
const result = await runner.run();
|
||||
console.log("Applied:", result.migrations);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `new MigrationRunner(sqlite, options?, clock?)`
|
||||
|
||||
Constructs a `MigrationRunner`. Validates `options` via `MigrationRunnerOptionsSchema`.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `sqlite` | `bun:sqlite Database` | Database instance to migrate |
|
||||
| `options` | `Partial<MigrationRunnerOptions>` | Folder, table, dry-run settings |
|
||||
| `clock` | `Clock` | Injectable clock for `applied_at` (default: `Date.now`) |
|
||||
|
||||
### `run(options?: Partial<MigrationRunnerOptions>): Promise<MigrationResult>`
|
||||
|
||||
Reads `.sql` files from `migrationsFolder`, skips already-applied entries, applies each in order, records to ledger. Returns `{ applied: number, migrations: string[] }`.
|
||||
|
||||
### `dryRun(options?: Partial<MigrationRunnerOptions>): Promise<MigrationResult>`
|
||||
|
||||
Same as `run()` but makes no database changes. Useful for change-preview UIs and CI checks.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Migrations are sorted lexicographically by filename. Conventional naming (`001_init.sql`, `002_add_users.sql`) produces chronological order. Non-`.sql` files in the folder are silently ignored.
|
||||
|
||||
The `#isMigrationApplied` check uses a raw SQL query rather than a prepared statement to avoid circular dependency with `PreparedStatementCache` — the migration runner runs before the adapter is fully connected and the cache is available.
|
||||
|
||||
The `applied_at` column stores a Unix millisecond timestamp as `INTEGER` rather than ISO 8601 text. This keeps sorting and comparison simple and avoids timezone ambiguity. Consumers that need a `Date` object divide by 1000 or use `new Date(applied_at)`.
|
||||
65
docs/persistence/database/db-sqlite/src/otel/otel.md
Normal file
65
docs/persistence/database/db-sqlite/src/otel/otel.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# otel
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
OpenTelemetry instrumentation for `@suaveplan/db-sqlite`. The `withQuerySpan` function wraps any async database operation in a named OTel span, setting `db.system=sqlite` and `db.statement` (sanitised) as span attributes, recording exceptions on failure, and emitting latency and count metrics. It is a no-op when no OTel SDK is registered — the `@opentelemetry/api` no-op provider handles all calls silently.
|
||||
|
||||
`sanitizeSql` strips string literals and numeric values from SQL strings before attaching them to span attributes, preventing PII leakage into telemetry backends. Parameterised queries still record their structure (`SELECT * FROM users WHERE id = ?`) without revealing actual parameter values.
|
||||
|
||||
## Features
|
||||
|
||||
- `withQuerySpan(operationName, rawSql, fn)` — wraps fn in a named span with `db.system=sqlite` and sanitised `db.statement`
|
||||
- `sanitizeSql(sql)` — replaces string literals, quoted identifiers, and bare numbers with `?` placeholders
|
||||
- Histograms: `sqlite.query.duration_ms` (labels: `operation`)
|
||||
- Counters: `sqlite.query.count`, `sqlite.error.count` (labels: `operation`)
|
||||
- `resetOtelInstruments()` — clears the lazy instruments singleton for test isolation
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { withQuerySpan } from "@suaveplan/db-sqlite/otel";
|
||||
|
||||
const rows = await withQuerySpan(
|
||||
"db.query",
|
||||
"SELECT * FROM users WHERE id = 42",
|
||||
async () => sqlite.query("SELECT * FROM users WHERE id = ?").all([42])
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { sanitizeSql, withQuerySpan } from "@suaveplan/db-sqlite/otel";
|
||||
|
||||
// Preview sanitisation before instrumentation
|
||||
const safe = sanitizeSql("SELECT * FROM t WHERE name = 'Alice' AND age = 30");
|
||||
// => "SELECT * FROM t WHERE name = ? AND age = ?"
|
||||
|
||||
const result = await withQuerySpan("db.execute", safe, async () => {
|
||||
sqlite.run("INSERT INTO events VALUES (?, ?)", ["login", 1234567890]);
|
||||
return { rowsAffected: 1 };
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `withQuerySpan<T>(operationName, rawSql, fn): Promise<T>`
|
||||
|
||||
Starts a span named `operationName`, sets `db.system=sqlite` and `db.statement=sanitizeSql(rawSql)`, then calls `fn()`. On success records latency and increments `sqlite.query.count`. On failure records exception, sets `ERROR` status, and increments `sqlite.error.count`. Always ends the span.
|
||||
|
||||
### `sanitizeSql(sql: string): string`
|
||||
|
||||
Replaces single-quoted strings, double-quoted strings, and numeric literals (integer and decimal) with `?`. Safe to call on arbitrary SQL strings — does not parse SQL, only applies a regex substitution.
|
||||
|
||||
### `resetOtelInstruments(): void`
|
||||
|
||||
Clears the lazy OTel instruments singleton. Use in tests so each suite starts with fresh counter baselines. Also called internally by `SqliteAdapter.disconnect()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The instruments lazy singleton is initialised on the first `withQuerySpan` call rather than at module load to ensure the OTel provider is registered before the meter is obtained. Tests that call `resetOtelInstruments()` between suites prevent counter state from leaking across test files.
|
||||
|
||||
`sanitizeSql` uses a single-pass regex and is intentionally simple — it does not understand SQL syntax, so complex nested strings or edge-case identifiers may not be fully sanitised. This is acceptable for telemetry purposes where approximate sanitisation is sufficient.
|
||||
71
docs/persistence/database/db-sqlite/src/schemas/schemas.md
Normal file
71
docs/persistence/database/db-sqlite/src/schemas/schemas.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# schemas
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Zod schemas and inferred TypeScript types for all `@suaveplan/db-sqlite` configuration objects. Centralising validation in one module ensures that every entry point — `createBunDriver`, `createWasmDriver`, `SqliteAdapter`, `MigrationRunner` — applies the same rules with the same error messages, rather than duplicating ad-hoc checks across modules.
|
||||
|
||||
All schemas use `.strict()` where applicable so extra unknown properties are rejected at runtime, preventing silent misconfiguration from typos in option objects.
|
||||
|
||||
## Features
|
||||
|
||||
- `BunDriverOptionsSchema` — validates `filename` (optional string) and `readonly` (optional boolean, default `false`)
|
||||
- `WasmDriverOptionsSchema` — validates `filename` (optional string) for OPFS persistence
|
||||
- `SqliteAdapterOptionsSchema` — alias for `BunDriverOptionsSchema`; adapter and driver share the same shape
|
||||
- `MigrationRunnerOptionsSchema` — validates `migrationsFolder` (default `"./migrations"`), `migrationsTable` (default `"__sqlite_migrations"`), `dryRun` (default `false`)
|
||||
- Inferred TypeScript types exported for all four schemas
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { BunDriverOptionsSchema } from "@suaveplan/db-sqlite/schemas";
|
||||
|
||||
const opts = BunDriverOptionsSchema.parse({ filename: ":memory:" });
|
||||
// opts.readonly === false (default applied)
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { MigrationRunnerOptionsSchema } from "@suaveplan/db-sqlite/schemas";
|
||||
|
||||
const opts = MigrationRunnerOptionsSchema.parse({
|
||||
migrationsFolder: "./db/migrations",
|
||||
dryRun: true,
|
||||
});
|
||||
// opts.migrationsTable === "__sqlite_migrations" (default applied)
|
||||
// opts.dryRun === true
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `BunDriverOptionsSchema`
|
||||
|
||||
`z.object({ filename?: string, readonly?: boolean }).strict()` — `readonly` defaults to `false`.
|
||||
|
||||
### `WasmDriverOptionsSchema`
|
||||
|
||||
`z.object({ filename?: string }).strict()` — no defaults; absence means in-memory.
|
||||
|
||||
### `SqliteAdapterOptionsSchema`
|
||||
|
||||
Alias for `BunDriverOptionsSchema`. Both driver and adapter accept the same option shape.
|
||||
|
||||
### `MigrationRunnerOptionsSchema`
|
||||
|
||||
`z.object({ migrationsFolder?: string, migrationsTable?: string, dryRun?: boolean }).strict()`.
|
||||
|
||||
Defaults: `migrationsFolder = "./migrations"`, `migrationsTable = "__sqlite_migrations"`, `dryRun = false`.
|
||||
|
||||
### Type aliases
|
||||
|
||||
- `BunDriverOptions = z.infer<typeof BunDriverOptionsSchema>`
|
||||
- `WasmDriverOptions = z.infer<typeof WasmDriverOptionsSchema>`
|
||||
- `SqliteAdapterOptions = BunDriverOptions`
|
||||
- `MigrationRunnerOptions = z.infer<typeof MigrationRunnerOptionsSchema>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`SqliteAdapterOptions` is deliberately an alias for `BunDriverOptions` rather than a separate schema so that passing an `SqliteAdapterOptions` object to `createBunDriver` is type-safe without casting. If the WASM adapter ever gains `readonly` support the alias can be split without a breaking change to the option names callers already use.
|
||||
68
docs/persistence/database/db-sqlite/src/unsafe/unsafe.md
Normal file
68
docs/persistence/database/db-sqlite/src/unsafe/unsafe.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# unsafe
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The `unsafe` subpath exports `rawQuery` — a direct SQL execution function that bypasses the Drizzle ORM layer entirely. It is intentionally segregated behind the `@suaveplan/db-sqlite/unsafe` subpath import to make its use explicit and auditable: any import of this path stands out in code review as a deliberate bypass of the normal query builder.
|
||||
|
||||
**Security warning:** `rawQuery` provides no SQL injection protection. Never pass user-supplied or externally-sourced strings as the `sql` argument. Use only for migration scripts, low-level tooling, or read-only administrative queries where the SQL is fully under developer control.
|
||||
|
||||
## Features
|
||||
|
||||
- `rawQuery<T>(db, sql, params?)` — executes a prepared statement on any `bun:sqlite`-compatible Database instance
|
||||
- Returns rows typed as `T[]` (caller asserts the row shape)
|
||||
- Maps execution errors to `SqliteError(SQLITE.QUERY_FAILED)`
|
||||
- Supports parameterised queries via the `params` array — always use `?` placeholders and pass values in `params` rather than string-interpolating them
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { rawQuery } from "@suaveplan/db-sqlite/unsafe";
|
||||
|
||||
const rows = await rawQuery<{ count: number }>(
|
||||
sqlite,
|
||||
"SELECT count(*) AS count FROM sqlite_master WHERE type = ?",
|
||||
["table"]
|
||||
);
|
||||
console.log(rows[0]?.count);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { rawQuery } from "@suaveplan/db-sqlite/unsafe";
|
||||
|
||||
// Used in a migration helper to read schema state before Drizzle is set up
|
||||
async function tableExists(sqlite: unknown, name: string): Promise<boolean> {
|
||||
const rows = await rawQuery<{ found: number }>(
|
||||
sqlite,
|
||||
"SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ?",
|
||||
[name]
|
||||
);
|
||||
return rows.length > 0;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `rawQuery<T>(db, sql, params?): Promise<T[]>`
|
||||
|
||||
Executes `sql` as a prepared statement on `db`. Throws `SqliteError(SQLITE.QUERY_FAILED)` on error.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `db` | `any` | A `bun:sqlite` Database instance |
|
||||
| `sql` | `string` | The SQL to execute — must not contain user input |
|
||||
| `params` | `unknown[]` | Bind parameters for `?` placeholders |
|
||||
|
||||
**Returns** `Promise<T[]>` — rows shaped as `T`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`rawQuery` uses `db.prepare(sql).all(params)` rather than `db.query(sql).all(params)` to be consistent with the caching strategy used by `SqliteAdapter`. The `params` spread form (`stmt.all(...params)`) passes each parameter as a positional argument, matching `bun:sqlite`'s expected calling convention.
|
||||
|
||||
The `db` parameter is typed as `any` to accept both `bun:sqlite Database` instances and test doubles without requiring a concrete import of `bun:sqlite` types from this module.
|
||||
Reference in New Issue
Block a user