@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.
197 lines (196 loc) • 5.62 kB
JavaScript
var h = Object.defineProperty;
var l = (t, e, i) => e in t ? h(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i;
var o = (t, e, i) => l(t, typeof e != "symbol" ? e + "" : e, i);
class a extends Error {
constructor(e, i) {
super(e), this.context = i, this.name = this.constructor.name;
}
}
const f = (t) => class extends a {
constructor(e) {
super(t, e);
}
};
class y extends f("Task timed out") {
}
var d = /* @__PURE__ */ ((t) => (t.PENDING = "PENDING", t.RUNNING = "RUNNING", t.COMPLETED = "COMPLETED", t.FAILED = "FAILED", t.CANCELLED = "CANCELLED", t))(d || {});
const m = {
calculateDelay: (t) => Math.min(1e3 * 2 ** t, 3e4)
};
class E {
/**
* 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: m,
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, n) => (n.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) {
var n;
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((r) => {
this.notifyProgress(e.id, r.progress, r.status, r.error);
}), this.notifyProgress(
e.id,
100,
"COMPLETED"
/* COMPLETED */
);
} catch (r) {
console.error(`Task ${e.id} failed:`, r);
const { attempts: s } = i;
if (s < (e.maxRetries ?? this.options.maxRetries)) {
const u = (n = e.retryPolicy) == null ? void 0 : n.calculateDelay(s);
return this.notifyProgress(e.id, 0, "RUNNING", r), await new Promise((c) => setTimeout(c, u)), this.running.set(e.id, { task: e, attempts: s + 1 }), this.executeTask(e);
}
this.notifyProgress(e.id, 0, "FAILED", r), 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, n) => {
const r = setTimeout(() => {
var s;
(s = e.cancel) == null || s.call(e), n(new y({ taskId: e.id, timeout: e.timeout }));
}, e.timeout);
e.execute((s) => {
this.notifyProgress(e.id, s.progress, s.status, s.error);
}).then(() => i()).catch(() => {
clearTimeout(r), n();
});
});
}
/**
* 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, n, r) {
const s = { taskId: e, progress: i, status: n, error: r, date: /* @__PURE__ */ new Date() };
this.listeners.forEach((u) => {
u(s);
});
}
}
export {
m as ExponentialBackoff,
E as QueueManager,
d as TaskStatus
};