# OpenSpec Change: enterprise-db-sqlite - **Change ID**: enterprise-db-sqlite - **Package**: `@suaveplan/db-sqlite` - **Category**: universal - **Target path**: `universal/database/db-sqlite` - **Status**: IN PROGRESS - **Date**: 2026-04-23 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/db-sqlite.md` ## Summary Greenfield implementation of `@suaveplan/db-sqlite` for the genesis branch. Universal SQLite adapter — `bun:sqlite` on the Bun server, `@sqlite.org/sqlite-wasm` with OPFS persistence in the browser, unified behind a single Drizzle-ORM surface. One schema definition, one query builder API, two runtimes selected transparently via `package.json` conditional exports. The main-branch version (v1.0.0) provides the baseline public-API surface to preserve. Genesis adds enterprise-grade structured errors, an LRU prepared-statement cache, a forward-only migration runner with a ledger table and dry-run mode, OTel instrumentation, property-based tests, benchmarks, and a full co-located documentation suite. ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. The `universal/database/` domain directory does not exist either and must be created alongside the required predecessor package `@suaveplan/db`. ## Available workspace deps on genesis | Package | Status | Notes | |---------|--------|-------| | `@suaveplan/error` | ✅ built | Extend for `SqliteError` with `SQLITE.*` codes | | `@suaveplan/types` | ✅ built | `Clock`, `IdGenerator`, `LoggerLike` interfaces | | `@suaveplan/db` | ❌ not yet built | Must land before `db-sqlite` — provides `DatabaseAdapter`, `DatabaseConfig`, `MigrationConfig`, `MigrationResult`, singleton helpers | | `@suaveplan/logger` | ✅ built | Structured logging inside adapter | | `@suaveplan/opentelemetry` | ❌ not yet built | Use `@opentelemetry/api` directly | | `@suaveplan/schema-registry` | ✅ built | Wire Zod schemas for config/row types | | `@suaveplan/retry` | ✅ built | Retry on `SQLITE_BUSY` write paths | | `@suaveplan/feature-flags` | ✅ built | Gate risky migration or pragma changes | External deps — require a new `catalog:database` block in the root `package.json` (none of these exist in any current catalog): | Package | Catalog entry to add | |---------|---------------------| | `drizzle-orm` | `catalog:database` | | `@sqlite.org/sqlite-wasm` | `catalog:database` | `bun:sqlite` is a built-in Bun module — no npm dep needed; import is conditional. Dev deps (all already in `catalog:testing`): - `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` Build deps (already in `catalog:build`): - `vite`, `vite-plugin-dts`, `tslib` ## Preserved public API surface (from main branch) All of the following must be exported from the genesis build at `"."` and at the corresponding feature subpaths. ### Types - `SqliteAdapterOptions` — alias for `BunDriverOptions` (filename, readonly) - `BunDriverOptions` — `{ filename?: string; readonly?: boolean }` - `BunDriverResult` — `{ sqlite: Database; close: () => Promise }` - `WasmDriverOptions` — `{ filename?: string }` - `WasmDriverResult` — `{ db: SqliteRemoteDatabase; close: () => Promise }` ### Classes - `SqliteAdapter` — implements `DatabaseAdapter` from `@suaveplan/db` - `constructor(options?: SqliteAdapterOptions, schema?: TSchema)` - `connect(config?: Partial): Promise` - `disconnect(): Promise` - `query(sql: string, params?: unknown[]): Promise` - `execute(sql: string, params?: unknown[]): Promise` - `transaction(fn: () => Promise): Promise` - `migrate(config?: MigrationConfig): Promise` - `get isConnected: boolean` - `get drizzle: BunSQLiteDatabase` — available after `connect()` ### Functions - `createBunDriver(options?: BunDriverOptions): Promise` - `isBunEnvironment(): boolean` - `createWasmDriver(options?: WasmDriverOptions, schema?: TSchema): Promise>` - `getDefaultSqlite(options?, config?, name?): Promise` - `closeSqlite(name: string): Promise` - `closeAllSqlite(): Promise` - `hasSqlite(name: string): boolean` ### Unsafe subpath (`@suaveplan/db-sqlite/unsafe`) - `rawQuery(db, sql, params?)` — direct SQL execution, bypasses query builder ## New additions for genesis ### Modules - **`src/errors/`** — `SqliteError` extending `SuaveplanError`; stable code registry `SQLITE_CODES`: `SQLITE.NOT_CONNECTED`, `SQLITE.QUERY_FAILED`, `SQLITE.EXECUTE_FAILED`, `SQLITE.TRANSACTION_FAILED`, `SQLITE.MIGRATION_FAILED`, `SQLITE.ENVIRONMENT_ERROR`, `SQLITE.BUSY`, `SQLITE.CONSTRAINT`, `SQLITE.CORRUPT`, `SQLITE.DRIVER_INIT_FAILED` - **`src/cache/`** — `PreparedStatementCache` — LRU-bounded (configurable max size, default 128), keyed by SQL text, invalidated on migration, OTel hit/miss counters - **`src/migration/`** — `MigrationRunner` — forward-only, idempotent, ledger table (`__sqlite_migrations`), dry-run mode, `MigrationRunnerOptions` Zod schema, integrates with Drizzle migrator; emits OTel span per migration file applied - **`src/schemas/`** — Zod schemas for `SqliteAdapterOptions`, `BunDriverOptions`, `WasmDriverOptions`, `MigrationRunnerOptions`; exported as re-usable validators - **`src/otel/`** — `withQuerySpan(name, sql, fn)` wrapper — sanitises SQL (strips literal values), records `db.system=sqlite`, `db.statement`, latency histogram, error event on failure; no-op when no OTel provider registered - **`src/conformance/`** — shared test suite (`conformance-suite.ts`) exercising both Bun and WASM drivers: CRUD, transactions, rollback semantics, prepared-statement cache hits, migration ledger idempotency, `AbortSignal` cancellation - **`src/__benchmarks__/`** — mitata benchmarks: single-row insert, bulk insert (1 k rows), point-lookup, full-table scan, prepared vs. ad-hoc statement comparison ### Cross-cutting enterprise requirements - `SqliteAdapter` constructor throws `SqliteError(SQLITE.ENVIRONMENT_ERROR)` when `isBunEnvironment()` is false — keeps runtime guarantee at the type level - `PreparedStatementCache` evicts on `migrate()` to prevent stale plan reuse - All public-facing async methods accept optional `AbortSignal` where the underlying driver supports cancellation (query abort path on `bun:sqlite` via `statement.finalize()`) - `transaction()` uses `BEGIN IMMEDIATE` to avoid deadlock on concurrent writes - `sideEffects: false` confirmed and guarded by a tree-shake size test - Property-based tests (fast-check) for: SQL-injection resistance via Drizzle parameterisation, transaction isolation invariants, cache eviction correctness - Zero `any`, zero `ts-ignore`, zero mock usage in tests — real in-memory SQLite only - Injectable `clock` for migration ledger timestamps (defaults to `Date.now`) - Conditional `package.json` exports route `./adapter` to `adapter.bun.js` (Bun runtime) and `adapter.wasm.js` (browser), keeping hot-path free of runtime detection ## Impact - Introduces a new domain directory: `universal/database/` - Adds `catalog:database` block to root `package.json` (new entries: `drizzle-orm`, `@sqlite.org/sqlite-wasm`) - Blocks on `@suaveplan/db` landing first (dependency-order constraint) - No existing genesis packages depend on `@suaveplan/db-sqlite` yet — zero breaking-change risk within the current tree - WASM test suite requires `fileParallelism: false` in the browser-environment vitest config (WSL2 concurrent OPFS forks limitation)