// SQLite database client (sql.js — pure WASM, no native deps) import initSqlJs, { type Database, type BindParams } from "sql.js"; import { createTables } from "./schema.js"; let db: Database | null = null; let initPromise: Promise | null = null; /** Initialize the database (call once at startup). */ export async function initDb(dbPath?: string): Promise { if (db) return db; if (initPromise) return initPromise; initPromise = (async () => { const SQL = await initSqlJs(); const path = dbPath || process.env.DATABASE_URL || ":memory:"; // Try to load existing database from file let buffer: ArrayLike | undefined; if (path !== ":memory:") { try { const fs = await import("node:fs/promises"); const data = await fs.readFile(path); buffer = new Uint8Array(data); } catch { // File doesn't exist yet — start fresh } } db = new SQL.Database(buffer); db.run("PRAGMA foreign_keys = ON"); createTables(db); return db; })(); return initPromise; } /** Get the initialized database (must call initDb first). */ export function getDb(): Database { if (!db) throw new Error("Database not initialized. Call initDb() first."); return db; } /** Save database to disk. */ export async function saveDb(dbPath?: string): Promise { if (!db) return; const path = dbPath || process.env.DATABASE_URL || "./data/app.db"; if (path === ":memory:") return; const fs = await import("node:fs/promises"); const { dirname } = await import("node:path"); await fs.mkdir(dirname(path), { recursive: true }); const data = db.export(); await fs.writeFile(path, Buffer.from(data)); } export async function closeDb(): Promise { if (db) { await saveDb(); db.close(); db = null; initPromise = null; } } // Helper: run a query and return all rows as objects export function queryAll>(sql: string, params: BindParams = []): T[] { const d = getDb(); const stmt = d.prepare(sql); if (params) stmt.bind(params); const results: T[] = []; while (stmt.step()) { const row = stmt.getAsObject(); results.push(row as unknown as T); } stmt.free(); return results; } // Helper: run a query and return the first row export function queryOne>(sql: string, params: BindParams = []): T | undefined { const rows = queryAll(sql, params); return rows[0]; } // Helper: run a mutation and return { changes, lastInsertRowid } export function execute(sql: string, params: BindParams = []): { changes: number; lastInsertRowid: number } { const d = getDb(); d.run(sql, params); return { changes: d.getRowsModified(), lastInsertRowid: 0, }; }