openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
97 lines (96 loc) • 4.31 kB
JavaScript
import { r as resolveExistingSqliteFileUri, t as openNodeSqliteDatabase } from "./node-sqlite-XIcwmOrl.mjs";
import "./openclaw-state-db-contract-Cm9fKF2T.mjs";
import { r as readFiniteSqliteNumber } from "./sqlite-number-DZt1m75H.mjs";
import { _ as truncateSqliteWal, m as SqliteWalCheckpointBusyError } from "./sqlite-wal-DUSYRhyv.mjs";
import { n as assertSqliteIntegrity } from "./sqlite-integrity-GtqcmwG3.mjs";
import "./openclaw-state-db-D1jWDzK3.mjs";
import fs from "node:fs";
//#region src/commands/doctor-sqlite-compact.ts
/** Shared doctor-only SQLite compaction mechanics. */
/** The initial checkpoint was busy, before conversion, and the connection has closed. */
var DoctorSqliteCompactionDeferredError = class extends Error {};
/**
* 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.requireExisting ? resolveExistingSqliteFileUri(options.sqlitePath) : options.sqlitePath);
let operationError;
let initialCheckpointBusy = false;
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);
let { integrityCheck } = assertSqliteIntegrity(database, options.sqlitePath);
if (!(options.operation === "import-finalize" && before.autoVacuum === 2 && before.freelistPages === 0 && before.walSizeBytes === 0)) {
try {
truncateSqliteWal(database, options.sqlitePath);
} catch (error) {
initialCheckpointBusy = error instanceof SqliteWalCheckpointBusyError;
throw error;
}
database.exec("PRAGMA auto_vacuum = INCREMENTAL;");
database.exec(options.operation === "import-finalize" && before.autoVacuum !== 0 ? "PRAGMA incremental_vacuum;" : "VACUUM;");
truncateSqliteWal(database, options.sqlitePath);
({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) {
initialCheckpointBusy = false;
operationError = operationError !== void 0 ? new AggregateError([operationError, error], "SQLite compaction and close failed.") : error;
}
if (operationError === void 0 && result) try {
options.afterSuccess?.();
} catch (error) {
operationError ??= error;
}
if (operationError !== void 0) {
if (initialCheckpointBusy && operationError instanceof Error) throw new DoctorSqliteCompactionDeferredError(operationError.message, { cause: operationError });
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 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 = readFiniteSqliteNumber(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 fileSize(filePath) {
try {
return fs.statSync(filePath).size;
} catch (error) {
if (error.code === "ENOENT") return 0;
throw error;
}
}
//#endregion
export { compactDoctorSqliteFile as n, DoctorSqliteCompactionDeferredError as t };