import { randomUUID } from 'crypto'; export interface JobLine { event?: string; // 'result', 'progress', etc. — undefined for bare data lines data: unknown; } export interface Job { id: string; status: 'running' | 'done' | 'error'; lines: JobLine[]; result?: unknown; createdAt: number; updatedAt: number; /** Set when a client requests cancellation; long-running jobs should poll this. */ cancelRequested?: boolean; } const jobs = new Map(); export function createJob(): Job { const job: Job = { id: randomUUID(), status: 'running', lines: [], createdAt: Date.now(), updatedAt: Date.now() }; jobs.set(job.id, job); return job; } export function getJob(id: string): Job | undefined { return jobs.get(id); } /** Live job counts by status — for the metrics endpoint. */ export function getJobStats(): { running: number; done: number; error: number; total: number } { let running = 0, done = 0, error = 0; for (const job of jobs.values()) { if (job.status === 'running') running++; else if (job.status === 'done') done++; else if (job.status === 'error') error++; } return { running, done, error, total: jobs.size }; } export function appendLine(job: Job, line: JobLine): void { job.lines.push(line); job.updatedAt = Date.now(); } export function completeJob(job: Job, result: unknown): void { job.result = result; job.status = 'done'; job.updatedAt = Date.now(); } export function failJob(job: Job, error: string): void { job.result = { success: false, error }; job.status = 'error'; job.updatedAt = Date.now(); } /** Request cancellation of a running job. The job's operation must poll job.cancelRequested. */ export function cancelJob(id: string): boolean { const job = jobs.get(id); if (!job || job.status !== 'running') return false; job.cancelRequested = true; job.updatedAt = Date.now(); return true; } // Cleanup jobs older than 10 minutes that are no longer running const CLEANUP_INTERVAL_MS = 60_000; const JOB_TTL_MS = 10 * 60_000; setInterval(() => { const cutoff = Date.now() - JOB_TTL_MS; for (const [id, job] of jobs) { if (job.status !== 'running' && job.updatedAt < cutoff) { jobs.delete(id); } } }, CLEANUP_INTERVAL_MS);