bun-downloader-manager
Version:
bun-download-manager is a simple yet powerful package manager-like download manager built with Bun.js. It allows you to download files sequentially or with a queue-based approach, handling retries and concurrency limits efficiently.
115 lines (114 loc) • 5.46 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Queue {
heap = []; // A heap to store tasks. This is a max-heap (higher priority tasks are at the root).
activeCount = 0; // The number of active tasks currently being processed.
concurrencyLimit; // The maximum number of concurrent tasks that can run at the same time.
maxRetries; // The maximum number of retries allowed for failed tasks.
log;
constructor(concurrencyLimit, maxRetries, consoleLog) {
this.concurrencyLimit = concurrencyLimit; // Set the concurrency limit for active tasks
this.maxRetries = maxRetries; // Set the maximum retries allowed for failed tasks
this.log = consoleLog;
}
logger(message, type = "info") {
if (this.log || type === "error") {
console[type](`[${process.pid}] : [${new Date().toLocaleString()}] : `, message);
}
}
// Helper function to ensure the heap property is maintained when inserting a task (heapify up).
// This function moves a task up in the heap to its correct position based on priority.
heapifyUp(index) {
const task = this.heap[index]; // Get the task at the current index
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2); // Find the parent index
// Ensure the parent index is valid and within bounds
if (parentIndex < 0 || parentIndex >= this.heap.length)
break;
// If the parent task's priority is higher or equal, break (as the heap property is satisfied)
if (this.heap[parentIndex] &&
this.heap[parentIndex].priority >= task.priority)
break;
// Move the parent task down to the current index
this.heap[index] = this.heap[parentIndex];
index = parentIndex; // Move up to the parent's index
}
// Place the task at its correct position in the heap
this.heap[index] = task;
}
// Method to add a new task to the queue.
enqueue(task) {
this.heap.push(task); // Add the new task to the heap
this.heapifyUp(this.heap.length - 1); // Maintain the heap property by pushing the task up
this.runNext(); // Try to run the next task if possible
}
// Helper function to maintain the heap property after removing the root (heapify down).
heapifyDown(index) {
const length = this.heap.length;
while (true) {
const leftChildIndex = 2 * index + 1;
const rightChildIndex = 2 * index + 2;
let largestIndex = index;
// Check if the left child has a higher priority.
if (leftChildIndex < length &&
this.heap[leftChildIndex].priority > this.heap[largestIndex].priority) {
largestIndex = leftChildIndex;
}
// Check if the right child has a higher priority than the current largest.
if (rightChildIndex < length &&
this.heap[rightChildIndex].priority > this.heap[largestIndex].priority) {
largestIndex = rightChildIndex;
}
// If no swap is needed, break the loop.
if (largestIndex === index)
break;
// Swap the current task with the largest child.
[this.heap[index], this.heap[largestIndex]] = [
this.heap[largestIndex],
this.heap[index],
];
index = largestIndex; // Update the index to continue heapifying down.
}
}
// Method to start processing the next task in the queue.
async runNext() {
// If the number of active tasks has reached the concurrency limit, or if there are no tasks in the queue, stop processing
if (this.activeCount >= this.concurrencyLimit || this.heap.length === 0) {
return;
}
// Get the highest priority task (the root of the heap)
const task = this.heap[0];
if (!task)
return;
// Replace the root task with the last task in the heap, and remove the last task
this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();
// Restore the heap property by moving the new root down to its correct position
this.heapifyDown(0);
// Increment the active task counter
this.activeCount++;
try {
// Attempt to run the task (asynchronously)
await task.action();
this.logger(`Task ${task.id} completed successfully.`);
}
catch (err) {
// If the task fails, log the error and check if it should be retried
this.logger(`Task ${task.id} failed: ${err}`, "error");
if (task.retries < this.maxRetries) {
this.logger(`Retrying task ${task.id}...`);
// If the task can be retried, increment the retry count and add it back to the queue
this.enqueue({ ...task, retries: task.retries + 1 });
}
else {
this.logger(`Task ${task.id} exceeded max retries.`, "warn");
}
}
finally {
// After processing, decrement the active task counter and attempt to run the next task
this.activeCount--;
this.runNext(); // Trigger the next task if possible
}
}
}
exports.default = Queue;