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