@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 +91 -31
- package/dist/index.cjs +27 -19
- package/dist/index.js +394 -171
- package/dist/types/tools/define-worker.d.ts +21 -0
- package/dist/types/tools/define-worker.test.d.ts +1 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/main-worker-factory/main-worker-factory.d.ts +27 -4
- package/dist/types/tools/main-worker-factory/memory-store.d.ts +45 -0
- package/dist/types/tools/main-worker-factory/memory-worker.d.ts +7 -0
- package/dist/types/tools/main-worker-factory/types.d.ts +19 -0
- package/package.json +1 -1
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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,
|
|
203
|
-
- Each subsequent step receives the previous step's output as `{ data: previousOutput, index: 0 }`.
|
|
204
|
-
- Worker functions
|
|
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
|
|
209
|
-
|
|
|
210
|
-
| Single step, or steps that need partitioning
|
|
211
|
-
| Multi-step chain where intermediate data is large
|
|
212
|
-
| Steps that are independent (not sequential)
|
|
213
|
-
| Steps where only the final result matters to the UI
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ${
|
|
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
|
-
`,
|
|
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 = ${
|
|
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
|
|
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
|
-
`,
|
|
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 = ${
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
const
|
|
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 ${
|
|
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
|
-
`,
|
|
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 = ${
|
|
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
|
|
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
|
-
`,
|
|
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 = ${
|
|
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
|
|
157
|
-
const
|
|
158
|
-
persistent:
|
|
159
|
-
pipeline:
|
|
160
|
-
default:
|
|
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
|
|
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
|
-
|
|
180
|
+
_(this, "_worker");
|
|
173
181
|
if (r != null && r.createWorker)
|
|
174
182
|
this._worker = r.createWorker();
|
|
175
183
|
else if (e) {
|
|
176
|
-
const
|
|
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
|
|
203
|
+
class I {
|
|
196
204
|
constructor(e) {
|
|
197
205
|
this.results = e;
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
(
|
|
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
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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
|
|
275
|
-
let
|
|
276
|
-
for (let
|
|
277
|
-
const
|
|
278
|
-
|
|
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
|
|
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
|
-
|
|
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, ...
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
),
|
|
333
|
-
return
|
|
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,
|
|
350
|
-
const { data:
|
|
351
|
-
return Array.from({ length:
|
|
352
|
-
const
|
|
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:
|
|
359
|
-
data: { data:
|
|
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 (
|
|
497
|
+
} catch (a) {
|
|
381
498
|
if (r > 0)
|
|
382
499
|
return console.error(
|
|
383
500
|
`Worker ${e.index} failed, retrying (${r} left):`,
|
|
384
|
-
|
|
501
|
+
a
|
|
385
502
|
), this.runWorkerWithRetry(e, r - 1);
|
|
386
|
-
throw console.error("Worker failed after all retries:",
|
|
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:
|
|
410
|
-
index:
|
|
526
|
+
workerName: a,
|
|
527
|
+
index: t,
|
|
411
528
|
data: i
|
|
412
529
|
}) {
|
|
413
|
-
return new Promise((
|
|
414
|
-
const
|
|
415
|
-
name:
|
|
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
|
-
|
|
421
|
-
this.terminateWorker(
|
|
422
|
-
index:
|
|
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:
|
|
427
|
-
index:
|
|
543
|
+
workerName: a,
|
|
544
|
+
index: t,
|
|
428
545
|
data: i
|
|
429
546
|
},
|
|
430
|
-
failedResult:
|
|
547
|
+
failedResult: m
|
|
431
548
|
});
|
|
432
|
-
},
|
|
433
|
-
var
|
|
434
|
-
if (((
|
|
435
|
-
this.terminateWorker(
|
|
436
|
-
index:
|
|
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:
|
|
441
|
-
index:
|
|
557
|
+
workerName: a,
|
|
558
|
+
index: t,
|
|
442
559
|
data: i
|
|
443
560
|
},
|
|
444
561
|
failedResult: new ErrorEvent("error", {
|
|
445
|
-
message:
|
|
562
|
+
message: m.data.error
|
|
446
563
|
})
|
|
447
564
|
});
|
|
448
565
|
return;
|
|
449
566
|
}
|
|
450
|
-
|
|
451
|
-
|
|
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:
|
|
456
|
-
index:
|
|
573
|
+
workerName: a,
|
|
574
|
+
index: t,
|
|
457
575
|
data: i
|
|
458
576
|
},
|
|
459
577
|
successResult: new MessageEvent("message", {
|
|
460
|
-
data:
|
|
578
|
+
data: p
|
|
461
579
|
})
|
|
462
|
-
}), this.terminateWorker(
|
|
580
|
+
}), this.terminateWorker(o);
|
|
463
581
|
};
|
|
464
|
-
const
|
|
465
|
-
index:
|
|
582
|
+
const u = {
|
|
583
|
+
index: t,
|
|
466
584
|
...Array.isArray(i) ? { data: i } : i
|
|
467
585
|
};
|
|
468
|
-
|
|
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
|
|
507
|
-
(
|
|
508
|
-
),
|
|
509
|
-
(
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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],
|
|
568
|
-
if (!
|
|
569
|
-
const
|
|
570
|
-
return new Promise((
|
|
571
|
-
|
|
572
|
-
var
|
|
573
|
-
this.terminateWorker(
|
|
574
|
-
},
|
|
575
|
-
this.terminateWorker(
|
|
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
|
|
578
|
-
|
|
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,
|
|
585
|
-
const
|
|
586
|
-
for (const
|
|
587
|
-
const
|
|
588
|
-
if (!
|
|
589
|
-
|
|
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
|
|
593
|
-
mode:
|
|
594
|
-
createWorker:
|
|
595
|
-
}),
|
|
596
|
-
|
|
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
|
|
748
|
+
for (let n = 0; n < t.length - 1; n++)
|
|
599
749
|
i.push(new MessageChannel());
|
|
600
|
-
for (let
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
608
|
-
|
|
609
|
-
var
|
|
610
|
-
|
|
611
|
-
},
|
|
612
|
-
|
|
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
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
|
659
|
-
if (!
|
|
660
|
-
let
|
|
661
|
-
if (!
|
|
662
|
-
const i = new
|
|
663
|
-
mode:
|
|
664
|
-
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
|
-
|
|
858
|
+
t = this.trackWorker(i.getWorker), this._persistentWorkers.set(e, t);
|
|
667
859
|
}
|
|
668
|
-
return new Promise((i,
|
|
669
|
-
|
|
670
|
-
var
|
|
671
|
-
((
|
|
672
|
-
},
|
|
673
|
-
|
|
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
|
|
867
|
+
const d = {
|
|
676
868
|
type: "run",
|
|
677
869
|
config: r.config
|
|
678
870
|
};
|
|
679
|
-
r.dataset !== void 0 && (
|
|
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
|
-
|
|
743
|
-
|
|
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,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,
|
|
119
|
-
srcData
|
|
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