@offmain/workerkit 0.8.8 → 0.9.2
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 +21 -19
- package/dist/index.cjs +9 -6
- package/dist/index.js +217 -61
- package/dist/types/tools/main-worker-factory/main-worker-factory.d.ts +149 -11
- package/dist/types/tools/main-worker-factory/types.d.ts +98 -5
- package/dist/types/tools/worker-factory/worker-factory.d.ts +27 -0
- package/dist/types/workers/initiator.d.ts +16 -0
- package/package.json +8 -6
- package/dist/types/workers/initiator.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight TypeScript library for running functions in Web Workers with support for partitioning, retries, and concurrency control — all without the boilerplate.
|
|
4
4
|
|
|
5
|
-
Instead of manually creating worker scripts and wiring up `postMessage` / `onmessage`, you write plain exported functions and hand them to `MainWorkerFactory`. The library
|
|
5
|
+
Instead of manually creating worker scripts and wiring up `postMessage` / `onmessage`, you write plain exported functions and hand them to `MainWorkerFactory`. The library serializes them into Blob workers, manages threads per function, handles retries on failure, and merges results back on the main thread.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install workerkit
|
|
12
|
+
npm install @offmain/workerkit
|
|
13
13
|
# or
|
|
14
|
-
pnpm add workerkit
|
|
14
|
+
pnpm add @offmain/workerkit
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
---
|
|
@@ -32,10 +32,10 @@ export function sum({ data }: { data: number[] }): number {
|
|
|
32
32
|
### 2. Register and run it
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
import { MainWorkerFactory } from 'workerkit';
|
|
35
|
+
import { MainWorkerFactory } from '@offmain/workerkit';
|
|
36
36
|
import { sum } from './sum.worker.ts';
|
|
37
37
|
|
|
38
|
-
const factory = new MainWorkerFactory(
|
|
38
|
+
const factory = new MainWorkerFactory({
|
|
39
39
|
workers: [
|
|
40
40
|
{
|
|
41
41
|
name: 'sum',
|
|
@@ -47,10 +47,10 @@ const factory = new MainWorkerFactory(initiator, {
|
|
|
47
47
|
],
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
const
|
|
51
|
-
const { data } = await factory.collectResults(
|
|
50
|
+
const settled = await factory.runWorker('sum', { srcData: [1, 2, 3, 4, 5] });
|
|
51
|
+
const { data } = await factory.collectResults(settled);
|
|
52
52
|
|
|
53
|
-
console.log(data); // 15
|
|
53
|
+
console.log(data); // [15]
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
---
|
|
@@ -63,7 +63,7 @@ console.log(data); // 15
|
|
|
63
63
|
| `role` | `string` | — | Logical grouping label |
|
|
64
64
|
| `func` | `Function` | — | The exported worker function to run |
|
|
65
65
|
| `maxConcurrency` | `number` | `navigator.hardwareConcurrency` | Max parallel worker instances — defaults to the number of logical CPU cores reported by the browser |
|
|
66
|
-
| `retries` | `number` | `0` | How many times to retry a failed shard
|
|
66
|
+
| `retries` | `number` | `0` | How many times to retry a failed shard before marking it as rejected |
|
|
67
67
|
| `partition` | `boolean` | `false` | Split array input across multiple workers automatically |
|
|
68
68
|
|
|
69
69
|
---
|
|
@@ -73,21 +73,23 @@ console.log(data); // 15
|
|
|
73
73
|
When `partition: true`, an array passed as `srcData` is automatically split across worker instances and results are merged back.
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
|
-
const
|
|
76
|
+
const settled = await factory.runWorker('sum', {
|
|
77
77
|
srcData: largeArray, // split across workers
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
const { data, succeeded, failed } = await factory.collectResults(
|
|
80
|
+
const { data, succeeded, failed } = await factory.collectResults(settled);
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
You can also provide a custom reducer to control how shard results are merged:
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
|
-
const { data } = await factory.collectResults(
|
|
86
|
+
const { data } = await factory.collectResults(settled, {
|
|
87
87
|
reducer: (shards) => shards.flat().sort((a, b) => b.score - a.score),
|
|
88
88
|
});
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
> **Note:** The reducer runs inside a worker thread and must be self-contained — it cannot reference variables from the outer scope.
|
|
92
|
+
|
|
91
93
|
---
|
|
92
94
|
|
|
93
95
|
## ESLint Plugin
|
|
@@ -98,7 +100,7 @@ The package ships with two ESLint rules to catch common worker mistakes at lint
|
|
|
98
100
|
|
|
99
101
|
```js
|
|
100
102
|
// eslint.config.js
|
|
101
|
-
import workerPlugin from 'workerkit/eslint-plugin';
|
|
103
|
+
import workerPlugin from '@offmain/workerkit/eslint-plugin';
|
|
102
104
|
|
|
103
105
|
export default [...workerPlugin.configs.recommended];
|
|
104
106
|
```
|
|
@@ -113,16 +115,16 @@ Flags usage of browser main-thread-only APIs that are unavailable inside Web Wor
|
|
|
113
115
|
|
|
114
116
|
```ts
|
|
115
117
|
// sum.worker.ts ❌ — will be flagged
|
|
116
|
-
export function sum({ data }:
|
|
118
|
+
export function sum({ data }: { data: number[] }) {
|
|
117
119
|
document.title = 'working...'; // Error: 'document' is not available inside Web Workers
|
|
118
|
-
return data.reduce((a
|
|
120
|
+
return data.reduce((a, b) => a + b, 0);
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
122
124
|
```ts
|
|
123
125
|
// sum.worker.ts ✅
|
|
124
|
-
export function sum({ data }:
|
|
125
|
-
return data.reduce((a
|
|
126
|
+
export function sum({ data }: { data: number[] }) {
|
|
127
|
+
return data.reduce((a, b) => a + b, 0);
|
|
126
128
|
}
|
|
127
129
|
```
|
|
128
130
|
|
|
@@ -150,8 +152,8 @@ You can also import rules individually if you don't want the full recommended co
|
|
|
150
152
|
|
|
151
153
|
```js
|
|
152
154
|
// eslint.config.js
|
|
153
|
-
import noDomInWorker from 'workerkit/eslint-rules/no-dom-in-worker';
|
|
154
|
-
import workerExportable from 'workerkit/eslint-rules/worker-exportable';
|
|
155
|
+
import noDomInWorker from '@offmain/workerkit/eslint-rules/no-dom-in-worker';
|
|
156
|
+
import workerExportable from '@offmain/workerkit/eslint-rules/worker-exportable';
|
|
155
157
|
|
|
156
158
|
export default [
|
|
157
159
|
{
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var y=Object.defineProperty;var m=(t,e,r)=>e in t?y(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var g=(t,e,r)=>m(t,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=t=>`
|
|
2
2
|
const extractTransferables = (value, seen = new Set()) => {
|
|
3
3
|
if (value === null || typeof value !== 'object') return [];
|
|
4
4
|
if (seen.has(value)) return [];
|
|
@@ -14,11 +14,14 @@ const extractTransferables = (value, seen = new Set()) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
self.addEventListener('message', async (event) => {
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
18
|
const output = await ${t}(event.data);
|
|
19
|
-
self.postMessage(output, extractTransferables(output));
|
|
20
|
-
})
|
|
21
|
-
|
|
19
|
+
self.postMessage({ ok: true, data: output }, extractTransferables(output));
|
|
20
|
+
} catch (err) {
|
|
21
|
+
self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
`;class p{constructor(e){g(this,"_worker");const r=w(e.toString()),s=new Blob([r],{type:"application/javascript"});this._worker=new Worker(URL.createObjectURL(s))}get getWorker(){return this._worker}}class W{constructor(e){this.results=e}}function k(t,e=new Set){return t===null||typeof t!="object"?[]:e.has(t)?[]:(e.add(t),t instanceof ArrayBuffer||t instanceof MessagePort||typeof ImageBitmap<"u"&&t instanceof ImageBitmap||typeof OffscreenCanvas<"u"&&t instanceof OffscreenCanvas?[t]:ArrayBuffer.isView(t)?[t.buffer]:Array.isArray(t)?t.flatMap(r=>k(r,e)):Object.values(t).flatMap(r=>k(r,e)))}class v{constructor(e){g(this,"_workers");g(this,"_threads");this._workers=e.workers,this._threads=navigator.hardwareConcurrency}initWorker(e){return new p(e)}partitionArray(e,r){if(!e.length)return[];if(r<=0)throw new Error("numChunks must be positive");const s=Math.min(r,e.length),n=Math.floor(e.length/s),c=e.length%s,o=[];let f=0;for(let a=0;a<s;a++){const i=n+(a<c?1:0);o.push(e.slice(f,f+i)),f+=i}return o}findWorkerByName(e){return this._workers.find(r=>r.name===e)}async runWorker(e,{srcData:r,...s}){const n=this.findWorkerByName(e);if(!n)return Promise.reject(new Error(`Worker "${e}" not found`));const c=n.maxConcurrency??this._threads,o=!!(Array.isArray(r)&&r.length>1&&n.partition),f=o?this.partitionArray(r,c):r,a=this.createWorkerPromises(n,e,{data:f,...s},c,o),i=await Promise.allSettled(a);return new W(i)}createWorkerPromises(e,r,s,n,c){const{data:o,...f}=s;return Array.from({length:n},(a,i)=>{const l=c&&Array.isArray(o)?o[i]:o;return this.runWorkerWithRetry({workerFunc:e.func,workerName:r,index:i,data:{data:l,...f}},e.retries)})}async runWorkerWithRetry(e,r=2){try{return await this.initiateWorker(e)}catch(s){if(r>0)return console.error(`Worker ${e.index} failed, retrying (${r} left):`,s),this.runWorkerWithRetry(e,r-1);throw console.error("Worker failed after all retries:",s),s}}initiateWorker({workerFunc:e,workerName:r,index:s,data:n}){return new Promise((c,o)=>{const a=this.initWorker(e).getWorker;a.onerror=l=>{a.terminate(),o({index:s,workerConfigs:{workerFunc:e,workerName:r,index:s,data:n},failedResult:l})},a.onmessage=l=>{var h,u;if(((h=l.data)==null?void 0:h.ok)===!1){a.terminate(),o({index:s,workerConfigs:{workerFunc:e,workerName:r,index:s,data:n},failedResult:new ErrorEvent("error",{message:l.data.error})});return}c({index:s,workerConfigs:{workerFunc:e,workerName:r,index:s,data:n},successResult:new MessageEvent("message",{data:(u=l.data)==null?void 0:u.data})}),a.terminate()};const i={index:s,...Array.isArray(n)?{data:n}:n};a.postMessage(i,k(i))})}async collectResults(e,r={}){const s=e.results.filter(a=>a.status==="fulfilled"),n=e.results.filter(a=>a.status==="rejected"),c=s.map(a=>a.value.successResult.data),o=r.reducer?r.reducer.toString():"(shards) => shards.flat()";return{data:await new Promise((a,i)=>{const l=`
|
|
22
25
|
const reducer = ${o};
|
|
23
26
|
self.addEventListener('message', (event) => {
|
|
24
27
|
try {
|
|
@@ -28,4 +31,4 @@ self.addEventListener('message', async (event) => {
|
|
|
28
31
|
self.postMessage({ ok: false, error: String(err) });
|
|
29
32
|
}
|
|
30
33
|
});
|
|
31
|
-
`,
|
|
34
|
+
`,h=new Blob([l],{type:"application/javascript"}),u=new Worker(URL.createObjectURL(h));u.onmessage=d=>{u.terminate(),d.data.ok?a(d.data.data):i(new Error(d.data.error))},u.onerror=d=>{u.terminate(),i(d)},u.postMessage(c)}),succeeded:s.length,failed:n.length,errors:n}}}exports.MainWorkerFactory=v;exports.WorkerFactory=p;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
1
|
+
var k = Object.defineProperty;
|
|
2
|
+
var y = (t, e, r) => e in t ? k(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
|
|
3
|
+
var p = (t, e, r) => y(t, typeof e != "symbol" ? e + "" : e, r);
|
|
4
4
|
const m = (t) => `
|
|
5
5
|
const extractTransferables = (value, seen = new Set()) => {
|
|
6
6
|
if (value === null || typeof value !== 'object') return [];
|
|
@@ -17,82 +17,191 @@ const extractTransferables = (value, seen = new Set()) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
self.addEventListener('message', async (event) => {
|
|
20
|
-
|
|
20
|
+
try {
|
|
21
21
|
const output = await ${t}(event.data);
|
|
22
|
-
self.postMessage(output, extractTransferables(output));
|
|
23
|
-
})
|
|
22
|
+
self.postMessage({ ok: true, data: output }, extractTransferables(output));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
self.postMessage({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
25
|
+
}
|
|
26
|
+
})
|
|
24
27
|
`;
|
|
25
28
|
class w {
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new `Worker` from the given function.
|
|
31
|
+
*
|
|
32
|
+
* The function is stringified, embedded into a self-contained worker script,
|
|
33
|
+
* converted to a `Blob` URL, and passed to the `Worker` constructor.
|
|
34
|
+
*
|
|
35
|
+
* @param workerFunction - The function to run inside the worker thread.
|
|
36
|
+
* Must be self-contained — it cannot reference variables from the outer
|
|
37
|
+
* scope because it is serialised via `.toString()`.
|
|
38
|
+
*/
|
|
26
39
|
constructor(e) {
|
|
27
|
-
|
|
40
|
+
p(this, "_worker");
|
|
28
41
|
const r = m(e.toString()), s = new Blob([r], {
|
|
29
42
|
type: "application/javascript"
|
|
30
43
|
});
|
|
31
44
|
this._worker = new Worker(URL.createObjectURL(s));
|
|
32
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns the underlying native `Worker` instance.
|
|
48
|
+
*
|
|
49
|
+
* Use this to attach `onmessage` / `onerror` handlers and call
|
|
50
|
+
* `postMessage` / `terminate` directly.
|
|
51
|
+
*/
|
|
33
52
|
get getWorker() {
|
|
34
53
|
return this._worker;
|
|
35
54
|
}
|
|
36
55
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
class W {
|
|
57
|
+
constructor(e) {
|
|
58
|
+
this.results = e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function g(t, e = /* @__PURE__ */ new Set()) {
|
|
62
|
+
return t === null || typeof t != "object" ? [] : e.has(t) ? [] : (e.add(t), t instanceof ArrayBuffer || t instanceof MessagePort || typeof ImageBitmap < "u" && t instanceof ImageBitmap || typeof OffscreenCanvas < "u" && t instanceof OffscreenCanvas ? [t] : ArrayBuffer.isView(t) ? [t.buffer] : Array.isArray(t) ? t.flatMap((r) => g(r, e)) : Object.values(t).flatMap(
|
|
63
|
+
(r) => g(r, e)
|
|
40
64
|
));
|
|
41
65
|
}
|
|
42
|
-
class
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
class b {
|
|
67
|
+
/**
|
|
68
|
+
* Creates a new `MainWorkerFactory`.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Configuration object containing the `workers` registry.
|
|
71
|
+
*/
|
|
72
|
+
constructor(e) {
|
|
73
|
+
p(this, "_workers");
|
|
74
|
+
p(this, "_threads");
|
|
75
|
+
this._workers = e.workers, this._threads = navigator.hardwareConcurrency;
|
|
47
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Instantiates a {@link WorkerFactory} for the given worker function.
|
|
79
|
+
*
|
|
80
|
+
* @param workerFunction - The function to run inside the worker thread.
|
|
81
|
+
* @returns A new `WorkerFactory` wrapping the worker.
|
|
82
|
+
*/
|
|
48
83
|
initWorker(e) {
|
|
49
84
|
return new w(e);
|
|
50
85
|
}
|
|
51
86
|
/**
|
|
52
|
-
*
|
|
87
|
+
* Splits an array into up to `numChunks` evenly-sized sub-arrays.
|
|
88
|
+
*
|
|
89
|
+
* When the array length is not evenly divisible, the first `remainder`
|
|
90
|
+
* chunks receive one extra element so no data is lost.
|
|
91
|
+
*
|
|
92
|
+
* @param array - The source array to partition.
|
|
93
|
+
* @param numChunks - Maximum number of chunks to produce.
|
|
94
|
+
* Clamped to `array.length` so you never get empty chunks.
|
|
95
|
+
* @returns An array of sub-arrays. Returns `[]` when `array` is empty.
|
|
96
|
+
* @throws {Error} When `numChunks` is not a positive integer.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* partitionArray([1, 2, 3, 4, 5], 3);
|
|
100
|
+
* // → [[1, 2], [3, 4], [5]]
|
|
53
101
|
*/
|
|
54
102
|
partitionArray(e, r) {
|
|
55
103
|
if (!e.length) return [];
|
|
56
104
|
if (r <= 0) throw new Error("numChunks must be positive");
|
|
57
|
-
const s = Math.min(r, e.length),
|
|
105
|
+
const s = Math.min(r, e.length), n = Math.floor(e.length / s), c = e.length % s, o = [];
|
|
58
106
|
let f = 0;
|
|
59
|
-
for (let
|
|
60
|
-
const i =
|
|
107
|
+
for (let a = 0; a < s; a++) {
|
|
108
|
+
const i = n + (a < c ? 1 : 0);
|
|
61
109
|
o.push(e.slice(f, f + i)), f += i;
|
|
62
110
|
}
|
|
63
111
|
return o;
|
|
64
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Looks up a registered worker configuration by name.
|
|
115
|
+
*
|
|
116
|
+
* @param name - The `name` field of the target {@link WorkerConfig}.
|
|
117
|
+
* @returns The matching config, or `undefined` if not found.
|
|
118
|
+
*/
|
|
65
119
|
findWorkerByName(e) {
|
|
66
120
|
return this._workers.find((r) => r.name === e);
|
|
67
121
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Runs a named worker against the provided data, distributing work across
|
|
124
|
+
* threads when the worker is configured for partitioning.
|
|
125
|
+
*
|
|
126
|
+
* When `config.partition` is `true` and `srcData` is an array with more
|
|
127
|
+
* than one element, the array is split into up to `maxConcurrency` (or
|
|
128
|
+
* `navigator.hardwareConcurrency`) shards and each shard is processed by
|
|
129
|
+
* a separate worker thread in parallel.
|
|
130
|
+
*
|
|
131
|
+
* All threads are awaited with `Promise.allSettled`, so a failure in one
|
|
132
|
+
* shard does not cancel the others. Use {@link collectResults} to merge
|
|
133
|
+
* the settled output.
|
|
134
|
+
*
|
|
135
|
+
* @typeParam TName - The literal name of the worker to run (inferred from
|
|
136
|
+
* the registered `workers` tuple).
|
|
137
|
+
*
|
|
138
|
+
* @param workerName - Name of the worker as declared in the `workers` config.
|
|
139
|
+
* @param params - Object containing `srcData` (the payload) plus any
|
|
140
|
+
* additional key/value pairs forwarded to the worker verbatim.
|
|
141
|
+
*
|
|
142
|
+
* @returns A {@link TypedSettledResults} wrapping the settled promises from
|
|
143
|
+
* all spawned worker threads.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3] });
|
|
147
|
+
*/
|
|
148
|
+
async runWorker(e, {
|
|
149
|
+
srcData: r,
|
|
150
|
+
...s
|
|
151
|
+
}) {
|
|
152
|
+
const n = this.findWorkerByName(e);
|
|
153
|
+
if (!n)
|
|
71
154
|
return Promise.reject(new Error(`Worker "${e}" not found`));
|
|
72
|
-
const c =
|
|
73
|
-
|
|
155
|
+
const c = n.maxConcurrency ?? this._threads, o = !!(Array.isArray(r) && r.length > 1 && n.partition), f = o ? this.partitionArray(r, c) : r, a = this.createWorkerPromises(
|
|
156
|
+
n,
|
|
74
157
|
e,
|
|
75
158
|
{ data: f, ...s },
|
|
76
159
|
c,
|
|
77
160
|
o
|
|
78
|
-
);
|
|
79
|
-
return
|
|
161
|
+
), i = await Promise.allSettled(a);
|
|
162
|
+
return new W(i);
|
|
80
163
|
}
|
|
81
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Builds the array of per-thread worker promises for a single `runWorker`
|
|
166
|
+
* call.
|
|
167
|
+
*
|
|
168
|
+
* When `isPartitioned` is `true`, each promise receives its own slice of
|
|
169
|
+
* `srcData`; otherwise every thread receives the full payload.
|
|
170
|
+
*
|
|
171
|
+
* @param config - The resolved {@link WorkerConfig} for this run.
|
|
172
|
+
* @param workerName - Name used in error/retry logging.
|
|
173
|
+
* @param srcWorkerData - Combined `{ data, ...otherParams }` payload.
|
|
174
|
+
* @param threadCount - Number of parallel worker threads to spawn.
|
|
175
|
+
* @param isPartitioned - Whether `data` is a pre-split array of shards.
|
|
176
|
+
* @returns An array of promises, one per thread.
|
|
177
|
+
*/
|
|
178
|
+
createWorkerPromises(e, r, s, n, c) {
|
|
82
179
|
const { data: o, ...f } = s;
|
|
83
|
-
return Array.from({ length:
|
|
84
|
-
const
|
|
180
|
+
return Array.from({ length: n }, (a, i) => {
|
|
181
|
+
const l = c && Array.isArray(o) ? o[i] : o;
|
|
85
182
|
return this.runWorkerWithRetry(
|
|
86
183
|
{
|
|
87
184
|
workerFunc: e.func,
|
|
88
185
|
workerName: r,
|
|
89
186
|
index: i,
|
|
90
|
-
data: { data:
|
|
187
|
+
data: { data: l, ...f }
|
|
91
188
|
},
|
|
92
189
|
e.retries
|
|
93
190
|
);
|
|
94
191
|
});
|
|
95
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Runs a single worker instance, retrying on failure up to `retryCount`
|
|
195
|
+
* times before re-throwing the last error.
|
|
196
|
+
*
|
|
197
|
+
* Each retry is logged to `console.error` with the remaining attempt count
|
|
198
|
+
* so failures are visible during development.
|
|
199
|
+
*
|
|
200
|
+
* @param instanceConfig - Full configuration for the worker instance.
|
|
201
|
+
* @param retryCount - Remaining retry attempts (default `2`).
|
|
202
|
+
* @returns The successful {@link WorkerResult} once the worker resolves.
|
|
203
|
+
* @throws The last caught error when all retries are exhausted.
|
|
204
|
+
*/
|
|
96
205
|
async runWorkerWithRetry(e, r = 2) {
|
|
97
206
|
try {
|
|
98
207
|
return await this.initiateWorker(e);
|
|
@@ -105,39 +214,86 @@ class v {
|
|
|
105
214
|
throw console.error("Worker failed after all retries:", s), s;
|
|
106
215
|
}
|
|
107
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Spawns a single worker thread, posts the payload, and resolves or rejects
|
|
219
|
+
* based on the message the worker sends back.
|
|
220
|
+
*
|
|
221
|
+
* The worker is expected to respond with either:
|
|
222
|
+
* - `{ ok: true, data: T }` — success; resolves with a {@link WorkerResult}.
|
|
223
|
+
* - `{ ok: false, error: string }` — logical failure; rejects with a
|
|
224
|
+
* structured error object.
|
|
225
|
+
*
|
|
226
|
+
* Any transferable objects found in the payload are moved (not copied) to
|
|
227
|
+
* the worker via the `transfer` list of `postMessage`.
|
|
228
|
+
*
|
|
229
|
+
* The underlying `Worker` is always terminated after the first message,
|
|
230
|
+
* whether it succeeded or failed.
|
|
231
|
+
*
|
|
232
|
+
* @param instanceConfig - Worker function, name, shard index, and data.
|
|
233
|
+
* @returns A promise that resolves with the worker's result.
|
|
234
|
+
*/
|
|
108
235
|
initiateWorker({
|
|
109
236
|
workerFunc: e,
|
|
110
237
|
workerName: r,
|
|
111
238
|
index: s,
|
|
112
|
-
data:
|
|
239
|
+
data: n
|
|
113
240
|
}) {
|
|
114
241
|
return new Promise((c, o) => {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
242
|
+
const a = this.initWorker(e).getWorker;
|
|
243
|
+
a.onerror = (l) => {
|
|
244
|
+
a.terminate(), o({
|
|
118
245
|
index: s,
|
|
119
|
-
workerConfigs: { workerFunc: e, workerName: r, index: s, data:
|
|
120
|
-
failedResult:
|
|
246
|
+
workerConfigs: { workerFunc: e, workerName: r, index: s, data: n },
|
|
247
|
+
failedResult: l
|
|
121
248
|
});
|
|
122
|
-
},
|
|
249
|
+
}, a.onmessage = (l) => {
|
|
250
|
+
var h, u;
|
|
251
|
+
if (((h = l.data) == null ? void 0 : h.ok) === !1) {
|
|
252
|
+
a.terminate(), o({
|
|
253
|
+
index: s,
|
|
254
|
+
workerConfigs: { workerFunc: e, workerName: r, index: s, data: n },
|
|
255
|
+
failedResult: new ErrorEvent("error", {
|
|
256
|
+
message: l.data.error
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
123
261
|
c({
|
|
124
262
|
index: s,
|
|
125
|
-
workerConfigs: { workerFunc: e, workerName: r, index: s, data:
|
|
126
|
-
successResult:
|
|
127
|
-
|
|
263
|
+
workerConfigs: { workerFunc: e, workerName: r, index: s, data: n },
|
|
264
|
+
successResult: new MessageEvent("message", {
|
|
265
|
+
data: (u = l.data) == null ? void 0 : u.data
|
|
266
|
+
})
|
|
267
|
+
}), a.terminate();
|
|
128
268
|
};
|
|
129
269
|
const i = {
|
|
130
270
|
index: s,
|
|
131
|
-
...Array.isArray(
|
|
271
|
+
...Array.isArray(n) ? { data: n } : n
|
|
132
272
|
};
|
|
133
|
-
|
|
273
|
+
a.postMessage(i, g(i));
|
|
134
274
|
});
|
|
135
275
|
}
|
|
136
276
|
/**
|
|
137
|
-
* Collects and merges the settled results from
|
|
277
|
+
* Collects and merges the settled results from {@link runWorker} — off the
|
|
278
|
+
* main thread.
|
|
279
|
+
*
|
|
280
|
+
* Fulfilled shards are extracted and passed to the `reducer` function, which
|
|
281
|
+
* runs inside a dedicated inline worker so the merge itself never blocks the
|
|
282
|
+
* main thread. Failed shards are counted and their raw rejection reasons are
|
|
283
|
+
* preserved in `errors`.
|
|
284
|
+
*
|
|
285
|
+
* @typeParam T - The per-shard data type (inferred from `settled`).
|
|
286
|
+
* @typeParam R - The final merged output type (defaults to a flat array of
|
|
287
|
+
* `T` items when no custom reducer is provided).
|
|
288
|
+
*
|
|
289
|
+
* @param settled - The {@link TypedSettledResults} returned by `runWorker`.
|
|
290
|
+
* @param options - Optional {@link CollectOptions}. Supply a `reducer` to
|
|
291
|
+
* control how shards are merged. The reducer **must be self-contained**
|
|
292
|
+
* (no closures over external variables) because it is serialised and run
|
|
293
|
+
* inside a worker.
|
|
138
294
|
*
|
|
139
|
-
* @
|
|
140
|
-
*
|
|
295
|
+
* @returns A {@link CollectedResult} with the merged `data`, counts of
|
|
296
|
+
* `succeeded`/`failed` shards, and the raw `errors` array.
|
|
141
297
|
*
|
|
142
298
|
* @example
|
|
143
299
|
* // default: flat array of all shard data
|
|
@@ -150,14 +306,14 @@ class v {
|
|
|
150
306
|
* });
|
|
151
307
|
*/
|
|
152
308
|
async collectResults(e, r = {}) {
|
|
153
|
-
const s = e.filter(
|
|
154
|
-
(
|
|
155
|
-
),
|
|
156
|
-
(
|
|
157
|
-
), c = s.map((
|
|
309
|
+
const s = e.results.filter(
|
|
310
|
+
(a) => a.status === "fulfilled"
|
|
311
|
+
), n = e.results.filter(
|
|
312
|
+
(a) => a.status === "rejected"
|
|
313
|
+
), c = s.map((a) => a.value.successResult.data), o = r.reducer ? r.reducer.toString() : "(shards) => shards.flat()";
|
|
158
314
|
return {
|
|
159
|
-
data: await new Promise((
|
|
160
|
-
const
|
|
315
|
+
data: await new Promise((a, i) => {
|
|
316
|
+
const l = `
|
|
161
317
|
const reducer = ${o};
|
|
162
318
|
self.addEventListener('message', (event) => {
|
|
163
319
|
try {
|
|
@@ -167,20 +323,20 @@ class v {
|
|
|
167
323
|
self.postMessage({ ok: false, error: String(err) });
|
|
168
324
|
}
|
|
169
325
|
});
|
|
170
|
-
`,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
},
|
|
326
|
+
`, h = new Blob([l], { type: "application/javascript" }), u = new Worker(URL.createObjectURL(h));
|
|
327
|
+
u.onmessage = (d) => {
|
|
328
|
+
u.terminate(), d.data.ok ? a(d.data.data) : i(new Error(d.data.error));
|
|
329
|
+
}, u.onerror = (d) => {
|
|
330
|
+
u.terminate(), i(d);
|
|
331
|
+
}, u.postMessage(c);
|
|
176
332
|
}),
|
|
177
333
|
succeeded: s.length,
|
|
178
|
-
failed:
|
|
179
|
-
errors:
|
|
334
|
+
failed: n.length,
|
|
335
|
+
errors: n
|
|
180
336
|
};
|
|
181
337
|
}
|
|
182
338
|
}
|
|
183
339
|
export {
|
|
184
|
-
|
|
340
|
+
b as MainWorkerFactory,
|
|
185
341
|
w as WorkerFactory
|
|
186
342
|
};
|
|
@@ -1,31 +1,169 @@
|
|
|
1
|
-
import { CollectOptions, CollectedResult,
|
|
1
|
+
import { CollectOptions, CollectedResult, WorkerConfig, WorkerConfigMap, WorkerDataParam, WorkerFunction, WorkerReturnType, TypedSettledResults } from './types.ts';
|
|
2
2
|
/**
|
|
3
3
|
* Recursively collects all Transferable objects from a value.
|
|
4
4
|
* Transferables (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas)
|
|
5
5
|
* are zero-copy — they are moved to the worker instead of cloned.
|
|
6
6
|
*/
|
|
7
7
|
export declare function extractTransferables(value: unknown, seen?: Set<object>): Transferable[];
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Central orchestrator for running typed Web Workers in parallel.
|
|
10
|
+
*
|
|
11
|
+
* `MainWorkerFactory` manages a registry of named worker configurations and
|
|
12
|
+
* handles the full lifecycle of each worker: spawning, partitioning input
|
|
13
|
+
* data across threads, retrying on failure, and collecting results.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam TConfigs - A readonly tuple of {@link WorkerConfig} objects that
|
|
16
|
+
* defines the set of available workers and their typed signatures.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const foreman = new MainWorkerFactory({
|
|
20
|
+
* workers: [
|
|
21
|
+
* { name: 'sum', role: 'compute', func: sumWorker, partition: true },
|
|
22
|
+
* ],
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3, 4] });
|
|
26
|
+
* const { data } = await foreman.collectResults(settled);
|
|
27
|
+
*/
|
|
28
|
+
declare class MainWorkerFactory<TConfigs extends readonly WorkerConfig<WorkerFunction<any, any>>[]> {
|
|
9
29
|
private readonly _workers;
|
|
10
30
|
private readonly _threads;
|
|
11
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new `MainWorkerFactory`.
|
|
33
|
+
*
|
|
34
|
+
* @param options - Configuration object containing the `workers` registry.
|
|
35
|
+
*/
|
|
36
|
+
constructor(options: {
|
|
37
|
+
workers: TConfigs;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Instantiates a {@link WorkerFactory} for the given worker function.
|
|
41
|
+
*
|
|
42
|
+
* @param workerFunction - The function to run inside the worker thread.
|
|
43
|
+
* @returns A new `WorkerFactory` wrapping the worker.
|
|
44
|
+
*/
|
|
12
45
|
private initWorker;
|
|
13
46
|
/**
|
|
14
|
-
*
|
|
47
|
+
* Splits an array into up to `numChunks` evenly-sized sub-arrays.
|
|
48
|
+
*
|
|
49
|
+
* When the array length is not evenly divisible, the first `remainder`
|
|
50
|
+
* chunks receive one extra element so no data is lost.
|
|
51
|
+
*
|
|
52
|
+
* @param array - The source array to partition.
|
|
53
|
+
* @param numChunks - Maximum number of chunks to produce.
|
|
54
|
+
* Clamped to `array.length` so you never get empty chunks.
|
|
55
|
+
* @returns An array of sub-arrays. Returns `[]` when `array` is empty.
|
|
56
|
+
* @throws {Error} When `numChunks` is not a positive integer.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* partitionArray([1, 2, 3, 4, 5], 3);
|
|
60
|
+
* // → [[1, 2], [3, 4], [5]]
|
|
15
61
|
*/
|
|
16
62
|
partitionArray<T>(array: T[], numChunks: number): T[][];
|
|
63
|
+
/**
|
|
64
|
+
* Looks up a registered worker configuration by name.
|
|
65
|
+
*
|
|
66
|
+
* @param name - The `name` field of the target {@link WorkerConfig}.
|
|
67
|
+
* @returns The matching config, or `undefined` if not found.
|
|
68
|
+
*/
|
|
17
69
|
private findWorkerByName;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Runs a named worker against the provided data, distributing work across
|
|
72
|
+
* threads when the worker is configured for partitioning.
|
|
73
|
+
*
|
|
74
|
+
* When `config.partition` is `true` and `srcData` is an array with more
|
|
75
|
+
* than one element, the array is split into up to `maxConcurrency` (or
|
|
76
|
+
* `navigator.hardwareConcurrency`) shards and each shard is processed by
|
|
77
|
+
* a separate worker thread in parallel.
|
|
78
|
+
*
|
|
79
|
+
* All threads are awaited with `Promise.allSettled`, so a failure in one
|
|
80
|
+
* shard does not cancel the others. Use {@link collectResults} to merge
|
|
81
|
+
* the settled output.
|
|
82
|
+
*
|
|
83
|
+
* @typeParam TName - The literal name of the worker to run (inferred from
|
|
84
|
+
* the registered `workers` tuple).
|
|
85
|
+
*
|
|
86
|
+
* @param workerName - Name of the worker as declared in the `workers` config.
|
|
87
|
+
* @param params - Object containing `srcData` (the payload) plus any
|
|
88
|
+
* additional key/value pairs forwarded to the worker verbatim.
|
|
89
|
+
*
|
|
90
|
+
* @returns A {@link TypedSettledResults} wrapping the settled promises from
|
|
91
|
+
* all spawned worker threads.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3] });
|
|
95
|
+
*/
|
|
96
|
+
runWorker<TName extends keyof WorkerConfigMap<TConfigs> & string>(workerName: TName, { srcData, ...otherParams }: {
|
|
97
|
+
srcData: WorkerDataParam<WorkerConfigMap<TConfigs>[TName]>;
|
|
98
|
+
} & Record<string, unknown>): Promise<TypedSettledResults<WorkerReturnType<WorkerConfigMap<TConfigs>[TName]>>>;
|
|
99
|
+
/**
|
|
100
|
+
* Builds the array of per-thread worker promises for a single `runWorker`
|
|
101
|
+
* call.
|
|
102
|
+
*
|
|
103
|
+
* When `isPartitioned` is `true`, each promise receives its own slice of
|
|
104
|
+
* `srcData`; otherwise every thread receives the full payload.
|
|
105
|
+
*
|
|
106
|
+
* @param config - The resolved {@link WorkerConfig} for this run.
|
|
107
|
+
* @param workerName - Name used in error/retry logging.
|
|
108
|
+
* @param srcWorkerData - Combined `{ data, ...otherParams }` payload.
|
|
109
|
+
* @param threadCount - Number of parallel worker threads to spawn.
|
|
110
|
+
* @param isPartitioned - Whether `data` is a pre-split array of shards.
|
|
111
|
+
* @returns An array of promises, one per thread.
|
|
112
|
+
*/
|
|
21
113
|
private createWorkerPromises;
|
|
114
|
+
/**
|
|
115
|
+
* Runs a single worker instance, retrying on failure up to `retryCount`
|
|
116
|
+
* times before re-throwing the last error.
|
|
117
|
+
*
|
|
118
|
+
* Each retry is logged to `console.error` with the remaining attempt count
|
|
119
|
+
* so failures are visible during development.
|
|
120
|
+
*
|
|
121
|
+
* @param instanceConfig - Full configuration for the worker instance.
|
|
122
|
+
* @param retryCount - Remaining retry attempts (default `2`).
|
|
123
|
+
* @returns The successful {@link WorkerResult} once the worker resolves.
|
|
124
|
+
* @throws The last caught error when all retries are exhausted.
|
|
125
|
+
*/
|
|
22
126
|
private runWorkerWithRetry;
|
|
127
|
+
/**
|
|
128
|
+
* Spawns a single worker thread, posts the payload, and resolves or rejects
|
|
129
|
+
* based on the message the worker sends back.
|
|
130
|
+
*
|
|
131
|
+
* The worker is expected to respond with either:
|
|
132
|
+
* - `{ ok: true, data: T }` — success; resolves with a {@link WorkerResult}.
|
|
133
|
+
* - `{ ok: false, error: string }` — logical failure; rejects with a
|
|
134
|
+
* structured error object.
|
|
135
|
+
*
|
|
136
|
+
* Any transferable objects found in the payload are moved (not copied) to
|
|
137
|
+
* the worker via the `transfer` list of `postMessage`.
|
|
138
|
+
*
|
|
139
|
+
* The underlying `Worker` is always terminated after the first message,
|
|
140
|
+
* whether it succeeded or failed.
|
|
141
|
+
*
|
|
142
|
+
* @param instanceConfig - Worker function, name, shard index, and data.
|
|
143
|
+
* @returns A promise that resolves with the worker's result.
|
|
144
|
+
*/
|
|
23
145
|
private initiateWorker;
|
|
24
146
|
/**
|
|
25
|
-
* Collects and merges the settled results from
|
|
147
|
+
* Collects and merges the settled results from {@link runWorker} — off the
|
|
148
|
+
* main thread.
|
|
149
|
+
*
|
|
150
|
+
* Fulfilled shards are extracted and passed to the `reducer` function, which
|
|
151
|
+
* runs inside a dedicated inline worker so the merge itself never blocks the
|
|
152
|
+
* main thread. Failed shards are counted and their raw rejection reasons are
|
|
153
|
+
* preserved in `errors`.
|
|
154
|
+
*
|
|
155
|
+
* @typeParam T - The per-shard data type (inferred from `settled`).
|
|
156
|
+
* @typeParam R - The final merged output type (defaults to a flat array of
|
|
157
|
+
* `T` items when no custom reducer is provided).
|
|
158
|
+
*
|
|
159
|
+
* @param settled - The {@link TypedSettledResults} returned by `runWorker`.
|
|
160
|
+
* @param options - Optional {@link CollectOptions}. Supply a `reducer` to
|
|
161
|
+
* control how shards are merged. The reducer **must be self-contained**
|
|
162
|
+
* (no closures over external variables) because it is serialised and run
|
|
163
|
+
* inside a worker.
|
|
26
164
|
*
|
|
27
|
-
* @
|
|
28
|
-
*
|
|
165
|
+
* @returns A {@link CollectedResult} with the merged `data`, counts of
|
|
166
|
+
* `succeeded`/`failed` shards, and the raw `errors` array.
|
|
29
167
|
*
|
|
30
168
|
* @example
|
|
31
169
|
* // default: flat array of all shard data
|
|
@@ -37,6 +175,6 @@ declare class MainWorkerFactory {
|
|
|
37
175
|
* reducer: (shards) => shards.flat().reduce((a, b) => a + b, 0),
|
|
38
176
|
* });
|
|
39
177
|
*/
|
|
40
|
-
collectResults<T = unknown, R = T[]>(settled:
|
|
178
|
+
collectResults<T = unknown, R = T extends (infer Item)[] ? Item[] : T[]>(settled: TypedSettledResults<T>, options?: CollectOptions<T, R>): Promise<CollectedResult<R>>;
|
|
41
179
|
}
|
|
42
180
|
export default MainWorkerFactory;
|
|
@@ -1,35 +1,128 @@
|
|
|
1
1
|
import { WorkerFactory } from '../worker-factory';
|
|
2
|
+
/** A unique string identifier for a worker, matching its `name` field. */
|
|
2
3
|
export type WorkerName = string;
|
|
4
|
+
/** A descriptive label for the worker's role (e.g. `'compute'`, `'io'`). */
|
|
3
5
|
export type WorkerRole = string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The shape of a function that runs inside a Web Worker.
|
|
8
|
+
*
|
|
9
|
+
* Workers receive a single `params` argument posted from the main thread and
|
|
10
|
+
* return (or resolve) a result that is posted back.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam TParams - The type of the message payload sent to the worker.
|
|
13
|
+
* @typeParam TResult - The type of the value the worker posts back.
|
|
14
|
+
*/
|
|
15
|
+
export type WorkerFunction<TParams = unknown, TResult = unknown> = (params: TParams) => TResult;
|
|
16
|
+
/**
|
|
17
|
+
* Configuration object that registers a named worker with the factory.
|
|
18
|
+
*
|
|
19
|
+
* @typeParam TFunc - The concrete {@link WorkerFunction} type for this worker.
|
|
20
|
+
*/
|
|
21
|
+
export interface WorkerConfig<TFunc extends WorkerFunction = WorkerFunction> {
|
|
22
|
+
/** Unique name used to look up this worker via `runWorker`. */
|
|
6
23
|
name: WorkerName;
|
|
24
|
+
/** Human-readable role label (e.g. `'compute'`, `'transform'`). */
|
|
7
25
|
role: WorkerRole;
|
|
8
|
-
|
|
26
|
+
/** The worker function that will be serialised and run in a thread. */
|
|
27
|
+
func: TFunc;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum number of parallel threads to spawn for this worker.
|
|
30
|
+
* Defaults to `navigator.hardwareConcurrency` when omitted.
|
|
31
|
+
*/
|
|
9
32
|
maxConcurrency?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Number of times a failed worker thread is retried before the shard is
|
|
35
|
+
* marked as rejected. Defaults to `2`.
|
|
36
|
+
*/
|
|
10
37
|
retries?: number;
|
|
38
|
+
/**
|
|
39
|
+
* When `true`, an array `srcData` is split into per-thread shards before
|
|
40
|
+
* being dispatched. Each thread receives one shard instead of the full
|
|
41
|
+
* array.
|
|
42
|
+
*/
|
|
11
43
|
partition?: boolean;
|
|
12
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Derives a `name → function` map from a readonly tuple of
|
|
47
|
+
* {@link WorkerConfig} objects.
|
|
48
|
+
*
|
|
49
|
+
* Used internally to give `runWorker` a fully-typed `workerName` parameter
|
|
50
|
+
* and to infer the correct `srcData` type for each worker.
|
|
51
|
+
*
|
|
52
|
+
* @typeParam T - The readonly tuple of `WorkerConfig` values.
|
|
53
|
+
*/
|
|
54
|
+
export type WorkerConfigMap<T extends readonly WorkerConfig<WorkerFunction<any, any>>[]> = {
|
|
55
|
+
[K in T[number]['name']]: Extract<T[number], {
|
|
56
|
+
name: K;
|
|
57
|
+
}>['func'];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Extracts the `params` type from a {@link WorkerFunction}.
|
|
61
|
+
*
|
|
62
|
+
* @typeParam TFunc - The worker function to inspect.
|
|
63
|
+
*/
|
|
64
|
+
export type WorkerParams<TFunc extends WorkerFunction> = TFunc extends WorkerFunction<infer P, unknown> ? P : never;
|
|
65
|
+
/**
|
|
66
|
+
* Extracts the `data` field type from a worker function's params.
|
|
67
|
+
* Workers receive `{ data: T, index: number, ...otherParams }` — this
|
|
68
|
+
* pulls out just `T` so callers only need to supply the payload.
|
|
69
|
+
*
|
|
70
|
+
* Always allows `D | D[]` so partitioned workers can receive an array
|
|
71
|
+
* that the framework splits into per-shard items.
|
|
72
|
+
*/
|
|
73
|
+
export type WorkerDataParam<TFunc extends WorkerFunction> = WorkerParams<TFunc> extends {
|
|
74
|
+
data: infer D;
|
|
75
|
+
} ? D extends (infer Item)[] ? Item[] : D | D[] : WorkerParams<TFunc>;
|
|
76
|
+
/** Extracts the return type from a {@link WorkerFunction}, unwrapping `Promise<T>` → `T`. */
|
|
77
|
+
export type WorkerReturnType<TFunc extends WorkerFunction> = TFunc extends WorkerFunction<any, infer R> ? R extends Promise<infer Resolved> ? Resolved : R : never;
|
|
78
|
+
/** Options passed to the `MainWorkerFactory` constructor. */
|
|
13
79
|
export interface MainWorkerFactoryOptions {
|
|
14
80
|
workers: WorkerConfig[];
|
|
15
81
|
}
|
|
82
|
+
/** Internal representation of a worker config that has been instantiated. */
|
|
16
83
|
export interface MainWorkerFactoryWorker extends WorkerConfig {
|
|
17
84
|
worker: WorkerFactory;
|
|
18
85
|
}
|
|
19
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Runtime configuration for a single worker thread instance.
|
|
88
|
+
*
|
|
89
|
+
* @typeParam TFunc - The worker function type for this instance.
|
|
90
|
+
*/
|
|
91
|
+
export interface WorkerInstanceConfig<TFunc extends WorkerFunction = WorkerFunction> {
|
|
92
|
+
/** Name of the parent worker config, used in logs and error objects. */
|
|
20
93
|
workerName: WorkerName;
|
|
21
|
-
|
|
94
|
+
/** The function serialised and executed inside the thread. */
|
|
95
|
+
workerFunc: TFunc;
|
|
96
|
+
/** Zero-based shard index assigned to this thread. */
|
|
22
97
|
index: number;
|
|
98
|
+
/** The data payload (full or partitioned shard) sent to the thread. */
|
|
23
99
|
data: unknown;
|
|
24
100
|
}
|
|
101
|
+
/** The raw `MessageEvent` received when a worker thread succeeds. */
|
|
25
102
|
export type WorkerSuccessResult = MessageEvent;
|
|
103
|
+
/** The raw `MessageEvent` (or `ErrorEvent`) received when a worker thread fails. */
|
|
26
104
|
export type WorkerFailedResult = MessageEvent;
|
|
105
|
+
/** Structured result returned by a single worker thread, whether it succeeded or failed. */
|
|
27
106
|
export interface WorkerResult {
|
|
107
|
+
/** Zero-based shard index of the thread that produced this result. */
|
|
28
108
|
index: number;
|
|
109
|
+
/** The full instance config used to spawn this thread. */
|
|
29
110
|
workerConfigs: WorkerInstanceConfig;
|
|
111
|
+
/** Present when the thread resolved successfully. */
|
|
30
112
|
successResult?: WorkerSuccessResult;
|
|
113
|
+
/** Present when the thread rejected or posted `{ ok: false }`. */
|
|
31
114
|
failedResult?: WorkerFailedResult;
|
|
32
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Typed wrapper around the settled results from `runWorker`.
|
|
118
|
+
* Carries `T` (the worker's return type) so `collectResults` can infer it.
|
|
119
|
+
*/
|
|
120
|
+
export declare class TypedSettledResults<T> {
|
|
121
|
+
readonly results: PromiseSettledResult<WorkerResult>[];
|
|
122
|
+
constructor(results: PromiseSettledResult<WorkerResult>[]);
|
|
123
|
+
/** Never actually exists at runtime — used only for type inference. */
|
|
124
|
+
readonly __type: T;
|
|
125
|
+
}
|
|
33
126
|
/** Options for collectResults */
|
|
34
127
|
export interface CollectOptions<T, R = T[]> {
|
|
35
128
|
/**
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import { WorkerFunction } from '../main-worker-factory/types';
|
|
2
|
+
/**
|
|
3
|
+
* Low-level factory that serialises a {@link WorkerFunction} into a Blob URL
|
|
4
|
+
* and spawns a native `Worker` from it.
|
|
5
|
+
*
|
|
6
|
+
* `WorkerFactory` is an internal building block used by `MainWorkerFactory`.
|
|
7
|
+
* It handles the mechanics of turning a plain TypeScript function into a
|
|
8
|
+
* runnable worker thread — you rarely need to use it directly.
|
|
9
|
+
*
|
|
10
|
+
* The worker script is generated by {@link workerTemplate}, which wraps the
|
|
11
|
+
* function with a message listener and transferable-extraction logic.
|
|
12
|
+
*/
|
|
2
13
|
declare class WorkerFactory {
|
|
3
14
|
readonly _worker: Worker;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new `Worker` from the given function.
|
|
17
|
+
*
|
|
18
|
+
* The function is stringified, embedded into a self-contained worker script,
|
|
19
|
+
* converted to a `Blob` URL, and passed to the `Worker` constructor.
|
|
20
|
+
*
|
|
21
|
+
* @param workerFunction - The function to run inside the worker thread.
|
|
22
|
+
* Must be self-contained — it cannot reference variables from the outer
|
|
23
|
+
* scope because it is serialised via `.toString()`.
|
|
24
|
+
*/
|
|
4
25
|
constructor(workerFunction: WorkerFunction);
|
|
26
|
+
/**
|
|
27
|
+
* Returns the underlying native `Worker` instance.
|
|
28
|
+
*
|
|
29
|
+
* Use this to attach `onmessage` / `onerror` handlers and call
|
|
30
|
+
* `postMessage` / `terminate` directly.
|
|
31
|
+
*/
|
|
5
32
|
get getWorker(): Worker;
|
|
6
33
|
}
|
|
7
34
|
export default WorkerFactory;
|
|
@@ -1,2 +1,18 @@
|
|
|
1
|
+
/**o with
|
|
2
|
+
* Default initiator — a minimal echo worker used as a no-op placeholder.
|
|
3
|
+
*
|
|
4
|
+
* When no custom initiator is passed to `MainWorkerFactory`, this function
|
|
5
|
+
* is used. It simply reflects every incoming message back to the sender,
|
|
6
|
+
* which is useful for testing the messaging pipeline without any real
|
|
7
|
+
* computation.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Automatically used as the default:
|
|
11
|
+
* new MainWorkerFactory({ workers: [...] });
|
|
12
|
+
*
|
|
13
|
+
* // Equivalent explicit usage:
|
|
14
|
+
* import defaultInitiator from './initiator';
|
|
15
|
+
* new MainWorkerFactory({ workers: [...] }, defaultInitiator);
|
|
16
|
+
*/
|
|
1
17
|
declare const _default: () => void;
|
|
2
18
|
export default _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@offmain/workerkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "A lightweight manager for running functions in Web Workers with partitioning, retries, and concurrency control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"lint": "eslint --fix .",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
-
"release
|
|
32
|
-
"release:
|
|
33
|
-
"release:
|
|
34
|
-
"release:
|
|
35
|
-
"
|
|
31
|
+
"release": "release-it --ci",
|
|
32
|
+
"release:patch": "release-it --increment patch --ci",
|
|
33
|
+
"release:minor": "release-it --increment minor --ci",
|
|
34
|
+
"release:major": "release-it --increment major --ci",
|
|
35
|
+
"release:dry": "release-it --dry-run",
|
|
36
36
|
"lint-staged": "lint-staged",
|
|
37
37
|
"prepare": "husky"
|
|
38
38
|
},
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
+
"@release-it/conventional-changelog": "11.0.0",
|
|
62
63
|
"@typescript-eslint/eslint-plugin": "^8.58.2",
|
|
63
64
|
"@typescript-eslint/parser": "^8.58.2",
|
|
64
65
|
"date-fns": "^4.1.0",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"jsdom": "^24.0.0",
|
|
72
73
|
"lint-staged": "^16.4.0",
|
|
73
74
|
"prettier": "^3.4.2",
|
|
75
|
+
"release-it": "19.0.3",
|
|
74
76
|
"typescript": "^5.7.2",
|
|
75
77
|
"vite": "^6.3.3",
|
|
76
78
|
"vite-plugin-dts": "^4.5.4",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|