@offmain/workerkit 0.10.0 → 0.11.0

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
@@ -154,6 +154,110 @@ console.log(result); // only this small result crossed to main thread
154
154
 
155
155
  ---
156
156
 
157
+ ## Persistent Workers
158
+
159
+ Keep a worker alive with a cached dataset, then re-run it with different configs without re-sending the data.
160
+
161
+ ### Why use persistent workers?
162
+
163
+ In a typical workflow where you apply multiple transformations to the same dataset, the standard `runWorker` approach re-serializes the entire dataset on every call:
164
+
165
+ ```
166
+ Call 1: Main ──[200k items]──→ Worker → Main
167
+ Call 2: Main ──[200k items]──→ Worker → Main ← same data, different config
168
+ Call 3: Main ──[200k items]──→ Worker → Main ← same data again
169
+ ```
170
+
171
+ With 5 config variations on a 1.6 MB dataset, that's ~8 MB of redundant serialization. Persistent workers eliminate this by caching the dataset inside the worker:
172
+
173
+ ```
174
+ Call 1: Main ──[200k items + config]──→ Worker → Main ← dataset cached
175
+ Call 2: Main ──[config only]──────────→ Worker → Main ← reuses cache
176
+ Call 3: Main ──[config only]──────────→ Worker → Main ← reuses cache
177
+ ```
178
+
179
+ Only the first call transfers the dataset. Subsequent calls send just the config object (typically a few bytes), saving both serialization time and memory pressure.
180
+
181
+ ### Usage
182
+
183
+ ```ts
184
+ import { MainWorkerFactory } from '@offmain/workerkit';
185
+ import { transformArray } from './transform.worker.ts';
186
+
187
+ const factory = new MainWorkerFactory({
188
+ workers: [
189
+ { name: 'transform', role: 'computation', func: transformArray },
190
+ ] as const,
191
+ });
192
+
193
+ // First call: send dataset + config (dataset gets cached in worker memory)
194
+ const r1 = await factory.runPersistent('transform', {
195
+ dataset: largeArray,
196
+ config: { multiplier: 2, filter: 'even' },
197
+ });
198
+
199
+ // Subsequent calls: only config — dataset is reused from cache
200
+ const r2 = await factory.runPersistent('transform', {
201
+ config: { multiplier: 5, filter: 'odd' },
202
+ });
203
+
204
+ const r3 = await factory.runPersistent('transform', {
205
+ config: { multiplier: 1, filter: 'none', limit: 1000 },
206
+ });
207
+
208
+ // Update the dataset when it changes
209
+ const r4 = await factory.runPersistent('transform', {
210
+ dataset: newArray, // replaces cached dataset
211
+ config: { multiplier: 3, filter: 'even' },
212
+ });
213
+
214
+ // Release the worker when done — frees memory
215
+ factory.release('transform');
216
+ ```
217
+
218
+ ### How the worker function receives data
219
+
220
+ The worker function signature stays the same as a regular worker — it receives `{ data, config }`:
221
+
222
+ ```ts
223
+ // transform.worker.ts
224
+ export function transformArray({
225
+ data,
226
+ config,
227
+ }: {
228
+ data: number[];
229
+ config: { multiplier: number; filter: string };
230
+ }) {
231
+ return data
232
+ .filter((n) => /* apply filter */)
233
+ .map((n) => n * config.multiplier);
234
+ }
235
+ ```
236
+
237
+ The framework handles the caching transparently — your function always receives the full `data` (from cache or freshly provided) plus the current `config`.
238
+
239
+ ### When to use persistent vs runWorker
240
+
241
+ | Scenario | Use |
242
+ | ------------------------------------------------------ | --------------- |
243
+ | One-off computation | `runWorker` |
244
+ | Same dataset, multiple config variations | `runPersistent` |
245
+ | Interactive UI where user tweaks params on static data | `runPersistent` |
246
+ | Dataset changes frequently | `runWorker` |
247
+ | Need partitioning across multiple threads | `runWorker` |
248
+
249
+ ### Memory management
250
+
251
+ 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:
252
+
253
+ ```ts
254
+ factory.release('transform');
255
+ ```
256
+
257
+ After releasing, the next `runPersistent` call will create a fresh worker instance (requiring a new dataset).
258
+
259
+ ---
260
+
157
261
  ## ESLint Plugin
158
262
 
159
263
  The package ships with two ESLint rules to catch common worker mistakes at lint time.
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var w=Object.defineProperty;var y=(o,e,t)=>e in o?w(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var h=(o,e,t)=>y(o,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=o=>`
1
+ "use strict";var y=Object.defineProperty;var m=(s,e,t)=>e in s?y(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t;var h=(s,e,t)=>m(s,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=s=>`
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 ${o}(event.data);
18
+ const output = await ${s}(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
- `,v=o=>`
24
+ `,P=s=>`
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 = ${o};
39
+ const workerFn = ${s};
40
40
  let outputPort = null;
41
41
  let inputPort = null;
42
42
  let pendingData = null;
@@ -93,8 +93,64 @@ self.addEventListener('message', (event) => {
93
93
  pendingData = event.data;
94
94
  }
95
95
  });
96
- `;class k{constructor(e,t){h(this,"_worker");const a=(t!=null&&t.pipeline?v:m)(e.toString()),u=new Blob([a],{type:"application/javascript"});this._worker=new Worker(URL.createObjectURL(u))}get getWorker(){return this._worker}}class P{constructor(e){this.results=e}}function g(o,e=new Set){return o===null||typeof o!="object"?[]:e.has(o)?[]:(e.add(o),o instanceof ArrayBuffer||o instanceof MessagePort||typeof ImageBitmap<"u"&&o instanceof ImageBitmap||typeof OffscreenCanvas<"u"&&o instanceof OffscreenCanvas?[o]:ArrayBuffer.isView(o)?[o.buffer]:Array.isArray(o)?o.flatMap(t=>g(t,e)):Object.values(o).flatMap(t=>g(t,e)))}class W{constructor(e){h(this,"_workers");h(this,"_threads");this._workers=e.workers,this._threads=navigator.hardwareConcurrency}initWorker(e){return new k(e)}partitionArray(e,t){if(!e.length)return[];if(t<=0)throw new Error("numChunks must be positive");const n=Math.min(t,e.length),a=Math.floor(e.length/n),u=e.length%n,l=[];let f=0;for(let r=0;r<n;r++){const s=a+(r<u?1:0);l.push(e.slice(f,f+s)),f+=s}return l}findWorkerByName(e){return this._workers.find(t=>t.name===e)}async runWorker(e,{srcData:t,...n}){const a=this.findWorkerByName(e);if(!a)return Promise.reject(new Error(`Worker "${e}" not found`));const u=a.maxConcurrency??this._threads,l=!!(Array.isArray(t)&&t.length>1&&a.partition),f=l?this.partitionArray(t,u):t,r=this.createWorkerPromises(a,e,{data:f,...n},u,l),s=await Promise.allSettled(r);return new P(s)}createWorkerPromises(e,t,n,a,u){const{data:l,...f}=n;return Array.from({length:a},(r,s)=>{const i=u&&Array.isArray(l)?l[s]:l;return this.runWorkerWithRetry({workerFunc:e.func,workerName:t,index:s,data:{data:i,...f}},e.retries)})}async runWorkerWithRetry(e,t=2){try{return await this.initiateWorker(e)}catch(n){if(t>0)return console.error(`Worker ${e.index} failed, retrying (${t} left):`,n),this.runWorkerWithRetry(e,t-1);throw console.error("Worker failed after all retries:",n),n}}initiateWorker({workerFunc:e,workerName:t,index:n,data:a}){return new Promise((u,l)=>{const r=this.initWorker(e).getWorker;r.onerror=i=>{r.terminate(),l({index:n,workerConfigs:{workerFunc:e,workerName:t,index:n,data:a},failedResult:i})},r.onmessage=i=>{var c,p;if(((c=i.data)==null?void 0:c.ok)===!1){r.terminate(),l({index:n,workerConfigs:{workerFunc:e,workerName:t,index:n,data:a},failedResult:new ErrorEvent("error",{message:i.data.error})});return}u({index:n,workerConfigs:{workerFunc:e,workerName:t,index:n,data:a},successResult:new MessageEvent("message",{data:(p=i.data)==null?void 0:p.data})}),r.terminate()};const s={index:n,...Array.isArray(a)?{data:a}:a};r.postMessage(s,g(s))})}async collectResults(e,t={}){const n=e.results.filter(r=>r.status==="fulfilled"),a=e.results.filter(r=>r.status==="rejected"),u=n.map(r=>r.value.successResult.data),l=t.reducer?t.reducer.toString():"(shards) => shards.flat()";return{data:await new Promise((r,s)=>{const i=`
97
- const reducer = ${l};
96
+ `,M=s=>`
97
+ const extractTransferables = (value, seen = new Set()) => {
98
+ if (value === null || typeof value !== 'object') return [];
99
+ if (seen.has(value)) return [];
100
+ seen.add(value);
101
+ if (value instanceof ArrayBuffer || value instanceof MessagePort ||
102
+ (typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap) ||
103
+ (typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas)) {
104
+ return [value];
105
+ }
106
+ if (ArrayBuffer.isView(value)) return [value.buffer];
107
+ if (Array.isArray(value)) return value.flatMap(i => extractTransferables(i, seen));
108
+ return Object.values(value).flatMap(v => extractTransferables(v, seen));
109
+ };
110
+
111
+ const workerFn = ${s};
112
+ let cachedDataset = null;
113
+
114
+ self.addEventListener('message', async (event) => {
115
+ const msg = event.data;
116
+
117
+ if (msg && msg.type === 'release') {
118
+ cachedDataset = null;
119
+ self.postMessage({ ok: true, data: null, type: 'released' });
120
+ self.close();
121
+ return;
122
+ }
123
+
124
+ if (msg && msg.type === 'run') {
125
+ // Update cache if new dataset provided
126
+ if (msg.dataset !== undefined) {
127
+ cachedDataset = msg.dataset;
128
+ }
129
+
130
+ if (cachedDataset === null) {
131
+ self.postMessage({ ok: false, error: 'No dataset cached. Provide a dataset on the first call.' });
132
+ return;
133
+ }
134
+
135
+ try {
136
+ const output = await workerFn({ data: cachedDataset, config: msg.config });
137
+ self.postMessage({ ok: true, data: output }, extractTransferables(output));
138
+ } catch (err) {
139
+ self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
140
+ }
141
+ return;
142
+ }
143
+
144
+ // Fallback: treat as a regular one-shot call for backwards compat
145
+ try {
146
+ const output = await workerFn(msg);
147
+ self.postMessage({ ok: true, data: output }, extractTransferables(output));
148
+ } catch (err) {
149
+ self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
150
+ }
151
+ });
152
+ `;var v=(s=>(s.Default="default",s.Pipeline="pipeline",s.Persistent="persistent",s))(v||{});const W=Object.freeze({persistent:M,pipeline:P,default:w});class k{constructor(e,t){h(this,"_worker");const n=(t==null?void 0:t.mode)??"default",a=W[n](e.toString()),u=new Blob([a],{type:"application/javascript"});this._worker=new Worker(URL.createObjectURL(u))}get getWorker(){return this._worker}}class b{constructor(e){this.results=e}}function p(s,e=new Set){return s===null||typeof s!="object"?[]:e.has(s)?[]:(e.add(s),s instanceof ArrayBuffer||s instanceof MessagePort||typeof ImageBitmap<"u"&&s instanceof ImageBitmap||typeof OffscreenCanvas<"u"&&s instanceof OffscreenCanvas?[s]:ArrayBuffer.isView(s)?[s.buffer]:Array.isArray(s)?s.flatMap(t=>p(t,e)):Object.values(s).flatMap(t=>p(t,e)))}class A{constructor(e){h(this,"_workers");h(this,"_threads");h(this,"_persistentWorkers",new Map);this._workers=e.workers,this._threads=navigator.hardwareConcurrency}initWorker(e){return new k(e)}partitionArray(e,t){if(!e.length)return[];if(t<=0)throw new Error("numChunks must be positive");const n=Math.min(t,e.length),a=Math.floor(e.length/n),u=e.length%n,f=[];let l=0;for(let r=0;r<n;r++){const o=a+(r<u?1:0);f.push(e.slice(l,l+o)),l+=o}return f}findWorkerByName(e){return this._workers.find(t=>t.name===e)}async runWorker(e,{srcData:t,...n}){const a=this.findWorkerByName(e);if(!a)return Promise.reject(new Error(`Worker "${e}" not found`));const u=a.maxConcurrency??this._threads,f=!!(Array.isArray(t)&&t.length>1&&a.partition),l=f?this.partitionArray(t,u):t,r=this.createWorkerPromises(a,e,{data:l,...n},u,f),o=await Promise.allSettled(r);return new b(o)}createWorkerPromises(e,t,n,a,u){const{data:f,...l}=n;return Array.from({length:a},(r,o)=>{const i=u&&Array.isArray(f)?f[o]:f;return this.runWorkerWithRetry({workerFunc:e.func,workerName:t,index:o,data:{data:i,...l}},e.retries)})}async runWorkerWithRetry(e,t=2){try{return await this.initiateWorker(e)}catch(n){if(t>0)return console.error(`Worker ${e.index} failed, retrying (${t} left):`,n),this.runWorkerWithRetry(e,t-1);throw console.error("Worker failed after all retries:",n),n}}initiateWorker({workerFunc:e,workerName:t,index:n,data:a}){return new Promise((u,f)=>{const r=this.initWorker(e).getWorker;r.onerror=i=>{r.terminate(),f({index:n,workerConfigs:{workerFunc:e,workerName:t,index:n,data:a},failedResult:i})},r.onmessage=i=>{var c,d;if(((c=i.data)==null?void 0:c.ok)===!1){r.terminate(),f({index:n,workerConfigs:{workerFunc:e,workerName:t,index:n,data:a},failedResult:new ErrorEvent("error",{message:i.data.error})});return}u({index:n,workerConfigs:{workerFunc:e,workerName:t,index:n,data:a},successResult:new MessageEvent("message",{data:(d=i.data)==null?void 0:d.data})}),r.terminate()};const o={index:n,...Array.isArray(a)?{data:a}:a};r.postMessage(o,p(o))})}async collectResults(e,t={}){const n=e.results.filter(r=>r.status==="fulfilled"),a=e.results.filter(r=>r.status==="rejected"),u=n.map(r=>r.value.successResult.data),f=t.reducer?t.reducer.toString():"(shards) => shards.flat()";return{data:await new Promise((r,o)=>{const i=`
153
+ const reducer = ${f};
98
154
  self.addEventListener('message', (event) => {
99
155
  try {
100
156
  const result = reducer(event.data);
@@ -103,4 +159,4 @@ self.addEventListener('message', (event) => {
103
159
  self.postMessage({ ok: false, error: String(err) });
104
160
  }
105
161
  });
106
- `,c=new Blob([i],{type:"application/javascript"}),p=new Worker(URL.createObjectURL(c));p.onmessage=d=>{p.terminate(),d.data.ok?r(d.data.data):s(new Error(d.data.error))},p.onerror=d=>{p.terminate(),s(d)},p.postMessage(u)}),succeeded:n.length,failed:a.length,errors:a}}async pipeline(e){if(e.length===0)throw new Error("Pipeline requires at least one step");if(e.length===1){const t=e[0],n=this.findWorkerByName(t.worker);if(!n)throw new Error(`Worker "${t.worker}" not found`);const u=this.initWorker(n.func).getWorker;return new Promise((l,f)=>{u.onmessage=s=>{var i,c;u.terminate(),((i=s.data)==null?void 0:i.ok)===!1?f(new Error(s.data.error)):l((c=s.data)==null?void 0:c.data)},u.onerror=s=>{u.terminate(),f(s)};const r=t.srcData??{};u.postMessage({data:r,index:0},g(r))})}return new Promise((t,n)=>{const a=[],u=[];for(const r of e){const s=this.findWorkerByName(r.worker);if(!s){n(new Error(`Worker "${r.worker}" not found`));return}const i=new k(s.func,{pipeline:!0});a.push(i.getWorker)}for(let r=0;r<a.length-1;r++)u.push(new MessageChannel);for(let r=0;r<a.length;r++){const s=[],i={};r>0&&(i.inputPort=u[r-1].port1,s.push(i.inputPort)),r<a.length-1&&(i.outputPort=u[r].port2,s.push(i.outputPort)),a[r].postMessage({__pipeline_ports__:!0,...i},s)}const l=a[a.length-1];l.onmessage=r=>{var s,i;a.forEach(c=>c.terminate()),((s=r.data)==null?void 0:s.ok)===!1?n(new Error(r.data.error)):t((i=r.data)==null?void 0:i.data)},l.onerror=r=>{a.forEach(s=>s.terminate()),n(r)};const f=e[0].srcData??{};a[0].postMessage({data:f,index:0},g(f))})}}exports.MainWorkerFactory=W;exports.WorkerFactory=k;
162
+ `,c=new Blob([i],{type:"application/javascript"}),d=new Worker(URL.createObjectURL(c));d.onmessage=g=>{d.terminate(),g.data.ok?r(g.data.data):o(new Error(g.data.error))},d.onerror=g=>{d.terminate(),o(g)},d.postMessage(u)}),succeeded:n.length,failed:a.length,errors:a}}async pipeline(e){if(e.length===0)throw new Error("Pipeline requires at least one step");if(e.length===1){const t=e[0],n=this.findWorkerByName(t.worker);if(!n)throw new Error(`Worker "${t.worker}" not found`);const u=this.initWorker(n.func).getWorker;return new Promise((f,l)=>{u.onmessage=o=>{var i,c;u.terminate(),((i=o.data)==null?void 0:i.ok)===!1?l(new Error(o.data.error)):f((c=o.data)==null?void 0:c.data)},u.onerror=o=>{u.terminate(),l(o)};const r=t.srcData??{};u.postMessage({data:r,index:0},p(r))})}return new Promise((t,n)=>{const a=[],u=[];for(const r of e){const o=this.findWorkerByName(r.worker);if(!o){n(new Error(`Worker "${r.worker}" not found`));return}const i=new k(o.func,{mode:v.Pipeline});a.push(i.getWorker)}for(let r=0;r<a.length-1;r++)u.push(new MessageChannel);for(let r=0;r<a.length;r++){const o=[],i={};r>0&&(i.inputPort=u[r-1].port1,o.push(i.inputPort)),r<a.length-1&&(i.outputPort=u[r].port2,o.push(i.outputPort)),a[r].postMessage({__pipeline_ports__:!0,...i},o)}const f=a[a.length-1];f.onmessage=r=>{var o,i;a.forEach(c=>c.terminate()),((o=r.data)==null?void 0:o.ok)===!1?n(new Error(r.data.error)):t((i=r.data)==null?void 0:i.data)},f.onerror=r=>{a.forEach(o=>o.terminate()),n(r)};const l=e[0].srcData??{};a[0].postMessage({data:l,index:0},p(l))})}async runPersistent(e,t){const n=this.findWorkerByName(e);if(!n)throw new Error(`Worker "${e}" not found`);let a=this._persistentWorkers.get(e);return a||(a=new k(n.func,{mode:v.Persistent}).getWorker,this._persistentWorkers.set(e,a)),new Promise((u,f)=>{a.onmessage=r=>{var o,i;((o=r.data)==null?void 0:o.ok)===!1?f(new Error(r.data.error)):u((i=r.data)==null?void 0:i.data)},a.onerror=r=>{f(r)};const l={type:"run",config:t.config};t.dataset!==void 0&&(l.dataset=t.dataset),a.postMessage(l,p(l))})}release(e){const t=this._persistentWorkers.get(e);t&&(t.postMessage({type:"release"}),t.terminate(),this._persistentWorkers.delete(e))}}exports.MainWorkerFactory=A;exports.WorkerFactory=k;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  var w = Object.defineProperty;
2
- var m = (o, e, t) => e in o ? w(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
3
- var h = (o, e, t) => m(o, typeof e != "symbol" ? e + "" : e, t);
4
- const y = (o) => `
2
+ var m = (s, e, t) => e in s ? w(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
3
+ var h = (s, e, t) => m(s, typeof e != "symbol" ? e + "" : e, t);
4
+ const y = (s) => `
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 ${o}(event.data);
21
+ const output = await ${s}(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
- `, v = (o) => `
27
+ `, P = (s) => `
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 = ${o};
42
+ const workerFn = ${s};
43
43
  let outputPort = null;
44
44
  let inputPort = null;
45
45
  let pendingData = null;
@@ -96,7 +96,69 @@ self.addEventListener('message', (event) => {
96
96
  pendingData = event.data;
97
97
  }
98
98
  });
99
+ `, M = (s) => `
100
+ const extractTransferables = (value, seen = new Set()) => {
101
+ if (value === null || typeof value !== 'object') return [];
102
+ if (seen.has(value)) return [];
103
+ seen.add(value);
104
+ if (value instanceof ArrayBuffer || value instanceof MessagePort ||
105
+ (typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap) ||
106
+ (typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas)) {
107
+ return [value];
108
+ }
109
+ if (ArrayBuffer.isView(value)) return [value.buffer];
110
+ if (Array.isArray(value)) return value.flatMap(i => extractTransferables(i, seen));
111
+ return Object.values(value).flatMap(v => extractTransferables(v, seen));
112
+ };
113
+
114
+ const workerFn = ${s};
115
+ let cachedDataset = null;
116
+
117
+ self.addEventListener('message', async (event) => {
118
+ const msg = event.data;
119
+
120
+ if (msg && msg.type === 'release') {
121
+ cachedDataset = null;
122
+ self.postMessage({ ok: true, data: null, type: 'released' });
123
+ self.close();
124
+ return;
125
+ }
126
+
127
+ if (msg && msg.type === 'run') {
128
+ // Update cache if new dataset provided
129
+ if (msg.dataset !== undefined) {
130
+ cachedDataset = msg.dataset;
131
+ }
132
+
133
+ if (cachedDataset === null) {
134
+ self.postMessage({ ok: false, error: 'No dataset cached. Provide a dataset on the first call.' });
135
+ return;
136
+ }
137
+
138
+ try {
139
+ const output = await workerFn({ data: cachedDataset, config: msg.config });
140
+ self.postMessage({ ok: true, data: output }, extractTransferables(output));
141
+ } catch (err) {
142
+ self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
143
+ }
144
+ return;
145
+ }
146
+
147
+ // Fallback: treat as a regular one-shot call for backwards compat
148
+ try {
149
+ const output = await workerFn(msg);
150
+ self.postMessage({ ok: true, data: output }, extractTransferables(output));
151
+ } catch (err) {
152
+ self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
153
+ }
154
+ });
99
155
  `;
156
+ var v = /* @__PURE__ */ ((s) => (s.Default = "default", s.Pipeline = "pipeline", s.Persistent = "persistent", s))(v || {});
157
+ const W = Object.freeze({
158
+ persistent: M,
159
+ pipeline: P,
160
+ default: y
161
+ });
100
162
  class k {
101
163
  /**
102
164
  * Creates a new `Worker` from the given function.
@@ -107,15 +169,15 @@ class k {
107
169
  * @param workerFunction - The function to run inside the worker thread.
108
170
  * Must be self-contained — it cannot reference variables from the outer
109
171
  * scope because it is serialised via `.toString()`.
110
- * @param options - Optional configuration. Set `pipeline: true` for
111
- * pipeline-aware workers that support MessagePort forwarding.
172
+ * @param options - Optional configuration. Set `mode` to control the
173
+ * worker execution mode (default, pipeline, or persistent).
112
174
  */
113
175
  constructor(e, t) {
114
176
  h(this, "_worker");
115
- const a = (t != null && t.pipeline ? v : y)(e.toString()), u = new Blob([a], {
177
+ const n = (t == null ? void 0 : t.mode) ?? "default", a = W[n](e.toString()), f = new Blob([a], {
116
178
  type: "application/javascript"
117
179
  });
118
- this._worker = new Worker(URL.createObjectURL(u));
180
+ this._worker = new Worker(URL.createObjectURL(f));
119
181
  }
120
182
  /**
121
183
  * Returns the underlying native `Worker` instance.
@@ -127,17 +189,17 @@ class k {
127
189
  return this._worker;
128
190
  }
129
191
  }
130
- class P {
192
+ class b {
131
193
  constructor(e) {
132
194
  this.results = e;
133
195
  }
134
196
  }
135
- function g(o, e = /* @__PURE__ */ new Set()) {
136
- return o === null || typeof o != "object" ? [] : e.has(o) ? [] : (e.add(o), o instanceof ArrayBuffer || o instanceof MessagePort || typeof ImageBitmap < "u" && o instanceof ImageBitmap || typeof OffscreenCanvas < "u" && o instanceof OffscreenCanvas ? [o] : ArrayBuffer.isView(o) ? [o.buffer] : Array.isArray(o) ? o.flatMap((t) => g(t, e)) : Object.values(o).flatMap(
137
- (t) => g(t, e)
197
+ function p(s, e = /* @__PURE__ */ new Set()) {
198
+ return s === null || typeof s != "object" ? [] : e.has(s) ? [] : (e.add(s), s instanceof ArrayBuffer || s instanceof MessagePort || typeof ImageBitmap < "u" && s instanceof ImageBitmap || typeof OffscreenCanvas < "u" && s instanceof OffscreenCanvas ? [s] : ArrayBuffer.isView(s) ? [s.buffer] : Array.isArray(s) ? s.flatMap((t) => p(t, e)) : Object.values(s).flatMap(
199
+ (t) => p(t, e)
138
200
  ));
139
201
  }
140
- class M {
202
+ class B {
141
203
  /**
142
204
  * Creates a new `MainWorkerFactory`.
143
205
  *
@@ -146,6 +208,7 @@ class M {
146
208
  constructor(e) {
147
209
  h(this, "_workers");
148
210
  h(this, "_threads");
211
+ h(this, "_persistentWorkers", /* @__PURE__ */ new Map());
149
212
  this._workers = e.workers, this._threads = navigator.hardwareConcurrency;
150
213
  }
151
214
  /**
@@ -176,13 +239,13 @@ class M {
176
239
  partitionArray(e, t) {
177
240
  if (!e.length) return [];
178
241
  if (t <= 0) throw new Error("numChunks must be positive");
179
- const n = Math.min(t, e.length), a = Math.floor(e.length / n), u = e.length % n, l = [];
180
- let f = 0;
242
+ const n = Math.min(t, e.length), a = Math.floor(e.length / n), f = e.length % n, u = [];
243
+ let l = 0;
181
244
  for (let r = 0; r < n; r++) {
182
- const s = a + (r < u ? 1 : 0);
183
- l.push(e.slice(f, f + s)), f += s;
245
+ const o = a + (r < f ? 1 : 0);
246
+ u.push(e.slice(l, l + o)), l += o;
184
247
  }
185
- return l;
248
+ return u;
186
249
  }
187
250
  /**
188
251
  * Looks up a registered worker configuration by name.
@@ -226,14 +289,14 @@ class M {
226
289
  const a = this.findWorkerByName(e);
227
290
  if (!a)
228
291
  return Promise.reject(new Error(`Worker "${e}" not found`));
229
- const u = a.maxConcurrency ?? this._threads, l = !!(Array.isArray(t) && t.length > 1 && a.partition), f = l ? this.partitionArray(t, u) : t, r = this.createWorkerPromises(
292
+ const f = a.maxConcurrency ?? this._threads, u = !!(Array.isArray(t) && t.length > 1 && a.partition), l = u ? this.partitionArray(t, f) : t, r = this.createWorkerPromises(
230
293
  a,
231
294
  e,
232
- { data: f, ...n },
233
- u,
234
- l
235
- ), s = await Promise.allSettled(r);
236
- return new P(s);
295
+ { data: l, ...n },
296
+ f,
297
+ u
298
+ ), o = await Promise.allSettled(r);
299
+ return new b(o);
237
300
  }
238
301
  /**
239
302
  * Builds the array of per-thread worker promises for a single `runWorker`
@@ -249,16 +312,16 @@ class M {
249
312
  * @param isPartitioned - Whether `data` is a pre-split array of shards.
250
313
  * @returns An array of promises, one per thread.
251
314
  */
252
- createWorkerPromises(e, t, n, a, u) {
253
- const { data: l, ...f } = n;
254
- return Array.from({ length: a }, (r, s) => {
255
- const i = u && Array.isArray(l) ? l[s] : l;
315
+ createWorkerPromises(e, t, n, a, f) {
316
+ const { data: u, ...l } = n;
317
+ return Array.from({ length: a }, (r, o) => {
318
+ const i = f && Array.isArray(u) ? u[o] : u;
256
319
  return this.runWorkerWithRetry(
257
320
  {
258
321
  workerFunc: e.func,
259
322
  workerName: t,
260
- index: s,
261
- data: { data: i, ...f }
323
+ index: o,
324
+ data: { data: i, ...l }
262
325
  },
263
326
  e.retries
264
327
  );
@@ -312,18 +375,18 @@ class M {
312
375
  index: n,
313
376
  data: a
314
377
  }) {
315
- return new Promise((u, l) => {
378
+ return new Promise((f, u) => {
316
379
  const r = this.initWorker(e).getWorker;
317
380
  r.onerror = (i) => {
318
- r.terminate(), l({
381
+ r.terminate(), u({
319
382
  index: n,
320
383
  workerConfigs: { workerFunc: e, workerName: t, index: n, data: a },
321
384
  failedResult: i
322
385
  });
323
386
  }, r.onmessage = (i) => {
324
- var c, p;
387
+ var c, d;
325
388
  if (((c = i.data) == null ? void 0 : c.ok) === !1) {
326
- r.terminate(), l({
389
+ r.terminate(), u({
327
390
  index: n,
328
391
  workerConfigs: { workerFunc: e, workerName: t, index: n, data: a },
329
392
  failedResult: new ErrorEvent("error", {
@@ -332,19 +395,19 @@ class M {
332
395
  });
333
396
  return;
334
397
  }
335
- u({
398
+ f({
336
399
  index: n,
337
400
  workerConfigs: { workerFunc: e, workerName: t, index: n, data: a },
338
401
  successResult: new MessageEvent("message", {
339
- data: (p = i.data) == null ? void 0 : p.data
402
+ data: (d = i.data) == null ? void 0 : d.data
340
403
  })
341
404
  }), r.terminate();
342
405
  };
343
- const s = {
406
+ const o = {
344
407
  index: n,
345
408
  ...Array.isArray(a) ? { data: a } : a
346
409
  };
347
- r.postMessage(s, g(s));
410
+ r.postMessage(o, p(o));
348
411
  });
349
412
  }
350
413
  /**
@@ -384,11 +447,11 @@ class M {
384
447
  (r) => r.status === "fulfilled"
385
448
  ), a = e.results.filter(
386
449
  (r) => r.status === "rejected"
387
- ), u = n.map((r) => r.value.successResult.data), l = t.reducer ? t.reducer.toString() : "(shards) => shards.flat()";
450
+ ), f = n.map((r) => r.value.successResult.data), u = t.reducer ? t.reducer.toString() : "(shards) => shards.flat()";
388
451
  return {
389
- data: await new Promise((r, s) => {
452
+ data: await new Promise((r, o) => {
390
453
  const i = `
391
- const reducer = ${l};
454
+ const reducer = ${u};
392
455
  self.addEventListener('message', (event) => {
393
456
  try {
394
457
  const result = reducer(event.data);
@@ -397,12 +460,12 @@ class M {
397
460
  self.postMessage({ ok: false, error: String(err) });
398
461
  }
399
462
  });
400
- `, c = new Blob([i], { type: "application/javascript" }), p = new Worker(URL.createObjectURL(c));
401
- p.onmessage = (d) => {
402
- p.terminate(), d.data.ok ? r(d.data.data) : s(new Error(d.data.error));
403
- }, p.onerror = (d) => {
404
- p.terminate(), s(d);
405
- }, p.postMessage(u);
463
+ `, c = new Blob([i], { type: "application/javascript" }), d = new Worker(URL.createObjectURL(c));
464
+ d.onmessage = (g) => {
465
+ d.terminate(), g.data.ok ? r(g.data.data) : o(new Error(g.data.error));
466
+ }, d.onerror = (g) => {
467
+ d.terminate(), o(g);
468
+ }, d.postMessage(f);
406
469
  }),
407
470
  succeeded: n.length,
408
471
  failed: a.length,
@@ -417,13 +480,18 @@ class M {
417
480
  * Only the final result is sent back to the main thread, minimising
418
481
  * serialisation overhead for large intermediate data.
419
482
  *
420
- * @param steps - An ordered array of pipeline steps. The first step must
421
- * include `srcData`; subsequent steps receive the previous step's output.
483
+ * @typeParam TResult - The expected type of the final pipeline output.
484
+ * Defaults to `unknown` if not specified.
485
+ *
486
+ * @param steps - An ordered array of {@link PipelineStep} objects. The first
487
+ * step must include `srcData`; subsequent steps receive the previous
488
+ * step's output as `{ data: previousOutput, index: 0 }`.
422
489
  *
423
490
  * @returns A promise that resolves with the final step's output.
491
+ * @throws {Error} When `steps` is empty or a worker name is not found.
424
492
  *
425
493
  * @example
426
- * const result = await foreman.pipeline([
494
+ * const result = await foreman.pipeline<FilteredPost[]>([
427
495
  * { worker: 'fetchPosts', srcData: { url: '/api/posts' } },
428
496
  * { worker: 'transformPosts' },
429
497
  * { worker: 'filterPosts' },
@@ -436,57 +504,126 @@ class M {
436
504
  if (e.length === 1) {
437
505
  const t = e[0], n = this.findWorkerByName(t.worker);
438
506
  if (!n) throw new Error(`Worker "${t.worker}" not found`);
439
- const u = this.initWorker(n.func).getWorker;
440
- return new Promise((l, f) => {
441
- u.onmessage = (s) => {
507
+ const f = this.initWorker(n.func).getWorker;
508
+ return new Promise((u, l) => {
509
+ f.onmessage = (o) => {
442
510
  var i, c;
443
- u.terminate(), ((i = s.data) == null ? void 0 : i.ok) === !1 ? f(new Error(s.data.error)) : l((c = s.data) == null ? void 0 : c.data);
444
- }, u.onerror = (s) => {
445
- u.terminate(), f(s);
511
+ f.terminate(), ((i = o.data) == null ? void 0 : i.ok) === !1 ? l(new Error(o.data.error)) : u((c = o.data) == null ? void 0 : c.data);
512
+ }, f.onerror = (o) => {
513
+ f.terminate(), l(o);
446
514
  };
447
515
  const r = t.srcData ?? {};
448
- u.postMessage(
516
+ f.postMessage(
449
517
  { data: r, index: 0 },
450
- g(r)
518
+ p(r)
451
519
  );
452
520
  });
453
521
  }
454
522
  return new Promise((t, n) => {
455
- const a = [], u = [];
523
+ const a = [], f = [];
456
524
  for (const r of e) {
457
- const s = this.findWorkerByName(r.worker);
458
- if (!s) {
525
+ const o = this.findWorkerByName(r.worker);
526
+ if (!o) {
459
527
  n(new Error(`Worker "${r.worker}" not found`));
460
528
  return;
461
529
  }
462
- const i = new k(s.func, { pipeline: !0 });
530
+ const i = new k(o.func, {
531
+ mode: v.Pipeline
532
+ });
463
533
  a.push(i.getWorker);
464
534
  }
465
535
  for (let r = 0; r < a.length - 1; r++)
466
- u.push(new MessageChannel());
536
+ f.push(new MessageChannel());
467
537
  for (let r = 0; r < a.length; r++) {
468
- const s = [], i = {};
469
- r > 0 && (i.inputPort = u[r - 1].port1, s.push(i.inputPort)), r < a.length - 1 && (i.outputPort = u[r].port2, s.push(i.outputPort)), a[r].postMessage(
538
+ const o = [], i = {};
539
+ r > 0 && (i.inputPort = f[r - 1].port1, o.push(i.inputPort)), r < a.length - 1 && (i.outputPort = f[r].port2, o.push(i.outputPort)), a[r].postMessage(
470
540
  { __pipeline_ports__: !0, ...i },
471
- s
541
+ o
472
542
  );
473
543
  }
474
- const l = a[a.length - 1];
475
- l.onmessage = (r) => {
476
- var s, i;
477
- a.forEach((c) => c.terminate()), ((s = r.data) == null ? void 0 : s.ok) === !1 ? n(new Error(r.data.error)) : t((i = r.data) == null ? void 0 : i.data);
478
- }, l.onerror = (r) => {
479
- a.forEach((s) => s.terminate()), n(r);
544
+ const u = a[a.length - 1];
545
+ u.onmessage = (r) => {
546
+ var o, i;
547
+ a.forEach((c) => c.terminate()), ((o = r.data) == null ? void 0 : o.ok) === !1 ? n(new Error(r.data.error)) : t((i = r.data) == null ? void 0 : i.data);
548
+ }, u.onerror = (r) => {
549
+ a.forEach((o) => o.terminate()), n(r);
480
550
  };
481
- const f = e[0].srcData ?? {};
551
+ const l = e[0].srcData ?? {};
482
552
  a[0].postMessage(
483
- { data: f, index: 0 },
484
- g(f)
553
+ { data: l, index: 0 },
554
+ p(l)
485
555
  );
486
556
  });
487
557
  }
558
+ /**
559
+ * Runs a persistent worker that caches its dataset between calls.
560
+ *
561
+ * On the first call, provide both `dataset` and `config`. The worker stores
562
+ * the dataset in memory. On subsequent calls, only `config` is needed — the
563
+ * worker reuses the cached dataset and reprocesses it with the new config.
564
+ *
565
+ * The worker stays alive until {@link release} is called.
566
+ *
567
+ * @param workerName - Name of the registered worker.
568
+ * @param params - Object with optional `dataset` and required `config`.
569
+ * @returns The worker function's return value.
570
+ *
571
+ * @example
572
+ * // First call: send dataset + config
573
+ * const r1 = await factory.runPersistent('transform', {
574
+ * dataset: largeArray,
575
+ * config: { multiplier: 2 },
576
+ * });
577
+ *
578
+ * // Subsequent calls: only config, dataset is cached
579
+ * const r2 = await factory.runPersistent('transform', {
580
+ * config: { multiplier: 5 },
581
+ * });
582
+ *
583
+ * // Update dataset when needed
584
+ * const r3 = await factory.runPersistent('transform', {
585
+ * dataset: newArray,
586
+ * config: { multiplier: 3 },
587
+ * });
588
+ *
589
+ * // Release when done
590
+ * factory.release('transform');
591
+ */
592
+ async runPersistent(e, t) {
593
+ const n = this.findWorkerByName(e);
594
+ if (!n) throw new Error(`Worker "${e}" not found`);
595
+ let a = this._persistentWorkers.get(e);
596
+ return a || (a = new k(n.func, {
597
+ mode: v.Persistent
598
+ }).getWorker, this._persistentWorkers.set(e, a)), new Promise((f, u) => {
599
+ a.onmessage = (r) => {
600
+ var o, i;
601
+ ((o = r.data) == null ? void 0 : o.ok) === !1 ? u(new Error(r.data.error)) : f((i = r.data) == null ? void 0 : i.data);
602
+ }, a.onerror = (r) => {
603
+ u(r);
604
+ };
605
+ const l = {
606
+ type: "run",
607
+ config: t.config
608
+ };
609
+ t.dataset !== void 0 && (l.dataset = t.dataset), a.postMessage(l, p(l));
610
+ });
611
+ }
612
+ /**
613
+ * Releases a persistent worker, freeing its cached dataset and terminating
614
+ * the thread.
615
+ *
616
+ * After calling `release`, subsequent `runPersistent` calls for this worker
617
+ * will create a fresh instance (requiring a new dataset).
618
+ *
619
+ * @param workerName - Name of the persistent worker to release.
620
+ */
621
+ release(e) {
622
+ const t = this._persistentWorkers.get(e);
623
+ t && (t.postMessage({ type: "release" }), t.terminate(), this._persistentWorkers.delete(e));
624
+ }
488
625
  }
489
626
  export {
490
- M as MainWorkerFactory,
627
+ B as MainWorkerFactory,
491
628
  k as WorkerFactory
492
629
  };
@@ -12,6 +12,13 @@ export declare function extractTransferables(value: unknown, seen?: Set<object>)
12
12
  * handles the full lifecycle of each worker: spawning, partitioning input
13
13
  * data across threads, retrying on failure, and collecting results.
14
14
  *
15
+ * Also supports:
16
+ * - **Pipelines** — chain workers via `MessageChannel` so intermediate data
17
+ * never crosses back to the main thread ({@link pipeline}).
18
+ * - **Persistent workers** — keep a worker alive with a cached dataset,
19
+ * re-running it with different configs without re-sending the data
20
+ * ({@link runPersistent}, {@link release}).
21
+ *
15
22
  * @typeParam TConfigs - A readonly tuple of {@link WorkerConfig} objects that
16
23
  * defines the set of available workers and their typed signatures.
17
24
  *
@@ -19,7 +26,7 @@ export declare function extractTransferables(value: unknown, seen?: Set<object>)
19
26
  * const foreman = new MainWorkerFactory({
20
27
  * workers: [
21
28
  * { name: 'sum', role: 'compute', func: sumWorker, partition: true },
22
- * ],
29
+ * ] as const,
23
30
  * });
24
31
  *
25
32
  * const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3, 4] });
@@ -28,6 +35,7 @@ export declare function extractTransferables(value: unknown, seen?: Set<object>)
28
35
  declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFunction<any, any>>[]> {
29
36
  private readonly _workers;
30
37
  private readonly _threads;
38
+ private readonly _persistentWorkers;
31
39
  /**
32
40
  * Creates a new `MainWorkerFactory`.
33
41
  *
@@ -184,13 +192,18 @@ declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFun
184
192
  * Only the final result is sent back to the main thread, minimising
185
193
  * serialisation overhead for large intermediate data.
186
194
  *
187
- * @param steps - An ordered array of pipeline steps. The first step must
188
- * include `srcData`; subsequent steps receive the previous step's output.
195
+ * @typeParam TResult - The expected type of the final pipeline output.
196
+ * Defaults to `unknown` if not specified.
197
+ *
198
+ * @param steps - An ordered array of {@link PipelineStep} objects. The first
199
+ * step must include `srcData`; subsequent steps receive the previous
200
+ * step's output as `{ data: previousOutput, index: 0 }`.
189
201
  *
190
202
  * @returns A promise that resolves with the final step's output.
203
+ * @throws {Error} When `steps` is empty or a worker name is not found.
191
204
  *
192
205
  * @example
193
- * const result = await foreman.pipeline([
206
+ * const result = await foreman.pipeline<FilteredPost[]>([
194
207
  * { worker: 'fetchPosts', srcData: { url: '/api/posts' } },
195
208
  * { worker: 'transformPosts' },
196
209
  * { worker: 'filterPosts' },
@@ -198,5 +211,53 @@ declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFun
198
211
  * console.log(result); // final transformed + filtered data
199
212
  */
200
213
  pipeline<TResult = unknown>(steps: PipelineStep[]): Promise<TResult>;
214
+ /**
215
+ * Runs a persistent worker that caches its dataset between calls.
216
+ *
217
+ * On the first call, provide both `dataset` and `config`. The worker stores
218
+ * the dataset in memory. On subsequent calls, only `config` is needed — the
219
+ * worker reuses the cached dataset and reprocesses it with the new config.
220
+ *
221
+ * The worker stays alive until {@link release} is called.
222
+ *
223
+ * @param workerName - Name of the registered worker.
224
+ * @param params - Object with optional `dataset` and required `config`.
225
+ * @returns The worker function's return value.
226
+ *
227
+ * @example
228
+ * // First call: send dataset + config
229
+ * const r1 = await factory.runPersistent('transform', {
230
+ * dataset: largeArray,
231
+ * config: { multiplier: 2 },
232
+ * });
233
+ *
234
+ * // Subsequent calls: only config, dataset is cached
235
+ * const r2 = await factory.runPersistent('transform', {
236
+ * config: { multiplier: 5 },
237
+ * });
238
+ *
239
+ * // Update dataset when needed
240
+ * const r3 = await factory.runPersistent('transform', {
241
+ * dataset: newArray,
242
+ * config: { multiplier: 3 },
243
+ * });
244
+ *
245
+ * // Release when done
246
+ * factory.release('transform');
247
+ */
248
+ runPersistent<TResult = unknown>(workerName: string, params: {
249
+ dataset?: unknown;
250
+ config: unknown;
251
+ }): Promise<TResult>;
252
+ /**
253
+ * Releases a persistent worker, freeing its cached dataset and terminating
254
+ * the thread.
255
+ *
256
+ * After calling `release`, subsequent `runPersistent` calls for this worker
257
+ * will create a fresh instance (requiring a new dataset).
258
+ *
259
+ * @param workerName - Name of the persistent worker to release.
260
+ */
261
+ release(workerName: string): void;
201
262
  }
202
263
  export default MainWorkerFactory;
@@ -1,7 +1,12 @@
1
1
  import { WorkerFunction } from '../main-worker-factory/types';
2
+ export declare enum WorkerMode {
3
+ Default = "default",
4
+ Pipeline = "pipeline",
5
+ Persistent = "persistent"
6
+ }
2
7
  export interface WorkerFactoryOptions {
3
- /** When true, generates a pipeline-aware worker that supports MessagePort forwarding */
4
- pipeline?: boolean;
8
+ /** The worker execution mode. Defaults to `WorkerMode.Default`. */
9
+ mode?: WorkerMode;
5
10
  }
6
11
  /**
7
12
  * Low-level factory that serialises a {@link WorkerFunction} into a Blob URL
@@ -11,8 +16,18 @@ export interface WorkerFactoryOptions {
11
16
  * It handles the mechanics of turning a plain TypeScript function into a
12
17
  * runnable worker thread — you rarely need to use it directly.
13
18
  *
14
- * The worker script is generated by {@link workerTemplate}, which wraps the
15
- * function with a message listener and transferable-extraction logic.
19
+ * Supports three execution modes via {@link WorkerMode}:
20
+ * - **Default** — one-shot worker that processes a single message and is
21
+ * terminated after responding.
22
+ * - **Pipeline** — stays alive and forwards output to the next worker via
23
+ * `MessagePort`, enabling worker-to-worker data flow without main-thread
24
+ * round-trips.
25
+ * - **Persistent** — stays alive indefinitely, caches a dataset in memory,
26
+ * and re-processes it with different configs on subsequent messages.
27
+ *
28
+ * The worker script is generated by the template corresponding to the chosen
29
+ * mode, which wraps the function with a message listener and
30
+ * transferable-extraction logic.
16
31
  */
17
32
  declare class WorkerFactory {
18
33
  readonly _worker: Worker;
@@ -25,8 +40,8 @@ declare class WorkerFactory {
25
40
  * @param workerFunction - The function to run inside the worker thread.
26
41
  * Must be self-contained — it cannot reference variables from the outer
27
42
  * scope because it is serialised via `.toString()`.
28
- * @param options - Optional configuration. Set `pipeline: true` for
29
- * pipeline-aware workers that support MessagePort forwarding.
43
+ * @param options - Optional configuration. Set `mode` to control the
44
+ * worker execution mode (default, pipeline, or persistent).
30
45
  */
31
46
  constructor(workerFunction: WorkerFunction, options?: WorkerFactoryOptions);
32
47
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@offmain/workerkit",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
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",