@offmain/workerkit 0.13.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
@@ -75,27 +75,32 @@ When worker logic relies on external npm packages (such as Luxon, `date-fns`, `i
75
75
 
76
76
  Passing `createWorker` enables modern module bundlers (Webpack 5, Vite, Rollup, Parcel) to analyze and bundle the worker file along with all of its dependencies into a dedicated ES module worker chunk.
77
77
 
78
- ### Dedicated Worker Script
78
+ ### Dedicated Worker Script with `defineWorker`
79
+
80
+ When writing standalone worker files, wrap your export in `defineWorker`. This automatically handles standard runs, worker-to-worker pipelines (`pipeline()`), and persistent dataset caching without manual `postMessage` / `onmessage` boilerplate:
79
81
 
80
82
  ```ts
81
83
  // transform-data.worker.ts
84
+ import { defineWorker } from '@offmain/workerkit';
82
85
  import { format } from 'date-fns';
83
86
  import { t } from './i18n.ts';
84
87
 
85
- self.addEventListener('message', (event) => {
86
- try {
87
- const { items, locale } = event.data.data;
88
- const result = items.map((item: any) => ({
88
+ export default defineWorker(
89
+ async ({
90
+ data,
91
+ options,
92
+ }: {
93
+ data: { items: any[]; locale: string };
94
+ options?: { prefix?: string };
95
+ }) => {
96
+ const { items, locale } = data;
97
+ return items.map((item: any) => ({
89
98
  ...item,
90
99
  formattedDate: format(new Date(item.timestamp), 'yyyy-MM-dd'),
91
- label: t('transaction', locale),
100
+ label: (options?.prefix ?? '') + t('transaction', locale),
92
101
  }));
93
-
94
- self.postMessage({ ok: true, data: result });
95
- } catch (err) {
96
- self.postMessage({ ok: false, error: (err as Error).message });
97
- }
98
- });
102
+ },
103
+ );
99
104
  ```
100
105
 
101
106
  ### Webpack 5 & Vite Static Analysis (`createWorker`)
@@ -150,6 +155,14 @@ const { data } = await factory.collectResults(settled, {
150
155
 
151
156
  > **Note:** The reducer runs inside a worker thread and must be self-contained — it cannot reference variables from the outer scope.
152
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
+
153
166
  ---
154
167
 
155
168
  ## Pipeline
@@ -176,7 +189,7 @@ Only the final result crosses back to the main thread. If your pipeline generate
176
189
 
177
190
  ### Usage
178
191
 
179
- ```ts
192
+ ````ts
180
193
  import { MainWorkerFactory } from '@offmain/workerkit';
181
194
  import { fetchData, transform, aggregate } from './workers.ts';
182
195
 
@@ -188,29 +201,45 @@ const factory = new MainWorkerFactory({
188
201
  ] as const,
189
202
  });
190
203
 
204
+ ### Step-Specific Options and Configs in Pipeline
205
+
206
+ You can pass step-specific parameters (such as `options`, `configs`, etc.) directly to each pipeline step:
207
+
208
+ ```ts
191
209
  const result = await factory.pipeline<AggregateResult>([
192
- { worker: 'fetchData', srcData: { url: '/api/records' } },
193
- { worker: 'transform' }, // receives fetchData output directly
194
- { worker: 'aggregate' }, // receives transform output directly
210
+ {
211
+ worker: 'fetchData',
212
+ srcData: { url: '/api/records' },
213
+ options: { timeout: 5000 },
214
+ },
215
+ {
216
+ worker: 'transform',
217
+ configs: { multiplier: 2 },
218
+ },
219
+ {
220
+ worker: 'aggregate',
221
+ options: { threshold: 10 },
222
+ },
195
223
  ]);
224
+ ````
196
225
 
197
- console.log(result); // only this small result crossed to main thread
198
- ```
199
-
200
- ### How each step receives data
226
+ ### How each step receives data and parameters
201
227
 
202
- - The first step receives `srcData` as `{ data: srcData, index: 0 }` same as `runWorker`.
203
- - Each subsequent step receives the previous step's output as `{ data: previousOutput, index: 0 }`.
204
- - Worker functions don't need any special handling they use the same `{ data }` parameter signature as regular workers.
228
+ - The first step receives `srcData` merged with its step parameters as `{ data: srcData, options: { timeout: 5000 }, index: 0 }`.
229
+ - Each subsequent step receives the previous step's output merged with its step parameters as `{ data: previousOutput, configs: { multiplier: 2 }, index: 0 }`.
230
+ - Worker functions (both inline functions and native scripts using `defineWorker`) receive all step parameters in their first argument.
205
231
 
206
232
  ### When to use pipeline vs runWorker
207
233
 
208
- | Scenario | Use |
209
- | ---------------------------------------------------- | ----------------------- |
210
- | Single step, or steps that need partitioning/retries | `runWorker` |
211
- | Multi-step chain where intermediate data is large | `pipeline` |
212
- | Steps that are independent (not sequential) | `runWorker` in parallel |
213
- | 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()`.
214
243
 
215
244
  ---
216
245
 
@@ -310,11 +339,42 @@ The framework handles the caching transparently — your function always receive
310
339
 
311
340
  The cached dataset lives in worker memory until `release()` is called. For large datasets, always call `release()` when you're done to free the memory:
312
341
 
342
+ After releasing, the next `runPersistent` call will create a fresh worker instance (requiring a new dataset).
343
+
344
+ ---
345
+
346
+ ## Lifecycle Management (`terminate`, `destroy`, `reset`, `restart`)
347
+
348
+ `MainWorkerFactory` provides built-in lifecycle management to terminate running workers and clean up browser resources:
349
+
350
+ ### `terminate()` / `destroy()`
351
+
352
+ Immediately stops all active workers (one-shot tasks, pipelines, reducers) and releases all cached persistent workers:
353
+
313
354
  ```ts
314
- factory.release('transform');
355
+ // Stop all active threads and release persistent workers
356
+ factory.terminate();
357
+ // or
358
+ factory.destroy(); // Alias for terminate()
359
+
360
+ console.log(factory.isTerminated); // true
315
361
  ```
316
362
 
317
- After releasing, the next `runPersistent` call will create a fresh worker instance (requiring a new dataset).
363
+ After calling `terminate()`, any attempt to run workers on the factory instance will immediately reject.
364
+
365
+ ### `reset()` / `restart()`
366
+
367
+ Terminates all active/persistent worker instances and restores the factory to an active state (`isTerminated = false`), allowing new worker instances to be initiated cleanly:
368
+
369
+ ```ts
370
+ // Stop existing workers and reset factory state
371
+ factory.reset(); // or factory.restart()
372
+
373
+ console.log(factory.isTerminated); // false
374
+
375
+ // Factory is ready to initiate fresh worker instances again
376
+ const settled = await factory.runWorker('sum', { srcData: [1, 2, 3] });
377
+ ```
318
378
 
319
379
  ---
320
380
 
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var y=Object.defineProperty;var w=(n,e,r)=>e in n?y(n,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):n[e]=r;var h=(n,e,r)=>w(n,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=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
- `,W=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,14 +36,19 @@ 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;
43
+ let stepParams = {};
43
44
 
44
45
  async function processData(data) {
45
46
  try {
46
- const output = await workerFn(data);
47
+ const payload =
48
+ typeof data === 'object' && data !== null && 'data' in data
49
+ ? { ...stepParams, ...data }
50
+ : { data, ...stepParams, index: 0 };
51
+ const output = await workerFn(payload);
47
52
  const result = { ok: true, data: output };
48
53
  const transfers = extractTransferables(output);
49
54
  if (outputPort) {
@@ -63,6 +68,9 @@ async function processData(data) {
63
68
 
64
69
  self.addEventListener('message', (event) => {
65
70
  if (event.data && event.data.__pipeline_ports__) {
71
+ if (event.data.stepParams) {
72
+ stepParams = event.data.stepParams;
73
+ }
66
74
  if (event.data.outputPort) {
67
75
  outputPort = event.data.outputPort;
68
76
  }
@@ -74,7 +82,7 @@ self.addEventListener('message', (event) => {
74
82
  if (outputPort) outputPort.postMessage(e.data);
75
83
  else self.postMessage(e.data);
76
84
  } else {
77
- processData({ data: e.data.data, index: 0 });
85
+ processData({ data: e.data.data, ...stepParams, index: 0 });
78
86
  }
79
87
  };
80
88
  }
@@ -93,7 +101,7 @@ self.addEventListener('message', (event) => {
93
101
  pendingData = event.data;
94
102
  }
95
103
  });
96
- `,M=n=>`
104
+ `,L=l=>`
97
105
  const extractTransferables = (value, seen = new Set()) => {
98
106
  if (value === null || typeof value !== 'object') return [];
99
107
  if (seen.has(value)) return [];
@@ -108,7 +116,7 @@ const extractTransferables = (value, seen = new Set()) => {
108
116
  return Object.values(value).flatMap(v => extractTransferables(v, seen));
109
117
  };
110
118
 
111
- const workerFn = ${n};
119
+ const workerFn = ${l};
112
120
  let cachedDataset = null;
113
121
 
114
122
  self.addEventListener('message', async (event) => {
@@ -149,14 +157,14 @@ self.addEventListener('message', async (event) => {
149
157
  self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
150
158
  }
151
159
  });
152
- `;var m=(n=>(n.Default="default",n.Pipeline="pipeline",n.Persistent="persistent",n))(m||{});const P=Object.freeze({persistent:M,pipeline:W,default:v});class g{constructor(e,r){h(this,"_worker");if(r!=null&&r.createWorker)this._worker=r.createWorker();else if(e){const o=(r==null?void 0:r.mode)??"default",a=P[o](e.toString()),i=new Blob([a],{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 b{constructor(e){this.results=e}}function k(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=>k(r,e)):Object.values(n).flatMap(r=>k(r,e)))}class _{constructor(e){h(this,"_workers");h(this,"_threads");h(this,"_persistentWorkers",new Map);h(this,"_activeWorkers",new Set);h(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 g(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 o=Math.min(r,e.length),a=Math.floor(e.length/o),i=e.length%o,c=[];let u=0;for(let t=0;t<o;t++){const s=a+(t<i?1:0);c.push(e.slice(u,u+s)),u+=s}return c}findWorkerByName(e){return this._workers.find(r=>r.name===e)}async runWorker(e,{srcData:r,...o}){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`));const i=a.maxConcurrency??this._threads,c=!!(Array.isArray(r)&&r.length>1&&a.partition),u=c?this.partitionArray(r,i):r,t=this.createWorkerPromises(a,e,{data:u,...o},i,c),s=await Promise.allSettled(t);return new b(s)}createWorkerPromises(e,r,o,a,i){const{data:c,...u}=o;return Array.from({length:a},(t,s)=>{const f=i&&Array.isArray(c)?c[s]:c;return this.runWorkerWithRetry({workerFunc:e.func,createWorker:e.createWorker,workerName:r,index:s,data:{data:f,...u}},e.retries)})}async runWorkerWithRetry(e,r=2){try{return await this.initiateWorker(e)}catch(o){if(r>0)return console.error(`Worker ${e.index} failed, retrying (${r} left):`,o),this.runWorkerWithRetry(e,r-1);throw console.error("Worker failed after all retries:",o),o}}initiateWorker({workerFunc:e,createWorker:r,workerName:o,index:a,data:i}){return new Promise((c,u)=>{const s=this.initWorker({name:o,role:"",func:e,createWorker:r}).getWorker;s.onerror=l=>{this.terminateWorker(s),u({index:a,workerConfigs:{workerFunc:e,createWorker:r,workerName:o,index:a,data:i},failedResult:l})},s.onmessage=l=>{var d,p;if(((d=l.data)==null?void 0:d.ok)===!1){this.terminateWorker(s),u({index:a,workerConfigs:{workerFunc:e,createWorker:r,workerName:o,index:a,data:i},failedResult:new ErrorEvent("error",{message:l.data.error})});return}c({index:a,workerConfigs:{workerFunc:e,createWorker:r,workerName:o,index:a,data:i},successResult:new MessageEvent("message",{data:(p=l.data)==null?void 0:p.data})}),this.terminateWorker(s)};const f={index:a,...Array.isArray(i)?{data:i}:i};s.postMessage(f,k(f))})}async collectResults(e,r={}){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");const o=e.results.filter(t=>t.status==="fulfilled"),a=e.results.filter(t=>t.status==="rejected"),i=o.map(t=>t.value.successResult.data),c=r.reducer?r.reducer.toString():"(shards) => shards.flat()";return{data:await new Promise((t,s)=>{const f=`
153
- const reducer = ${c};
154
- self.addEventListener('message', (event) => {
155
- try {
156
- const result = reducer(event.data);
157
- self.postMessage({ ok: true, data: result });
158
- } catch (err) {
159
- self.postMessage({ ok: false, error: String(err) });
160
- }
161
- });
162
- `,l=new Blob([f],{type:"application/javascript"}),d=this.trackWorker(new Worker(URL.createObjectURL(l)));d.onmessage=p=>{this.terminateWorker(d),p.data.ok?t(p.data.data):s(new Error(p.data.error))},d.onerror=p=>{this.terminateWorker(d),s(p)},d.postMessage(i)}),succeeded:o.length,failed:a.length,errors:a}}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],o=this.findWorkerByName(r.worker);if(!o)throw new Error(`Worker "${r.worker}" not found`);const i=this.initWorker(o).getWorker;return new Promise((c,u)=>{i.onmessage=s=>{var f,l;this.terminateWorker(i),((f=s.data)==null?void 0:f.ok)===!1?u(new Error(s.data.error)):c((l=s.data)==null?void 0:l.data)},i.onerror=s=>{this.terminateWorker(i),u(s)};const t=r.srcData??{};i.postMessage({data:t,index:0},k(t))})}return new Promise((r,o)=>{const a=[],i=[];for(const t of e){const s=this.findWorkerByName(t.worker);if(!s){o(new Error(`Worker "${t.worker}" not found`));return}const f=new g(s.func,{mode:m.Pipeline,createWorker:s.createWorker}),l=this.trackWorker(f.getWorker);a.push(l)}for(let t=0;t<a.length-1;t++)i.push(new MessageChannel);for(let t=0;t<a.length;t++){const s=[],f={};t>0&&(f.inputPort=i[t-1].port1,s.push(f.inputPort)),t<a.length-1&&(f.outputPort=i[t].port2,s.push(f.outputPort)),a[t].postMessage({__pipeline_ports__:!0,...f},s)}const c=a[a.length-1];c.onmessage=t=>{var s,f;a.forEach(l=>this.terminateWorker(l)),((s=t.data)==null?void 0:s.ok)===!1?o(new Error(t.data.error)):r((f=t.data)==null?void 0:f.data)},c.onerror=t=>{a.forEach(s=>this.terminateWorker(s)),o(t)};const u=e[0].srcData??{};a[0].postMessage({data:u,index:0},k(u))})}async runPersistent(e,r){if(this._isTerminated)throw new Error("MainWorkerFactory has been terminated");const o=this.findWorkerByName(e);if(!o)throw new Error(`Worker "${e}" not found`);let a=this._persistentWorkers.get(e);if(!a){const i=new g(o.func,{mode:m.Persistent,createWorker:o.createWorker});a=this.trackWorker(i.getWorker),this._persistentWorkers.set(e,a)}return new Promise((i,c)=>{a.onmessage=t=>{var s,f;((s=t.data)==null?void 0:s.ok)===!1?c(new Error(t.data.error)):i((f=t.data)==null?void 0:f.data)},a.onerror=t=>{c(t)};const u={type:"run",config:r.config};r.dataset!==void 0&&(u.dataset=r.dataset),a.postMessage(u,k(u))})}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()}}exports.MainWorkerFactory=_;exports.WorkerFactory=g;
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 w = Object.defineProperty;
2
- var v = (n, e, r) => e in n ? w(n, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[e] = r;
3
- var h = (n, e, r) => v(n, typeof e != "symbol" ? e + "" : e, r);
4
- const y = (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
- `, W = (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,14 +39,19 @@ 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;
46
+ let stepParams = {};
46
47
 
47
48
  async function processData(data) {
48
49
  try {
49
- const output = await workerFn(data);
50
+ const payload =
51
+ typeof data === 'object' && data !== null && 'data' in data
52
+ ? { ...stepParams, ...data }
53
+ : { data, ...stepParams, index: 0 };
54
+ const output = await workerFn(payload);
50
55
  const result = { ok: true, data: output };
51
56
  const transfers = extractTransferables(output);
52
57
  if (outputPort) {
@@ -66,6 +71,9 @@ async function processData(data) {
66
71
 
67
72
  self.addEventListener('message', (event) => {
68
73
  if (event.data && event.data.__pipeline_ports__) {
74
+ if (event.data.stepParams) {
75
+ stepParams = event.data.stepParams;
76
+ }
69
77
  if (event.data.outputPort) {
70
78
  outputPort = event.data.outputPort;
71
79
  }
@@ -77,7 +85,7 @@ self.addEventListener('message', (event) => {
77
85
  if (outputPort) outputPort.postMessage(e.data);
78
86
  else self.postMessage(e.data);
79
87
  } else {
80
- processData({ data: e.data.data, index: 0 });
88
+ processData({ data: e.data.data, ...stepParams, index: 0 });
81
89
  }
82
90
  };
83
91
  }
@@ -96,7 +104,7 @@ self.addEventListener('message', (event) => {
96
104
  pendingData = event.data;
97
105
  }
98
106
  });
99
- `, M = (n) => `
107
+ `, C = (l) => `
100
108
  const extractTransferables = (value, seen = new Set()) => {
101
109
  if (value === null || typeof value !== 'object') return [];
102
110
  if (seen.has(value)) return [];
@@ -111,7 +119,7 @@ const extractTransferables = (value, seen = new Set()) => {
111
119
  return Object.values(value).flatMap(v => extractTransferables(v, seen));
112
120
  };
113
121
 
114
- const workerFn = ${n};
122
+ const workerFn = ${l};
115
123
  let cachedDataset = null;
116
124
 
117
125
  self.addEventListener('message', async (event) => {
@@ -153,13 +161,13 @@ self.addEventListener('message', async (event) => {
153
161
  }
154
162
  });
155
163
  `;
156
- var m = /* @__PURE__ */ ((n) => (n.Default = "default", n.Pipeline = "pipeline", n.Persistent = "persistent", n))(m || {});
157
- const P = Object.freeze({
158
- persistent: M,
159
- pipeline: W,
160
- default: y
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
161
169
  });
162
- class g {
170
+ class M {
163
171
  /**
164
172
  * Creates a new `Worker` from the given function or factory option.
165
173
  *
@@ -169,11 +177,11 @@ class g {
169
177
  * @param options - Optional configuration containing `createWorker` or `mode`.
170
178
  */
171
179
  constructor(e, r) {
172
- h(this, "_worker");
180
+ _(this, "_worker");
173
181
  if (r != null && r.createWorker)
174
182
  this._worker = r.createWorker();
175
183
  else if (e) {
176
- const o = (r == null ? void 0 : r.mode) ?? "default", a = P[o](e.toString()), i = new Blob([a], {
184
+ const a = (r == null ? void 0 : r.mode) ?? "default", t = F[a](e.toString()), i = new Blob([t], {
177
185
  type: "application/javascript"
178
186
  });
179
187
  this._worker = new Worker(URL.createObjectURL(i));
@@ -192,28 +200,84 @@ class g {
192
200
  return this._worker;
193
201
  }
194
202
  }
195
- class b {
203
+ class I {
196
204
  constructor(e) {
197
205
  this.results = e;
198
206
  }
199
207
  }
200
- function k(n, e = /* @__PURE__ */ new Set()) {
201
- 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) => k(r, e)) : Object.values(n).flatMap(
202
- (r) => k(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)
203
266
  ));
204
267
  }
205
- class E {
268
+ class q {
206
269
  /**
207
270
  * Creates a new `MainWorkerFactory`.
208
271
  *
209
272
  * @param options - Configuration object containing the `workers` registry.
210
273
  */
211
274
  constructor(e) {
212
- h(this, "_workers");
213
- h(this, "_threads");
214
- h(this, "_persistentWorkers", /* @__PURE__ */ new Map());
215
- h(this, "_activeWorkers", /* @__PURE__ */ new Set());
216
- h(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);
217
281
  this._workers = e.workers, this._threads = navigator.hardwareConcurrency;
218
282
  }
219
283
  /**
@@ -247,7 +311,7 @@ class E {
247
311
  * @returns A new `WorkerFactory` wrapping the worker.
248
312
  */
249
313
  initWorker(e) {
250
- const r = new g(e.func, {
314
+ const r = new M(e.func, {
251
315
  createWorker: e.createWorker
252
316
  });
253
317
  return this.trackWorker(r.getWorker), r;
@@ -271,13 +335,13 @@ class E {
271
335
  partitionArray(e, r) {
272
336
  if (!e.length) return [];
273
337
  if (r <= 0) throw new Error("numChunks must be positive");
274
- const o = Math.min(r, e.length), a = Math.floor(e.length / o), i = e.length % o, c = [];
275
- let u = 0;
276
- for (let t = 0; t < o; t++) {
277
- const s = a + (t < i ? 1 : 0);
278
- c.push(e.slice(u, u + s)), u += s;
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;
279
343
  }
280
- return c;
344
+ return f;
281
345
  }
282
346
  /**
283
347
  * Looks up a registered worker configuration by name.
@@ -314,23 +378,76 @@ class E {
314
378
  * @example
315
379
  * const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3] });
316
380
  */
317
- async runWorker(e, {
318
- srcData: r,
319
- ...o
320
- }) {
381
+ async runWorker(e, r) {
321
382
  if (this._isTerminated)
322
383
  return Promise.reject(new Error("MainWorkerFactory has been terminated"));
323
384
  const a = this.findWorkerByName(e);
324
385
  if (!a)
325
386
  return Promise.reject(new Error(`Worker "${e}" not found`));
326
- const i = a.maxConcurrency ?? this._threads, c = !!(Array.isArray(r) && r.length > 1 && a.partition), u = c ? this.partitionArray(r, i) : r, t = this.createWorkerPromises(
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(
327
398
  a,
328
399
  e,
329
- { data: u, ...o },
330
- i,
331
- c
332
- ), s = await Promise.allSettled(t);
333
- return new b(s);
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();
334
451
  }
335
452
  /**
336
453
  * Builds the array of per-thread worker promises for a single `runWorker`
@@ -346,17 +463,17 @@ class E {
346
463
  * @param isPartitioned - Whether `data` is a pre-split array of shards.
347
464
  * @returns An array of promises, one per thread.
348
465
  */
349
- createWorkerPromises(e, r, o, a, i) {
350
- const { data: c, ...u } = o;
351
- return Array.from({ length: a }, (t, s) => {
352
- const f = i && Array.isArray(c) ? c[s] : 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;
353
470
  return this.runWorkerWithRetry(
354
471
  {
355
472
  workerFunc: e.func,
356
473
  createWorker: e.createWorker,
357
474
  workerName: r,
358
- index: s,
359
- data: { data: f, ...u }
475
+ index: o,
476
+ data: { data: u, ...d }
360
477
  },
361
478
  e.retries
362
479
  );
@@ -377,13 +494,13 @@ class E {
377
494
  async runWorkerWithRetry(e, r = 2) {
378
495
  try {
379
496
  return await this.initiateWorker(e);
380
- } catch (o) {
497
+ } catch (a) {
381
498
  if (r > 0)
382
499
  return console.error(
383
500
  `Worker ${e.index} failed, retrying (${r} left):`,
384
- o
501
+ a
385
502
  ), this.runWorkerWithRetry(e, r - 1);
386
- throw console.error("Worker failed after all retries:", o), o;
503
+ throw console.error("Worker failed after all retries:", a), a;
387
504
  }
388
505
  }
389
506
  /**
@@ -406,66 +523,67 @@ class E {
406
523
  initiateWorker({
407
524
  workerFunc: e,
408
525
  createWorker: r,
409
- workerName: o,
410
- index: a,
526
+ workerName: a,
527
+ index: t,
411
528
  data: i
412
529
  }) {
413
- return new Promise((c, u) => {
414
- const s = this.initWorker({
415
- name: o,
530
+ return new Promise((f, d) => {
531
+ const o = this.initWorker({
532
+ name: a,
416
533
  role: "",
417
534
  func: e,
418
535
  createWorker: r
419
536
  }).getWorker;
420
- s.onerror = (l) => {
421
- this.terminateWorker(s), u({
422
- index: a,
537
+ o.onerror = (m) => {
538
+ this.terminateWorker(o), d({
539
+ index: t,
423
540
  workerConfigs: {
424
541
  workerFunc: e,
425
542
  createWorker: r,
426
- workerName: o,
427
- index: a,
543
+ workerName: a,
544
+ index: t,
428
545
  data: i
429
546
  },
430
- failedResult: l
547
+ failedResult: m
431
548
  });
432
- }, s.onmessage = (l) => {
433
- var d, p;
434
- if (((d = l.data) == null ? void 0 : d.ok) === !1) {
435
- this.terminateWorker(s), u({
436
- index: a,
549
+ }, o.onmessage = (m) => {
550
+ var c, n;
551
+ if (((c = m.data) == null ? void 0 : c.ok) === !1) {
552
+ this.terminateWorker(o), d({
553
+ index: t,
437
554
  workerConfigs: {
438
555
  workerFunc: e,
439
556
  createWorker: r,
440
- workerName: o,
441
- index: a,
557
+ workerName: a,
558
+ index: t,
442
559
  data: i
443
560
  },
444
561
  failedResult: new ErrorEvent("error", {
445
- message: l.data.error
562
+ message: m.data.error
446
563
  })
447
564
  });
448
565
  return;
449
566
  }
450
- c({
451
- index: a,
567
+ const p = ((n = m.data) == null ? void 0 : n.ok) !== void 0 ? m.data.data : m.data;
568
+ f({
569
+ index: t,
452
570
  workerConfigs: {
453
571
  workerFunc: e,
454
572
  createWorker: r,
455
- workerName: o,
456
- index: a,
573
+ workerName: a,
574
+ index: t,
457
575
  data: i
458
576
  },
459
577
  successResult: new MessageEvent("message", {
460
- data: (p = l.data) == null ? void 0 : p.data
578
+ data: p
461
579
  })
462
- }), this.terminateWorker(s);
580
+ }), this.terminateWorker(o);
463
581
  };
464
- const f = {
465
- index: a,
582
+ const u = {
583
+ index: t,
466
584
  ...Array.isArray(i) ? { data: i } : i
467
585
  };
468
- s.postMessage(f, k(f));
586
+ o.postMessage(u, w(u));
469
587
  });
470
588
  }
471
589
  /**
@@ -503,33 +621,68 @@ class E {
503
621
  async collectResults(e, r = {}) {
504
622
  if (this._isTerminated)
505
623
  throw new Error("MainWorkerFactory has been terminated");
506
- const o = e.results.filter(
507
- (t) => t.status === "fulfilled"
508
- ), a = e.results.filter(
509
- (t) => t.status === "rejected"
510
- ), i = o.map((t) => t.value.successResult.data), c = r.reducer ? r.reducer.toString() : "(shards) => shards.flat()";
511
- return {
512
- data: await new Promise((t, s) => {
513
- const f = `
514
- const reducer = ${c};
515
- self.addEventListener('message', (event) => {
516
- try {
517
- const result = reducer(event.data);
518
- self.postMessage({ ok: true, data: result });
519
- } catch (err) {
520
- self.postMessage({ ok: false, error: String(err) });
521
- }
624
+ const a = e.results.filter(
625
+ (p) => p.status === "fulfilled"
626
+ ), t = e.results.filter(
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);
522
672
  });
523
- `, l = new Blob([f], { type: "application/javascript" }), d = this.trackWorker(new Worker(URL.createObjectURL(l)));
524
- d.onmessage = (p) => {
525
- this.terminateWorker(d), p.data.ok ? t(p.data.data) : s(new Error(p.data.error));
526
- }, d.onerror = (p) => {
527
- this.terminateWorker(d), s(p);
528
- }, d.postMessage(i);
529
- }),
530
- succeeded: o.length,
531
- failed: a.length,
532
- errors: a
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,
684
+ failed: t.length,
685
+ errors: t
533
686
  };
534
687
  }
535
688
  /**
@@ -564,58 +717,97 @@ class E {
564
717
  if (e.length === 0)
565
718
  throw new Error("Pipeline requires at least one step");
566
719
  if (e.length === 1) {
567
- const r = e[0], o = this.findWorkerByName(r.worker);
568
- if (!o) throw new Error(`Worker "${r.worker}" not found`);
569
- const i = this.initWorker(o).getWorker;
570
- return new Promise((c, u) => {
571
- i.onmessage = (s) => {
572
- var f, l;
573
- this.terminateWorker(i), ((f = s.data) == null ? void 0 : f.ok) === !1 ? u(new Error(s.data.error)) : c((l = s.data) == null ? void 0 : l.data);
574
- }, i.onerror = (s) => {
575
- this.terminateWorker(i), u(s);
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);
576
729
  };
577
- const t = r.srcData ?? {};
578
- i.postMessage(
579
- { data: t, index: 0 },
580
- k(t)
581
- );
730
+ const p = { data: t ?? {}, ...i, index: 0 };
731
+ s.postMessage(p, w(p));
582
732
  });
583
733
  }
584
- return new Promise((r, o) => {
585
- const a = [], i = [];
586
- for (const t of e) {
587
- const s = this.findWorkerByName(t.worker);
588
- if (!s) {
589
- o(new Error(`Worker "${t.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`));
590
740
  return;
591
741
  }
592
- const f = new g(s.func, {
593
- mode: m.Pipeline,
594
- createWorker: s.createWorker
595
- }), l = this.trackWorker(f.getWorker);
596
- a.push(l);
742
+ const y = new M(h.func, {
743
+ mode: P.Pipeline,
744
+ createWorker: h.createWorker
745
+ }), k = this.trackWorker(y.getWorker);
746
+ t.push(k);
597
747
  }
598
- for (let t = 0; t < a.length - 1; t++)
748
+ for (let n = 0; n < t.length - 1; n++)
599
749
  i.push(new MessageChannel());
600
- for (let t = 0; t < a.length; t++) {
601
- const s = [], f = {};
602
- t > 0 && (f.inputPort = i[t - 1].port1, s.push(f.inputPort)), t < a.length - 1 && (f.outputPort = i[t].port2, s.push(f.outputPort)), a[t].postMessage(
603
- { __pipeline_ports__: !0, ...f },
604
- s
605
- );
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 },
758
+ v
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));
770
+ return;
771
+ }
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);
776
+ };
777
+ }
606
778
  }
607
- const c = a[a.length - 1];
608
- c.onmessage = (t) => {
609
- var s, f;
610
- a.forEach((l) => this.terminateWorker(l)), ((s = t.data) == null ? void 0 : s.ok) === !1 ? o(new Error(t.data.error)) : r((f = t.data) == null ? void 0 : f.data);
611
- }, c.onerror = (t) => {
612
- a.forEach((s) => this.terminateWorker(s)), o(t);
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);
613
785
  };
614
- const u = e[0].srcData ?? {};
615
- a[0].postMessage(
616
- { data: u, index: 0 },
617
- k(u)
618
- );
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));
619
811
  });
620
812
  }
621
813
  /**
@@ -655,28 +847,28 @@ class E {
655
847
  async runPersistent(e, r) {
656
848
  if (this._isTerminated)
657
849
  throw new Error("MainWorkerFactory has been terminated");
658
- const o = this.findWorkerByName(e);
659
- if (!o) throw new Error(`Worker "${e}" not found`);
660
- let a = this._persistentWorkers.get(e);
661
- if (!a) {
662
- const i = new g(o.func, {
663
- mode: m.Persistent,
664
- createWorker: o.createWorker
850
+ const a = this.findWorkerByName(e);
851
+ if (!a) throw new Error(`Worker "${e}" not found`);
852
+ let t = this._persistentWorkers.get(e);
853
+ if (!t) {
854
+ const i = new M(a.func, {
855
+ mode: P.Persistent,
856
+ createWorker: a.createWorker
665
857
  });
666
- a = this.trackWorker(i.getWorker), this._persistentWorkers.set(e, a);
858
+ t = this.trackWorker(i.getWorker), this._persistentWorkers.set(e, t);
667
859
  }
668
- return new Promise((i, c) => {
669
- a.onmessage = (t) => {
670
- var s, f;
671
- ((s = t.data) == null ? void 0 : s.ok) === !1 ? c(new Error(t.data.error)) : i((f = t.data) == null ? void 0 : f.data);
672
- }, a.onerror = (t) => {
673
- c(t);
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);
674
866
  };
675
- const u = {
867
+ const d = {
676
868
  type: "run",
677
869
  config: r.config
678
870
  };
679
- r.dataset !== void 0 && (u.dataset = r.dataset), a.postMessage(u, k(u));
871
+ r.dataset !== void 0 && (d.dataset = r.dataset), t.postMessage(d, w(d));
680
872
  });
681
873
  }
682
874
  /**
@@ -716,7 +908,7 @@ class E {
716
908
  this._persistentWorkers.clear();
717
909
  for (const e of Array.from(this._activeWorkers))
718
910
  this.terminateWorker(e);
719
- this._activeWorkers.clear();
911
+ this._activeWorkers.clear(), this._memoryStore.clear();
720
912
  }
721
913
  /**
722
914
  * Alias for {@link terminate}. Terminates the factory and all worker instances.
@@ -738,7 +930,38 @@ class E {
738
930
  this.reset();
739
931
  }
740
932
  }
933
+ function N(l) {
934
+ if (typeof self > "u") return;
935
+ let e = null, r = null, a = null, t = {};
936
+ const i = (d, s) => {
937
+ self.postMessage(d, s);
938
+ };
939
+ async function f(d) {
940
+ try {
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) {
944
+ const o = {
945
+ ok: !1,
946
+ error: s instanceof Error ? s.message : String(s)
947
+ };
948
+ e ? e.postMessage(o) : i(o);
949
+ }
950
+ }
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);
958
+ return;
959
+ }
960
+ r ? a = s : f(s);
961
+ });
962
+ }
741
963
  export {
742
- E as MainWorkerFactory,
743
- g as WorkerFactory
964
+ q as MainWorkerFactory,
965
+ M as WorkerFactory,
966
+ N as defineWorker
744
967
  };
@@ -0,0 +1,21 @@
1
+ import { WorkerFunction } from './main-worker-factory/types';
2
+ /**
3
+ * Defines and exports a function to run inside a native Web Worker script,
4
+ * providing full compatibility with `MainWorkerFactory` features including
5
+ * standard runs, worker-to-worker pipelines (`foreman.pipeline()`), and
6
+ * dataset caching (`foreman.runPersistent()`).
7
+ *
8
+ * @typeParam TParams - The type of the payload sent to the worker.
9
+ * @typeParam TResult - The return type of the worker function.
10
+ *
11
+ * @param workerFn - The worker execution function.
12
+ *
13
+ * @example
14
+ * // my-native-worker.ts
15
+ * import { defineWorker } from '@offmain/workerkit';
16
+ *
17
+ * export default defineWorker(async ({ data }: { data: number[] }) => {
18
+ * return data.map((x) => x * 2);
19
+ * });
20
+ */
21
+ export declare function defineWorker<TParams = unknown, TResult = unknown>(workerFn: WorkerFunction<TParams, TResult | Promise<TResult>>): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,3 @@
1
1
  export * from './worker-factory';
2
2
  export * from './main-worker-factory';
3
+ export * from './define-worker';
@@ -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
@@ -168,4 +185,6 @@ export interface PipelineStep {
168
185
  worker: string;
169
186
  /** Input data for the first step (subsequent steps receive previous output) */
170
187
  srcData?: unknown;
188
+ /** Any additional step parameters, configs, or options forwarded to the worker payload. */
189
+ [key: string]: unknown;
171
190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@offmain/workerkit",
3
- "version": "0.13.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",