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 forBunDriverOptions(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>— implementsDatabaseAdapterfrom@suaveplan/dbconstructor(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: booleanget drizzle: BunSQLiteDatabase<TSchema>— available afterconnect()
Functions
createBunDriver(options?: BunDriverOptions): Promise<BunDriverResult>isBunEnvironment(): booleancreateWasmDriver<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/—SqliteErrorextendingSuaveplanError; stable code registrySQLITE_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,MigrationRunnerOptionsZod schema, integrates with Drizzle migrator; emits OTel span per migration file applied -
src/schemas/— Zod schemas forSqliteAdapterOptions,BunDriverOptions,WasmDriverOptions,MigrationRunnerOptions; exported as re-usable validators -
src/otel/—withQuerySpan(name, sql, fn)wrapper — sanitises SQL (strips literal values), recordsdb.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,AbortSignalcancellation -
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
SqliteAdapterconstructor throwsSqliteError(SQLITE.ENVIRONMENT_ERROR)whenisBunEnvironment()is false — keeps runtime guarantee at the type levelPreparedStatementCacheevicts onmigrate()to prevent stale plan reuse- All public-facing async methods accept optional
AbortSignalwhere the underlying driver supports cancellation (query abort path onbun:sqliteviastatement.finalize()) transaction()usesBEGIN IMMEDIATEto avoid deadlock on concurrent writessideEffects: falseconfirmed 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, zerots-ignore, zero mock usage in tests — real in-memory SQLite only - Injectable
clockfor migration ledger timestamps (defaults toDate.now) - Conditional
package.jsonexports route./adaptertoadapter.bun.js(Bun runtime) andadapter.wasm.js(browser), keeping hot-path free of runtime detection
Impact
- Introduces a new domain directory:
universal/database/ - Adds
catalog:databaseblock to rootpackage.json(new entries:drizzle-orm,@sqlite.org/sqlite-wasm) - Blocks on
@suaveplan/dblanding first (dependency-order constraint) - No existing genesis packages depend on
@suaveplan/db-sqliteyet — zero breaking-change risk within the current tree - WASM test suite requires
fileParallelism: falsein the browser-environment vitest config (WSL2 concurrent OPFS forks limitation)