Files
dockhand-upstream/src/lib/server/jobs.ts
T
2026-07-11 15:31:52 +02:00

86 lines
2.2 KiB
TypeScript

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<string, Job>();
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);