@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
|
|
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 ${
|
|
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
|
-
`,
|
|
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 = ${
|
|
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
|
-
|
|
97
|
-
|
|
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"}),
|
|
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 = (
|
|
3
|
-
var h = (
|
|
4
|
-
const y = (
|
|
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 ${
|
|
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
|
-
`,
|
|
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 = ${
|
|
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 `
|
|
111
|
-
*
|
|
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
|
|
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(
|
|
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
|
|
192
|
+
class b {
|
|
131
193
|
constructor(e) {
|
|
132
194
|
this.results = e;
|
|
133
195
|
}
|
|
134
196
|
}
|
|
135
|
-
function
|
|
136
|
-
return
|
|
137
|
-
(t) =>
|
|
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
|
|
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),
|
|
180
|
-
let
|
|
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
|
|
183
|
-
|
|
245
|
+
const o = a + (r < f ? 1 : 0);
|
|
246
|
+
u.push(e.slice(l, l + o)), l += o;
|
|
184
247
|
}
|
|
185
|
-
return
|
|
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
|
|
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:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
),
|
|
236
|
-
return new
|
|
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,
|
|
253
|
-
const { data:
|
|
254
|
-
return Array.from({ length: a }, (r,
|
|
255
|
-
const i =
|
|
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:
|
|
261
|
-
data: { data: i, ...
|
|
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((
|
|
378
|
+
return new Promise((f, u) => {
|
|
316
379
|
const r = this.initWorker(e).getWorker;
|
|
317
380
|
r.onerror = (i) => {
|
|
318
|
-
r.terminate(),
|
|
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,
|
|
387
|
+
var c, d;
|
|
325
388
|
if (((c = i.data) == null ? void 0 : c.ok) === !1) {
|
|
326
|
-
r.terminate(),
|
|
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
|
-
|
|
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: (
|
|
402
|
+
data: (d = i.data) == null ? void 0 : d.data
|
|
340
403
|
})
|
|
341
404
|
}), r.terminate();
|
|
342
405
|
};
|
|
343
|
-
const
|
|
406
|
+
const o = {
|
|
344
407
|
index: n,
|
|
345
408
|
...Array.isArray(a) ? { data: a } : a
|
|
346
409
|
};
|
|
347
|
-
r.postMessage(
|
|
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
|
-
),
|
|
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,
|
|
452
|
+
data: await new Promise((r, o) => {
|
|
390
453
|
const i = `
|
|
391
|
-
const reducer = ${
|
|
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" }),
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
},
|
|
404
|
-
|
|
405
|
-
},
|
|
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
|
-
* @
|
|
421
|
-
*
|
|
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
|
|
440
|
-
return new Promise((
|
|
441
|
-
|
|
507
|
+
const f = this.initWorker(n.func).getWorker;
|
|
508
|
+
return new Promise((u, l) => {
|
|
509
|
+
f.onmessage = (o) => {
|
|
442
510
|
var i, c;
|
|
443
|
-
|
|
444
|
-
},
|
|
445
|
-
|
|
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
|
-
|
|
516
|
+
f.postMessage(
|
|
449
517
|
{ data: r, index: 0 },
|
|
450
|
-
|
|
518
|
+
p(r)
|
|
451
519
|
);
|
|
452
520
|
});
|
|
453
521
|
}
|
|
454
522
|
return new Promise((t, n) => {
|
|
455
|
-
const a = [],
|
|
523
|
+
const a = [], f = [];
|
|
456
524
|
for (const r of e) {
|
|
457
|
-
const
|
|
458
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
536
|
+
f.push(new MessageChannel());
|
|
467
537
|
for (let r = 0; r < a.length; r++) {
|
|
468
|
-
const
|
|
469
|
-
r > 0 && (i.inputPort =
|
|
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
|
-
|
|
541
|
+
o
|
|
472
542
|
);
|
|
473
543
|
}
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
var
|
|
477
|
-
a.forEach((c) => c.terminate()), ((
|
|
478
|
-
},
|
|
479
|
-
a.forEach((
|
|
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
|
|
551
|
+
const l = e[0].srcData ?? {};
|
|
482
552
|
a[0].postMessage(
|
|
483
|
-
{ data:
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
188
|
-
*
|
|
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
|
-
/**
|
|
4
|
-
|
|
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
|
-
*
|
|
15
|
-
*
|
|
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 `
|
|
29
|
-
*
|
|
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