@offmain/workerkit 0.14.0 → 0.14.1

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
@@ -155,6 +155,14 @@ const { data } = await factory.collectResults(settled, {
155
155
 
156
156
  > **Note:** The reducer runs inside a worker thread and must be self-contained — it cannot reference variables from the outer scope.
157
157
 
158
+ ### Dynamic Thread Scaling & Partitioning Behavior
159
+
160
+ When `partition: true` is enabled on a worker:
161
+
162
+ - **Dynamic Thread Allocation:** The library calculates worker thread count as `Math.min(maxConcurrency, srcData.length)`. For instance, if an array has 2 items and `maxConcurrency` is 20, the factory will spawn **only 2 worker threads** (instead of 20), eliminating idle thread overhead and memory pressure.
163
+ - **No Data Duplication:** Each thread receives only its assigned chunk (e.g. Worker 0 gets `[item1]`, Worker 1 gets `[item2]`), ensuring results are processed once without duplication.
164
+ - **Non-Partitioned Workers (`partition: false` / omitted):** If `partition` is not enabled, the input payload is not split, and up to `maxConcurrency` threads will each execute the full payload independently in parallel.
165
+
158
166
  ---
159
167
 
160
168
  ## Pipeline
@@ -223,12 +231,15 @@ const result = await factory.pipeline<AggregateResult>([
223
231
 
224
232
  ### When to use pipeline vs runWorker
225
233
 
226
- | Scenario | Use |
227
- | ---------------------------------------------------- | ----------------------- |
228
- | Single step, or steps that need partitioning/retries | `runWorker` |
229
- | Multi-step chain where intermediate data is large | `pipeline` |
230
- | Steps that are independent (not sequential) | `runWorker` in parallel |
231
- | Steps where only the final result matters to the UI | `pipeline` |
234
+ | Scenario | Use |
235
+ | ------------------------------------------------------------- | ----------------------- |
236
+ | Single step, or steps that need multi-core array partitioning | `runWorker` |
237
+ | Multi-step chain where intermediate data is large | `pipeline` |
238
+ | Steps that are independent (not sequential) | `runWorker` in parallel |
239
+ | Steps where only the final result matters to the UI | `pipeline` |
240
+
241
+ > **Note on `partition: true` in Pipelines:**
242
+ > `pipeline()` creates **1 worker thread per step** in a linear 1:1 `MessageChannel` chain. If a worker in a pipeline step has `partition: true`, `pipeline()` processes it as a single streaming step without splitting it across parallel worker threads. For parallel multi-core array partitioning across CPU threads, use `runWorker()`.
232
243
 
233
244
  ---
234
245
 
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var S=Object.defineProperty;var x=(n,e,r)=>e in n?S(n,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):n[e]=r;var y=(n,e,r)=>x(n,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const O=n=>`
1
+ "use strict";var O=Object.defineProperty;var x=(l,e,r)=>e in l?O(l,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):l[e]=r;var _=(l,e,r)=>x(l,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=l=>`
2
2
  const extractTransferables = (value, seen = new Set()) => {
3
3
  if (value === null || typeof value !== 'object') return [];
4
4
  if (seen.has(value)) return [];
@@ -15,13 +15,13 @@ const extractTransferables = (value, seen = new Set()) => {
15
15
 
16
16
  self.addEventListener('message', async (event) => {
17
17
  try {
18
- const output = await ${n}(event.data);
18
+ const output = await ${l}(event.data);
19
19
  self.postMessage({ ok: true, data: output }, extractTransferables(output));
20
20
  } catch (err) {
21
21
  self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
22
22
  }
23
23
  })
24
- `,F=n=>`
24
+ `,F=l=>`
25
25
  const extractTransferables = (value, seen = new Set()) => {
26
26
  if (value === null || typeof value !== 'object') return [];
27
27
  if (seen.has(value)) return [];
@@ -36,7 +36,7 @@ const extractTransferables = (value, seen = new Set()) => {
36
36
  return Object.values(value).flatMap(v => extractTransferables(v, seen));
37
37
  };
38
38
 
39
- const workerFn = ${n};
39
+ const workerFn = ${l};
40
40
  let outputPort = null;
41
41
  let inputPort = null;
42
42
  let pendingData = null;
@@ -101,7 +101,7 @@ self.addEventListener('message', (event) => {
101
101
  pendingData = event.data;
102
102
  }
103
103
  });
104
- `,R=n=>`
104
+ `,L=l=>`
105
105
  const extractTransferables = (value, seen = new Set()) => {
106
106
  if (value === null || typeof value !== 'object') return [];
107
107
  if (seen.has(value)) return [];
@@ -116,7 +116,7 @@ const extractTransferables = (value, seen = new Set()) => {
116
116
  return Object.values(value).flatMap(v => extractTransferables(v, seen));
117
117
  };
118
118
 
119
- const workerFn = ${n};
119
+ const workerFn = ${l};
120
120
  let cachedDataset = null;
121
121
 
122
122
  self.addEventListener('message', async (event) => {
@@ -157,14 +157,14 @@ self.addEventListener('message', async (event) => {
157
157
  self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
158
158
  }
159
159
  });
160
- `;var P=(n=>(n.Default="default",n.Pipeline="pipeline",n.Persistent="persistent",n))(P||{});const C=Object.freeze({persistent:R,pipeline:F,default:O});class v{constructor(e,r){y(this,"_worker");if(r!=null&&r.createWorker)this._worker=r.createWorker();else if(e){const s=(r==null?void 0:r.mode)??"default",t=C[s](e.toString()),l=new Blob([t],{type:"application/javascript"});this._worker=new Worker(URL.createObjectURL(l))}else throw new Error("Either workerFunction or options.createWorker must be provided to WorkerFactory.")}get getWorker(){return this._worker}}class j{constructor(e){this.results=e}}function m(n,e=new Set){return n===null||typeof n!="object"?[]:e.has(n)?[]:(e.add(n),n instanceof ArrayBuffer||n instanceof MessagePort||typeof ImageBitmap<"u"&&n instanceof ImageBitmap||typeof OffscreenCanvas<"u"&&n instanceof OffscreenCanvas?[n]:ArrayBuffer.isView(n)?[n.buffer]:Array.isArray(n)?n.flatMap(r=>m(r,e)):Object.values(n).flatMap(r=>m(r,e)))}class L{constructor(e){y(this,"_workers");y(this,"_threads");y(this,"_persistentWorkers",new Map);y(this,"_activeWorkers",new Set);y(this,"_isTerminated",!1);this._workers=e.workers,this._threads=navigator.hardwareConcurrency}get isTerminated(){return this._isTerminated}trackWorker(e){if(this._isTerminated)throw e.terminate(),new Error("MainWorkerFactory has been terminated");return this._activeWorkers.add(e),e}terminateWorker(e){this._activeWorkers.delete(e);try{e.terminate()}catch{}}initWorker(e){const r=new v(e.func,{createWorker:e.createWorker});return this.trackWorker(r.getWorker),r}partitionArray(e,r){if(!e.length)return[];if(r<=0)throw new Error("numChunks must be positive");const s=Math.min(r,e.length),t=Math.floor(e.length/s),l=e.length%s,c=[];let f=0;for(let a=0;a<s;a++){const o=t+(a<l?1:0);c.push(e.slice(f,f+o)),f+=o}return c}findWorkerByName(e){return this._workers.find(r=>r.name===e)}async runWorker(e,{srcData:r,...s}){if(this._isTerminated)return Promise.reject(new Error("MainWorkerFactory has been terminated"));const t=this.findWorkerByName(e);if(!t)return Promise.reject(new Error(`Worker "${e}" not found`));const l=t.maxConcurrency??this._threads,c=!!(Array.isArray(r)&&r.length>1&&t.partition),f=c?this.partitionArray(r,l):r,a=this.createWorkerPromises(t,e,{data:f,...s},l,c),o=await Promise.allSettled(a);return new j(o)}createWorkerPromises(e,r,s,t,l){const{data:c,...f}=s;return Array.from({length:t},(a,o)=>{const d=l&&Array.isArray(c)?c[o]:c;return this.runWorkerWithRetry({workerFunc:e.func,createWorker:e.createWorker,workerName:r,index:o,data:{data:d,...f}},e.retries)})}async runWorkerWithRetry(e,r=2){try{return await this.initiateWorker(e)}catch(s){if(r>0)return console.error(`Worker ${e.index} failed, retrying (${r} left):`,s),this.runWorkerWithRetry(e,r-1);throw console.error("Worker failed after all retries:",s),s}}initiateWorker({workerFunc:e,createWorker:r,workerName:s,index:t,data:l}){return new Promise((c,f)=>{const o=this.initWorker({name:s,role:"",func:e,createWorker:r}).getWorker;o.onerror=p=>{this.terminateWorker(o),f({index:t,workerConfigs:{workerFunc:e,createWorker:r,workerName:s,index:t,data:l},failedResult:p})},o.onmessage=p=>{var i,u;if(((i=p.data)==null?void 0:i.ok)===!1){this.terminateWorker(o),f({index:t,workerConfigs:{workerFunc:e,createWorker:r,workerName:s,index:t,data:l},failedResult:new ErrorEvent("error",{message:p.data.error})});return}c({index:t,workerConfigs:{workerFunc:e,createWorker:r,workerName:s,index:t,data:l},successResult:new MessageEvent("message",{data:(u=p.data)==null?void 0:u.data})}),this.terminateWorker(o)};const d={index:t,...Array.isArray(l)?{data:l}:l};o.postMessage(d,m(d))})}async collectResults(e,r={}){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");const s=e.results.filter(a=>a.status==="fulfilled"),t=e.results.filter(a=>a.status==="rejected"),l=s.map(a=>a.value.successResult.data),c=r.reducer?r.reducer.toString():"(shards) => shards.flat()";return{data:await new Promise((a,o)=>{const d=`
161
- const reducer = ${c};
162
- self.addEventListener('message', (event) => {
163
- try {
164
- const result = reducer(event.data);
165
- self.postMessage({ ok: true, data: result });
166
- } catch (err) {
167
- self.postMessage({ ok: false, error: String(err) });
168
- }
169
- });
170
- `,p=new Blob([d],{type:"application/javascript"}),i=this.trackWorker(new Worker(URL.createObjectURL(p)));i.onmessage=u=>{this.terminateWorker(i),u.data.ok?a(u.data.data):o(new Error(u.data.error))},i.onerror=u=>{this.terminateWorker(i),o(u)},i.postMessage(l)}),succeeded:s.length,failed:t.length,errors:t}}async pipeline(e){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");if(e.length===0)throw new Error("Pipeline requires at least one step");if(e.length===1){const r=e[0],{worker:s,srcData:t,...l}=r,c=this.findWorkerByName(r.worker);if(!c)throw new Error(`Worker "${r.worker}" not found`);const a=this.initWorker(c).getWorker;return new Promise((o,d)=>{a.onmessage=u=>{var k,g;this.terminateWorker(a),((k=u.data)==null?void 0:k.ok)===!1?d(new Error(u.data.error)):o((g=u.data)==null?void 0:g.data)},a.onerror=u=>{this.terminateWorker(a),d(u)};const i={data:t??{},...l,index:0};a.postMessage(i,m(i))})}return new Promise((r,s)=>{const t=[],l=[];for(const i of e){const u=this.findWorkerByName(i.worker);if(!u){s(new Error(`Worker "${i.worker}" not found`));return}const k=new v(u.func,{mode:P.Pipeline,createWorker:u.createWorker}),g=this.trackWorker(k.getWorker);t.push(g)}for(let i=0;i<t.length-1;i++)l.push(new MessageChannel);for(let i=0;i<t.length;i++){const{worker:u,srcData:k,...g}=e[i],W=[],w={};if(i>0&&(w.inputPort=l[i-1].port1,W.push(w.inputPort)),i<t.length-1&&(w.outputPort=l[i].port2,W.push(w.outputPort)),t[i].postMessage({__pipeline_ports__:!0,stepParams:g,...w},W),i<t.length-1){const _=t[i],A=t[i+1],{worker:I,srcData:U,...D}=e[i+1];_.onmessage=h=>{var E,T;if(h.data&&h.data.__pipeline_ports__)return;if(((E=h.data)==null?void 0:E.ok)===!1){t.forEach(B=>this.terminateWorker(B)),s(new Error(h.data.error));return}const b={data:((T=h.data)==null?void 0:T.ok)!==void 0?h.data.data:h.data,...D,index:0};A.postMessage(b,m(b))},_.onerror=h=>{t.forEach(M=>this.terminateWorker(M)),s(h)}}}const c=t[t.length-1];c.onmessage=i=>{var u,k;t.forEach(g=>this.terminateWorker(g)),((u=i.data)==null?void 0:u.ok)===!1?s(new Error(i.data.error)):r((k=i.data)==null?void 0:k.data)},c.onerror=i=>{t.forEach(u=>this.terminateWorker(u)),s(i)};const{worker:f,srcData:a,...o}=e[0],p={data:a??{},...o,index:0};t[0].postMessage(p,m(p))})}async runPersistent(e,r){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");const s=this.findWorkerByName(e);if(!s)throw new Error(`Worker "${e}" not found`);let t=this._persistentWorkers.get(e);if(!t){const l=new v(s.func,{mode:P.Persistent,createWorker:s.createWorker});t=this.trackWorker(l.getWorker),this._persistentWorkers.set(e,t)}return new Promise((l,c)=>{t.onmessage=a=>{var o,d;((o=a.data)==null?void 0:o.ok)===!1?c(new Error(a.data.error)):l((d=a.data)==null?void 0:d.data)},t.onerror=a=>{c(a)};const f={type:"run",config:r.config};r.dataset!==void 0&&(f.dataset=r.dataset),t.postMessage(f,m(f))})}release(e){const r=this._persistentWorkers.get(e);if(r){try{r.postMessage({type:"release"})}catch{}this.terminateWorker(r),this._persistentWorkers.delete(e)}}terminate(){this._isTerminated=!0;for(const e of this._persistentWorkers.values()){try{e.postMessage({type:"release"})}catch{}this.terminateWorker(e)}this._persistentWorkers.clear();for(const e of Array.from(this._activeWorkers))this.terminateWorker(e);this._activeWorkers.clear()}destroy(){this.terminate()}reset(){this.terminate(),this._isTerminated=!1}restart(){this.reset()}}function $(n){if(typeof self>"u")return;let e=null,r=null,s=null,t={};const l=(f,a)=>{self.postMessage(f,a)};async function c(f){try{const a=typeof f=="object"&&f!==null&&"data"in f?{...t,...f}:{data:f,...t,index:0},o=await n(a),d={ok:!0,data:o},p=m(o);e?e.postMessage(d,p):l(d,p)}catch(a){const o={ok:!1,error:a instanceof Error?a.message:String(a)};e?e.postMessage(o):l(o)}}self.addEventListener("message",f=>{const a=f.data;if(a&&a.__pipeline_ports__){a.stepParams&&(t=a.stepParams),a.outputPort&&(e=a.outputPort),a.inputPort&&(r=a.inputPort,r.onmessage=o=>{var d;o.data&&o.data.ok===!1?e?e.postMessage(o.data):l(o.data):c({data:(d=o.data)==null?void 0:d.data,...t,index:0})}),s!==null&&(c(s),s=null);return}r?s=a:c(a)})}exports.MainWorkerFactory=L;exports.WorkerFactory=v;exports.defineWorker=$;
160
+ `;var P=(l=>(l.Default="default",l.Pipeline="pipeline",l.Persistent="persistent",l))(P||{});const C=Object.freeze({persistent:L,pipeline:F,default:j});class v{constructor(e,r){_(this,"_worker");if(r!=null&&r.createWorker)this._worker=r.createWorker();else if(e){const a=(r==null?void 0:r.mode)??"default",t=C[a](e.toString()),i=new Blob([t],{type:"application/javascript"});this._worker=new Worker(URL.createObjectURL(i))}else throw new Error("Either workerFunction or options.createWorker must be provided to WorkerFactory.")}get getWorker(){return this._worker}}class I{constructor(e){this.results=e}}class ${constructor(){_(this,"store",new Map)}set(e,r){const a=r??`mem_${crypto.randomUUID()}`;return this.store.set(a,e),a}get(e){return this.store.get(e)}delete(e){return this.store.delete(e)}clear(){this.store.clear()}has(e){return this.store.has(e)}stats(){return{count:this.store.size,refs:Array.from(this.store.keys())}}}function w(l,e=new Set){return l===null||typeof l!="object"?[]:e.has(l)?[]:(e.add(l),l instanceof ArrayBuffer||l instanceof MessagePort||typeof ImageBitmap<"u"&&l instanceof ImageBitmap||typeof OffscreenCanvas<"u"&&l instanceof OffscreenCanvas?[l]:ArrayBuffer.isView(l)?[l.buffer]:Array.isArray(l)?l.flatMap(r=>w(r,e)):Object.values(l).flatMap(r=>w(r,e)))}class U{constructor(e){_(this,"_workers");_(this,"_threads");_(this,"_persistentWorkers",new Map);_(this,"_activeWorkers",new Set);_(this,"_memoryStore",new $);_(this,"_isTerminated",!1);this._workers=e.workers,this._threads=navigator.hardwareConcurrency}get isTerminated(){return this._isTerminated}trackWorker(e){if(this._isTerminated)throw e.terminate(),new Error("MainWorkerFactory has been terminated");return this._activeWorkers.add(e),e}terminateWorker(e){this._activeWorkers.delete(e);try{e.terminate()}catch{}}initWorker(e){const r=new v(e.func,{createWorker:e.createWorker});return this.trackWorker(r.getWorker),r}partitionArray(e,r){if(!e.length)return[];if(r<=0)throw new Error("numChunks must be positive");const a=Math.min(r,e.length),t=Math.floor(e.length/a),i=e.length%a,f=[];let d=0;for(let s=0;s<a;s++){const o=t+(s<i?1:0);f.push(e.slice(d,d+o)),d+=o}return f}findWorkerByName(e){return this._workers.find(r=>r.name===e)}async runWorker(e,r){if(this._isTerminated)return Promise.reject(new Error("MainWorkerFactory has been terminated"));const a=this.findWorkerByName(e);if(!a)return Promise.reject(new Error(`Worker "${e}" not found`));let{srcData:t,...i}=r||{};const f=i.__memory_ref__,d=!!i.deleteMemory;if(t===void 0&&f){if(!this._memoryStore.has(f))return Promise.reject(new Error(`Memory reference "${f}" not found in MemoryStore`));t=this._memoryStore.get(f),delete i.__memory_ref__,delete i.deleteMemory}else delete i.deleteMemory;const s=a.maxConcurrency??this._threads,o=!!(Array.isArray(t)&&a.partition),u=o?this.partitionArray(t,s):t,p=o&&Array.isArray(u)?u.length:s,m=this.createWorkerPromises(a,e,{data:u,...i},p,o),c=await Promise.allSettled(m);return d&&f&&this._memoryStore.delete(f),(a.memoryOnly||a.memory)&&this.storeWorkerMemoryResult(c,{memoryOnly:!!a.memoryOnly,shouldPartition:o}),new I(c)}async deleteMemory(e){return this._memoryStore.delete(e)}async clearMemory(){this._memoryStore.clear()}storeWorkerMemoryResult(e,{memoryOnly:r,shouldPartition:a}){const t=e.filter(s=>s.status==="fulfilled"&&!!s.value.successResult);if(t.length===0)return;const i=t.map(s=>s.value.successResult.data),f=a&&i.every(Array.isArray)?i.flat():i.length===1?i[0]:i,d=this._memoryStore.set(f);for(const s of t){const o=r?{__memory_ref__:d}:{data:s.value.successResult.data,__memory_ref__:d};s.value.successResult=new MessageEvent("message",{data:o})}}async getMemoryStats(){return this._memoryStore.stats()}createWorkerPromises(e,r,a,t,i){const{data:f,...d}=a;return Array.from({length:t},(s,o)=>{const u=i&&Array.isArray(f)?f[o]:f;return this.runWorkerWithRetry({workerFunc:e.func,createWorker:e.createWorker,workerName:r,index:o,data:{data:u,...d}},e.retries)})}async runWorkerWithRetry(e,r=2){try{return await this.initiateWorker(e)}catch(a){if(r>0)return console.error(`Worker ${e.index} failed, retrying (${r} left):`,a),this.runWorkerWithRetry(e,r-1);throw console.error("Worker failed after all retries:",a),a}}initiateWorker({workerFunc:e,createWorker:r,workerName:a,index:t,data:i}){return new Promise((f,d)=>{const o=this.initWorker({name:a,role:"",func:e,createWorker:r}).getWorker;o.onerror=p=>{this.terminateWorker(o),d({index:t,workerConfigs:{workerFunc:e,createWorker:r,workerName:a,index:t,data:i},failedResult:p})},o.onmessage=p=>{var c,n;if(((c=p.data)==null?void 0:c.ok)===!1){this.terminateWorker(o),d({index:t,workerConfigs:{workerFunc:e,createWorker:r,workerName:a,index:t,data:i},failedResult:new ErrorEvent("error",{message:p.data.error})});return}const m=((n=p.data)==null?void 0:n.ok)!==void 0?p.data.data:p.data;f({index:t,workerConfigs:{workerFunc:e,createWorker:r,workerName:a,index:t,data:i},successResult:new MessageEvent("message",{data:m})}),this.terminateWorker(o)};const u={index:t,...Array.isArray(i)?{data:i}:i};o.postMessage(u,w(u))})}async collectResults(e,r={}){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");const a=e.results.filter(m=>m.status==="fulfilled"),t=e.results.filter(m=>m.status==="rejected");if(a.length>0&&a.every(m=>{var n;const c=(n=m.value.successResult)==null?void 0:n.data;return c&&typeof c=="object"&&"__memory_ref__"in c&&!("data"in c)}))return{data:a[0].value.successResult.data,succeeded:a.length,failed:t.length,errors:t};const f=a.length>0&&a.every(m=>{var n;const c=(n=m.value.successResult)==null?void 0:n.data;return c&&typeof c=="object"&&"__memory_ref__"in c&&"data"in c}),d=f?a[0].value.successResult.data.__memory_ref__:void 0,s=a.map(m=>{const c=m.value.successResult.data;return f&&c&&typeof c=="object"&&"data"in c?c.data:c}),o=r.reducer?r.reducer.toString():"(shards) => shards.flat()";let u;if(typeof Worker<"u"&&typeof Blob<"u"&&typeof URL<"u"&&typeof URL.createObjectURL=="function")try{u=await new Promise((m,c)=>{const n=`
161
+ const reducer = ${o};
162
+ self.addEventListener('message', (event) => {
163
+ try {
164
+ const result = reducer(event.data);
165
+ self.postMessage({ ok: true, data: result });
166
+ } catch (error) {
167
+ self.postMessage({ ok: false, error: String(error) });
168
+ }
169
+ });
170
+ `,h=new Blob([n],{type:"application/javascript"}),y=this.trackWorker(new Worker(URL.createObjectURL(h)));y.onmessage=k=>{this.terminateWorker(y),k.data.ok?m(k.data.data):c(new Error(k.data.error))},y.onerror=k=>{this.terminateWorker(y),c(k)},y.postMessage(s)})}catch{const m=n=>n.flat();u=(r.reducer??m)(s)}else{const m=n=>n.flat();u=(r.reducer??m)(s)}return{data:f?{data:u,__memory_ref__:d}:u,succeeded:a.length,failed:t.length,errors:t}}async pipeline(e){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");if(e.length===0)throw new Error("Pipeline requires at least one step");if(e.length===1){const r=e[0],{worker:a,srcData:t,...i}=r,f=this.findWorkerByName(r.worker);if(!f)throw new Error(`Worker "${r.worker}" not found`);const s=this.initWorker(f).getWorker;return new Promise((o,u)=>{s.onmessage=c=>{var n,h;this.terminateWorker(s),((n=c.data)==null?void 0:n.ok)===!1?u(new Error(c.data.error)):o((h=c.data)==null?void 0:h.data)},s.onerror=c=>{this.terminateWorker(s),u(c)};const m={data:t??{},...i,index:0};s.postMessage(m,w(m))})}return new Promise((r,a)=>{const t=[],i=[];for(const n of e){const h=this.findWorkerByName(n.worker);if(!h){a(new Error(`Worker "${n.worker}" not found`));return}const y=new v(h.func,{mode:P.Pipeline,createWorker:h.createWorker}),k=this.trackWorker(y.getWorker);t.push(k)}for(let n=0;n<t.length-1;n++)i.push(new MessageChannel);for(let n=0;n<t.length;n++){const{worker:h,srcData:y,...k}=e[n],M=[],W={};if(n>0&&(W.inputPort=i[n-1].port1,M.push(W.inputPort)),n<t.length-1&&(W.outputPort=i[n].port2,M.push(W.outputPort)),t[n].postMessage({__pipeline_ports__:!0,stepParams:k,...W},M),n<t.length-1){const b=t[n],T=t[n+1],{worker:V,srcData:q,...A}=e[n+1];b.onmessage=g=>{var S,R;if(g.data&&g.data.__pipeline_ports__)return;if(((S=g.data)==null?void 0:S.ok)===!1){t.forEach(B=>this.terminateWorker(B)),a(new Error(g.data.error));return}const E={data:((R=g.data)==null?void 0:R.ok)!==void 0?g.data.data:g.data,...A,index:0};T.postMessage(E,w(E))},b.onerror=g=>{t.forEach(D=>this.terminateWorker(D)),a(g)}}}const f=t[t.length-1];f.onmessage=n=>{var h,y;t.forEach(k=>this.terminateWorker(k)),((h=n.data)==null?void 0:h.ok)===!1?a(new Error(n.data.error)):r((y=n.data)==null?void 0:y.data)},f.onerror=n=>{t.forEach(h=>this.terminateWorker(h)),a(n)};const{worker:d,srcData:s,...o}=e[0];let u=s;const p=o.__memory_ref__,m=!!o.deleteMemory;if(u===void 0&&p){if(!this._memoryStore.has(p)){t.forEach(n=>this.terminateWorker(n)),a(new Error(`Memory reference "${p}" not found in MemoryStore`));return}u=this._memoryStore.get(p),m&&this._memoryStore.delete(p),delete o.__memory_ref__,delete o.deleteMemory}u===void 0&&(u={});const c={data:u,...o,index:0};t[0].postMessage(c,w(c))})}async runPersistent(e,r){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");const a=this.findWorkerByName(e);if(!a)throw new Error(`Worker "${e}" not found`);let t=this._persistentWorkers.get(e);if(!t){const i=new v(a.func,{mode:P.Persistent,createWorker:a.createWorker});t=this.trackWorker(i.getWorker),this._persistentWorkers.set(e,t)}return new Promise((i,f)=>{t.onmessage=s=>{var o,u;((o=s.data)==null?void 0:o.ok)===!1?f(new Error(s.data.error)):i((u=s.data)==null?void 0:u.data)},t.onerror=s=>{f(s)};const d={type:"run",config:r.config};r.dataset!==void 0&&(d.dataset=r.dataset),t.postMessage(d,w(d))})}release(e){const r=this._persistentWorkers.get(e);if(r){try{r.postMessage({type:"release"})}catch{}this.terminateWorker(r),this._persistentWorkers.delete(e)}}terminate(){this._isTerminated=!0;for(const e of this._persistentWorkers.values()){try{e.postMessage({type:"release"})}catch{}this.terminateWorker(e)}this._persistentWorkers.clear();for(const e of Array.from(this._activeWorkers))this.terminateWorker(e);this._activeWorkers.clear(),this._memoryStore.clear()}destroy(){this.terminate()}reset(){this.terminate(),this._isTerminated=!1}restart(){this.reset()}}function z(l){if(typeof self>"u")return;let e=null,r=null,a=null,t={};const i=(d,s)=>{self.postMessage(d,s)};async function f(d){try{const s=typeof d=="object"&&d!==null&&"data"in d?{...t,...d}:{data:d,...t,index:0},o=await l(s),u={ok:!0,data:o},p=w(o);e?e.postMessage(u,p):i(u,p)}catch(s){const o={ok:!1,error:s instanceof Error?s.message:String(s)};e?e.postMessage(o):i(o)}}self.addEventListener("message",d=>{const s=d.data;if(s&&s.__pipeline_ports__){s.stepParams&&(t=s.stepParams),s.outputPort&&(e=s.outputPort),s.inputPort&&(r=s.inputPort,r.onmessage=o=>{var u;o.data&&o.data.ok===!1?e?e.postMessage(o.data):i(o.data):f({data:(u=o.data)==null?void 0:u.data,...t,index:0})}),a!==null&&(f(a),a=null);return}r?a=s:f(s)})}exports.MainWorkerFactory=U;exports.WorkerFactory=v;exports.defineWorker=z;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- var S = Object.defineProperty;
2
- var x = (n, e, r) => e in n ? S(n, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[e] = r;
3
- var y = (n, e, r) => x(n, typeof e != "symbol" ? e + "" : e, r);
4
- const O = (n) => `
1
+ var x = Object.defineProperty;
2
+ var O = (l, e, r) => e in l ? x(l, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : l[e] = r;
3
+ var _ = (l, e, r) => O(l, typeof e != "symbol" ? e + "" : e, r);
4
+ const j = (l) => `
5
5
  const extractTransferables = (value, seen = new Set()) => {
6
6
  if (value === null || typeof value !== 'object') return [];
7
7
  if (seen.has(value)) return [];
@@ -18,13 +18,13 @@ const extractTransferables = (value, seen = new Set()) => {
18
18
 
19
19
  self.addEventListener('message', async (event) => {
20
20
  try {
21
- const output = await ${n}(event.data);
21
+ const output = await ${l}(event.data);
22
22
  self.postMessage({ ok: true, data: output }, extractTransferables(output));
23
23
  } catch (err) {
24
24
  self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
25
25
  }
26
26
  })
27
- `, R = (n) => `
27
+ `, L = (l) => `
28
28
  const extractTransferables = (value, seen = new Set()) => {
29
29
  if (value === null || typeof value !== 'object') return [];
30
30
  if (seen.has(value)) return [];
@@ -39,7 +39,7 @@ const extractTransferables = (value, seen = new Set()) => {
39
39
  return Object.values(value).flatMap(v => extractTransferables(v, seen));
40
40
  };
41
41
 
42
- const workerFn = ${n};
42
+ const workerFn = ${l};
43
43
  let outputPort = null;
44
44
  let inputPort = null;
45
45
  let pendingData = null;
@@ -104,7 +104,7 @@ self.addEventListener('message', (event) => {
104
104
  pendingData = event.data;
105
105
  }
106
106
  });
107
- `, F = (n) => `
107
+ `, C = (l) => `
108
108
  const extractTransferables = (value, seen = new Set()) => {
109
109
  if (value === null || typeof value !== 'object') return [];
110
110
  if (seen.has(value)) return [];
@@ -119,7 +119,7 @@ const extractTransferables = (value, seen = new Set()) => {
119
119
  return Object.values(value).flatMap(v => extractTransferables(v, seen));
120
120
  };
121
121
 
122
- const workerFn = ${n};
122
+ const workerFn = ${l};
123
123
  let cachedDataset = null;
124
124
 
125
125
  self.addEventListener('message', async (event) => {
@@ -161,13 +161,13 @@ self.addEventListener('message', async (event) => {
161
161
  }
162
162
  });
163
163
  `;
164
- var P = /* @__PURE__ */ ((n) => (n.Default = "default", n.Pipeline = "pipeline", n.Persistent = "persistent", n))(P || {});
165
- const C = Object.freeze({
166
- persistent: F,
167
- pipeline: R,
168
- default: O
164
+ var P = /* @__PURE__ */ ((l) => (l.Default = "default", l.Pipeline = "pipeline", l.Persistent = "persistent", l))(P || {});
165
+ const F = Object.freeze({
166
+ persistent: C,
167
+ pipeline: L,
168
+ default: j
169
169
  });
170
- class W {
170
+ class M {
171
171
  /**
172
172
  * Creates a new `Worker` from the given function or factory option.
173
173
  *
@@ -177,14 +177,14 @@ class W {
177
177
  * @param options - Optional configuration containing `createWorker` or `mode`.
178
178
  */
179
179
  constructor(e, r) {
180
- y(this, "_worker");
180
+ _(this, "_worker");
181
181
  if (r != null && r.createWorker)
182
182
  this._worker = r.createWorker();
183
183
  else if (e) {
184
- const s = (r == null ? void 0 : r.mode) ?? "default", t = C[s](e.toString()), l = new Blob([t], {
184
+ const a = (r == null ? void 0 : r.mode) ?? "default", t = F[a](e.toString()), i = new Blob([t], {
185
185
  type: "application/javascript"
186
186
  });
187
- this._worker = new Worker(URL.createObjectURL(l));
187
+ this._worker = new Worker(URL.createObjectURL(i));
188
188
  } else
189
189
  throw new Error(
190
190
  "Either workerFunction or options.createWorker must be provided to WorkerFactory."
@@ -200,28 +200,84 @@ class W {
200
200
  return this._worker;
201
201
  }
202
202
  }
203
- class L {
203
+ class I {
204
204
  constructor(e) {
205
205
  this.results = e;
206
206
  }
207
207
  }
208
- function m(n, e = /* @__PURE__ */ new Set()) {
209
- return n === null || typeof n != "object" ? [] : e.has(n) ? [] : (e.add(n), n instanceof ArrayBuffer || n instanceof MessagePort || typeof ImageBitmap < "u" && n instanceof ImageBitmap || typeof OffscreenCanvas < "u" && n instanceof OffscreenCanvas ? [n] : ArrayBuffer.isView(n) ? [n.buffer] : Array.isArray(n) ? n.flatMap((r) => m(r, e)) : Object.values(n).flatMap(
210
- (r) => m(r, e)
208
+ class $ {
209
+ constructor() {
210
+ _(this, "store", /* @__PURE__ */ new Map());
211
+ }
212
+ /**
213
+ * Stores a dataset in RAM under an unguessable reference ID.
214
+ *
215
+ * @param data - The dataset to store.
216
+ * @param refId - Optional reference ID; if omitted, a UUID will be generated.
217
+ * @returns The reference ID under which the dataset is stored.
218
+ */
219
+ set(e, r) {
220
+ const a = r ?? `mem_${crypto.randomUUID()}`;
221
+ return this.store.set(a, e), a;
222
+ }
223
+ /**
224
+ * Retrieves a dataset from RAM by its reference ID.
225
+ *
226
+ * @param refId - The reference ID to retrieve.
227
+ * @returns The stored dataset, or undefined if not found.
228
+ */
229
+ get(e) {
230
+ return this.store.get(e);
231
+ }
232
+ /**
233
+ * Deletes a dataset reference from RAM.
234
+ *
235
+ * @param refId - The reference ID to delete.
236
+ * @returns `true` if the key existed and was removed, `false` otherwise.
237
+ */
238
+ delete(e) {
239
+ return this.store.delete(e);
240
+ }
241
+ /**
242
+ * Clears all dataset handles from RAM.
243
+ */
244
+ clear() {
245
+ this.store.clear();
246
+ }
247
+ /**
248
+ * Checks if a reference ID exists in RAM.
249
+ */
250
+ has(e) {
251
+ return this.store.has(e);
252
+ }
253
+ /**
254
+ * Returns statistics about current memory handles.
255
+ */
256
+ stats() {
257
+ return {
258
+ count: this.store.size,
259
+ refs: Array.from(this.store.keys())
260
+ };
261
+ }
262
+ }
263
+ function w(l, e = /* @__PURE__ */ new Set()) {
264
+ return l === null || typeof l != "object" ? [] : e.has(l) ? [] : (e.add(l), l instanceof ArrayBuffer || l instanceof MessagePort || typeof ImageBitmap < "u" && l instanceof ImageBitmap || typeof OffscreenCanvas < "u" && l instanceof OffscreenCanvas ? [l] : ArrayBuffer.isView(l) ? [l.buffer] : Array.isArray(l) ? l.flatMap((r) => w(r, e)) : Object.values(l).flatMap(
265
+ (r) => w(r, e)
211
266
  ));
212
267
  }
213
- class U {
268
+ class q {
214
269
  /**
215
270
  * Creates a new `MainWorkerFactory`.
216
271
  *
217
272
  * @param options - Configuration object containing the `workers` registry.
218
273
  */
219
274
  constructor(e) {
220
- y(this, "_workers");
221
- y(this, "_threads");
222
- y(this, "_persistentWorkers", /* @__PURE__ */ new Map());
223
- y(this, "_activeWorkers", /* @__PURE__ */ new Set());
224
- y(this, "_isTerminated", !1);
275
+ _(this, "_workers");
276
+ _(this, "_threads");
277
+ _(this, "_persistentWorkers", /* @__PURE__ */ new Map());
278
+ _(this, "_activeWorkers", /* @__PURE__ */ new Set());
279
+ _(this, "_memoryStore", new $());
280
+ _(this, "_isTerminated", !1);
225
281
  this._workers = e.workers, this._threads = navigator.hardwareConcurrency;
226
282
  }
227
283
  /**
@@ -255,7 +311,7 @@ class U {
255
311
  * @returns A new `WorkerFactory` wrapping the worker.
256
312
  */
257
313
  initWorker(e) {
258
- const r = new W(e.func, {
314
+ const r = new M(e.func, {
259
315
  createWorker: e.createWorker
260
316
  });
261
317
  return this.trackWorker(r.getWorker), r;
@@ -279,13 +335,13 @@ class U {
279
335
  partitionArray(e, r) {
280
336
  if (!e.length) return [];
281
337
  if (r <= 0) throw new Error("numChunks must be positive");
282
- const s = Math.min(r, e.length), t = Math.floor(e.length / s), l = e.length % s, c = [];
283
- let f = 0;
284
- for (let a = 0; a < s; a++) {
285
- const o = t + (a < l ? 1 : 0);
286
- c.push(e.slice(f, f + o)), f += o;
338
+ const a = Math.min(r, e.length), t = Math.floor(e.length / a), i = e.length % a, f = [];
339
+ let d = 0;
340
+ for (let s = 0; s < a; s++) {
341
+ const o = t + (s < i ? 1 : 0);
342
+ f.push(e.slice(d, d + o)), d += o;
287
343
  }
288
- return c;
344
+ return f;
289
345
  }
290
346
  /**
291
347
  * Looks up a registered worker configuration by name.
@@ -322,23 +378,76 @@ class U {
322
378
  * @example
323
379
  * const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3] });
324
380
  */
325
- async runWorker(e, {
326
- srcData: r,
327
- ...s
328
- }) {
381
+ async runWorker(e, r) {
329
382
  if (this._isTerminated)
330
383
  return Promise.reject(new Error("MainWorkerFactory has been terminated"));
331
- const t = this.findWorkerByName(e);
332
- if (!t)
384
+ const a = this.findWorkerByName(e);
385
+ if (!a)
333
386
  return Promise.reject(new Error(`Worker "${e}" not found`));
334
- const l = t.maxConcurrency ?? this._threads, c = !!(Array.isArray(r) && r.length > 1 && t.partition), f = c ? this.partitionArray(r, l) : r, a = this.createWorkerPromises(
335
- t,
387
+ let { srcData: t, ...i } = r || {};
388
+ const f = i.__memory_ref__, d = !!i.deleteMemory;
389
+ if (t === void 0 && f) {
390
+ if (!this._memoryStore.has(f))
391
+ return Promise.reject(
392
+ new Error(`Memory reference "${f}" not found in MemoryStore`)
393
+ );
394
+ t = this._memoryStore.get(f), delete i.__memory_ref__, delete i.deleteMemory;
395
+ } else
396
+ delete i.deleteMemory;
397
+ const s = a.maxConcurrency ?? this._threads, o = !!(Array.isArray(t) && a.partition), u = o ? this.partitionArray(t, s) : t, m = o && Array.isArray(u) ? u.length : s, p = this.createWorkerPromises(
398
+ a,
336
399
  e,
337
- { data: f, ...s },
338
- l,
339
- c
340
- ), o = await Promise.allSettled(a);
341
- return new L(o);
400
+ { data: u, ...i },
401
+ m,
402
+ o
403
+ ), c = await Promise.allSettled(p);
404
+ return d && f && this._memoryStore.delete(f), (a.memoryOnly || a.memory) && this.storeWorkerMemoryResult(c, {
405
+ memoryOnly: !!a.memoryOnly,
406
+ shouldPartition: o
407
+ }), new I(c);
408
+ }
409
+ /**
410
+ * Deletes a specific memory reference from the factory's memory store.
411
+ *
412
+ * @param ref - The `__memory_ref__` token string to delete.
413
+ * @returns A promise that resolves to `true` if deleted, `false` otherwise.
414
+ */
415
+ async deleteMemory(e) {
416
+ return this._memoryStore.delete(e);
417
+ }
418
+ /**
419
+ * Clears all stored dataset references from the factory's memory store.
420
+ */
421
+ async clearMemory() {
422
+ this._memoryStore.clear();
423
+ }
424
+ /**
425
+ * Saves the output of a completed worker execution into MemoryStore
426
+ * and modifies the returned settled results with a `__memory_ref__` token.
427
+ */
428
+ storeWorkerMemoryResult(e, {
429
+ memoryOnly: r,
430
+ shouldPartition: a
431
+ }) {
432
+ const t = e.filter(
433
+ (s) => s.status === "fulfilled" && !!s.value.successResult
434
+ );
435
+ if (t.length === 0) return;
436
+ const i = t.map(
437
+ (s) => s.value.successResult.data
438
+ ), f = a && i.every(Array.isArray) ? i.flat() : i.length === 1 ? i[0] : i, d = this._memoryStore.set(f);
439
+ for (const s of t) {
440
+ const o = r ? { __memory_ref__: d } : { data: s.value.successResult.data, __memory_ref__: d };
441
+ s.value.successResult = new MessageEvent("message", {
442
+ data: o
443
+ });
444
+ }
445
+ }
446
+ /**
447
+ * Returns statistics about active memory references in the factory.
448
+ */
449
+ async getMemoryStats() {
450
+ return this._memoryStore.stats();
342
451
  }
343
452
  /**
344
453
  * Builds the array of per-thread worker promises for a single `runWorker`
@@ -354,17 +463,17 @@ class U {
354
463
  * @param isPartitioned - Whether `data` is a pre-split array of shards.
355
464
  * @returns An array of promises, one per thread.
356
465
  */
357
- createWorkerPromises(e, r, s, t, l) {
358
- const { data: c, ...f } = s;
359
- return Array.from({ length: t }, (a, o) => {
360
- const d = l && Array.isArray(c) ? c[o] : c;
466
+ createWorkerPromises(e, r, a, t, i) {
467
+ const { data: f, ...d } = a;
468
+ return Array.from({ length: t }, (s, o) => {
469
+ const u = i && Array.isArray(f) ? f[o] : f;
361
470
  return this.runWorkerWithRetry(
362
471
  {
363
472
  workerFunc: e.func,
364
473
  createWorker: e.createWorker,
365
474
  workerName: r,
366
475
  index: o,
367
- data: { data: d, ...f }
476
+ data: { data: u, ...d }
368
477
  },
369
478
  e.retries
370
479
  );
@@ -385,13 +494,13 @@ class U {
385
494
  async runWorkerWithRetry(e, r = 2) {
386
495
  try {
387
496
  return await this.initiateWorker(e);
388
- } catch (s) {
497
+ } catch (a) {
389
498
  if (r > 0)
390
499
  return console.error(
391
500
  `Worker ${e.index} failed, retrying (${r} left):`,
392
- s
501
+ a
393
502
  ), this.runWorkerWithRetry(e, r - 1);
394
- throw console.error("Worker failed after all retries:", s), s;
503
+ throw console.error("Worker failed after all retries:", a), a;
395
504
  }
396
505
  }
397
506
  /**
@@ -414,66 +523,67 @@ class U {
414
523
  initiateWorker({
415
524
  workerFunc: e,
416
525
  createWorker: r,
417
- workerName: s,
526
+ workerName: a,
418
527
  index: t,
419
- data: l
528
+ data: i
420
529
  }) {
421
- return new Promise((c, f) => {
530
+ return new Promise((f, d) => {
422
531
  const o = this.initWorker({
423
- name: s,
532
+ name: a,
424
533
  role: "",
425
534
  func: e,
426
535
  createWorker: r
427
536
  }).getWorker;
428
- o.onerror = (p) => {
429
- this.terminateWorker(o), f({
537
+ o.onerror = (m) => {
538
+ this.terminateWorker(o), d({
430
539
  index: t,
431
540
  workerConfigs: {
432
541
  workerFunc: e,
433
542
  createWorker: r,
434
- workerName: s,
543
+ workerName: a,
435
544
  index: t,
436
- data: l
545
+ data: i
437
546
  },
438
- failedResult: p
547
+ failedResult: m
439
548
  });
440
- }, o.onmessage = (p) => {
441
- var i, u;
442
- if (((i = p.data) == null ? void 0 : i.ok) === !1) {
443
- this.terminateWorker(o), f({
549
+ }, o.onmessage = (m) => {
550
+ var c, n;
551
+ if (((c = m.data) == null ? void 0 : c.ok) === !1) {
552
+ this.terminateWorker(o), d({
444
553
  index: t,
445
554
  workerConfigs: {
446
555
  workerFunc: e,
447
556
  createWorker: r,
448
- workerName: s,
557
+ workerName: a,
449
558
  index: t,
450
- data: l
559
+ data: i
451
560
  },
452
561
  failedResult: new ErrorEvent("error", {
453
- message: p.data.error
562
+ message: m.data.error
454
563
  })
455
564
  });
456
565
  return;
457
566
  }
458
- c({
567
+ const p = ((n = m.data) == null ? void 0 : n.ok) !== void 0 ? m.data.data : m.data;
568
+ f({
459
569
  index: t,
460
570
  workerConfigs: {
461
571
  workerFunc: e,
462
572
  createWorker: r,
463
- workerName: s,
573
+ workerName: a,
464
574
  index: t,
465
- data: l
575
+ data: i
466
576
  },
467
577
  successResult: new MessageEvent("message", {
468
- data: (u = p.data) == null ? void 0 : u.data
578
+ data: p
469
579
  })
470
580
  }), this.terminateWorker(o);
471
581
  };
472
- const d = {
582
+ const u = {
473
583
  index: t,
474
- ...Array.isArray(l) ? { data: l } : l
584
+ ...Array.isArray(i) ? { data: i } : i
475
585
  };
476
- o.postMessage(d, m(d));
586
+ o.postMessage(u, w(u));
477
587
  });
478
588
  }
479
589
  /**
@@ -511,31 +621,66 @@ class U {
511
621
  async collectResults(e, r = {}) {
512
622
  if (this._isTerminated)
513
623
  throw new Error("MainWorkerFactory has been terminated");
514
- const s = e.results.filter(
515
- (a) => a.status === "fulfilled"
624
+ const a = e.results.filter(
625
+ (p) => p.status === "fulfilled"
516
626
  ), t = e.results.filter(
517
- (a) => a.status === "rejected"
518
- ), l = s.map((a) => a.value.successResult.data), c = r.reducer ? r.reducer.toString() : "(shards) => shards.flat()";
519
- return {
520
- data: await new Promise((a, o) => {
521
- const d = `
522
- const reducer = ${c};
523
- self.addEventListener('message', (event) => {
524
- try {
525
- const result = reducer(event.data);
526
- self.postMessage({ ok: true, data: result });
527
- } catch (err) {
528
- self.postMessage({ ok: false, error: String(err) });
529
- }
627
+ (p) => p.status === "rejected"
628
+ );
629
+ if (a.length > 0 && a.every((p) => {
630
+ var n;
631
+ const c = (n = p.value.successResult) == null ? void 0 : n.data;
632
+ return c && typeof c == "object" && "__memory_ref__" in c && !("data" in c);
633
+ }))
634
+ return {
635
+ data: a[0].value.successResult.data,
636
+ succeeded: a.length,
637
+ failed: t.length,
638
+ errors: t
639
+ };
640
+ const f = a.length > 0 && a.every((p) => {
641
+ var n;
642
+ const c = (n = p.value.successResult) == null ? void 0 : n.data;
643
+ return c && typeof c == "object" && "__memory_ref__" in c && "data" in c;
644
+ }), d = f ? a[0].value.successResult.data.__memory_ref__ : void 0, s = a.map((p) => {
645
+ const c = p.value.successResult.data;
646
+ return f && c && typeof c == "object" && "data" in c ? c.data : c;
647
+ }), o = r.reducer ? r.reducer.toString() : "(shards) => shards.flat()";
648
+ let u;
649
+ if (typeof Worker < "u" && typeof Blob < "u" && typeof URL < "u" && typeof URL.createObjectURL == "function")
650
+ try {
651
+ u = await new Promise((p, c) => {
652
+ const n = `
653
+ const reducer = ${o};
654
+ self.addEventListener('message', (event) => {
655
+ try {
656
+ const result = reducer(event.data);
657
+ self.postMessage({ ok: true, data: result });
658
+ } catch (error) {
659
+ self.postMessage({ ok: false, error: String(error) });
660
+ }
661
+ });
662
+ `, h = new Blob([n], {
663
+ type: "application/javascript"
664
+ }), y = this.trackWorker(
665
+ new Worker(URL.createObjectURL(h))
666
+ );
667
+ y.onmessage = (k) => {
668
+ this.terminateWorker(y), k.data.ok ? p(k.data.data) : c(new Error(k.data.error));
669
+ }, y.onerror = (k) => {
670
+ this.terminateWorker(y), c(k);
671
+ }, y.postMessage(s);
530
672
  });
531
- `, p = new Blob([d], { type: "application/javascript" }), i = this.trackWorker(new Worker(URL.createObjectURL(p)));
532
- i.onmessage = (u) => {
533
- this.terminateWorker(i), u.data.ok ? a(u.data.data) : o(new Error(u.data.error));
534
- }, i.onerror = (u) => {
535
- this.terminateWorker(i), o(u);
536
- }, i.postMessage(l);
537
- }),
538
- succeeded: s.length,
673
+ } catch {
674
+ const p = (n) => n.flat();
675
+ u = (r.reducer ?? p)(s);
676
+ }
677
+ else {
678
+ const p = (n) => n.flat();
679
+ u = (r.reducer ?? p)(s);
680
+ }
681
+ return {
682
+ data: f ? { data: u, __memory_ref__: d } : u,
683
+ succeeded: a.length,
539
684
  failed: t.length,
540
685
  errors: t
541
686
  };
@@ -572,66 +717,97 @@ class U {
572
717
  if (e.length === 0)
573
718
  throw new Error("Pipeline requires at least one step");
574
719
  if (e.length === 1) {
575
- const r = e[0], { worker: s, srcData: t, ...l } = r, c = this.findWorkerByName(r.worker);
576
- if (!c) throw new Error(`Worker "${r.worker}" not found`);
577
- const a = this.initWorker(c).getWorker;
578
- return new Promise((o, d) => {
579
- a.onmessage = (u) => {
580
- var k, g;
581
- this.terminateWorker(a), ((k = u.data) == null ? void 0 : k.ok) === !1 ? d(new Error(u.data.error)) : o((g = u.data) == null ? void 0 : g.data);
582
- }, a.onerror = (u) => {
583
- this.terminateWorker(a), d(u);
720
+ const r = e[0], { worker: a, srcData: t, ...i } = r, f = this.findWorkerByName(r.worker);
721
+ if (!f) throw new Error(`Worker "${r.worker}" not found`);
722
+ const s = this.initWorker(f).getWorker;
723
+ return new Promise((o, u) => {
724
+ s.onmessage = (c) => {
725
+ var n, h;
726
+ this.terminateWorker(s), ((n = c.data) == null ? void 0 : n.ok) === !1 ? u(new Error(c.data.error)) : o((h = c.data) == null ? void 0 : h.data);
727
+ }, s.onerror = (c) => {
728
+ this.terminateWorker(s), u(c);
584
729
  };
585
- const i = { data: t ?? {}, ...l, index: 0 };
586
- a.postMessage(i, m(i));
730
+ const p = { data: t ?? {}, ...i, index: 0 };
731
+ s.postMessage(p, w(p));
587
732
  });
588
733
  }
589
- return new Promise((r, s) => {
590
- const t = [], l = [];
591
- for (const i of e) {
592
- const u = this.findWorkerByName(i.worker);
593
- if (!u) {
594
- s(new Error(`Worker "${i.worker}" not found`));
734
+ return new Promise((r, a) => {
735
+ const t = [], i = [];
736
+ for (const n of e) {
737
+ const h = this.findWorkerByName(n.worker);
738
+ if (!h) {
739
+ a(new Error(`Worker "${n.worker}" not found`));
595
740
  return;
596
741
  }
597
- const k = new W(u.func, {
742
+ const y = new M(h.func, {
598
743
  mode: P.Pipeline,
599
- createWorker: u.createWorker
600
- }), g = this.trackWorker(k.getWorker);
601
- t.push(g);
744
+ createWorker: h.createWorker
745
+ }), k = this.trackWorker(y.getWorker);
746
+ t.push(k);
602
747
  }
603
- for (let i = 0; i < t.length - 1; i++)
604
- l.push(new MessageChannel());
605
- for (let i = 0; i < t.length; i++) {
606
- const { worker: u, srcData: k, ...g } = e[i], v = [], w = {};
607
- if (i > 0 && (w.inputPort = l[i - 1].port1, v.push(w.inputPort)), i < t.length - 1 && (w.outputPort = l[i].port2, v.push(w.outputPort)), t[i].postMessage(
608
- { __pipeline_ports__: !0, stepParams: g, ...w },
748
+ for (let n = 0; n < t.length - 1; n++)
749
+ i.push(new MessageChannel());
750
+ for (let n = 0; n < t.length; n++) {
751
+ const {
752
+ worker: h,
753
+ srcData: y,
754
+ ...k
755
+ } = e[n], v = [], W = {};
756
+ if (n > 0 && (W.inputPort = i[n - 1].port1, v.push(W.inputPort)), n < t.length - 1 && (W.outputPort = i[n].port2, v.push(W.outputPort)), t[n].postMessage(
757
+ { __pipeline_ports__: !0, stepParams: k, ...W },
609
758
  v
610
- ), i < t.length - 1) {
611
- const _ = t[i], A = t[i + 1], { worker: j, srcData: $, ...D } = e[i + 1];
612
- _.onmessage = (h) => {
613
- var E, T;
614
- if (h.data && h.data.__pipeline_ports__) return;
615
- if (((E = h.data) == null ? void 0 : E.ok) === !1) {
616
- t.forEach((B) => this.terminateWorker(B)), s(new Error(h.data.error));
759
+ ), n < t.length - 1) {
760
+ const b = t[n], A = t[n + 1], {
761
+ worker: U,
762
+ srcData: z,
763
+ ...T
764
+ } = e[n + 1];
765
+ b.onmessage = (g) => {
766
+ var S, R;
767
+ if (g.data && g.data.__pipeline_ports__) return;
768
+ if (((S = g.data) == null ? void 0 : S.ok) === !1) {
769
+ t.forEach((B) => this.terminateWorker(B)), a(new Error(g.data.error));
617
770
  return;
618
771
  }
619
- const b = { data: ((T = h.data) == null ? void 0 : T.ok) !== void 0 ? h.data.data : h.data, ...D, index: 0 };
620
- A.postMessage(b, m(b));
621
- }, _.onerror = (h) => {
622
- t.forEach((M) => this.terminateWorker(M)), s(h);
772
+ const E = { data: ((R = g.data) == null ? void 0 : R.ok) !== void 0 ? g.data.data : g.data, ...T, index: 0 };
773
+ A.postMessage(E, w(E));
774
+ }, b.onerror = (g) => {
775
+ t.forEach((D) => this.terminateWorker(D)), a(g);
623
776
  };
624
777
  }
625
778
  }
626
- const c = t[t.length - 1];
627
- c.onmessage = (i) => {
628
- var u, k;
629
- t.forEach((g) => this.terminateWorker(g)), ((u = i.data) == null ? void 0 : u.ok) === !1 ? s(new Error(i.data.error)) : r((k = i.data) == null ? void 0 : k.data);
630
- }, c.onerror = (i) => {
631
- t.forEach((u) => this.terminateWorker(u)), s(i);
779
+ const f = t[t.length - 1];
780
+ f.onmessage = (n) => {
781
+ var h, y;
782
+ t.forEach((k) => this.terminateWorker(k)), ((h = n.data) == null ? void 0 : h.ok) === !1 ? a(new Error(n.data.error)) : r((y = n.data) == null ? void 0 : y.data);
783
+ }, f.onerror = (n) => {
784
+ t.forEach((h) => this.terminateWorker(h)), a(n);
632
785
  };
633
- const { worker: f, srcData: a, ...o } = e[0], p = { data: a ?? {}, ...o, index: 0 };
634
- t[0].postMessage(p, m(p));
786
+ const {
787
+ worker: d,
788
+ srcData: s,
789
+ ...o
790
+ } = e[0];
791
+ let u = s;
792
+ const m = o.__memory_ref__, p = !!o.deleteMemory;
793
+ if (u === void 0 && m) {
794
+ if (!this._memoryStore.has(m)) {
795
+ t.forEach((n) => this.terminateWorker(n)), a(
796
+ new Error(
797
+ `Memory reference "${m}" not found in MemoryStore`
798
+ )
799
+ );
800
+ return;
801
+ }
802
+ u = this._memoryStore.get(m), p && this._memoryStore.delete(m), delete o.__memory_ref__, delete o.deleteMemory;
803
+ }
804
+ u === void 0 && (u = {});
805
+ const c = {
806
+ data: u,
807
+ ...o,
808
+ index: 0
809
+ };
810
+ t[0].postMessage(c, w(c));
635
811
  });
636
812
  }
637
813
  /**
@@ -671,28 +847,28 @@ class U {
671
847
  async runPersistent(e, r) {
672
848
  if (this._isTerminated)
673
849
  throw new Error("MainWorkerFactory has been terminated");
674
- const s = this.findWorkerByName(e);
675
- if (!s) throw new Error(`Worker "${e}" not found`);
850
+ const a = this.findWorkerByName(e);
851
+ if (!a) throw new Error(`Worker "${e}" not found`);
676
852
  let t = this._persistentWorkers.get(e);
677
853
  if (!t) {
678
- const l = new W(s.func, {
854
+ const i = new M(a.func, {
679
855
  mode: P.Persistent,
680
- createWorker: s.createWorker
856
+ createWorker: a.createWorker
681
857
  });
682
- t = this.trackWorker(l.getWorker), this._persistentWorkers.set(e, t);
858
+ t = this.trackWorker(i.getWorker), this._persistentWorkers.set(e, t);
683
859
  }
684
- return new Promise((l, c) => {
685
- t.onmessage = (a) => {
686
- var o, d;
687
- ((o = a.data) == null ? void 0 : o.ok) === !1 ? c(new Error(a.data.error)) : l((d = a.data) == null ? void 0 : d.data);
688
- }, t.onerror = (a) => {
689
- c(a);
860
+ return new Promise((i, f) => {
861
+ t.onmessage = (s) => {
862
+ var o, u;
863
+ ((o = s.data) == null ? void 0 : o.ok) === !1 ? f(new Error(s.data.error)) : i((u = s.data) == null ? void 0 : u.data);
864
+ }, t.onerror = (s) => {
865
+ f(s);
690
866
  };
691
- const f = {
867
+ const d = {
692
868
  type: "run",
693
869
  config: r.config
694
870
  };
695
- r.dataset !== void 0 && (f.dataset = r.dataset), t.postMessage(f, m(f));
871
+ r.dataset !== void 0 && (d.dataset = r.dataset), t.postMessage(d, w(d));
696
872
  });
697
873
  }
698
874
  /**
@@ -732,7 +908,7 @@ class U {
732
908
  this._persistentWorkers.clear();
733
909
  for (const e of Array.from(this._activeWorkers))
734
910
  this.terminateWorker(e);
735
- this._activeWorkers.clear();
911
+ this._activeWorkers.clear(), this._memoryStore.clear();
736
912
  }
737
913
  /**
738
914
  * Alias for {@link terminate}. Terminates the factory and all worker instances.
@@ -754,38 +930,38 @@ class U {
754
930
  this.reset();
755
931
  }
756
932
  }
757
- function V(n) {
933
+ function N(l) {
758
934
  if (typeof self > "u") return;
759
- let e = null, r = null, s = null, t = {};
760
- const l = (f, a) => {
761
- self.postMessage(f, a);
935
+ let e = null, r = null, a = null, t = {};
936
+ const i = (d, s) => {
937
+ self.postMessage(d, s);
762
938
  };
763
- async function c(f) {
939
+ async function f(d) {
764
940
  try {
765
- const a = typeof f == "object" && f !== null && "data" in f ? { ...t, ...f } : { data: f, ...t, index: 0 }, o = await n(a), d = { ok: !0, data: o }, p = m(o);
766
- e ? e.postMessage(d, p) : l(d, p);
767
- } catch (a) {
941
+ const s = typeof d == "object" && d !== null && "data" in d ? { ...t, ...d } : { data: d, ...t, index: 0 }, o = await l(s), u = { ok: !0, data: o }, m = w(o);
942
+ e ? e.postMessage(u, m) : i(u, m);
943
+ } catch (s) {
768
944
  const o = {
769
945
  ok: !1,
770
- error: a instanceof Error ? a.message : String(a)
946
+ error: s instanceof Error ? s.message : String(s)
771
947
  };
772
- e ? e.postMessage(o) : l(o);
948
+ e ? e.postMessage(o) : i(o);
773
949
  }
774
950
  }
775
- self.addEventListener("message", (f) => {
776
- const a = f.data;
777
- if (a && a.__pipeline_ports__) {
778
- a.stepParams && (t = a.stepParams), a.outputPort && (e = a.outputPort), a.inputPort && (r = a.inputPort, r.onmessage = (o) => {
779
- var d;
780
- o.data && o.data.ok === !1 ? e ? e.postMessage(o.data) : l(o.data) : c({ data: (d = o.data) == null ? void 0 : d.data, ...t, index: 0 });
781
- }), s !== null && (c(s), s = null);
951
+ self.addEventListener("message", (d) => {
952
+ const s = d.data;
953
+ if (s && s.__pipeline_ports__) {
954
+ s.stepParams && (t = s.stepParams), s.outputPort && (e = s.outputPort), s.inputPort && (r = s.inputPort, r.onmessage = (o) => {
955
+ var u;
956
+ o.data && o.data.ok === !1 ? e ? e.postMessage(o.data) : i(o.data) : f({ data: (u = o.data) == null ? void 0 : u.data, ...t, index: 0 });
957
+ }), a !== null && (f(a), a = null);
782
958
  return;
783
959
  }
784
- r ? s = a : c(a);
960
+ r ? a = s : f(s);
785
961
  });
786
962
  }
787
963
  export {
788
- U as MainWorkerFactory,
789
- W as WorkerFactory,
790
- V as defineWorker
964
+ q as MainWorkerFactory,
965
+ M as WorkerFactory,
966
+ N as defineWorker
791
967
  };
@@ -1,4 +1,4 @@
1
- import { CollectOptions, CollectedResult, PipelineStep, WorkerConfig, WorkerConfigMap, WorkerDataParam, WorkerFunction, WorkerReturnType, TypedSettledResults } from './types.ts';
1
+ import { CollectOptions, CollectedResult, PipelineStep, WorkerConfig, WorkerConfigMap, WorkerDataParam, WorkerFunction, WorkerReturnType, TypedSettledResults, MemoryStats } from './types.ts';
2
2
  /**
3
3
  * Recursively collects all Transferable objects from a value.
4
4
  * Transferable (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas)
@@ -37,6 +37,7 @@ declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFun
37
37
  private readonly _threads;
38
38
  private readonly _persistentWorkers;
39
39
  private readonly _activeWorkers;
40
+ private readonly _memoryStore;
40
41
  private _isTerminated;
41
42
  /**
42
43
  * Creates a new `MainWorkerFactory`.
@@ -115,9 +116,31 @@ declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFun
115
116
  * @example
116
117
  * const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3] });
117
118
  */
118
- runWorker<TName extends keyof WorkerConfigMap<TConfigs> & string>(workerName: TName, { srcData, ...otherParams }: {
119
- srcData: WorkerDataParam<WorkerConfigMap<TConfigs>[TName]>;
119
+ runWorker<TName extends keyof WorkerConfigMap<TConfigs> & string>(workerName: TName, rawParams: {
120
+ srcData?: WorkerDataParam<WorkerConfigMap<TConfigs>[TName]>;
121
+ __memory_ref__?: string;
122
+ deleteMemory?: boolean;
120
123
  } & Record<string, unknown>): Promise<TypedSettledResults<WorkerReturnType<WorkerConfigMap<TConfigs>[TName]>>>;
124
+ /**
125
+ * Deletes a specific memory reference from the factory's memory store.
126
+ *
127
+ * @param ref - The `__memory_ref__` token string to delete.
128
+ * @returns A promise that resolves to `true` if deleted, `false` otherwise.
129
+ */
130
+ deleteMemory(ref: string): Promise<boolean>;
131
+ /**
132
+ * Clears all stored dataset references from the factory's memory store.
133
+ */
134
+ clearMemory(): Promise<void>;
135
+ /**
136
+ * Saves the output of a completed worker execution into MemoryStore
137
+ * and modifies the returned settled results with a `__memory_ref__` token.
138
+ */
139
+ private storeWorkerMemoryResult;
140
+ /**
141
+ * Returns statistics about active memory references in the factory.
142
+ */
143
+ getMemoryStats(): Promise<MemoryStats>;
121
144
  /**
122
145
  * Builds the array of per-thread worker promises for a single `runWorker`
123
146
  * call.
@@ -196,7 +219,7 @@ declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFun
196
219
  * reducer: (shards) => shards.flat().reduce((a, b) => a + b, 0),
197
220
  * });
198
221
  */
199
- collectResults<T = unknown, R = T extends (infer Item)[] ? Item[] : T[]>(settled: TypedSettledResults<T>, options?: CollectOptions<T, R>): Promise<CollectedResult<R>>;
222
+ collectResults<T = unknown, R = T extends (infer Item)[] ? Item[] : T[]>(settled: TypedSettledResults<T> | TypedSettledResults<unknown>, options?: CollectOptions<T, R>): Promise<CollectedResult<R>>;
200
223
  /**
201
224
  * Runs a chain of workers where each step's output feeds directly into the
202
225
  * next step — **without passing through the main thread**.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * MemoryStore manages dataset handles stored in volatile RAM inside the MemoryWorker.
3
+ *
4
+ * Each dataset is indexed by a cryptographically generated UUID (`__memory_ref__`).
5
+ */
6
+ export declare class MemoryStore {
7
+ private readonly store;
8
+ /**
9
+ * Stores a dataset in RAM under an unguessable reference ID.
10
+ *
11
+ * @param data - The dataset to store.
12
+ * @param refId - Optional reference ID; if omitted, a UUID will be generated.
13
+ * @returns The reference ID under which the dataset is stored.
14
+ */
15
+ set(data: unknown, refId?: string): string;
16
+ /**
17
+ * Retrieves a dataset from RAM by its reference ID.
18
+ *
19
+ * @param refId - The reference ID to retrieve.
20
+ * @returns The stored dataset, or undefined if not found.
21
+ */
22
+ get(refId: string): unknown;
23
+ /**
24
+ * Deletes a dataset reference from RAM.
25
+ *
26
+ * @param refId - The reference ID to delete.
27
+ * @returns `true` if the key existed and was removed, `false` otherwise.
28
+ */
29
+ delete(refId: string): boolean;
30
+ /**
31
+ * Clears all dataset handles from RAM.
32
+ */
33
+ clear(): void;
34
+ /**
35
+ * Checks if a reference ID exists in RAM.
36
+ */
37
+ has(refId: string): boolean;
38
+ /**
39
+ * Returns statistics about current memory handles.
40
+ */
41
+ stats(): {
42
+ count: number;
43
+ refs: string[];
44
+ };
45
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Script source for the dedicated MemoryWorker thread.
3
+ *
4
+ * Runs inside an isolated Web Worker thread and maintains the MemoryStore.
5
+ * Handshakes and authenticates all incoming MessagePort requests using a secret `factoryToken`.
6
+ */
7
+ export declare const memoryWorkerScript = "\nconst store = new Map();\n\nself.addEventListener('message', (event) => {\n const msg = event.data;\n if (!msg || typeof msg !== 'object') return;\n\n const { action, factoryToken, expectedToken, ref, data, id } = msg;\n\n // Initial handshake to set expected token if needed\n if (action === 'INIT_TOKEN') {\n self.__expectedToken = expectedToken;\n self.postMessage({ ok: true, action: 'INIT_TOKEN_ACK' });\n return;\n }\n\n // Validate factory token\n if (self.__expectedToken && factoryToken !== self.__expectedToken) {\n self.postMessage({ ok: false, error: 'Unauthorized: invalid factory token', id });\n return;\n }\n\n try {\n switch (action) {\n case 'SET': {\n const refId = ref || ('mem_' + crypto.randomUUID());\n store.set(refId, data);\n self.postMessage({ ok: true, ref: refId, id });\n break;\n }\n case 'GET': {\n const resultData = store.get(ref);\n const exists = store.has(ref);\n self.postMessage({ ok: true, exists, data: resultData, ref, id });\n break;\n }\n case 'DELETE': {\n const deleted = store.delete(ref);\n self.postMessage({ ok: true, deleted, ref, id });\n break;\n }\n case 'CLEAR': {\n store.clear();\n self.postMessage({ ok: true, action: 'CLEAR_ACK', id });\n break;\n }\n case 'STATS': {\n self.postMessage({\n ok: true,\n stats: {\n count: store.size,\n refs: Array.from(store.keys()),\n },\n id,\n });\n break;\n }\n default:\n self.postMessage({ ok: false, error: 'Unknown action: ' + action, id });\n }\n } catch (err) {\n self.postMessage({\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n id,\n });\n }\n});\n";
@@ -52,6 +52,23 @@ export interface WorkerConfig<TFunc extends WorkerFunction = WorkerFunction> {
52
52
  * If any dependency fails, this worker will not run.
53
53
  */
54
54
  dependencies?: Array<() => void>;
55
+ /**
56
+ * When `true`, the worker's output is saved in the isolated `MemoryWorker`
57
+ * and returned alongside a `__memory_ref__` token.
58
+ */
59
+ memory?: boolean;
60
+ /**
61
+ * When `true`, the worker's output is saved in the isolated `MemoryWorker`
62
+ * and **only** the `__memory_ref__` token is returned (0 bytes data transferred back to main thread).
63
+ */
64
+ memoryOnly?: boolean;
65
+ }
66
+ /** Statistics about stored memory handles in MemoryWorker. */
67
+ export interface MemoryStats {
68
+ /** Total number of active memory references. */
69
+ count: number;
70
+ /** Array of active reference IDs. */
71
+ refs: string[];
55
72
  }
56
73
  /**
57
74
  * Derives a `name → function` map from a readonly tuple of
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@offmain/workerkit",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "A lightweight manager for running functions in Web Workers with partitioning, retries, and concurrency control",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",