Files
DentalManagementMH06/apps/Backend/src/queue/inProcessQueue.ts
ff 90302a76b7 feat: add BullMQ queue infrastructure and frontend job status hook
- apps/Backend/src/queue/: connection, queues, workers, processors
- apps/Frontend/src/hooks/use-job-status.ts: WebSocket job progress hook
- apps/Frontend/src/lib/socket.ts: shared Socket.IO singleton

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:30:40 -04:00

80 lines
2.1 KiB
TypeScript

/**
* A lightweight in-process async job queue.
* No Redis, no external dependencies — just Node.js Promises.
*
* Features:
* - Configurable concurrency limit
* - Non-blocking add() — returns a jobId immediately
* - Job status tracking in-memory
* - onComplete / onFail callbacks for WebSocket notifications
*/
import { randomUUID } from "crypto";
export type JobStatus = "queued" | "active" | "completed" | "failed";
export interface QueueJob<T = any> {
id: string;
data: T;
status: JobStatus;
result?: any;
error?: string;
}
type Processor<T> = (job: QueueJob<T>) => Promise<any>;
export class InProcessQueue<T = any> {
private concurrency: number;
private running = 0;
private waitQueue: Array<() => void> = [];
private jobs = new Map<string, QueueJob<T>>();
constructor(concurrency = 1) {
this.concurrency = concurrency;
}
/**
* Enqueue a job. Returns the jobId immediately; processing starts
* as soon as a concurrency slot is free.
*/
add(data: T, processor: Processor<T>): string {
const id = randomUUID();
const job: QueueJob<T> = { id, data, status: "queued" };
this.jobs.set(id, job);
// Fire-and-forget — errors are captured in job.error
this._run(job, processor).catch(() => {});
return id;
}
getJob(id: string): QueueJob<T> | undefined {
return this.jobs.get(id);
}
/** How many jobs are waiting for a slot. */
get waiting() {
return this.waitQueue.length;
}
private async _run(job: QueueJob<T>, processor: Processor<T>) {
// Block until a concurrency slot is available
if (this.running >= this.concurrency) {
await new Promise<void>((resolve) => this.waitQueue.push(resolve));
}
this.running++;
job.status = "active";
try {
job.result = await processor(job);
job.status = "completed";
} catch (err: any) {
job.status = "failed";
job.error = err?.message ?? String(err);
} finally {
this.running--;
// Wake up next waiting job
const next = this.waitQueue.shift();
if (next) next();
}
}
}