Files
suaveplan/openspec/archive/2026-04-24-enterprise-db-sqlite/proposal.md
2026-05-27 23:29:53 +01:00

7.5 KiB

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<void> }
  • WasmDriverOptions — { filename?: string }
  • WasmDriverResult<TSchema> — { db: SqliteRemoteDatabase<TSchema>; close: () => Promise<void> }

Classes

  • SqliteAdapter<TSchema> — implements DatabaseAdapter from @suaveplan/db
    • constructor(options?: SqliteAdapterOptions, schema?: TSchema)
    • connect(config?: Partial<DatabaseConfig>): Promise<void>
    • disconnect(): Promise<void>
    • query<T>(sql: string, params?: unknown[]): Promise<T[]>
    • execute(sql: string, params?: unknown[]): Promise<void>
    • transaction<T>(fn: () => Promise<T>): Promise<T>
    • migrate(config?: MigrationConfig): Promise<MigrationResult>
    • get isConnected: boolean
    • get drizzle: BunSQLiteDatabase<TSchema> — available after connect()

Functions

  • createBunDriver(options?: BunDriverOptions): Promise<BunDriverResult>
  • isBunEnvironment(): boolean
  • createWasmDriver<TSchema>(options?: WasmDriverOptions, schema?: TSchema): Promise<WasmDriverResult<TSchema>>
  • getDefaultSqlite(options?, config?, name?): Promise<SqliteAdapter>
  • closeSqlite(name: string): Promise<void>
  • closeAllSqlite(): Promise<void>
  • 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)