import fs from "node:fs"; import path from "node:path"; import Database from "better-sqlite3"; export interface DatabaseVerification { integrityCheck: "ok"; foreignKeyViolations: number; } function resolveDatabasePath(filename: string): string { return path.resolve(filename); } export function verifyDatabaseFile(filename: string): DatabaseVerification { const databasePath = resolveDatabasePath(filename); const sqlite = new Database(databasePath, { readonly: true, fileMustExist: true, }); try { const integrityCheck = sqlite.pragma("integrity_check", { simple: true, }); if (integrityCheck !== "ok") { throw new Error( `SQLite integrity check failed for ${databasePath}: ${String(integrityCheck)}` ); } const foreignKeyCheck = sqlite.pragma("foreign_key_check") as unknown[]; const foreignKeyViolations = foreignKeyCheck.length; if (foreignKeyViolations > 0) { throw new Error( `SQLite foreign-key check failed for ${databasePath}: ${foreignKeyViolations} violation(s)` ); } return { integrityCheck: "ok", foreignKeyViolations, }; } finally { sqlite.close(); } } export async function createVerifiedDatabaseBackup( sourceFilename: string, destinationFilename: string ): Promise { const sourcePath = resolveDatabasePath(sourceFilename); const destinationPath = resolveDatabasePath(destinationFilename); if (sourcePath === destinationPath) { throw new Error("Backup source and destination must be different files."); } if (!fs.existsSync(sourcePath)) { throw new Error(`Database file not found: ${sourcePath}`); } if (fs.existsSync(destinationPath)) { throw new Error(`Backup destination already exists: ${destinationPath}`); } fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); const source = new Database(sourcePath, { readonly: true, fileMustExist: true, }); try { await source.backup(destinationPath); } finally { source.close(); } return verifyDatabaseFile(destinationPath); }