import "server-only"; import { after } from "next/server"; const workerKey = "__generated_image_archive_worker__"; type Worker = { running: boolean; scheduled: boolean; requested: boolean; retryTimer?: ReturnType; }; function getWorker(): Worker { const root = globalThis as typeof globalThis & { [workerKey]?: Worker }; return (root[workerKey] ??= { running: false, scheduled: false, requested: false }); } export function scheduleImageArchiveWorker(deliveryConfirmed = false) { const worker = getWorker(); worker.requested = true; if (worker.running || (worker.scheduled && !deliveryConfirmed)) return; worker.scheduled = true; if (worker.retryTimer) { clearTimeout(worker.retryTimer); worker.retryTimer = undefined; } const run = async () => { if (worker.running) return; worker.scheduled = false; worker.running = true; let retryNeeded = false; try { const { runImageArchiveJob } = await import("@/lib/jobs/image-archive-job"); do { worker.requested = false; const result = await runImageArchiveJob(); retryNeeded = result.stats.pendingStore > 0 || result.stats.pendingReplace > 0; // Stop when only backoff tasks remain; do not spin while storage is down. if (!worker.requested && result.store.total === 0 && result.replace.total === 0) break; } while (true); } catch (error) { retryNeeded = true; console.error("[ImageArchiveWorker] background archive failed", error); } finally { worker.running = false; if (worker.requested) scheduleImageArchiveWorker(); else if (retryNeeded) { worker.retryTimer = setTimeout(scheduleImageArchiveWorker, 60_000); worker.retryTimer.unref?.(); } } }; // Detached generation tasks may inherit a request that has already closed. Keep // a Node-server fallback so archiving never depends on the user leaving a tab open. const deliveryFallback = setTimeout(() => { if (worker.scheduled && !worker.running) void run(); }, 1_000); deliveryFallback.unref?.(); try { // Request handlers deliver first. Durable task rows also allow cron/restart recovery. after(run); } catch { // Durable canvas tasks can run outside Next's request context on the Node server. setImmediate(() => { void run(); }); } }