@nodesecure/tarball
Version:
NodeSecure tarball scanner
127 lines • 4.18 kB
JavaScript
// Import Node.js Dependencies
import { EventEmitter } from "node:events";
import path from "node:path";
// Import Third-party Dependencies
import hyperid from "hyperid";
// Import Internal Dependencies
import { PooledWorker } from "./PooledWorker.class.js";
/**
* O(1) amortized FIFO queue using a head-pointer to avoid
* the O(n) cost of Array.shift().
*/
class TaskQueue {
#items = [];
#head = 0;
enqueue(task) {
this.#items.push(task);
}
dequeue() {
if (this.#head >= this.#items.length) {
return undefined;
}
const item = this.#items[this.#head++];
if (this.#head > 0 && this.#head >= this.#items.length / 2) {
this.#items = this.#items.slice(this.#head);
this.#head = 0;
}
return item;
}
clear() {
this.#items = [];
this.#head = 0;
}
}
export class NpmTarballWorkerPool extends EventEmitter {
#generateTaskId = hyperid();
#workers = [];
#availableWorkers = [];
#processingTasks = new Map();
#waitingTasks = new TaskQueue();
#isTerminated = false;
constructor(options = {}) {
super();
const { workerCount = 4, workerFactory } = options;
const workerPath = path.join(import.meta.dirname, "NpmTarballWorkerScript.js");
const factory = workerFactory ??
((events) => new PooledWorker(workerPath, events));
for (let i = 0; i < workerCount; i++) {
const worker = factory({
onComplete: (worker, message) => this.#onWorkerComplete(worker, message),
onError: (worker, error, taskId) => this.#onWorkerError(worker, error, taskId)
});
this.#workers.push(worker);
this.#availableWorkers.push(worker);
}
}
#onWorkerComplete(worker, message) {
const handler = this.#processingTasks.get(message.id);
if (handler) {
this.#processingTasks.delete(message.id);
if ("error" in message) {
handler.reject(new Error(message.error));
}
else {
handler.resolve(message.result);
}
}
const nextTask = this.#waitingTasks.dequeue();
if (nextTask) {
worker.execute(nextTask);
}
else {
this.#availableWorkers.push(worker);
}
}
#onWorkerError(worker, error, taskId) {
if (taskId) {
const handler = this.#processingTasks.get(taskId);
if (handler) {
this.#processingTasks.delete(taskId);
handler.reject(error);
}
}
this.emit("error", error);
const nextTask = this.#waitingTasks.dequeue();
if (nextTask) {
worker.execute(nextTask);
}
else {
this.#availableWorkers.push(worker);
}
}
scan(task) {
if (this.#isTerminated) {
return Promise.reject(new Error("NpmTarballWorkerPool has been terminated"));
}
const fullTask = {
id: this.#generateTaskId(),
...task
};
const { promise, resolve, reject } = Promise.withResolvers();
this.#processingTasks.set(fullTask.id, { resolve, reject });
const availableWorker = this.#availableWorkers.pop() ?? null;
if (availableWorker) {
availableWorker.execute(fullTask);
}
else {
this.#waitingTasks.enqueue(fullTask);
}
return promise;
}
async terminate() {
this.#isTerminated = true;
const terminationError = new Error("NpmTarballWorkerPool terminated");
for (const handler of this.#processingTasks.values()) {
handler.reject(terminationError);
}
this.#processingTasks.clear();
this.#waitingTasks.clear();
this.#availableWorkers = [];
await Promise.all(this.#workers.map((worker) => worker.terminate()));
this.#workers = [];
}
[Symbol.asyncDispose]() {
return this.terminate();
}
}
//# sourceMappingURL=NpmTarballWorkerPool.class.js.map