79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
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<DatabaseVerification> {
|
|
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);
|
|
}
|