UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

131 lines (130 loc) 5.17 kB
import { I as sameFileIdentity } from "./fs-safe-B6pvPGnf.js"; import fs from "node:fs"; import { createHash } from "node:crypto"; //#region src/infra/sqlite-file-generation.ts const SQLITE_GENERATION_HASH_BUFFER_BYTES = 1048576; function assertRegularFile(stat) { if (!stat.isFile()) throw new Error("SQLite generation target must be a regular file"); } function sameFileState(left, right) { return sameFileIdentity(left, right) && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs && left.size === right.size; } function hashFileDescriptor(fd) { const hash = createHash("sha256"); const buffer = Buffer.allocUnsafe(SQLITE_GENERATION_HASH_BUFFER_BYTES); let position = 0; while (true) { const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position); if (bytesRead === 0) break; hash.update(buffer.subarray(0, bytesRead)); position += bytesRead; } return hash.digest("hex"); } function fingerprintFile(pathname) { const fd = fs.openSync(pathname, "r"); try { const before = fs.fstatSync(fd, { bigint: true }); assertRegularFile(before); const sha256 = hashFileDescriptor(fd); const after = fs.fstatSync(fd, { bigint: true }); const current = fs.statSync(pathname, { bigint: true }); if (!sameFileState(before, after) || !sameFileState(after, current)) throw new Error(`SQLite generation target changed while hashing: ${pathname}`); return { birthtimeNs: after.birthtimeNs, ctimeNs: after.ctimeNs, dev: after.dev, ino: after.ino, mtimeNs: after.mtimeNs, sha256, size: after.size }; } finally { fs.closeSync(fd); } } function readOptionalFile(pathname) { try { return fingerprintFile(pathname); } catch (error) { if (error.code === "ENOENT") return; throw error; } } function readGeneration(pathname) { const database = fingerprintFile(pathname); const journal = readOptionalFile(`${pathname}-journal`); const wal = readOptionalFile(`${pathname}-wal`); return { database, ...journal ? { journal } : {}, ...wal ? { wal } : {} }; } function readStableSqliteFileGeneration(pathname) { const first = readGeneration(pathname); const second = readGeneration(pathname); if (!sameSqliteFileGeneration(first, second)) throw new Error(`SQLite file generation changed while reading: ${pathname}`); return second; } function sameFileFingerprint(left, right) { return left.dev === right.dev && left.ino === right.ino && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs && left.sha256 === right.sha256 && left.size === right.size; } function sameOptionalFileFingerprint(left, right) { return left === void 0 ? right === void 0 : right !== void 0 && sameFileFingerprint(left, right); } function sameSqliteFileGeneration(left, right) { return sameFileFingerprint(left.database, right.database) && sameOptionalFileFingerprint(left.journal, right.journal) && sameOptionalFileFingerprint(left.wal, right.wal); } function serializeFileFingerprint(fingerprint) { return { birthtimeNs: fingerprint.birthtimeNs.toString(), ctimeNs: fingerprint.ctimeNs.toString(), dev: fingerprint.dev.toString(), ino: fingerprint.ino.toString(), mtimeNs: fingerprint.mtimeNs.toString(), sha256: fingerprint.sha256, size: fingerprint.size.toString() }; } function serializeSqliteFileGeneration(generation) { return JSON.stringify({ database: serializeFileFingerprint(generation.database), ...generation.journal ? { journal: serializeFileFingerprint(generation.journal) } : {}, ...generation.wal ? { wal: serializeFileFingerprint(generation.wal) } : {} }); } function parseFileFingerprint(value) { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("SQLite file fingerprint must be an object"); const fingerprint = value; for (const field of [ "birthtimeNs", "ctimeNs", "dev", "ino", "mtimeNs", "size" ]) if (typeof fingerprint[field] !== "string" || !/^-?\d+$/u.test(fingerprint[field])) throw new Error(`SQLite file fingerprint ${field} is invalid`); if (typeof fingerprint.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(fingerprint.sha256)) throw new Error("SQLite file fingerprint sha256 is invalid"); return { birthtimeNs: BigInt(fingerprint.birthtimeNs), ctimeNs: BigInt(fingerprint.ctimeNs), dev: BigInt(fingerprint.dev), ino: BigInt(fingerprint.ino), mtimeNs: BigInt(fingerprint.mtimeNs), sha256: fingerprint.sha256, size: BigInt(fingerprint.size) }; } function parseSqliteFileGeneration(serialized) { const value = JSON.parse(serialized); if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("SQLite file generation must be an object"); const generation = value; return { database: parseFileFingerprint(generation.database), ...generation.journal === void 0 ? {} : { journal: parseFileFingerprint(generation.journal) }, ...generation.wal === void 0 ? {} : { wal: parseFileFingerprint(generation.wal) } }; } //#endregion export { serializeSqliteFileGeneration as i, readStableSqliteFileGeneration as n, sameSqliteFileGeneration as r, parseSqliteFileGeneration as t };