{"version":3,"file":"sqlite.mjs","names":[],"sources":["../src/adapters/sqlite/adapter.ts","../src/adapters/sqlite/dialect.ts"],"sourcesContent":["import {\n  lockTableDdl,\n  migrationsTableDdl,\n  type Dialect,\n  type MigrationAdapter,\n  type MigrationRow,\n  type SchemaSnapshot,\n} from '../../index'\nimport { introspectSqlite } from '../../migrate/introspect-sqlite'\n\n/**\n * better-sqlite3-shaped database handle that `sqliteAdapter` consumes.\n * Mirrors the methods we actually use; both `new Database(...)` from\n * `better-sqlite3` and bun's `bun:sqlite` Database match structurally.\n *\n * better-sqlite3's API is synchronous — we wrap everything in `async`\n * to match the `MigrationAdapter` contract. No I/O actually awaits;\n * the wrapping is for shape compatibility.\n */\n/**\n * `all` / `get` are deliberately NOT method-generic: better-sqlite3\n * v12's own `Statement` methods are non-generic, so generic signatures\n * here would make a real `Database` instance structurally incompatible\n * (callers had to cast — same fix as `SqliteIntrospectDb`). Row typing\n * happens at the call sites via assertions instead.\n */\nexport interface SqliteStatement {\n  run(...params: readonly unknown[]): { changes: number; lastInsertRowid: number | bigint }\n  all(...params: readonly unknown[]): unknown[]\n  get(...params: readonly unknown[]): unknown\n}\n\nexport interface SqliteDatabaseLike {\n  prepare(sql: string): SqliteStatement\n  exec(sql: string): unknown\n  close(): unknown\n}\n\nexport interface SqliteAdapterOptions {\n  /**\n   * better-sqlite3 (or compatible) Database handle. Caller-owned —\n   * the adapter's `close()` does NOT close the database because\n   * adopters typically share a single handle across the migration\n   * adapter and the KickDbClient.\n   */\n  database: SqliteDatabaseLike\n}\n\n/**\n * MigrationAdapter implementation backed by better-sqlite3.\n *\n * Lock semantics: single-row UPDATE WHERE locked_at IS NULL on\n * `kick_migrations_lock`. Only the row created by `ensureMigrationTables()`\n * exists, so the UPDATE either flips `locked_at` and returns\n * `changes=1` (we won) or matches zero rows (someone else holds it).\n *\n * Introspection: `introspect()` walks `sqlite_master` + `PRAGMA` via\n * {@link introspectSqlite}. Types come back as SQLite affinities (lossy\n * vs the code-first DSL), so it powers `kick db introspect` rather than\n * byte-exact drift detection.\n */\nexport function sqliteAdapter(opts: SqliteAdapterOptions): MigrationAdapter {\n  const dialect: Dialect = 'sqlite'\n  const { database } = opts\n\n  // Multi-statement DDL runs through better-sqlite3's exec() (it\n  // handles `;`-separated batches natively).\n  const runBatch = (sql: string) => database.exec(sql)\n\n  return {\n    dialect,\n\n    async ensureMigrationTables() {\n      runBatch(migrationsTableDdl(dialect))\n      runBatch(lockTableDdl(dialect))\n    },\n\n    async listApplied(): Promise<MigrationRow[]> {\n      const rows = database\n        .prepare(\n          `SELECT id, name, hash, batch, applied_at, direction\n           FROM kick_migrations\n           ORDER BY applied_at ASC, id ASC`,\n        )\n        .all() as {\n        id: string\n        name: string\n        hash: string\n        batch: number\n        applied_at: string\n        direction: 'up' | 'down'\n      }[]\n      return rows.map((row) => ({\n        id: row.id,\n        name: row.name,\n        hash: row.hash,\n        batch: Number(row.batch),\n        appliedAt: row.applied_at,\n        direction: row.direction,\n      }))\n    },\n\n    async recordApplied(row) {\n      database\n        .prepare(\n          `INSERT INTO kick_migrations (id, name, hash, batch, direction)\n           VALUES (?, ?, ?, ?, ?)`,\n        )\n        .run(row.id, row.name, row.hash, row.batch, row.direction)\n    },\n\n    async removeApplied(id: string) {\n      database.prepare(`DELETE FROM kick_migrations WHERE id = ?`).run(id)\n    },\n\n    async acquireLock(owner: string): Promise<boolean> {\n      const r = database\n        .prepare(\n          `UPDATE kick_migrations_lock\n           SET locked_at = datetime('now'), locked_by = ?\n           WHERE id = 1 AND locked_at IS NULL`,\n        )\n        .run(owner)\n      return r.changes === 1\n    },\n\n    async releaseLock() {\n      database\n        .prepare(\n          `UPDATE kick_migrations_lock\n           SET locked_at = NULL, locked_by = NULL\n           WHERE id = 1`,\n        )\n        .run()\n    },\n\n    async applySqlInTx(sql: string) {\n      // BEGIN / COMMIT around a multi-statement batch gives us\n      // atomicity. SQLite rolls back to pre-BEGIN on any error\n      // inside the block.\n      runBatch('BEGIN')\n      try {\n        runBatch(sql)\n        runBatch('COMMIT')\n      } catch (err) {\n        try {\n          runBatch('ROLLBACK')\n        } catch {\n          // Swallow rollback errors; we're already throwing the original.\n        }\n        throw err\n      }\n    },\n\n    async applySqlNoTx(sql: string) {\n      runBatch(sql)\n    },\n\n    async introspect(): Promise<SchemaSnapshot> {\n      // Reverse-engineer the live schema via sqlite_master + PRAGMA walks.\n      // Types come back as SQLite affinities (a code-first `uuid()` reads as\n      // `text`), so this powers `kick db introspect`; byte-exact drift\n      // against a code-first snapshot needs a dialect-normalised compare.\n      return introspectSqlite(database)\n    },\n\n    async close() {\n      // Caller owns the database handle. The adapter doesn't close\n      // it — adopters typically share the same handle with the\n      // KickDbClient.\n    },\n  }\n}\n","// sqliteDialect — thin factory over Kysely's SqliteDialect so adopters\n// never have to reach for `import { SqliteDialect } from 'kysely'`.\n// Mirrors the `@forinda/kickjs-db/pg` template; the kysely subpackage\n// is a pinned internal dep of the framework.\n\nimport { SqliteDialect, type Dialect as KyselyDialect } from 'kysely'\n\nimport { markDialect } from '../../dialect-marker'\nimport type { SqliteDatabaseLike } from './adapter'\n\nexport interface SqliteDialectOptions {\n  /**\n   * better-sqlite3-compatible database handle. Both `new Database(...)`\n   * from `better-sqlite3` and bun's `bun:sqlite` `Database` match\n   * structurally (Bun is best-effort — same `.prepare()` shape, no\n   * version assertion).\n   */\n  database: SqliteDatabaseLike\n}\n\n/**\n * Construct the dialect that `createDbClient({ dialect })` consumes.\n *\n * @example\n * ```ts\n * import { createDbClient } from '@forinda/kickjs-db'\n * import { sqliteDialect, sqliteAdapter } from '@forinda/kickjs-db/sqlite'\n * import Database from 'better-sqlite3'\n *\n * const database = new Database(':memory:')\n *\n * export const db = createDbClient({\n *   schema,\n *   dialect: sqliteDialect({ database }),\n * })\n *\n * export const migrationAdapter = sqliteAdapter({ database })\n * ```\n */\nexport function sqliteDialect(opts: SqliteDialectOptions): KyselyDialect {\n  return markDialect(new SqliteDialect({ database: opts.database as never }), 'sqlite')\n}\n"],"mappings":";;;;;;;;;;sJA6DA,SAAgB,cAAc,KAA8C,CAC1E,IAAM,QAAmB,SACnB,CAAE,UAAa,KAIf,SAAY,KAAgB,SAAS,KAAK,GAAG,EAEnD,MAAO,CACL,QAEA,MAAM,uBAAwB,CAC5B,SAAS,mBAAmB,OAAO,CAAC,EACpC,SAAS,aAAa,OAAO,CAAC,CAChC,EAEA,MAAM,aAAuC,CAe3C,OAda,SACV,QACC;;2CAGF,CAAC,CACA,IAQO,CAAC,CAAC,IAAK,MAAS,CACxB,GAAI,IAAI,GACR,KAAM,IAAI,KACV,KAAM,IAAI,KACV,MAAO,OAAO,IAAI,KAAK,EACvB,UAAW,IAAI,WACf,UAAW,IAAI,SACjB,EAAE,CACJ,EAEA,MAAM,cAAc,IAAK,CACvB,SACG,QACC;kCAEF,CAAC,CACA,IAAI,IAAI,GAAI,IAAI,KAAM,IAAI,KAAM,IAAI,MAAO,IAAI,SAAS,CAC7D,EAEA,MAAM,cAAc,GAAY,CAC9B,SAAS,QAAQ,0CAA0C,CAAC,CAAC,IAAI,EAAE,CACrE,EAEA,MAAM,YAAY,MAAiC,CAQjD,OAPU,SACP,QACC;;8CAGF,CAAC,CACA,IAAI,KACA,CAAC,CAAC,UAAY,CACvB,EAEA,MAAM,aAAc,CAClB,SACG,QACC;;wBAGF,CAAC,CACA,IAAI,CACT,EAEA,MAAM,aAAa,IAAa,CAI9B,SAAS,OAAO,EAChB,GAAI,CACF,SAAS,GAAG,EACZ,SAAS,QAAQ,CACnB,OAAS,IAAK,CACZ,GAAI,CACF,SAAS,UAAU,CACrB,MAAQ,CAER,CACA,MAAM,GACR,CACF,EAEA,MAAM,aAAa,IAAa,CAC9B,SAAS,GAAG,CACd,EAEA,MAAM,YAAsC,CAK1C,OAAO,iBAAiB,QAAQ,CAClC,EAEA,MAAM,OAAQ,CAId,CACF,CACF,CCrIA,SAAgB,cAAc,KAA2C,CACvE,OAAO,YAAY,IAAI,cAAc,CAAE,SAAU,KAAK,QAAkB,CAAC,EAAG,QAAQ,CACtF"}