UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

182 lines (181 loc) 7.3 kB
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 };