# `adapters/sqlite` — SQLite migration adapter _(no submodules)_ ## Purpose Implements the `MigrationAdapter` contract for SQLite-family engines. The factory `createSqliteAdapter(client)` accepts any object exposing three methods (`runScript`, `selectAll`, `runStatement`); a small structural wrapper makes `@sqlite.org/sqlite-wasm`, `bun:sqlite`, and `better-sqlite3` all conform. This decouples the adapter from the WASM build cost when a consumer wants to use a native driver, while still letting universal callers bring `sqlite-wasm` if they need browser-tier portability. The DDL is plain ANSI SQLite (`TEXT PRIMARY KEY`, `INTEGER`, `TEXT NOT NULL`), so the same adapter works against the upstream `sqlite3` CLI and against any wrapper that supports parameterised statements. ## Features - Idempotent `CREATE TABLE IF NOT EXISTS` for the tracking table. - Parameterised inserts and deletes use `?` placeholders — no caller data is interpolated into SQL. - Stores `applied_at` as ISO 8601 text (SQLite has no native datetime); the adapter rehydrates to a JavaScript `Date` on read. - Stores `duration_ms` as `INTEGER`. - Wraps `ensureTable` failures as `MigrationError(MIGRATION.TABLE_BOOTSTRAP_FAILED)`. - Sync clients (`better-sqlite3`, `bun:sqlite`) work without modification — the adapter awaits methods that may return `void` synchronously. ## Usage: Basic Example ```ts import { Database } from "bun:sqlite"; import { createSqliteAdapter } from "@suaveplan/api-migrations/adapters/sqlite/sqlite"; import { MigrationRunner } from "@suaveplan/api-migrations/runner/runner"; const db = new Database(":memory:"); const client = { runScript(sql: string) { db.run(sql); }, selectAll(sql: string, params: unknown[] = []) { return db.query(sql).all(...params) as T[]; }, runStatement(sql: string, params: unknown[] = []) { db.query(sql).run(...params); }, }; const runner = new MigrationRunner(createSqliteAdapter(client)); await runner.runFromDirectory("./db/migrations"); ``` ## Usage: Advanced Example ```ts import sqlite3InitModule from "@sqlite.org/sqlite-wasm"; import { createSqliteAdapter } from "@suaveplan/api-migrations/adapters/sqlite/sqlite"; const sqlite3 = await sqlite3InitModule(); const db = new sqlite3.oo1.DB(":memory:"); const client = { runScript(sql: string) { db.executeBatch(sql); }, selectAll(sql: string, params: unknown[] = []) { const out: T[] = []; db.executeBatch({ sql, bind: params, rowMode: "object", callback(row: T) { out.push(row); return undefined; }, }); return out; }, runStatement(sql: string, params: unknown[] = []) { db.executeBatch({ sql, bind: params }); }, }; const adapter = createSqliteAdapter(client); ``` ## API Reference - `interface SqliteClient { runScript(sql); selectAll(sql, params?); runStatement(sql, params?) }` — structural client surface. - `createSqliteAdapter(client): MigrationAdapter` — returns an adapter with `dialect: "sqlite"`. ## Implementation Notes - The three-method surface is the smallest common denominator across the three SQLite drivers the suaveplan tree is likely to encounter. Every shipped adapter normalises to async by awaiting the result, so sync drivers work without modification. - `applied_at` is stored as ISO 8601 text because SQLite has no native datetime type. Round-trip preserves wall-clock ordering — sorting on the raw `applied_at` string yields the same order as sorting on parsed `Date`. - The adapter does not currently forward `AbortSignal` into `runScript` because none of the supported SQLite drivers honor mid-query cancellation. The runner's per-migration abort gate still applies between apply attempts. - The DDL is intentionally minimal — no indices beyond the `name` uniqueness implicit in `UNIQUE NOT NULL`, no foreign keys. The runner reads applied rows in unspecified order and sorts by `name` itself.