UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

94 lines (93 loc) 3.85 kB
import { t as openNodeSqliteDatabase } from "./node-sqlite-BpQX3W0e.js"; import "./openclaw-state-db-contract-DYCYxE4w.js"; import { t as assertSqliteIntegrity } from "./sqlite-integrity-NpEtFIdK.js"; import "./openclaw-state-db-BRTnL-D8.js"; import fs from "node:fs"; //#region src/commands/doctor-sqlite-compact.ts /** Shared doctor-only SQLite compaction mechanics. */ /** * Compact one SQLite file during an explicit offline doctor operation. * * Validation runs before the first checkpoint because checkpointing mutates * the database files. A busy checkpoint is a hard failure, never partial * success, so VACUUM cannot race an active reader or writer. */ function compactDoctorSqliteFile(options) { const database = openNodeSqliteDatabase(options.sqlitePath); let operationError; let result; try { database.exec(`PRAGMA busy_timeout = ${options.busyTimeoutMs ?? 5e3};`); database.exec("PRAGMA trusted_schema = OFF;"); options.validateBeforeMutation?.(database); const before = readCompactSnapshot(database, options.sqlitePath); assertSqliteIntegrity(database, options.sqlitePath); checkpointTruncate(database, options.sqlitePath); database.exec("PRAGMA auto_vacuum = INCREMENTAL;"); database.exec(options.operation === "import-finalize" && before.autoVacuum !== 0 ? "PRAGMA incremental_vacuum;" : "VACUUM;"); checkpointTruncate(database, options.sqlitePath); const { integrityCheck } = assertSqliteIntegrity(database, options.sqlitePath); const after = readCompactSnapshot(database, options.sqlitePath); const beforeBytes = before.dbSizeBytes + before.walSizeBytes; const afterBytes = after.dbSizeBytes + after.walSizeBytes; result = { after, before, integrityCheck, reclaimedBytes: Math.max(0, beforeBytes - afterBytes) }; } catch (error) { operationError = error; } try { database.close(); } catch (error) { operationError ??= error; } if (operationError === void 0 && result) try { options.afterSuccess?.(); } catch (error) { operationError ??= error; } if (operationError !== void 0) throw operationError instanceof Error ? operationError : /* @__PURE__ */ new Error("SQLite compaction failed with a non-Error value."); if (!result) throw new Error(`SQLite compaction produced no result for ${options.sqlitePath}.`); return result; } function checkpointTruncate(database, sqlitePath) { const row = database.prepare("PRAGMA wal_checkpoint(TRUNCATE);").get(); const busy = readFiniteNumber(row?.busy ?? (row ? Object.values(row)[0] : void 0)); if (busy === void 0) throw new Error(`SQLite checkpoint returned an invalid result for ${sqlitePath}.`); if (busy !== 0) throw new Error(`SQLite checkpoint remained busy for ${sqlitePath}. Stop OpenClaw and retry.`); } function readCompactSnapshot(database, sqlitePath) { return { autoVacuum: readPragmaNumber(database, "auto_vacuum"), dbSizeBytes: fileSize(sqlitePath), freelistPages: readPragmaNumber(database, "freelist_count"), pageSizeBytes: readPragmaNumber(database, "page_size"), walSizeBytes: fileSize(`${sqlitePath}-wal`) }; } function readPragmaNumber(database, pragmaName) { const row = database.prepare(`PRAGMA ${pragmaName};`).get(); const value = readFiniteNumber(row?.[pragmaName] ?? (row ? Object.values(row)[0] : void 0)); if (value === void 0) throw new Error(`SQLite PRAGMA ${pragmaName} returned an invalid result.`); return value; } function readFiniteNumber(value) { if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value === "bigint") { const numberValue = Number(value); return Number.isFinite(numberValue) ? numberValue : void 0; } } function fileSize(filePath) { try { return fs.statSync(filePath).size; } catch (error) { if (error.code === "ENOENT") return 0; throw error; } } //#endregion export { compactDoctorSqliteFile as t };