@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
JavaScript
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
};