UNPKG

@tomsons/queue-manager

Version:

A powerful and flexible queue manager for handling asynchronous tasks in TypeScript applications. It provides features like concurrency control, task prioritization, automatic retries, cancellation, and progress tracking.

192 lines (191 loc) 5.56 kB
var c = Object.defineProperty; var h = (t, e, i) => e in t ? c(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i; var o = (t, e, i) => h(t, typeof e != "symbol" ? e + "" : e, i); class l extends Error { constructor(e, i) { super(e), this.context = i, this.name = this.constructor.name; } } const a = (t) => class extends l { constructor(e) { super(t, e); } }; class f extends a("Task timed out") { } var y = /* @__PURE__ */ ((t) => (t.PENDING = "PENDING", t.RUNNING = "RUNNING", t.COMPLETED = "COMPLETED", t.FAILED = "FAILED", t.CANCELLED = "CANCELLED", t))(y || {}); const d = { calculateDelay: (t) => Math.min(1e3 * Math.pow(2, t), 3e4) }; class g { /** * Creates a new queue manager instance * @param options Configuration options for the queue */ constructor(e) { o(this, "queue", []); o(this, "failedQueue", []); o(this, "running", /* @__PURE__ */ new Map()); o(this, "options"); o(this, "listeners", []); this.options = { defaultRetryPolicy: d, maxRetries: 3, ...e }; } /** * Returns a copy of the tasks in the failed queue. */ getFailedTasks() { return [...this.failedQueue]; } /** * Adds a new task to the queue * @param task Task to be queued */ enqueue(e) { this.queue.push({ ...e, priority: e.priority ?? 0, retryPolicy: e.retryPolicy ?? this.options.defaultRetryPolicy, maxRetries: e.maxRetries ?? this.options.maxRetries }), this.notifyProgress( e.id, 0, "PENDING" /* PENDING */ ), this.processQueue(); } /** * Re-queues all tasks from the failed queue for processing. */ reprocessFailedTasks() { this.failedQueue.forEach((e) => this.enqueue(e)), this.failedQueue = []; } /** * Registers a callback for task progress updates * @param callback Function to be called with progress updates * @returns Function to unregister the callback */ onProgress(e) { return this.listeners.push(e), () => { this.listeners = this.listeners.filter((i) => i !== e); }; } /** * Clears all pending tasks from the queue * @param cancelRunning If true, also cancels currently running tasks */ clearQueue(e = !1) { e && this.cancelAll(), this.queue = []; } /** * Waits for all tasks in the queue to complete * @returns Promise that resolves when all tasks are finished */ async waitForCompletion() { return this.queue.length === 0 && this.running.size === 0 ? Promise.resolve() : new Promise((e) => { const i = () => { if (this.queue.length === 0 && this.running.size === 0) { e(); return; } setTimeout(i, 100); }; i(); }); } /** * Cancels all queued and running tasks */ async cancelAll() { this.queue = []; for (const [e, { task: i }] of this.running) i.cancel && i.cancel(), this.notifyProgress( e, 0, "CANCELLED" /* CANCELLED */ ); this.running.clear(); } /** * Processes the next task in the queue if possible */ async processQueue() { if (this.running.size >= this.options.concurrency || this.queue.length === 0) return; this.queue.sort((i, s) => (s.priority ?? 0) - (i.priority ?? 0)); const e = this.queue.shift(); e && (this.running.set(e.id, { task: e, attempts: 0 }), await this.executeTask(e), this.processQueue()); } /** * Executes a single task with retry logic * @param task Task to execute */ async executeTask(e) { this.notifyProgress( e.id, 0, "RUNNING" /* RUNNING */ ); const i = this.running.get(e.id); if (i) try { e.timeout && !isNaN(e.timeout) ? await this.executeTaskWithTimeout(e) : await e.execute((s) => { this.notifyProgress(e.id, s.progress, s.status, s.error); }), this.notifyProgress( e.id, 100, "COMPLETED" /* COMPLETED */ ); } catch (s) { console.error(`Task ${e.id} failed:`, s); const { attempts: n } = i; if (n < (e.maxRetries ?? this.options.maxRetries)) { const r = e.retryPolicy.calculateDelay(n); return this.notifyProgress(e.id, 0, "RUNNING", s), await new Promise((u) => setTimeout(u, r)), this.running.set(e.id, { task: e, attempts: n + 1 }), this.executeTask(e); } this.notifyProgress(e.id, 0, "FAILED", s), this.failedQueue.push(e); } finally { this.running.delete(e.id); } } /** * Executes a task with a timeout * @param task * @private */ async executeTaskWithTimeout(e) { return new Promise((i, s) => { const n = setTimeout(() => { var r; (r = e.cancel) == null || r.call(e), s(new f({ taskId: e.id, timeout: e.timeout })); }, e.timeout); e.execute((r) => { this.notifyProgress(e.id, r.progress, r.status, r.error); }).then(() => i()).catch(() => { clearTimeout(n), s(); }); }); } /** * Notifies all registered listeners of task progress * @param taskId ID of the task * @param progress Current progress value * @param status Current task status * @param error Error if task failed */ notifyProgress(e, i, s, n) { const r = { taskId: e, progress: i, status: s, error: n, date: /* @__PURE__ */ new Date() }; this.listeners.forEach((u) => u(r)); } } export { d as ExponentialBackoff, g as QueueManager, y as TaskStatus };