@offmain/workerkit 0.8.9 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -19
- package/dist/index.cjs +83 -8
- package/dist/index.js +395 -89
- package/dist/types/tools/main-worker-factory/index.d.ts +1 -1
- package/dist/types/tools/main-worker-factory/main-worker-factory.d.ts +171 -11
- package/dist/types/tools/main-worker-factory/types.d.ts +105 -5
- package/dist/types/tools/worker-factory/worker-factory.d.ts +34 -1
- 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/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var h = (
|
|
4
|
-
const
|
|
1
|
+
var w = Object.defineProperty;
|
|
2
|
+
var m = (o, e, t) => e in o ? w(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
|
|
3
|
+
var h = (o, e, t) => m(o, typeof e != "symbol" ? e + "" : e, t);
|
|
4
|
+
const y = (o) => `
|
|
5
5
|
const extractTransferables = (value, seen = new Set()) => {
|
|
6
6
|
if (value === null || typeof value !== 'object') return [];
|
|
7
7
|
if (seen.has(value)) return [];
|
|
@@ -17,127 +17,357 @@ const extractTransferables = (value, seen = new Set()) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
self.addEventListener('message', async (event) => {
|
|
20
|
-
|
|
21
|
-
const output = await ${
|
|
22
|
-
self.postMessage(output, extractTransferables(output));
|
|
23
|
-
})
|
|
20
|
+
try {
|
|
21
|
+
const output = await ${o}(event.data);
|
|
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
|
+
})
|
|
27
|
+
`, v = (o) => `
|
|
28
|
+
const extractTransferables = (value, seen = new Set()) => {
|
|
29
|
+
if (value === null || typeof value !== 'object') return [];
|
|
30
|
+
if (seen.has(value)) return [];
|
|
31
|
+
seen.add(value);
|
|
32
|
+
if (value instanceof ArrayBuffer || value instanceof MessagePort ||
|
|
33
|
+
(typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap) ||
|
|
34
|
+
(typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas)) {
|
|
35
|
+
return [value];
|
|
36
|
+
}
|
|
37
|
+
if (ArrayBuffer.isView(value)) return [value.buffer];
|
|
38
|
+
if (Array.isArray(value)) return value.flatMap(i => extractTransferables(i, seen));
|
|
39
|
+
return Object.values(value).flatMap(v => extractTransferables(v, seen));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const workerFn = ${o};
|
|
43
|
+
let outputPort = null;
|
|
44
|
+
let inputPort = null;
|
|
45
|
+
let pendingData = null;
|
|
46
|
+
|
|
47
|
+
async function processData(data) {
|
|
48
|
+
try {
|
|
49
|
+
const output = await workerFn(data);
|
|
50
|
+
const result = { ok: true, data: output };
|
|
51
|
+
const transfers = extractTransferables(output);
|
|
52
|
+
if (outputPort) {
|
|
53
|
+
outputPort.postMessage(result, transfers);
|
|
54
|
+
} else {
|
|
55
|
+
self.postMessage(result, transfers);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const result = { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
59
|
+
if (outputPort) {
|
|
60
|
+
outputPort.postMessage(result);
|
|
61
|
+
} else {
|
|
62
|
+
self.postMessage(result);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
self.addEventListener('message', (event) => {
|
|
68
|
+
if (event.data && event.data.__pipeline_ports__) {
|
|
69
|
+
if (event.data.outputPort) {
|
|
70
|
+
outputPort = event.data.outputPort;
|
|
71
|
+
}
|
|
72
|
+
if (event.data.inputPort) {
|
|
73
|
+
inputPort = event.data.inputPort;
|
|
74
|
+
inputPort.onmessage = (e) => {
|
|
75
|
+
if (e.data && e.data.ok === false) {
|
|
76
|
+
// Propagate errors through the pipeline
|
|
77
|
+
if (outputPort) outputPort.postMessage(e.data);
|
|
78
|
+
else self.postMessage(e.data);
|
|
79
|
+
} else {
|
|
80
|
+
processData({ data: e.data.data, index: 0 });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// If we already received data before ports, process it now
|
|
85
|
+
if (pendingData !== null) {
|
|
86
|
+
processData(pendingData);
|
|
87
|
+
pendingData = null;
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// First worker in pipeline or standalone — process directly
|
|
92
|
+
if (!inputPort) {
|
|
93
|
+
processData(event.data);
|
|
94
|
+
} else {
|
|
95
|
+
// Store data until ports are configured
|
|
96
|
+
pendingData = event.data;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
24
99
|
`;
|
|
25
|
-
class
|
|
26
|
-
|
|
100
|
+
class k {
|
|
101
|
+
/**
|
|
102
|
+
* Creates a new `Worker` from the given function.
|
|
103
|
+
*
|
|
104
|
+
* The function is stringified, embedded into a self-contained worker script,
|
|
105
|
+
* converted to a `Blob` URL, and passed to the `Worker` constructor.
|
|
106
|
+
*
|
|
107
|
+
* @param workerFunction - The function to run inside the worker thread.
|
|
108
|
+
* Must be self-contained — it cannot reference variables from the outer
|
|
109
|
+
* scope because it is serialised via `.toString()`.
|
|
110
|
+
* @param options - Optional configuration. Set `pipeline: true` for
|
|
111
|
+
* pipeline-aware workers that support MessagePort forwarding.
|
|
112
|
+
*/
|
|
113
|
+
constructor(e, t) {
|
|
27
114
|
h(this, "_worker");
|
|
28
|
-
const
|
|
115
|
+
const a = (t != null && t.pipeline ? v : y)(e.toString()), u = new Blob([a], {
|
|
29
116
|
type: "application/javascript"
|
|
30
117
|
});
|
|
31
|
-
this._worker = new Worker(URL.createObjectURL(
|
|
118
|
+
this._worker = new Worker(URL.createObjectURL(u));
|
|
32
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns the underlying native `Worker` instance.
|
|
122
|
+
*
|
|
123
|
+
* Use this to attach `onmessage` / `onerror` handlers and call
|
|
124
|
+
* `postMessage` / `terminate` directly.
|
|
125
|
+
*/
|
|
33
126
|
get getWorker() {
|
|
34
127
|
return this._worker;
|
|
35
128
|
}
|
|
36
129
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
130
|
+
class P {
|
|
131
|
+
constructor(e) {
|
|
132
|
+
this.results = e;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function g(o, e = /* @__PURE__ */ new Set()) {
|
|
136
|
+
return o === null || typeof o != "object" ? [] : e.has(o) ? [] : (e.add(o), o instanceof ArrayBuffer || o instanceof MessagePort || typeof ImageBitmap < "u" && o instanceof ImageBitmap || typeof OffscreenCanvas < "u" && o instanceof OffscreenCanvas ? [o] : ArrayBuffer.isView(o) ? [o.buffer] : Array.isArray(o) ? o.flatMap((t) => g(t, e)) : Object.values(o).flatMap(
|
|
137
|
+
(t) => g(t, e)
|
|
40
138
|
));
|
|
41
139
|
}
|
|
42
|
-
class
|
|
43
|
-
|
|
140
|
+
class M {
|
|
141
|
+
/**
|
|
142
|
+
* Creates a new `MainWorkerFactory`.
|
|
143
|
+
*
|
|
144
|
+
* @param options - Configuration object containing the `workers` registry.
|
|
145
|
+
*/
|
|
146
|
+
constructor(e) {
|
|
44
147
|
h(this, "_workers");
|
|
45
148
|
h(this, "_threads");
|
|
46
|
-
this._workers =
|
|
149
|
+
this._workers = e.workers, this._threads = navigator.hardwareConcurrency;
|
|
47
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Instantiates a {@link WorkerFactory} for the given worker function.
|
|
153
|
+
*
|
|
154
|
+
* @param workerFunction - The function to run inside the worker thread.
|
|
155
|
+
* @returns A new `WorkerFactory` wrapping the worker.
|
|
156
|
+
*/
|
|
48
157
|
initWorker(e) {
|
|
49
|
-
return new
|
|
158
|
+
return new k(e);
|
|
50
159
|
}
|
|
51
160
|
/**
|
|
52
|
-
*
|
|
161
|
+
* Splits an array into up to `numChunks` evenly-sized sub-arrays.
|
|
162
|
+
*
|
|
163
|
+
* When the array length is not evenly divisible, the first `remainder`
|
|
164
|
+
* chunks receive one extra element so no data is lost.
|
|
165
|
+
*
|
|
166
|
+
* @param array - The source array to partition.
|
|
167
|
+
* @param numChunks - Maximum number of chunks to produce.
|
|
168
|
+
* Clamped to `array.length` so you never get empty chunks.
|
|
169
|
+
* @returns An array of sub-arrays. Returns `[]` when `array` is empty.
|
|
170
|
+
* @throws {Error} When `numChunks` is not a positive integer.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* partitionArray([1, 2, 3, 4, 5], 3);
|
|
174
|
+
* // → [[1, 2], [3, 4], [5]]
|
|
53
175
|
*/
|
|
54
|
-
partitionArray(e,
|
|
176
|
+
partitionArray(e, t) {
|
|
55
177
|
if (!e.length) return [];
|
|
56
|
-
if (
|
|
57
|
-
const
|
|
178
|
+
if (t <= 0) throw new Error("numChunks must be positive");
|
|
179
|
+
const n = Math.min(t, e.length), a = Math.floor(e.length / n), u = e.length % n, l = [];
|
|
58
180
|
let f = 0;
|
|
59
|
-
for (let
|
|
60
|
-
const
|
|
61
|
-
|
|
181
|
+
for (let r = 0; r < n; r++) {
|
|
182
|
+
const s = a + (r < u ? 1 : 0);
|
|
183
|
+
l.push(e.slice(f, f + s)), f += s;
|
|
62
184
|
}
|
|
63
|
-
return
|
|
185
|
+
return l;
|
|
64
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Looks up a registered worker configuration by name.
|
|
189
|
+
*
|
|
190
|
+
* @param name - The `name` field of the target {@link WorkerConfig}.
|
|
191
|
+
* @returns The matching config, or `undefined` if not found.
|
|
192
|
+
*/
|
|
65
193
|
findWorkerByName(e) {
|
|
66
|
-
return this._workers.find((
|
|
194
|
+
return this._workers.find((t) => t.name === e);
|
|
67
195
|
}
|
|
68
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Runs a named worker against the provided data, distributing work across
|
|
198
|
+
* threads when the worker is configured for partitioning.
|
|
199
|
+
*
|
|
200
|
+
* When `config.partition` is `true` and `srcData` is an array with more
|
|
201
|
+
* than one element, the array is split into up to `maxConcurrency` (or
|
|
202
|
+
* `navigator.hardwareConcurrency`) shards and each shard is processed by
|
|
203
|
+
* a separate worker thread in parallel.
|
|
204
|
+
*
|
|
205
|
+
* All threads are awaited with `Promise.allSettled`, so a failure in one
|
|
206
|
+
* shard does not cancel the others. Use {@link collectResults} to merge
|
|
207
|
+
* the settled output.
|
|
208
|
+
*
|
|
209
|
+
* @typeParam TName - The literal name of the worker to run (inferred from
|
|
210
|
+
* the registered `workers` tuple).
|
|
211
|
+
*
|
|
212
|
+
* @param workerName - Name of the worker as declared in the `workers` config.
|
|
213
|
+
* @param params - Object containing `srcData` (the payload) plus any
|
|
214
|
+
* additional key/value pairs forwarded to the worker verbatim.
|
|
215
|
+
*
|
|
216
|
+
* @returns A {@link TypedSettledResults} wrapping the settled promises from
|
|
217
|
+
* all spawned worker threads.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* const settled = await foreman.runWorker('sum', { srcData: [1, 2, 3] });
|
|
221
|
+
*/
|
|
222
|
+
async runWorker(e, {
|
|
223
|
+
srcData: t,
|
|
224
|
+
...n
|
|
225
|
+
}) {
|
|
69
226
|
const a = this.findWorkerByName(e);
|
|
70
227
|
if (!a)
|
|
71
228
|
return Promise.reject(new Error(`Worker "${e}" not found`));
|
|
72
|
-
const
|
|
229
|
+
const u = a.maxConcurrency ?? this._threads, l = !!(Array.isArray(t) && t.length > 1 && a.partition), f = l ? this.partitionArray(t, u) : t, r = this.createWorkerPromises(
|
|
73
230
|
a,
|
|
74
231
|
e,
|
|
75
|
-
{ data: f, ...
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
232
|
+
{ data: f, ...n },
|
|
233
|
+
u,
|
|
234
|
+
l
|
|
235
|
+
), s = await Promise.allSettled(r);
|
|
236
|
+
return new P(s);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Builds the array of per-thread worker promises for a single `runWorker`
|
|
240
|
+
* call.
|
|
241
|
+
*
|
|
242
|
+
* When `isPartitioned` is `true`, each promise receives its own slice of
|
|
243
|
+
* `srcData`; otherwise every thread receives the full payload.
|
|
244
|
+
*
|
|
245
|
+
* @param config - The resolved {@link WorkerConfig} for this run.
|
|
246
|
+
* @param workerName - Name used in error/retry logging.
|
|
247
|
+
* @param srcWorkerData - Combined `{ data, ...otherParams }` payload.
|
|
248
|
+
* @param threadCount - Number of parallel worker threads to spawn.
|
|
249
|
+
* @param isPartitioned - Whether `data` is a pre-split array of shards.
|
|
250
|
+
* @returns An array of promises, one per thread.
|
|
251
|
+
*/
|
|
252
|
+
createWorkerPromises(e, t, n, a, u) {
|
|
253
|
+
const { data: l, ...f } = n;
|
|
254
|
+
return Array.from({ length: a }, (r, s) => {
|
|
255
|
+
const i = u && Array.isArray(l) ? l[s] : l;
|
|
85
256
|
return this.runWorkerWithRetry(
|
|
86
257
|
{
|
|
87
258
|
workerFunc: e.func,
|
|
88
|
-
workerName:
|
|
89
|
-
index:
|
|
90
|
-
data: { data:
|
|
259
|
+
workerName: t,
|
|
260
|
+
index: s,
|
|
261
|
+
data: { data: i, ...f }
|
|
91
262
|
},
|
|
92
263
|
e.retries
|
|
93
264
|
);
|
|
94
265
|
});
|
|
95
266
|
}
|
|
96
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Runs a single worker instance, retrying on failure up to `retryCount`
|
|
269
|
+
* times before re-throwing the last error.
|
|
270
|
+
*
|
|
271
|
+
* Each retry is logged to `console.error` with the remaining attempt count
|
|
272
|
+
* so failures are visible during development.
|
|
273
|
+
*
|
|
274
|
+
* @param instanceConfig - Full configuration for the worker instance.
|
|
275
|
+
* @param retryCount - Remaining retry attempts (default `2`).
|
|
276
|
+
* @returns The successful {@link WorkerResult} once the worker resolves.
|
|
277
|
+
* @throws The last caught error when all retries are exhausted.
|
|
278
|
+
*/
|
|
279
|
+
async runWorkerWithRetry(e, t = 2) {
|
|
97
280
|
try {
|
|
98
281
|
return await this.initiateWorker(e);
|
|
99
|
-
} catch (
|
|
100
|
-
if (
|
|
282
|
+
} catch (n) {
|
|
283
|
+
if (t > 0)
|
|
101
284
|
return console.error(
|
|
102
|
-
`Worker ${e.index} failed, retrying (${
|
|
103
|
-
|
|
104
|
-
), this.runWorkerWithRetry(e,
|
|
105
|
-
throw console.error("Worker failed after all retries:",
|
|
285
|
+
`Worker ${e.index} failed, retrying (${t} left):`,
|
|
286
|
+
n
|
|
287
|
+
), this.runWorkerWithRetry(e, t - 1);
|
|
288
|
+
throw console.error("Worker failed after all retries:", n), n;
|
|
106
289
|
}
|
|
107
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Spawns a single worker thread, posts the payload, and resolves or rejects
|
|
293
|
+
* based on the message the worker sends back.
|
|
294
|
+
*
|
|
295
|
+
* The worker is expected to respond with either:
|
|
296
|
+
* - `{ ok: true, data: T }` — success; resolves with a {@link WorkerResult}.
|
|
297
|
+
* - `{ ok: false, error: string }` — logical failure; rejects with a
|
|
298
|
+
* structured error object.
|
|
299
|
+
*
|
|
300
|
+
* Any transferable objects found in the payload are moved (not copied) to
|
|
301
|
+
* the worker via the `transfer` list of `postMessage`.
|
|
302
|
+
*
|
|
303
|
+
* The underlying `Worker` is always terminated after the first message,
|
|
304
|
+
* whether it succeeded or failed.
|
|
305
|
+
*
|
|
306
|
+
* @param instanceConfig - Worker function, name, shard index, and data.
|
|
307
|
+
* @returns A promise that resolves with the worker's result.
|
|
308
|
+
*/
|
|
108
309
|
initiateWorker({
|
|
109
310
|
workerFunc: e,
|
|
110
|
-
workerName:
|
|
111
|
-
index:
|
|
311
|
+
workerName: t,
|
|
312
|
+
index: n,
|
|
112
313
|
data: a
|
|
113
314
|
}) {
|
|
114
|
-
return new Promise((
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
index:
|
|
119
|
-
workerConfigs: { workerFunc: e, workerName:
|
|
120
|
-
failedResult:
|
|
315
|
+
return new Promise((u, l) => {
|
|
316
|
+
const r = this.initWorker(e).getWorker;
|
|
317
|
+
r.onerror = (i) => {
|
|
318
|
+
r.terminate(), l({
|
|
319
|
+
index: n,
|
|
320
|
+
workerConfigs: { workerFunc: e, workerName: t, index: n, data: a },
|
|
321
|
+
failedResult: i
|
|
121
322
|
});
|
|
122
|
-
},
|
|
123
|
-
c
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
323
|
+
}, r.onmessage = (i) => {
|
|
324
|
+
var c, p;
|
|
325
|
+
if (((c = i.data) == null ? void 0 : c.ok) === !1) {
|
|
326
|
+
r.terminate(), l({
|
|
327
|
+
index: n,
|
|
328
|
+
workerConfigs: { workerFunc: e, workerName: t, index: n, data: a },
|
|
329
|
+
failedResult: new ErrorEvent("error", {
|
|
330
|
+
message: i.data.error
|
|
331
|
+
})
|
|
332
|
+
});
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
u({
|
|
336
|
+
index: n,
|
|
337
|
+
workerConfigs: { workerFunc: e, workerName: t, index: n, data: a },
|
|
338
|
+
successResult: new MessageEvent("message", {
|
|
339
|
+
data: (p = i.data) == null ? void 0 : p.data
|
|
340
|
+
})
|
|
341
|
+
}), r.terminate();
|
|
128
342
|
};
|
|
129
|
-
const
|
|
130
|
-
index:
|
|
343
|
+
const s = {
|
|
344
|
+
index: n,
|
|
131
345
|
...Array.isArray(a) ? { data: a } : a
|
|
132
346
|
};
|
|
133
|
-
|
|
347
|
+
r.postMessage(s, g(s));
|
|
134
348
|
});
|
|
135
349
|
}
|
|
136
350
|
/**
|
|
137
|
-
* Collects and merges the settled results from
|
|
351
|
+
* Collects and merges the settled results from {@link runWorker} — off the
|
|
352
|
+
* main thread.
|
|
353
|
+
*
|
|
354
|
+
* Fulfilled shards are extracted and passed to the `reducer` function, which
|
|
355
|
+
* runs inside a dedicated inline worker so the merge itself never blocks the
|
|
356
|
+
* main thread. Failed shards are counted and their raw rejection reasons are
|
|
357
|
+
* preserved in `errors`.
|
|
138
358
|
*
|
|
139
|
-
* @
|
|
140
|
-
* @
|
|
359
|
+
* @typeParam T - The per-shard data type (inferred from `settled`).
|
|
360
|
+
* @typeParam R - The final merged output type (defaults to a flat array of
|
|
361
|
+
* `T` items when no custom reducer is provided).
|
|
362
|
+
*
|
|
363
|
+
* @param settled - The {@link TypedSettledResults} returned by `runWorker`.
|
|
364
|
+
* @param options - Optional {@link CollectOptions}. Supply a `reducer` to
|
|
365
|
+
* control how shards are merged. The reducer **must be self-contained**
|
|
366
|
+
* (no closures over external variables) because it is serialised and run
|
|
367
|
+
* inside a worker.
|
|
368
|
+
*
|
|
369
|
+
* @returns A {@link CollectedResult} with the merged `data`, counts of
|
|
370
|
+
* `succeeded`/`failed` shards, and the raw `errors` array.
|
|
141
371
|
*
|
|
142
372
|
* @example
|
|
143
373
|
* // default: flat array of all shard data
|
|
@@ -149,16 +379,16 @@ class v {
|
|
|
149
379
|
* reducer: (shards) => shards.flat().reduce((a, b) => a + b, 0),
|
|
150
380
|
* });
|
|
151
381
|
*/
|
|
152
|
-
async collectResults(e,
|
|
153
|
-
const
|
|
154
|
-
(
|
|
155
|
-
), a = e.filter(
|
|
156
|
-
(
|
|
157
|
-
),
|
|
382
|
+
async collectResults(e, t = {}) {
|
|
383
|
+
const n = e.results.filter(
|
|
384
|
+
(r) => r.status === "fulfilled"
|
|
385
|
+
), a = e.results.filter(
|
|
386
|
+
(r) => r.status === "rejected"
|
|
387
|
+
), u = n.map((r) => r.value.successResult.data), l = t.reducer ? t.reducer.toString() : "(shards) => shards.flat()";
|
|
158
388
|
return {
|
|
159
|
-
data: await new Promise((
|
|
160
|
-
const
|
|
161
|
-
const reducer = ${
|
|
389
|
+
data: await new Promise((r, s) => {
|
|
390
|
+
const i = `
|
|
391
|
+
const reducer = ${l};
|
|
162
392
|
self.addEventListener('message', (event) => {
|
|
163
393
|
try {
|
|
164
394
|
const result = reducer(event.data);
|
|
@@ -167,20 +397,96 @@ class v {
|
|
|
167
397
|
self.postMessage({ ok: false, error: String(err) });
|
|
168
398
|
}
|
|
169
399
|
});
|
|
170
|
-
`,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
},
|
|
400
|
+
`, c = new Blob([i], { type: "application/javascript" }), p = new Worker(URL.createObjectURL(c));
|
|
401
|
+
p.onmessage = (d) => {
|
|
402
|
+
p.terminate(), d.data.ok ? r(d.data.data) : s(new Error(d.data.error));
|
|
403
|
+
}, p.onerror = (d) => {
|
|
404
|
+
p.terminate(), s(d);
|
|
405
|
+
}, p.postMessage(u);
|
|
176
406
|
}),
|
|
177
|
-
succeeded:
|
|
407
|
+
succeeded: n.length,
|
|
178
408
|
failed: a.length,
|
|
179
409
|
errors: a
|
|
180
410
|
};
|
|
181
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Runs a chain of workers where each step's output feeds directly into the
|
|
414
|
+
* next step — **without passing through the main thread**.
|
|
415
|
+
*
|
|
416
|
+
* Internally, adjacent workers are connected via `MessageChannel` ports.
|
|
417
|
+
* Only the final result is sent back to the main thread, minimising
|
|
418
|
+
* serialisation overhead for large intermediate data.
|
|
419
|
+
*
|
|
420
|
+
* @param steps - An ordered array of pipeline steps. The first step must
|
|
421
|
+
* include `srcData`; subsequent steps receive the previous step's output.
|
|
422
|
+
*
|
|
423
|
+
* @returns A promise that resolves with the final step's output.
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* const result = await foreman.pipeline([
|
|
427
|
+
* { worker: 'fetchPosts', srcData: { url: '/api/posts' } },
|
|
428
|
+
* { worker: 'transformPosts' },
|
|
429
|
+
* { worker: 'filterPosts' },
|
|
430
|
+
* ]);
|
|
431
|
+
* console.log(result); // final transformed + filtered data
|
|
432
|
+
*/
|
|
433
|
+
async pipeline(e) {
|
|
434
|
+
if (e.length === 0)
|
|
435
|
+
throw new Error("Pipeline requires at least one step");
|
|
436
|
+
if (e.length === 1) {
|
|
437
|
+
const t = e[0], n = this.findWorkerByName(t.worker);
|
|
438
|
+
if (!n) throw new Error(`Worker "${t.worker}" not found`);
|
|
439
|
+
const u = this.initWorker(n.func).getWorker;
|
|
440
|
+
return new Promise((l, f) => {
|
|
441
|
+
u.onmessage = (s) => {
|
|
442
|
+
var i, c;
|
|
443
|
+
u.terminate(), ((i = s.data) == null ? void 0 : i.ok) === !1 ? f(new Error(s.data.error)) : l((c = s.data) == null ? void 0 : c.data);
|
|
444
|
+
}, u.onerror = (s) => {
|
|
445
|
+
u.terminate(), f(s);
|
|
446
|
+
};
|
|
447
|
+
const r = t.srcData ?? {};
|
|
448
|
+
u.postMessage(
|
|
449
|
+
{ data: r, index: 0 },
|
|
450
|
+
g(r)
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return new Promise((t, n) => {
|
|
455
|
+
const a = [], u = [];
|
|
456
|
+
for (const r of e) {
|
|
457
|
+
const s = this.findWorkerByName(r.worker);
|
|
458
|
+
if (!s) {
|
|
459
|
+
n(new Error(`Worker "${r.worker}" not found`));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const i = new k(s.func, { pipeline: !0 });
|
|
463
|
+
a.push(i.getWorker);
|
|
464
|
+
}
|
|
465
|
+
for (let r = 0; r < a.length - 1; r++)
|
|
466
|
+
u.push(new MessageChannel());
|
|
467
|
+
for (let r = 0; r < a.length; r++) {
|
|
468
|
+
const s = [], i = {};
|
|
469
|
+
r > 0 && (i.inputPort = u[r - 1].port1, s.push(i.inputPort)), r < a.length - 1 && (i.outputPort = u[r].port2, s.push(i.outputPort)), a[r].postMessage(
|
|
470
|
+
{ __pipeline_ports__: !0, ...i },
|
|
471
|
+
s
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const l = a[a.length - 1];
|
|
475
|
+
l.onmessage = (r) => {
|
|
476
|
+
var s, i;
|
|
477
|
+
a.forEach((c) => c.terminate()), ((s = r.data) == null ? void 0 : s.ok) === !1 ? n(new Error(r.data.error)) : t((i = r.data) == null ? void 0 : i.data);
|
|
478
|
+
}, l.onerror = (r) => {
|
|
479
|
+
a.forEach((s) => s.terminate()), n(r);
|
|
480
|
+
};
|
|
481
|
+
const f = e[0].srcData ?? {};
|
|
482
|
+
a[0].postMessage(
|
|
483
|
+
{ data: f, index: 0 },
|
|
484
|
+
g(f)
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
182
488
|
}
|
|
183
489
|
export {
|
|
184
|
-
|
|
185
|
-
|
|
490
|
+
M as MainWorkerFactory,
|
|
491
|
+
k as WorkerFactory
|
|
186
492
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as MainWorkerFactory } from './main-worker-factory.ts';
|
|
2
|
-
export type { WorkerFunction, MainWorkerFactoryWorker, MainWorkerFactoryOptions, WorkerConfig, WorkerName, WorkerRole, } from './types.ts';
|
|
2
|
+
export type { WorkerFunction, MainWorkerFactoryWorker, MainWorkerFactoryOptions, PipelineStep, WorkerConfig, WorkerName, WorkerRole, } from './types.ts';
|