Files
openclaw/src/infra/sqlite-wal.ts
2026-04-27 12:25:10 +01:00

75 lines
2.0 KiB
TypeScript

import type { DatabaseSync } from "node:sqlite";
export const DEFAULT_SQLITE_WAL_AUTOCHECKPOINT_PAGES = 1000;
export const DEFAULT_SQLITE_WAL_TRUNCATE_INTERVAL_MS = 30 * 60 * 1000;
type IntervalHandle = ReturnType<typeof setInterval> & {
unref?: () => void;
};
type SqliteWalCheckpointMode = "PASSIVE" | "FULL" | "RESTART" | "TRUNCATE";
export type SqliteWalMaintenance = {
checkpoint: () => boolean;
close: () => boolean;
};
export type SqliteWalMaintenanceOptions = {
autoCheckpointPages?: number;
checkpointIntervalMs?: number;
checkpointMode?: SqliteWalCheckpointMode;
onCheckpointError?: (error: unknown) => void;
};
function normalizeNonNegativeInteger(value: number, label: string): number {
if (!Number.isInteger(value) || value < 0) {
throw new Error(`${label} must be a non-negative integer`);
}
return value;
}
export function configureSqliteWalMaintenance(
db: DatabaseSync,
options: SqliteWalMaintenanceOptions = {},
): SqliteWalMaintenance {
const autoCheckpointPages = normalizeNonNegativeInteger(
options.autoCheckpointPages ?? DEFAULT_SQLITE_WAL_AUTOCHECKPOINT_PAGES,
"autoCheckpointPages",
);
const checkpointIntervalMs = normalizeNonNegativeInteger(
options.checkpointIntervalMs ?? DEFAULT_SQLITE_WAL_TRUNCATE_INTERVAL_MS,
"checkpointIntervalMs",
);
const checkpointMode = options.checkpointMode ?? "TRUNCATE";
db.exec("PRAGMA journal_mode = WAL;");
db.exec(`PRAGMA wal_autocheckpoint = ${autoCheckpointPages};`);
const checkpoint = (): boolean => {
try {
db.exec(`PRAGMA wal_checkpoint(${checkpointMode});`);
return true;
} catch (error) {
options.onCheckpointError?.(error);
return false;
}
};
let timer: IntervalHandle | null = null;
if (checkpointIntervalMs > 0) {
timer = setInterval(checkpoint, checkpointIntervalMs) as IntervalHandle;
timer.unref?.();
}
return {
checkpoint,
close: () => {
if (timer) {
clearInterval(timer);
timer = null;
}
return checkpoint();
},
};
}