openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
182 lines (181 loc) • 7.3 kB
JavaScript
import { u as toStringifiedError } from "./error-coercion-D_-xJ90S.js";
import { s as isSqliteCorruptionError, t as openNodeSqliteDatabase } from "./node-sqlite-BpQX3W0e.js";
import { n as readStableSqliteFileGeneration, r as sameSqliteFileGeneration } from "./sqlite-file-generation-CjIoCteQ.js";
//#region src/infra/sqlite-integrity.ts
/** Run the same admission steps synchronously when the caller cannot yield. */
function runSqliteIntegrityOperationSync(operation) {
let step = operation.next();
while (!step.done) {
try {
assertSqliteIntegrity(step.value.database, step.value.databaseLabel);
} catch (error) {
step = operation.throw(error);
continue;
}
step = operation.next();
}
return step.value;
}
const MAX_REPORTED_FOREIGN_KEY_VIOLATIONS = 5;
/** Return whether a named integrity failure proves persistent database damage. */
function isTerminalSqliteIntegrityError(error) {
if (error.name !== "SqliteIntegrityError") return false;
if (!error.cause) return true;
return isSqliteCorruptionError(error.cause);
}
/** Require structural, table/index, and referential consistency before trusting a database. */
function assertSqliteIntegrity(database, databaseLabel) {
const integrityCheck = runSqliteCheck(database, databaseLabel, "integrity_check");
runSqliteForeignKeyCheck(database, databaseLabel);
return { integrityCheck };
}
/** Run integrity checks and preserve whether a failure proves persistent damage. */
function confirmSqliteIntegrity(database, databaseLabel) {
try {
assertSqliteIntegrity(database, databaseLabel);
return { status: "healthy" };
} catch (error) {
return failedSqliteIntegrityConfirmation(error);
}
}
/** Reconfirm an advisory failure against the database currently at a closed path. */
function confirmSqliteFileIntegrity(pathname, databaseLabel) {
for (let attempt = 0; attempt < 3; attempt += 1) {
let initial;
try {
initial = readStableSqliteFileGeneration(pathname);
} catch (error) {
return unboundSqliteIntegrityFailure(error);
}
let database;
try {
database = openNodeSqliteDatabase(pathname, { readOnly: true });
} catch (error) {
return unboundSqliteIntegrityFailure(error);
}
let opened;
try {
opened = readStableSqliteFileGeneration(pathname);
} catch {
const closeError = closeSqliteDatabase(database);
if (closeError) return unboundSqliteIntegrityFailure(closeError);
continue;
}
if (!sameSqliteFileGeneration(initial, opened)) {
const closeError = closeSqliteDatabase(database);
if (closeError) return unboundSqliteIntegrityFailure(closeError);
continue;
}
let confirmation = confirmSqliteIntegrity(database, databaseLabel);
const closeError = closeSqliteDatabase(database);
if (closeError && confirmation.status === "healthy") confirmation = failedSqliteIntegrityConfirmation(closeError);
let final;
try {
final = readStableSqliteFileGeneration(pathname);
} catch {
continue;
}
if (!sameSqliteFileGeneration(opened, final)) continue;
return bindSqliteIntegrityConfirmation(confirmation, final);
}
return unboundSqliteIntegrityFailure(/* @__PURE__ */ new Error(`SQLite file generation did not stabilize during confirmation: ${pathname}`));
}
function bindSqliteIntegrityConfirmation(confirmation, generation) {
if (confirmation.status === "healthy") return {
status: "healthy",
generation
};
if (confirmation.terminal) return {
...confirmation,
generation,
terminal: true
};
return {
...confirmation,
terminal: false
};
}
function failedSqliteIntegrityConfirmation(error) {
const normalized = toStringifiedError(error);
return {
status: "failed",
error: normalized,
terminal: isTerminalSqliteIntegrityError(normalized)
};
}
function unboundSqliteIntegrityFailure(error) {
return {
status: "failed",
error: toStringifiedError(error),
terminal: false
};
}
function closeSqliteDatabase(database) {
try {
database.close();
return;
} catch (error) {
return toStringifiedError(error);
}
}
/** Require table and associated index consistency before trusting indexed reads. */
function assertSqliteTableIntegrity(database, databaseLabel, tableName) {
runSqliteCheck(database, `${databaseLabel} table ${tableName}`, "integrity_check", tableName);
}
function runSqliteCheck(database, databaseLabel, pragma, tableName) {
const argument = tableName ? `('${tableName.replaceAll("'", "''")}')` : "";
let rows;
try {
rows = database.prepare(`PRAGMA ${pragma}${argument};`).all();
} catch (error) {
throw createSqliteIntegrityError(`SQLite ${pragma} failed for ${databaseLabel}: ${error instanceof Error ? error.message : String(error)}`, error);
}
const results = rows.map((row) => row[pragma] ?? Object.values(row)[0]);
if (results.length === 1 && results[0] === "ok") return "ok";
throw createSqliteIntegrityError(`SQLite ${pragma} failed for ${databaseLabel}: ${results.map((result) => String(result)).join("; ") || "no result"}`);
}
function runSqliteForeignKeyCheck(database, databaseLabel) {
let violationCount = 0;
const violations = [];
try {
const statement = database.prepare("PRAGMA foreign_key_check;");
statement.setReadBigInts(true);
for (const violation of statement.iterate()) {
violationCount += 1;
retainSortedForeignKeyViolation(violations, violation);
}
} catch (error) {
throw createSqliteIntegrityError(`SQLite foreign_key_check failed for ${databaseLabel}: ${error instanceof Error ? error.message : String(error)}`, error);
}
if (violations.length === 0) return;
const details = violations.map(formatSqliteForeignKeyViolation);
if (violationCount > MAX_REPORTED_FOREIGN_KEY_VIOLATIONS) details.push("additional violations omitted");
throw createSqliteIntegrityError(`SQLite foreign_key_check failed for ${databaseLabel}: ${details.join("; ")}`);
}
function createSqliteIntegrityError(message, cause) {
const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
error.name = "SqliteIntegrityError";
return error;
}
function retainSortedForeignKeyViolation(retained, violation) {
retained.push(violation);
retained.sort(compareSqliteForeignKeyViolations);
if (retained.length > MAX_REPORTED_FOREIGN_KEY_VIOLATIONS) retained.pop();
}
function compareSqliteForeignKeyViolations(left, right) {
const tableOrder = Buffer.compare(Buffer.from(left.table), Buffer.from(right.table));
if (tableOrder !== 0) return tableOrder;
if (left.rowid === null || right.rowid === null) {
if (left.rowid !== right.rowid) return left.rowid === null ? -1 : 1;
} else if (left.rowid !== right.rowid) return left.rowid < right.rowid ? -1 : 1;
const parentOrder = Buffer.compare(Buffer.from(left.parent), Buffer.from(right.parent));
if (parentOrder !== 0) return parentOrder;
if (left.fkid === right.fkid) return 0;
return left.fkid < right.fkid ? -1 : 1;
}
function formatSqliteForeignKeyViolation(violation) {
const row = violation.rowid === null ? "row without rowid" : `row ${violation.rowid.toString()}`;
return `${violation.table} ${row} references ${violation.parent} (foreign key ${violation.fkid.toString()})`;
}
//#endregion
export { runSqliteIntegrityOperationSync as a, isTerminalSqliteIntegrityError as i, assertSqliteTableIntegrity as n, confirmSqliteFileIntegrity as r, assertSqliteIntegrity as t };