poolifier 2.3.10-2 → 2.4.0-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -163,7 +163,8 @@ Node versions >= 16.x are supported.
163
163
  - `workerChoiceStrategy` (optional) - The worker choice strategy to use in this pool:
164
164
 
165
165
  - `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round robbin fashion
166
- - `WorkerChoiceStrategies.LESS_RECENTLY_USED`: Submit tasks to the less recently used worker
166
+ - `WorkerChoiceStrategies.LESS_USED`: Submit tasks to the less used worker
167
+ - `WorkerChoiceStrategies.LESS_BUSY`: Submit tasks to the less busy worker
167
168
  - `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` Submit tasks to worker using a weighted round robin scheduling algorithm based on tasks execution time
168
169
  - `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker using a fair share tasks scheduling algorithm based on tasks execution time
169
170
 
@@ -203,7 +204,7 @@ This method will call the terminate method on each worker.
203
204
  - `async` - true/false, true if your function contains async code pieces, else false
204
205
  - `killBehavior` - Dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
205
206
  **KillBehaviors.SOFT**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
206
- **KillBehaviors.HARD**: If `lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
207
+ **KillBehaviors.HARD**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
207
208
  This option only apply to the newly created workers.
208
209
  Default: `KillBehaviors.SOFT`
209
210
 
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,r=require("cluster"),t=require("events"),s=require("os"),o=require("worker_threads"),i=require("async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));const n=Object.freeze((()=>{})),a=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class h extends t{}const k=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class c{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC}}class u extends c{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const t of this.pool.workers){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.getWorkerAverageTasksRunTime(e)??0)})}}class l extends c{reset(){return!0}choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class p extends c{nextWorkerIndex=0;reset(){return this.nextWorkerIndex=0,!0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class g extends c{requiredStatistics={runTime:!0};currentWorkerIndex=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerIndex=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.pool.workers[this.currentWorkerIndex];this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerIndex=this.currentWorkerIndex===this.pool.workers.length-1?0:this.currentWorkerIndex+1,this.setWorkerTaskRunTime(this.pool.workers[this.currentWorkerIndex],t,0)),e}initWorkersTaskRunTime(){for(const e of this.pool.workers)this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.getWorkerAverageTasksRunTime(e)}computeWorkerWeight(){let e=0;for(const r of s.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s.cpus().length)}}function m(e,r=k.ROUND_ROBIN){switch(r){case k.ROUND_ROBIN:return new p(e);case k.LESS_RECENTLY_USED:return new l(e);case k.FAIR_SHARE:return new u(e);case k.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class T extends c{createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=m(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class W{pool;createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=k.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(r=k.ROUND_ROBIN){return this.pool.type===e.DYNAMIC?new T(this.pool,this.createDynamicallyWorkerCallback,r):m(this.pool,r)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class w{numberOfWorkers;filePath;opts;workers=[];workersTasksUsage=new Map;emitter;promiseMap=new Map;nextMessageId=0;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new h),this.workerChoiceStrategyContext=new W(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=a.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||0===e.length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??k.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerIndex(e){return this.workers.indexOf(e)}getWorkerRunningTasks(e){return this.workersTasksUsage.get(e)?.running}getWorkerAverageTasksRunTime(e){return this.workersTasksUsage.get(e)?.avgRunTime}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const e of this.workers)this.resetWorkerTasksUsage(e);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}async execute(e){const r=this.chooseWorker(),t=this.internalExecute(r,this.nextMessageId);return this.checkAndEmitBusy(),this.sendToWorker(r,{data:e??{},id:this.nextMessageId}),++this.nextMessageId,t}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e)})))}setupHook(){}beforePromiseWorkerResponseHook(e){this.increaseWorkerRunningTasks(e)}afterPromiseWorkerResponseHook(e,r){this.decreaseWorkerRunningTasks(r.worker),this.stepWorkerRunTasks(r.worker,1),this.updateWorkerTasksRunTime(r.worker,e.taskRunTime)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.removeWorkerTasksUsage(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??n),e.on("error",this.opts.errorHandler??n),e.on("online",this.opts.onlineHandler??n),e.on("exit",this.opts.exitHandler??n),e.once("exit",(()=>{this.removeWorker(e)})),this.workers.push(e),this.initWorkerTasksUsage(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseWorkerResponseHook(e,r),this.promiseMap.delete(e.id))}}}async internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),await new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.running=t.running+r,this.workersTasksUsage.set(e,t)}}stepWorkerRunTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.run=t.run+r,this.workersTasksUsage.set(e,t)}}updateWorkerTasksRunTime(e,r){if(this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime&&this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}checkWorkerTasksUsage(e){const r=this.workersTasksUsage.has(e);if(!r)throw new Error("Worker could not be found in workers tasks usage map");return r}initWorkerTasksUsage(e){this.workersTasksUsage.set(e,{run:0,running:0,runTime:0,avgRunTime:0})}removeWorkerTasksUsage(e){this.workersTasksUsage.delete(e)}resetWorkerTasksUsage(e){this.removeWorkerTasksUsage(e),this.initWorkerTasksUsage(e)}}class d extends w{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}class y extends w{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}const f=6e4,R=a.SOFT;class x extends i.AsyncResource{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:R,maxInactiveTime:f}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),r||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??f)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??f,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??f)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(n)}}exports.ClusterWorker=class extends x{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends d{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends y{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=d,exports.FixedThreadPool=y,exports.KillBehaviors=a,exports.ThreadWorker=class extends x{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k;
1
+ "use strict";var e,r=require("node:cluster"),t=require("node:crypto"),s=require("node:events"),o=require("node:os"),i=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));const a=Object.freeze((()=>{})),h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});function k(e,r){return r===e}class u extends s{}const c=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class l{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC}}class p extends l{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workers.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.workers[e].tasksUsage.avgRunTime??0)})}}class m extends l{requiredStatistics={runTime:!0};reset(){return!0}choose(){let e,r=1/0;for(const[t,s]of this.pool.workers.entries()){const o=s.tasksUsage.runTime;if(!this.isDynamicPool&&0===o)return t;o<r&&(r=o,e=t)}return e}remove(e){return!0}}class d extends l{reset(){return!0}choose(){let e,r=1/0;for(const[t,s]of this.pool.workers.entries()){const o=s.tasksUsage,i=o?.run+o?.running;if(!this.isDynamicPool&&0===i)return t;i<r&&(r=i,e=t)}return e}remove(e){return!0}}class w extends l{nextWorkerId=0;reset(){return this.nextWorkerId=0,!0}choose(){const e=this.nextWorkerId;return this.nextWorkerId=this.nextWorkerId===this.pool.workers.length-1?0:this.nextWorkerId+1,e}remove(e){return this.nextWorkerId===e&&(this.nextWorkerId=this.nextWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.nextWorkerId),!0}}class g extends l{requiredStatistics={runTime:!0};currentWorkerId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerId=this.currentWorkerId===this.pool.workers.length-1?0:this.currentWorkerId+1,this.setWorkerTaskRunTime(this.currentWorkerId,t,0)),e}remove(e){this.currentWorkerId===e&&(this.currentWorkerId=this.currentWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.currentWorkerId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workers.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.workers[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of o.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/o.cpus().length)}}function W(e,r=c.ROUND_ROBIN){switch(r){case c.ROUND_ROBIN:return new w(e);case c.LESS_USED:return new d(e);case c.LESS_BUSY:return new m(e);case c.FAIR_SHARE:return new p(e);case c.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class T extends l{createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=c.ROUND_ROBIN){super(e),this.createWorkerCallback=r,this.workerChoiceStrategy=W(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorkerKey();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createWorkerCallback()}remove(e){return this.workerChoiceStrategy.remove(e)}}class y{pool;createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=c.ROUND_ROBIN){this.pool=e,this.createWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(r=c.ROUND_ROBIN){return this.pool.type===e.DYNAMIC?new T(this.pool,this.createWorkerCallback,r):W(this.pool,r)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}getRequiredStatistics(){return this.workerChoiceStrategy.requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}remove(e){return this.workerChoiceStrategy.remove(e)}}class f{numberOfWorkers;filePath;opts;workers=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new y(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{(k(h.HARD,r.kill)||0===this.getWorkerTasksUsage(e)?.running)&&this.destroyWorker(e)})),this.getWorkerKey(e)}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??c.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseResponseMap.size}getWorkerKey(e){return this.workers.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const[e,r]of this.workers.entries())this.setWorker(e,r.worker,{run:0,running:0,runTime:0,avgRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorkerKey()}findFreeWorkerKey(){const e=this.workers.findIndex((e=>0===e.tasksUsage.running));return-1!==e&&e}async execute(e){const[r,s]=this.chooseWorker(),o=t.randomUUID(),i=this.internalExecute(r,s,o);return this.checkAndEmitBusy(),this.sendToWorker(s,{data:e??{},id:o}),i}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e.worker)})))}setupHook(){}beforePromiseResponseHook(e){++this.workers[e].tasksUsage.running}afterPromiseResponseHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.taskRunTime??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run))}removeWorker(e){const r=this.getWorkerKey(e);this.workers.splice(r,1),this.workerChoiceStrategyContext.remove(r)}chooseWorker(){const e=this.workerChoiceStrategyContext.execute();return[e,this.workers[e].worker]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??a),e.on("error",this.opts.errorHandler??a),e.on("online",this.opts.onlineHandler??a),e.on("exit",this.opts.exitHandler??a),e.once("exit",(()=>{this.removeWorker(e)})),this.pushWorker(e,{run:0,running:0,runTime:0,avgRunTime:0,error:0}),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseResponseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseResponseHook(r.worker,e),this.promiseResponseMap.delete(e.id))}}}async internalExecute(e,r,t){return this.beforePromiseResponseHook(e),await new Promise(((e,s)=>{this.promiseResponseMap.set(t,{resolve:e,reject:s,worker:r})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}getWorkerTasksUsage(e){const r=this.getWorkerKey(e);if(-1!==r)return this.workers[r].tasksUsage;throw new Error("Worker could not be found in the pool")}pushWorker(e,r){this.workers.push({worker:e,tasksUsage:r})}setWorker(e,r,t){this.workers[e]={worker:r,tasksUsage:t}}}class R extends f{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}class S extends f{constructor(e,r,t={}){super(e,r,t)}isMain(){return i.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new i.Worker(this.filePath,{env:i.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new i.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}const I=6e4,x=h.SOFT;class v extends n.AsyncResource{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:x,maxInactiveTime:I}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),!r&&k(h.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??I)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??x,this.opts.maxInactiveTime=e.maxInactiveTime??I,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??I)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{k(h.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now())}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{k(h.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now())})).catch(a)}}exports.ClusterWorker=class extends v{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends R{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=R,exports.FixedThreadPool=S,exports.KillBehaviors=h,exports.ThreadWorker=class extends v{constructor(e,r={}){super("worker-thread-pool:poolifier",i.isMainThread,e,i.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=c;
package/lib/index.mjs CHANGED
@@ -1 +1 @@
1
- import e from"cluster";import r from"events";import{cpus as t}from"os";import{isMainThread as s,Worker as o,SHARE_ENV as i,MessageChannel as n,parentPort as a}from"worker_threads";import{AsyncResource as h}from"async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));const c=Object.freeze((()=>{})),u=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class l extends r{}const p=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class m{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===k.DYNAMIC}}class g extends m{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const t of this.pool.workers){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.getWorkerAverageTasksRunTime(e)??0)})}}class T extends m{reset(){return!0}choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class W extends m{nextWorkerIndex=0;reset(){return this.nextWorkerIndex=0,!0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class w extends m{requiredStatistics={runTime:!0};currentWorkerIndex=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerIndex=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.pool.workers[this.currentWorkerIndex];this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerIndex=this.currentWorkerIndex===this.pool.workers.length-1?0:this.currentWorkerIndex+1,this.setWorkerTaskRunTime(this.pool.workers[this.currentWorkerIndex],t,0)),e}initWorkersTaskRunTime(){for(const e of this.pool.workers)this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.getWorkerAverageTasksRunTime(e)}computeWorkerWeight(){let e=0;for(const r of t()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/t().length)}}function d(e,r=p.ROUND_ROBIN){switch(r){case p.ROUND_ROBIN:return new W(e);case p.LESS_RECENTLY_USED:return new T(e);case p.FAIR_SHARE:return new g(e);case p.WEIGHTED_ROUND_ROBIN:return new w(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class y extends m{createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=p.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=d(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class f{pool;createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=p.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(e=p.ROUND_ROBIN){return this.pool.type===k.DYNAMIC?new y(this.pool,this.createDynamicallyWorkerCallback,e):d(this.pool,e)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class R{numberOfWorkers;filePath;opts;workers=[];workersTasksUsage=new Map;emitter;promiseMap=new Map;nextMessageId=0;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new l),this.workerChoiceStrategyContext=new f(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=u.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||0===e.length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??p.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerIndex(e){return this.workers.indexOf(e)}getWorkerRunningTasks(e){return this.workersTasksUsage.get(e)?.running}getWorkerAverageTasksRunTime(e){return this.workersTasksUsage.get(e)?.avgRunTime}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const e of this.workers)this.resetWorkerTasksUsage(e);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}async execute(e){const r=this.chooseWorker(),t=this.internalExecute(r,this.nextMessageId);return this.checkAndEmitBusy(),this.sendToWorker(r,{data:e??{},id:this.nextMessageId}),++this.nextMessageId,t}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e)})))}setupHook(){}beforePromiseWorkerResponseHook(e){this.increaseWorkerRunningTasks(e)}afterPromiseWorkerResponseHook(e,r){this.decreaseWorkerRunningTasks(r.worker),this.stepWorkerRunTasks(r.worker,1),this.updateWorkerTasksRunTime(r.worker,e.taskRunTime)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.removeWorkerTasksUsage(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorker(e)})),this.workers.push(e),this.initWorkerTasksUsage(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseWorkerResponseHook(e,r),this.promiseMap.delete(e.id))}}}async internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),await new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.running=t.running+r,this.workersTasksUsage.set(e,t)}}stepWorkerRunTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.run=t.run+r,this.workersTasksUsage.set(e,t)}}updateWorkerTasksRunTime(e,r){if(this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime&&this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}checkWorkerTasksUsage(e){const r=this.workersTasksUsage.has(e);if(!r)throw new Error("Worker could not be found in workers tasks usage map");return r}initWorkerTasksUsage(e){this.workersTasksUsage.set(e,{run:0,running:0,runTime:0,avgRunTime:0})}removeWorkerTasksUsage(e){this.workersTasksUsage.delete(e)}resetWorkerTasksUsage(e){this.removeWorkerTasksUsage(e),this.initWorkerTasksUsage(e)}}class x extends R{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){e.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return e.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return e.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get busy(){return this.internalGetBusyStatus()}}class I extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get busy(){return this.workers.length===this.max}}class S extends R{constructor(e,r,t={}){super(e,r,t)}isMain(){return s}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o(this.filePath,{env:i})}afterWorkerSetup(e){const{port1:r,port2:t}=new n;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get busy(){return this.internalGetBusyStatus()}}class v extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get busy(){return this.workers.length===this.max}}const C=6e4,D=u.SOFT;class E extends h{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:D,maxInactiveTime:C}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),r||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??C)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??D,this.opts.maxInactiveTime=e.maxInactiveTime??C,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??C)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(c)}}class M extends E{constructor(r,t={}){super("worker-cluster-pool:poolifier",e.isPrimary,r,e.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class O extends E{constructor(e,r={}){super("worker-thread-pool:poolifier",s,e,a,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{M as ClusterWorker,I as DynamicClusterPool,v as DynamicThreadPool,x as FixedClusterPool,S as FixedThreadPool,u as KillBehaviors,O as ThreadWorker,p as WorkerChoiceStrategies};
1
+ import e from"node:cluster";import r from"node:crypto";import t from"node:events";import{cpus as s}from"node:os";import{isMainThread as o,Worker as i,SHARE_ENV as n,MessageChannel as a,parentPort as h}from"node:worker_threads";import{AsyncResource as k}from"node:async_hooks";var c;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(c||(c={}));const u=Object.freeze((()=>{})),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});function m(e,r){return r===e}class p extends t{}const w=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class d{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===c.DYNAMIC}}class g extends d{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workers.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.workers[e].tasksUsage.avgRunTime??0)})}}class W extends d{requiredStatistics={runTime:!0};reset(){return!0}choose(){let e,r=1/0;for(const[t,s]of this.pool.workers.entries()){const o=s.tasksUsage.runTime;if(!this.isDynamicPool&&0===o)return t;o<r&&(r=o,e=t)}return e}remove(e){return!0}}class T extends d{reset(){return!0}choose(){let e,r=1/0;for(const[t,s]of this.pool.workers.entries()){const o=s.tasksUsage,i=o?.run+o?.running;if(!this.isDynamicPool&&0===i)return t;i<r&&(r=i,e=t)}return e}remove(e){return!0}}class f extends d{nextWorkerId=0;reset(){return this.nextWorkerId=0,!0}choose(){const e=this.nextWorkerId;return this.nextWorkerId=this.nextWorkerId===this.pool.workers.length-1?0:this.nextWorkerId+1,e}remove(e){return this.nextWorkerId===e&&(this.nextWorkerId=this.nextWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.nextWorkerId),!0}}class y extends d{requiredStatistics={runTime:!0};currentWorkerId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerId=this.currentWorkerId===this.pool.workers.length-1?0:this.currentWorkerId+1,this.setWorkerTaskRunTime(this.currentWorkerId,t,0)),e}remove(e){this.currentWorkerId===e&&(this.currentWorkerId=this.currentWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.currentWorkerId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workers.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.workers[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of s()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s().length)}}function R(e,r=w.ROUND_ROBIN){switch(r){case w.ROUND_ROBIN:return new f(e);case w.LESS_USED:return new T(e);case w.LESS_BUSY:return new W(e);case w.FAIR_SHARE:return new g(e);case w.WEIGHTED_ROUND_ROBIN:return new y(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class S extends d{createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=w.ROUND_ROBIN){super(e),this.createWorkerCallback=r,this.workerChoiceStrategy=R(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorkerKey();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createWorkerCallback()}remove(e){return this.workerChoiceStrategy.remove(e)}}class I{pool;createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=w.ROUND_ROBIN){this.pool=e,this.createWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(e=w.ROUND_ROBIN){return this.pool.type===c.DYNAMIC?new S(this.pool,this.createWorkerCallback,e):R(this.pool,e)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}getRequiredStatistics(){return this.workerChoiceStrategy.requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}remove(e){return this.workerChoiceStrategy.remove(e)}}class v{numberOfWorkers;filePath;opts;workers=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new p),this.workerChoiceStrategyContext=new I(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{(m(l.HARD,r.kill)||0===this.getWorkerTasksUsage(e)?.running)&&this.destroyWorker(e)})),this.getWorkerKey(e)}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===c.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??w.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseResponseMap.size}getWorkerKey(e){return this.workers.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const[e,r]of this.workers.entries())this.setWorker(e,r.worker,{run:0,running:0,runTime:0,avgRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorkerKey()}findFreeWorkerKey(){const e=this.workers.findIndex((e=>0===e.tasksUsage.running));return-1!==e&&e}async execute(e){const[t,s]=this.chooseWorker(),o=r.randomUUID(),i=this.internalExecute(t,s,o);return this.checkAndEmitBusy(),this.sendToWorker(s,{data:e??{},id:o}),i}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e.worker)})))}setupHook(){}beforePromiseResponseHook(e){++this.workers[e].tasksUsage.running}afterPromiseResponseHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.taskRunTime??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run))}removeWorker(e){const r=this.getWorkerKey(e);this.workers.splice(r,1),this.workerChoiceStrategyContext.remove(r)}chooseWorker(){const e=this.workerChoiceStrategyContext.execute();return[e,this.workers[e].worker]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorker(e)})),this.pushWorker(e,{run:0,running:0,runTime:0,avgRunTime:0,error:0}),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseResponseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseResponseHook(r.worker,e),this.promiseResponseMap.delete(e.id))}}}async internalExecute(e,r,t){return this.beforePromiseResponseHook(e),await new Promise(((e,s)=>{this.promiseResponseMap.set(t,{resolve:e,reject:s,worker:r})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}getWorkerTasksUsage(e){const r=this.getWorkerKey(e);if(-1!==r)return this.workers[r].tasksUsage;throw new Error("Worker could not be found in the pool")}pushWorker(e,r){this.workers.push({worker:e,tasksUsage:r})}setWorker(e,r,t){this.workers[e]={worker:r,tasksUsage:t}}}class x extends v{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){e.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return e.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return e.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.FIXED}get busy(){return this.internalGetBusyStatus()}}class C extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return c.DYNAMIC}get busy(){return this.workers.length===this.max}}class D extends v{constructor(e,r,t={}){super(e,r,t)}isMain(){return o}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new i(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:r,port2:t}=new a;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.FIXED}get busy(){return this.internalGetBusyStatus()}}class E extends D{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return c.DYNAMIC}get busy(){return this.workers.length===this.max}}const O=6e4,b=l.SOFT;class M extends k{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:b,maxInactiveTime:O}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),!r&&m(l.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??O)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??b,this.opts.maxInactiveTime=e.maxInactiveTime??O,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??O)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{m(l.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now())}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{m(l.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now())})).catch(u)}}class U extends M{constructor(r,t={}){super("worker-cluster-pool:poolifier",e.isPrimary,r,e.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class N extends M{constructor(e,r={}){super("worker-thread-pool:poolifier",o,e,h,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{U as ClusterWorker,C as DynamicClusterPool,E as DynamicThreadPool,x as FixedClusterPool,D as FixedThreadPool,l as KillBehaviors,N as ThreadWorker,w as WorkerChoiceStrategies};
@@ -1,7 +1,7 @@
1
- import type { MessageValue, PromiseWorkerResponseWrapper } from '../utility-types';
1
+ import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
2
2
  import type { PoolOptions } from './pool';
3
3
  import { PoolEmitter } from './pool';
4
- import type { IPoolInternal, TasksUsage } from './pool-internal';
4
+ import type { IPoolInternal, WorkerType } from './pool-internal';
5
5
  import { PoolType } from './pool-internal';
6
6
  import type { IPoolWorker } from './pool-worker';
7
7
  import { type WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
@@ -18,24 +18,18 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
18
18
  readonly filePath: string;
19
19
  readonly opts: PoolOptions<Worker>;
20
20
  /** {@inheritDoc} */
21
- readonly workers: Worker[];
22
- /** {@inheritDoc} */
23
- readonly workersTasksUsage: Map<Worker, TasksUsage>;
21
+ readonly workers: Array<WorkerType<Worker>>;
24
22
  /** {@inheritDoc} */
25
23
  readonly emitter?: PoolEmitter;
26
24
  /**
27
- * The promise map.
25
+ * The promise response map.
28
26
  *
29
- * - `key`: This is the message Id of each submitted task.
30
- * - `value`: An object that contains the worker, the resolve function and the reject function.
27
+ * - `key`: The message id of each submitted task.
28
+ * - `value`: An object that contains the worker, the promise resolve and reject callbacks.
31
29
  *
32
- * When we receive a message from the worker we get a map entry and resolve/reject the promise based on the message.
33
- */
34
- protected promiseMap: Map<number, PromiseWorkerResponseWrapper<Worker, Response>>;
35
- /**
36
- * Id of the next message.
30
+ * When we receive a message from the worker we get a map entry with the promise resolve/reject bound to the message.
37
31
  */
38
- protected nextMessageId: number;
32
+ protected promiseResponseMap: Map<string, PromiseResponseWrapper<Worker, Response>>;
39
33
  /**
40
34
  * Worker choice strategy instance implementing the worker choice algorithm.
41
35
  *
@@ -57,19 +51,20 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
57
51
  abstract get type(): PoolType;
58
52
  /** {@inheritDoc} */
59
53
  get numberOfRunningTasks(): number;
60
- /** {@inheritDoc} */
61
- getWorkerIndex(worker: Worker): number;
62
- /** {@inheritDoc} */
63
- getWorkerRunningTasks(worker: Worker): number | undefined;
64
- /** {@inheritDoc} */
65
- getWorkerAverageTasksRunTime(worker: Worker): number | undefined;
54
+ /**
55
+ * Gets the given worker key.
56
+ *
57
+ * @param worker - The worker.
58
+ * @returns The worker key.
59
+ */
60
+ private getWorkerKey;
66
61
  /** {@inheritDoc} */
67
62
  setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
68
63
  /** {@inheritDoc} */
69
64
  abstract get busy(): boolean;
70
65
  protected internalGetBusyStatus(): boolean;
71
66
  /** {@inheritDoc} */
72
- findFreeWorker(): Worker | false;
67
+ findFreeWorkerKey(): number | false;
73
68
  /** {@inheritDoc} */
74
69
  execute(data: Data): Promise<Response>;
75
70
  /** {@inheritDoc} */
@@ -93,17 +88,17 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
93
88
  * Hook executed before the worker task promise resolution.
94
89
  * Can be overridden.
95
90
  *
96
- * @param worker - The worker.
91
+ * @param workerKey - The worker key.
97
92
  */
98
- protected beforePromiseWorkerResponseHook(worker: Worker): void;
93
+ protected beforePromiseResponseHook(workerKey: number): void;
99
94
  /**
100
95
  * Hook executed after the worker task promise resolution.
101
96
  * Can be overridden.
102
97
  *
98
+ * @param worker - The worker.
103
99
  * @param message - The received message.
104
- * @param promise - The Promise response.
105
100
  */
106
- protected afterPromiseWorkerResponseHook(message: MessageValue<Response>, promise: PromiseWorkerResponseWrapper<Worker, Response>): void;
101
+ protected afterPromiseResponseHook(worker: Worker, message: MessageValue<Response>): void;
107
102
  /**
108
103
  * Removes the given worker from the pool.
109
104
  *
@@ -115,9 +110,9 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
115
110
  *
116
111
  * The default implementation uses a round robin algorithm to distribute the load.
117
112
  *
118
- * @returns Worker.
113
+ * @returns [worker key, worker].
119
114
  */
120
- protected chooseWorker(): Worker;
115
+ protected chooseWorker(): [number, Worker];
121
116
  /**
122
117
  * Sends a message to the given worker.
123
118
  *
@@ -159,61 +154,25 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
159
154
  private internalExecute;
160
155
  private checkAndEmitBusy;
161
156
  /**
162
- * Increases the number of tasks that the given worker has applied.
163
- *
164
- * @param worker - Worker which running tasks is increased.
165
- */
166
- private increaseWorkerRunningTasks;
167
- /**
168
- * Decreases the number of tasks that the given worker has applied.
169
- *
170
- * @param worker - Worker which running tasks is decreased.
171
- */
172
- private decreaseWorkerRunningTasks;
173
- /**
174
- * Steps the number of tasks that the given worker has applied.
175
- *
176
- * @param worker - Worker which running tasks are stepped.
177
- * @param step - Number of running tasks step.
178
- */
179
- private stepWorkerRunningTasks;
180
- /**
181
- * Steps the number of tasks that the given worker has run.
182
- *
183
- * @param worker - Worker which has run tasks.
184
- * @param step - Number of run tasks step.
185
- */
186
- private stepWorkerRunTasks;
187
- /**
188
- * Updates tasks runtime for the given worker.
189
- *
190
- * @param worker - Worker which run the task.
191
- * @param taskRunTime - Worker task runtime.
192
- */
193
- private updateWorkerTasksRunTime;
194
- /**
195
- * Checks if the given worker is registered in the workers tasks usage map.
196
- *
197
- * @param worker - Worker to check.
198
- * @returns `true` if the worker is registered in the workers tasks usage map. `false` otherwise.
199
- */
200
- private checkWorkerTasksUsage;
201
- /**
202
- * Initializes tasks usage statistics.
157
+ * Gets worker tasks usage.
203
158
  *
204
159
  * @param worker - The worker.
160
+ * @returns The worker tasks usage.
205
161
  */
206
- private initWorkerTasksUsage;
162
+ private getWorkerTasksUsage;
207
163
  /**
208
- * Removes worker tasks usage statistics.
164
+ * Pushes the given worker.
209
165
  *
210
166
  * @param worker - The worker.
167
+ * @param tasksUsage - The worker tasks usage.
211
168
  */
212
- private removeWorkerTasksUsage;
169
+ private pushWorker;
213
170
  /**
214
- * Resets worker tasks usage statistics.
171
+ * Sets the given worker.
215
172
  *
173
+ * @param workerKey - The worker key.
216
174
  * @param worker - The worker.
175
+ * @param tasksUsage - The worker tasks usage.
217
176
  */
218
- private resetWorkerTasksUsage;
177
+ private setWorker;
219
178
  }
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { ClusterSettings, Worker } from 'cluster';
2
+ import type { ClusterSettings, Worker } from 'node:cluster';
3
3
  import type { MessageValue } from '../../utility-types';
4
4
  import { AbstractPool } from '../abstract-pool';
5
5
  import type { PoolOptions } from '../pool';
@@ -15,6 +15,16 @@ export interface TasksUsage {
15
15
  running: number;
16
16
  runTime: number;
17
17
  avgRunTime: number;
18
+ error: number;
19
+ }
20
+ /**
21
+ * Internal worker type.
22
+ *
23
+ * @typeParam Worker - Type of worker which manages this pool.
24
+ */
25
+ export interface WorkerType<Worker extends IPoolWorker> {
26
+ worker: Worker;
27
+ tasksUsage: TasksUsage;
18
28
  }
19
29
  /**
20
30
  * Internal contract definition for a poolifier pool.
@@ -25,16 +35,9 @@ export interface TasksUsage {
25
35
  */
26
36
  export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends IPool<Data, Response> {
27
37
  /**
28
- * List of currently available workers.
38
+ * Pool workers item array.
29
39
  */
30
- readonly workers: Worker[];
31
- /**
32
- * The workers tasks usage map.
33
- *
34
- * `key`: The `Worker`
35
- * `value`: Worker tasks usage statistics.
36
- */
37
- readonly workersTasksUsage: Map<Worker, TasksUsage>;
40
+ readonly workers: Array<WorkerType<Worker>>;
38
41
  /**
39
42
  * Pool type.
40
43
  *
@@ -52,34 +55,13 @@ export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Respo
52
55
  */
53
56
  readonly numberOfRunningTasks: number;
54
57
  /**
55
- * Finds a free worker based on the number of tasks the worker has applied.
58
+ * Finds a free worker key based on the number of tasks the worker has applied.
56
59
  *
57
- * If a worker is found with `0` running tasks, it is detected as free and returned.
60
+ * If a worker is found with `0` running tasks, it is detected as free and its key is returned.
58
61
  *
59
62
  * If no free worker is found, `false` is returned.
60
63
  *
61
- * @returns A free worker if there is one, otherwise `false`.
62
- */
63
- findFreeWorker: () => Worker | false;
64
- /**
65
- * Gets worker index.
66
- *
67
- * @param worker - The worker.
68
- * @returns The worker index.
69
- */
70
- getWorkerIndex: (worker: Worker) => number;
71
- /**
72
- * Gets worker running tasks.
73
- *
74
- * @param worker - The worker.
75
- * @returns The number of tasks currently running on the worker.
76
- */
77
- getWorkerRunningTasks: (worker: Worker) => number | undefined;
78
- /**
79
- * Gets worker average tasks runtime.
80
- *
81
- * @param worker - The worker.
82
- * @returns The average tasks runtime on the worker.
64
+ * @returns A worker key if there is one, otherwise `false`.
83
65
  */
84
- getWorkerAverageTasksRunTime: (worker: Worker) => number | undefined;
66
+ findFreeWorkerKey: () => number | false;
85
67
  }
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import EventEmitter from 'events';
2
+ import EventEmitter from 'node:events';
3
3
  import type { ErrorHandler, ExitHandler, MessageHandler, OnlineHandler } from './pool-worker';
4
4
  import type { WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
5
5
  /**
@@ -8,7 +8,7 @@ import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-stra
8
8
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
9
9
  * @typeParam Response - Type of response of execution. This can only be serializable data.
10
10
  */
11
- export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> implements IWorkerChoiceStrategy<Worker> {
11
+ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> implements IWorkerChoiceStrategy {
12
12
  protected readonly pool: IPoolInternal<Worker, Data, Response>;
13
13
  /** {@inheritDoc} */
14
14
  readonly isDynamicPool: boolean;
@@ -23,5 +23,7 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolW
23
23
  /** {@inheritDoc} */
24
24
  abstract reset(): boolean;
25
25
  /** {@inheritDoc} */
26
- abstract choose(): Worker;
26
+ abstract choose(): number;
27
+ /** {@inheritDoc} */
28
+ abstract remove(workerKey: number): boolean;
27
29
  }
@@ -10,18 +10,20 @@ import type { WorkerChoiceStrategy } from './selection-strategies-types';
10
10
  * @typeParam Response - Type of response of execution. This can only be serializable data.
11
11
  */
12
12
  export declare class DynamicPoolWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
13
- private readonly createDynamicallyWorkerCallback;
13
+ private readonly createWorkerCallback;
14
14
  private readonly workerChoiceStrategy;
15
15
  /**
16
16
  * Constructs a worker choice strategy for dynamic pool.
17
17
  *
18
18
  * @param pool - The pool instance.
19
- * @param createDynamicallyWorkerCallback - The worker creation callback for dynamic pool.
20
- * @param workerChoiceStrategy - The worker choice strategy when the pull is busy.
19
+ * @param createWorkerCallback - The worker creation callback for dynamic pool.
20
+ * @param workerChoiceStrategy - The worker choice strategy when the pool is busy.
21
21
  */
22
- constructor(pool: IPoolInternal<Worker, Data, Response>, createDynamicallyWorkerCallback: () => Worker, workerChoiceStrategy?: WorkerChoiceStrategy);
22
+ constructor(pool: IPoolInternal<Worker, Data, Response>, createWorkerCallback: () => number, workerChoiceStrategy?: WorkerChoiceStrategy);
23
23
  /** {@inheritDoc} */
24
24
  reset(): boolean;
25
25
  /** {@inheritDoc} */
26
- choose(): Worker;
26
+ choose(): number;
27
+ /** {@inheritDoc} */
28
+ remove(workerKey: number): boolean;
27
29
  }
@@ -19,11 +19,13 @@ export declare class FairShareWorkerChoiceStrategy<Worker extends IPoolWorker, D
19
19
  /** {@inheritDoc} */
20
20
  reset(): boolean;
21
21
  /** {@inheritDoc} */
22
- choose(): Worker;
22
+ choose(): number;
23
+ /** {@inheritDoc} */
24
+ remove(workerKey: number): boolean;
23
25
  /**
24
26
  * Computes worker last virtual task timestamp.
25
27
  *
26
- * @param worker - The worker.
28
+ * @param workerKey - The worker key.
27
29
  */
28
30
  private computeWorkerLastVirtualTaskTimestamp;
29
31
  }
@@ -0,0 +1,20 @@
1
+ import type { IPoolWorker } from '../pool-worker';
2
+ import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
+ import type { RequiredStatistics } from './selection-strategies-types';
4
+ /**
5
+ * Selects the less busy worker.
6
+ *
7
+ * @typeParam Worker - Type of worker which manages the strategy.
8
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
9
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
10
+ */
11
+ export declare class LessBusyWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
12
+ /** {@inheritDoc} */
13
+ readonly requiredStatistics: RequiredStatistics;
14
+ /** {@inheritDoc} */
15
+ reset(): boolean;
16
+ /** {@inheritDoc} */
17
+ choose(): number;
18
+ /** {@inheritDoc} */
19
+ remove(workerKey: number): boolean;
20
+ }
@@ -1,15 +1,17 @@
1
1
  import type { IPoolWorker } from '../pool-worker';
2
2
  import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
3
  /**
4
- * Selects the less recently used worker.
4
+ * Selects the less used worker.
5
5
  *
6
6
  * @typeParam Worker - Type of worker which manages the strategy.
7
7
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
8
8
  * @typeParam Response - Type of response of execution. This can only be serializable data.
9
9
  */
10
- export declare class LessRecentlyUsedWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
10
+ export declare class LessUsedWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
11
11
  /** {@inheritDoc} */
12
12
  reset(): boolean;
13
13
  /** {@inheritDoc} */
14
- choose(): Worker;
14
+ choose(): number;
15
+ /** {@inheritDoc} */
16
+ remove(workerKey: number): boolean;
15
17
  }
@@ -9,11 +9,13 @@ import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy'
9
9
  */
10
10
  export declare class RoundRobinWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
11
11
  /**
12
- * Index for the next worker.
12
+ * Id of the next worker.
13
13
  */
14
- private nextWorkerIndex;
14
+ private nextWorkerId;
15
15
  /** {@inheritDoc} */
16
16
  reset(): boolean;
17
17
  /** {@inheritDoc} */
18
- choose(): Worker;
18
+ choose(): number;
19
+ /** {@inheritDoc} */
20
+ remove(workerKey: number): boolean;
19
21
  }
@@ -1,4 +1,3 @@
1
- import type { IPoolWorker } from '../pool-worker';
2
1
  /**
3
2
  * Enumeration of worker choice strategies.
4
3
  */
@@ -8,9 +7,13 @@ export declare const WorkerChoiceStrategies: Readonly<{
8
7
  */
9
8
  readonly ROUND_ROBIN: "ROUND_ROBIN";
10
9
  /**
11
- * Less recently used worker selection strategy.
10
+ * Less used worker selection strategy.
12
11
  */
13
- readonly LESS_RECENTLY_USED: "LESS_RECENTLY_USED";
12
+ readonly LESS_USED: "LESS_USED";
13
+ /**
14
+ * Less busy worker selection strategy.
15
+ */
16
+ readonly LESS_BUSY: "LESS_BUSY";
14
17
  /**
15
18
  * Fair share worker selection strategy.
16
19
  */
@@ -32,10 +35,8 @@ export interface RequiredStatistics {
32
35
  }
33
36
  /**
34
37
  * Worker choice strategy interface.
35
- *
36
- * @typeParam Worker - Type of worker which manages the strategy.
37
38
  */
38
- export interface IWorkerChoiceStrategy<Worker extends IPoolWorker> {
39
+ export interface IWorkerChoiceStrategy {
39
40
  /**
40
41
  * Is the pool attached to the strategy dynamic?.
41
42
  */
@@ -49,7 +50,13 @@ export interface IWorkerChoiceStrategy<Worker extends IPoolWorker> {
49
50
  */
50
51
  reset: () => boolean;
51
52
  /**
52
- * Chooses a worker in the pool.
53
+ * Chooses a worker in the pool and returns its key.
54
+ */
55
+ choose: () => number;
56
+ /**
57
+ * Removes a worker reference from strategy internals.
58
+ *
59
+ * @param workerKey - The worker key.
53
60
  */
54
- choose: () => Worker;
61
+ remove: (workerKey: number) => boolean;
55
62
  }
@@ -8,4 +8,4 @@ import type { IWorkerChoiceStrategy, WorkerChoiceStrategy } from './selection-st
8
8
  * @param workerChoiceStrategy - The worker choice strategy.
9
9
  * @returns The worker choice strategy instance.
10
10
  */
11
- export declare function getWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response>(pool: IPoolInternal<Worker, Data, Response>, workerChoiceStrategy?: WorkerChoiceStrategy): IWorkerChoiceStrategy<Worker>;
11
+ export declare function getWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response>(pool: IPoolInternal<Worker, Data, Response>, workerChoiceStrategy?: WorkerChoiceStrategy): IWorkerChoiceStrategy;
@@ -14,9 +14,9 @@ export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IPool
14
14
  /** {@inheritDoc} */
15
15
  readonly requiredStatistics: RequiredStatistics;
16
16
  /**
17
- * Worker index where the current task will be submitted.
17
+ * Worker id where the current task will be submitted.
18
18
  */
19
- private currentWorkerIndex;
19
+ private currentWorkerId;
20
20
  /**
21
21
  * Default worker weight.
22
22
  */
@@ -34,7 +34,9 @@ export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IPool
34
34
  /** {@inheritDoc} */
35
35
  reset(): boolean;
36
36
  /** {@inheritDoc} */
37
- choose(): Worker;
37
+ choose(): number;
38
+ /** {@inheritDoc} */
39
+ remove(workerKey: number): boolean;
38
40
  private initWorkersTaskRunTime;
39
41
  private initWorkerTaskRunTime;
40
42
  private setWorkerTaskRunTime;
@@ -1,6 +1,6 @@
1
1
  import type { IPoolInternal } from '../pool-internal';
2
2
  import type { IPoolWorker } from '../pool-worker';
3
- import type { IWorkerChoiceStrategy, WorkerChoiceStrategy } from './selection-strategies-types';
3
+ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategy } from './selection-strategies-types';
4
4
  /**
5
5
  * The worker choice strategy context.
6
6
  *
@@ -10,16 +10,16 @@ import type { IWorkerChoiceStrategy, WorkerChoiceStrategy } from './selection-st
10
10
  */
11
11
  export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Data, Response> {
12
12
  private readonly pool;
13
- private readonly createDynamicallyWorkerCallback;
13
+ private readonly createWorkerCallback;
14
14
  private workerChoiceStrategy;
15
15
  /**
16
16
  * Worker choice strategy context constructor.
17
17
  *
18
18
  * @param pool - The pool instance.
19
- * @param createDynamicallyWorkerCallback - The worker creation callback for dynamic pool.
19
+ * @param createWorkerCallback - The worker creation callback for dynamic pool.
20
20
  * @param workerChoiceStrategy - The worker choice strategy.
21
21
  */
22
- constructor(pool: IPoolInternal<Worker, Data, Response>, createDynamicallyWorkerCallback: () => Worker, workerChoiceStrategy?: WorkerChoiceStrategy);
22
+ constructor(pool: IPoolInternal<Worker, Data, Response>, createWorkerCallback: () => number, workerChoiceStrategy?: WorkerChoiceStrategy);
23
23
  /**
24
24
  * Gets the worker choice strategy instance specific to the pool type.
25
25
  *
@@ -31,8 +31,15 @@ export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Dat
31
31
  * Gets the worker choice strategy used in the context.
32
32
  *
33
33
  * @returns The worker choice strategy.
34
+ * @deprecated Scheduled removal.
34
35
  */
35
- getWorkerChoiceStrategy(): IWorkerChoiceStrategy<Worker>;
36
+ getWorkerChoiceStrategy(): IWorkerChoiceStrategy;
37
+ /**
38
+ * Gets the worker choice strategy required statistics.
39
+ *
40
+ * @returns The required statistics.
41
+ */
42
+ getRequiredStatistics(): RequiredStatistics;
36
43
  /**
37
44
  * Sets the worker choice strategy to use in the context.
38
45
  *
@@ -42,7 +49,14 @@ export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Dat
42
49
  /**
43
50
  * Chooses a worker with the underlying selection strategy.
44
51
  *
45
- * @returns The chosen one.
52
+ * @returns The key of the chosen one.
53
+ */
54
+ execute(): number;
55
+ /**
56
+ * Removes a worker in the underlying selection strategy internals.
57
+ *
58
+ * @param workerKey - The key of the worker to remove.
59
+ * @returns `true` if the removal is successful, `false` otherwise.
46
60
  */
47
- execute(): Worker;
61
+ remove(workerKey: number): boolean;
48
62
  }
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { MessageChannel, Worker } from 'worker_threads';
2
+ import { MessageChannel, Worker } from 'node:worker_threads';
3
3
  import type { Draft, MessageValue } from '../../utility-types';
4
4
  import { AbstractPool } from '../abstract-pool';
5
5
  import type { PoolOptions } from '../pool';
@@ -1,9 +1,9 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- import type { Worker as ClusterWorker } from 'cluster';
4
- import type { MessagePort } from 'worker_threads';
5
- import type { IPoolWorker } from './pools/pool-worker';
3
+ import type { Worker as ClusterWorker } from 'node:cluster';
4
+ import type { MessagePort } from 'node:worker_threads';
6
5
  import type { KillBehavior } from './worker/worker-options';
6
+ import type { IPoolWorker } from './pools/pool-worker';
7
7
  /**
8
8
  * Make all properties in T non-readonly.
9
9
  */
@@ -21,7 +21,7 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
21
21
  /**
22
22
  * Id of the message.
23
23
  */
24
- readonly id?: number;
24
+ readonly id?: string;
25
25
  /**
26
26
  * Kill code.
27
27
  */
@@ -42,12 +42,12 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
42
42
  readonly parent?: MainWorker;
43
43
  }
44
44
  /**
45
- * An object holding the worker that will be used to resolve/rejects the promise later on.
45
+ * An object holding the execution response promise resolve/reject callbacks.
46
46
  *
47
47
  * @typeParam Worker - Type of worker.
48
- * @typeParam Response - Type of response of execution. This can only be serializable data.
48
+ * @typeParam Response - Type of execution response. This can only be serializable data.
49
49
  */
50
- export interface PromiseWorkerResponseWrapper<Worker extends IPoolWorker, Response = unknown> {
50
+ export interface PromiseResponseWrapper<Worker extends IPoolWorker, Response = unknown> {
51
51
  /**
52
52
  * Resolve callback to fulfill the promise.
53
53
  */
@@ -57,7 +57,7 @@ export interface PromiseWorkerResponseWrapper<Worker extends IPoolWorker, Respon
57
57
  */
58
58
  readonly reject: (reason?: string) => void;
59
59
  /**
60
- * The worker that has the assigned task.
60
+ * The worker handling the promise.
61
61
  */
62
62
  readonly worker: Worker;
63
63
  }
@@ -2,11 +2,11 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
- import { AsyncResource } from 'async_hooks';
6
- import type { Worker } from 'cluster';
7
- import type { MessagePort } from 'worker_threads';
5
+ import { AsyncResource } from 'node:async_hooks';
6
+ import type { Worker } from 'node:cluster';
7
+ import type { MessagePort } from 'node:worker_threads';
8
8
  import type { MessageValue } from '../utility-types';
9
- import type { WorkerOptions } from './worker-options';
9
+ import { type WorkerOptions } from './worker-options';
10
10
  /**
11
11
  * Base class that implements some shared logic for all poolifier workers.
12
12
  *
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { Worker } from 'cluster';
2
+ import type { Worker } from 'node:cluster';
3
3
  import type { MessageValue } from '../utility-types';
4
4
  import { AbstractWorker } from './abstract-worker';
5
5
  import type { WorkerOptions } from './worker-options';
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { MessagePort } from 'worker_threads';
2
+ import type { MessagePort } from 'node:worker_threads';
3
3
  import type { MessageValue } from '../utility-types';
4
4
  import { AbstractWorker } from './abstract-worker';
5
5
  import type { WorkerOptions } from './worker-options';
@@ -7,7 +7,7 @@ export declare const KillBehaviors: Readonly<{
7
7
  */
8
8
  readonly SOFT: "SOFT";
9
9
  /**
10
- * If `lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
10
+ * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
11
11
  */
12
12
  readonly HARD: "HARD";
13
13
  }>;
@@ -51,7 +51,7 @@ export interface WorkerOptions {
51
51
  * `killBehavior` dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
52
52
  *
53
53
  * - SOFT: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
54
- * - HARD: If `lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
54
+ * - HARD: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
55
55
  *
56
56
  * This option only apply to the newly created workers.
57
57
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.3.10-2",
3
+ "version": "2.4.0-2",
4
4
  "description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
5
5
  "license": "MIT",
6
6
  "main": "./lib/index.js",
@@ -10,26 +10,6 @@
10
10
  "import": "./lib/index.mjs"
11
11
  }
12
12
  },
13
- "scripts": {
14
- "prepare": "node prepare.js",
15
- "build": "rollup --config --environment BUILD:development",
16
- "build:typedoc": "rollup --config --environment BUILD:development --environment DOCUMENTATION",
17
- "build:prod": "rollup --config",
18
- "benchmark": "npm run build && node -r source-map-support/register benchmarks/internal/bench.js",
19
- "benchmark:debug": "npm run build && node -r source-map-support/register --inspect benchmarks/internal/bench.js",
20
- "benchmark:prod": "npm run build:prod && node -r source-map-support/register benchmarks/internal/bench.js",
21
- "test": "npm run build && c8 mocha 'tests/**/*.test.js'",
22
- "test:debug": "npm run build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
23
- "coverage": "c8 report --reporter=lcov",
24
- "coverage:html": "c8 report --reporter=html",
25
- "format": "prettier . --cache --write; ts-standard . --fix",
26
- "lint": "eslint . --cache",
27
- "lint:fix": "eslint . --cache --fix",
28
- "lint:report": "eslint . --cache --format json --output-file reports/eslint.json",
29
- "release": "release-it",
30
- "typedoc": "typedoc",
31
- "prepublishOnly": "npm run build:prod"
32
- },
33
13
  "ts-standard": {
34
14
  "globals": [
35
15
  "describe",
@@ -41,8 +21,7 @@
41
21
  ]
42
22
  },
43
23
  "engines": {
44
- "node": ">=16.0.0",
45
- "npm": ">=8.0.0"
24
+ "node": ">=16.0.0"
46
25
  },
47
26
  "repository": {
48
27
  "type": "git",
@@ -80,6 +59,10 @@
80
59
  {
81
60
  "type": "opencollective",
82
61
  "url": "https://opencollective.com/poolifier"
62
+ },
63
+ {
64
+ "type": "github",
65
+ "url": "https://github.com/sponsors/poolifier"
83
66
  }
84
67
  ],
85
68
  "bugs": {
@@ -90,25 +73,25 @@
90
73
  "lib"
91
74
  ],
92
75
  "devDependencies": {
93
- "@commitlint/cli": "^17.4.4",
76
+ "@commitlint/cli": "^17.5.1",
94
77
  "@commitlint/config-conventional": "^17.4.4",
95
78
  "@release-it/bumper": "^4.0.2",
96
79
  "@release-it/keep-a-changelog": "^3.1.0",
97
80
  "@rollup/plugin-terser": "^0.4.0",
98
81
  "@rollup/plugin-typescript": "^11.0.0",
99
- "@types/node": "^18.15.3",
100
- "@typescript-eslint/eslint-plugin": "^5.55.0",
101
- "@typescript-eslint/parser": "^5.55.0",
82
+ "@types/node": "^18.15.11",
83
+ "@typescript-eslint/eslint-plugin": "^5.57.1",
84
+ "@typescript-eslint/parser": "^5.57.1",
102
85
  "benny": "^3.7.1",
103
86
  "c8": "^7.13.0",
104
- "eslint": "^8.36.0",
87
+ "eslint": "^8.37.0",
105
88
  "eslint-config-standard": "^17.0.0",
106
89
  "eslint-config-standard-with-typescript": "^34.0.1",
107
90
  "eslint-define-config": "^1.17.0",
108
- "eslint-import-resolver-typescript": "^3.5.3",
91
+ "eslint-import-resolver-typescript": "^3.5.4",
109
92
  "eslint-plugin-import": "^2.27.5",
110
- "eslint-plugin-jsdoc": "^40.0.3",
111
- "eslint-plugin-n": "^15.6.1",
93
+ "eslint-plugin-jsdoc": "^40.1.1",
94
+ "eslint-plugin-n": "^15.7.0",
112
95
  "eslint-plugin-promise": "^6.1.1",
113
96
  "eslint-plugin-spellcheck": "^0.0.20",
114
97
  "eslint-plugin-tsdoc": "^0.2.17",
@@ -118,17 +101,36 @@
118
101
  "microtime": "^3.1.1",
119
102
  "mocha": "^10.2.0",
120
103
  "mochawesome": "^7.1.3",
121
- "prettier": "^2.8.4",
104
+ "prettier": "^2.8.7",
122
105
  "prettier-plugin-organize-imports": "^3.2.2",
123
- "release-it": "^15.9.0",
124
- "rollup": "^3.19.1",
106
+ "release-it": "^15.10.1",
107
+ "rollup": "^3.20.2",
125
108
  "rollup-plugin-analyzer": "^4.0.0",
126
109
  "rollup-plugin-command": "^1.1.3",
127
110
  "rollup-plugin-delete": "^2.0.0",
128
- "sinon": "^15.0.2",
111
+ "sinon": "^15.0.3",
129
112
  "source-map-support": "^0.5.21",
130
113
  "ts-standard": "^12.0.2",
131
- "typedoc": "^0.23.27",
132
- "typescript": "^5.0.2"
114
+ "typedoc": "^0.23.28",
115
+ "typescript": "^5.0.3"
116
+ },
117
+ "scripts": {
118
+ "preinstall": "npx only-allow pnpm",
119
+ "build": "rollup --config --environment BUILD:development",
120
+ "build:typedoc": "rollup --config --environment BUILD:development --environment DOCUMENTATION",
121
+ "build:prod": "rollup --config",
122
+ "benchmark": "pnpm run build && node -r source-map-support/register benchmarks/internal/bench.js",
123
+ "benchmark:debug": "pnpm run build && node -r source-map-support/register --inspect benchmarks/internal/bench.js",
124
+ "benchmark:prod": "pnpm run build:prod && node -r source-map-support/register benchmarks/internal/bench.js",
125
+ "test": "pnpm run build && c8 mocha 'tests/**/*.test.js'",
126
+ "test:debug": "pnpm run build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
127
+ "coverage": "c8 report --reporter=lcov",
128
+ "coverage:html": "c8 report --reporter=html",
129
+ "format": "prettier . --cache --write; ts-standard . --fix",
130
+ "lint": "eslint . --cache",
131
+ "lint:fix": "eslint . --cache --fix",
132
+ "lint:report": "eslint . --cache --format json --output-file reports/eslint.json",
133
+ "release": "release-it",
134
+ "typedoc": "typedoc"
133
135
  }
134
136
  }