import { createRequire } from "node:module"; import type BetterSqlite3 from "better-sqlite3"; const require = createRequire(import.meta.url); const Database = require("better-sqlite3") as typeof BetterSqlite3; export class RetentionCleanup { private readonly clock: () => number; private readonly database: BetterSqlite3.Database; private retentionPurgeActive = false; private retentionPurgeNow = 0; constructor(input: { clock?: () => number; databasePath: string }) { this.clock = input.clock ?? Date.now; const nativeBinding = process.env.DADA_SQLITE_NATIVE_BINDING; this.database = new Database(input.databasePath, nativeBinding ? { nativeBinding } : undefined); this.database.pragma("foreign_keys = ON"); this.database.pragma("busy_timeout = 5000"); this.database.function("dada_allow_privacy_purge", { deterministic: false }, () => 0); this.database.function("dada_allow_retention_purge", { deterministic: false }, () => this.retentionPurgeActive ? 1 : 0); this.database.function("dada_retention_purge_now", { deterministic: false }, () => this.retentionPurgeNow); } purgeExpired() { const now = this.clock(); this.database.exec("BEGIN IMMEDIATE"); try { this.retentionPurgeActive = true; this.retentionPurgeNow = now; const adminOperations = this.database.prepare("DELETE FROM admin_operation_logs WHERE expires_at <= ?").run(now); const privateAccess = this.database.prepare("DELETE FROM private_content_access_logs WHERE expires_at <= ?").run(now); const anonymous = this.database.prepare("DELETE FROM anonymous_retained_events WHERE expires_at <= ?").run(now); this.retentionPurgeActive = false; this.retentionPurgeNow = 0; this.database.exec("COMMIT"); return { admin_operation_logs: adminOperations.changes, anonymous_events: anonymous.changes, private_access_logs: privateAccess.changes, }; } catch (error) { this.retentionPurgeActive = false; this.retentionPurgeNow = 0; if (this.database.inTransaction) this.database.exec("ROLLBACK"); throw error; } } close() { this.database.close(); } }