openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
131 lines (130 loc) • 5.17 kB
JavaScript
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 };