@dmop/puru 0.1.5 → 0.1.10
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/AGENTS.md +18 -42
- package/README.md +159 -434
- package/dist/index.cjs +38 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -15
- package/dist/index.d.ts +26 -15
- package/dist/index.js +40 -43
- package/dist/index.js.map +1 -1
- package/llms-full.txt +1 -1
- package/package.json +34 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
interface JsonObject {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
}
|
|
4
|
+
type JsonValue = null | string | number | boolean | JsonValue[] | JsonObject;
|
|
5
|
+
interface StructuredCloneObject {
|
|
6
|
+
[key: string]: StructuredCloneValue;
|
|
7
|
+
}
|
|
8
|
+
type StructuredCloneValue = void | null | undefined | string | number | boolean | bigint | Date | RegExp | Error | ArrayBuffer | ArrayBufferView | StructuredCloneValue[] | Map<StructuredCloneValue, StructuredCloneValue> | Set<StructuredCloneValue> | StructuredCloneObject;
|
|
9
|
+
type ChannelValue = Exclude<StructuredCloneValue, null>;
|
|
1
10
|
interface PuruConfig {
|
|
2
11
|
maxThreads: number;
|
|
3
12
|
strategy: 'fifo' | 'work-stealing';
|
|
@@ -30,7 +39,7 @@ interface SpawnResult<T> {
|
|
|
30
39
|
* process(item)
|
|
31
40
|
* }
|
|
32
41
|
*/
|
|
33
|
-
interface Channel<T> {
|
|
42
|
+
interface Channel<T extends ChannelValue> {
|
|
34
43
|
send(value: T): Promise<void>;
|
|
35
44
|
/** Resolves with the next value, or `null` if the channel is closed. */
|
|
36
45
|
recv(): Promise<T | null>;
|
|
@@ -65,7 +74,7 @@ interface Channel<T> {
|
|
|
65
74
|
* }, { channels: { input, output } })
|
|
66
75
|
* }
|
|
67
76
|
*/
|
|
68
|
-
declare function chan<T extends
|
|
77
|
+
declare function chan<T extends ChannelValue>(capacity?: number): Channel<T>;
|
|
69
78
|
|
|
70
79
|
/**
|
|
71
80
|
* Run a function in a worker thread. Returns a handle with the result promise and a cancel function.
|
|
@@ -108,12 +117,13 @@ declare function chan<T extends NonNullable<unknown>>(capacity?: number): Channe
|
|
|
108
117
|
* ch.close()
|
|
109
118
|
* }, { channels: { ch } })
|
|
110
119
|
*/
|
|
111
|
-
declare function spawn<T
|
|
120
|
+
declare function spawn<T extends StructuredCloneValue, TChannels extends Record<string, Channel<ChannelValue>> = Record<never, never>>(fn: (() => T | Promise<T>) | ((channels: TChannels) => T | Promise<T>), opts?: {
|
|
112
121
|
priority?: 'low' | 'normal' | 'high';
|
|
113
122
|
concurrent?: boolean;
|
|
114
|
-
channels?:
|
|
123
|
+
channels?: TChannels;
|
|
115
124
|
}): SpawnResult<T>;
|
|
116
125
|
|
|
126
|
+
type SpawnChannels$1 = Record<string, Channel<ChannelValue>>;
|
|
117
127
|
/**
|
|
118
128
|
* Structured concurrency: spawn multiple tasks and wait for all to complete.
|
|
119
129
|
*
|
|
@@ -144,7 +154,7 @@ declare function spawn<T>(fn: (() => T | Promise<T>) | ((channels: Record<string
|
|
|
144
154
|
* else console.error(r.reason)
|
|
145
155
|
* }
|
|
146
156
|
*/
|
|
147
|
-
declare class WaitGroup {
|
|
157
|
+
declare class WaitGroup<T extends StructuredCloneValue = StructuredCloneValue> {
|
|
148
158
|
private tasks;
|
|
149
159
|
private controller;
|
|
150
160
|
/**
|
|
@@ -157,20 +167,20 @@ declare class WaitGroup {
|
|
|
157
167
|
*
|
|
158
168
|
* @throws If the group has already been cancelled.
|
|
159
169
|
*/
|
|
160
|
-
spawn(fn: (() =>
|
|
170
|
+
spawn<TChannels extends SpawnChannels$1 = Record<never, never>>(fn: (() => T | Promise<T>) | ((channels: TChannels) => T | Promise<T>), opts?: {
|
|
161
171
|
concurrent?: boolean;
|
|
162
|
-
channels?:
|
|
172
|
+
channels?: TChannels;
|
|
163
173
|
}): void;
|
|
164
174
|
/**
|
|
165
175
|
* Waits for all tasks to complete successfully.
|
|
166
176
|
* Rejects as soon as any task throws.
|
|
167
177
|
*/
|
|
168
|
-
wait(): Promise<
|
|
178
|
+
wait(): Promise<T[]>;
|
|
169
179
|
/**
|
|
170
180
|
* Waits for all tasks to settle (fulfilled or rejected) and returns each outcome.
|
|
171
181
|
* Never rejects — inspect each `PromiseSettledResult` to handle failures individually.
|
|
172
182
|
*/
|
|
173
|
-
waitSettled(): Promise<PromiseSettledResult<
|
|
183
|
+
waitSettled(): Promise<PromiseSettledResult<T>[]>;
|
|
174
184
|
/**
|
|
175
185
|
* Cancels all tasks in the group and signals the shared `AbortSignal`.
|
|
176
186
|
* Already-settled tasks are unaffected.
|
|
@@ -178,6 +188,7 @@ declare class WaitGroup {
|
|
|
178
188
|
cancel(): void;
|
|
179
189
|
}
|
|
180
190
|
|
|
191
|
+
type SpawnChannels = Record<string, Channel<ChannelValue>>;
|
|
181
192
|
/**
|
|
182
193
|
* Like `WaitGroup`, but cancels all remaining tasks on the first error.
|
|
183
194
|
*
|
|
@@ -207,17 +218,17 @@ declare class WaitGroup {
|
|
|
207
218
|
* // use task() with register() and check a channel or AbortSignal instead
|
|
208
219
|
* })
|
|
209
220
|
*/
|
|
210
|
-
declare class ErrGroup {
|
|
221
|
+
declare class ErrGroup<T extends StructuredCloneValue = StructuredCloneValue> {
|
|
211
222
|
private tasks;
|
|
212
223
|
private controller;
|
|
213
224
|
private firstError;
|
|
214
225
|
private hasError;
|
|
215
226
|
get signal(): AbortSignal;
|
|
216
|
-
spawn(fn: (() =>
|
|
227
|
+
spawn<TChannels extends SpawnChannels = Record<never, never>>(fn: (() => T | Promise<T>) | ((channels: TChannels) => T | Promise<T>), opts?: {
|
|
217
228
|
concurrent?: boolean;
|
|
218
|
-
channels?:
|
|
229
|
+
channels?: TChannels;
|
|
219
230
|
}): void;
|
|
220
|
-
wait(): Promise<
|
|
231
|
+
wait(): Promise<T[]>;
|
|
221
232
|
cancel(): void;
|
|
222
233
|
}
|
|
223
234
|
|
|
@@ -291,7 +302,7 @@ declare class Once<T = void> {
|
|
|
291
302
|
reset(): void;
|
|
292
303
|
}
|
|
293
304
|
|
|
294
|
-
type SelectCase<T =
|
|
305
|
+
type SelectCase<T = StructuredCloneValue> = [Promise<T>, (value: T) => void];
|
|
295
306
|
/**
|
|
296
307
|
* Options for `select()`.
|
|
297
308
|
*
|
|
@@ -427,7 +438,7 @@ declare function ticker(ms: number): Ticker;
|
|
|
427
438
|
* const result = await resizeImage('photo.jpg', 800, 600)
|
|
428
439
|
* const [a, b] = await Promise.all([resizeImage('a.jpg', 400, 300), resizeImage('b.jpg', 800, 600)])
|
|
429
440
|
*/
|
|
430
|
-
declare function task<TArgs extends
|
|
441
|
+
declare function task<TArgs extends JsonValue[], TReturn extends StructuredCloneValue>(fn: (...args: TArgs) => TReturn | Promise<TReturn>): (...args: TArgs) => Promise<TReturn>;
|
|
431
442
|
|
|
432
443
|
/**
|
|
433
444
|
* Configure the global thread pool. **Must be called before the first `spawn()`.**
|
package/dist/index.js
CHANGED
|
@@ -56,21 +56,20 @@ function getConfig() {
|
|
|
56
56
|
|
|
57
57
|
// src/runtime.ts
|
|
58
58
|
function detectRuntime() {
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
61
|
-
if (typeof globalThis.process !== "undefined" && globalThis.process.versions?.node)
|
|
62
|
-
return "node";
|
|
59
|
+
if ("Bun" in globalThis) return "bun";
|
|
60
|
+
if ("Deno" in globalThis) return "deno";
|
|
61
|
+
if (typeof globalThis.process !== "undefined" && globalThis.process.versions?.node) return "node";
|
|
63
62
|
return "browser";
|
|
64
63
|
}
|
|
65
64
|
function detectCapability() {
|
|
66
65
|
const runtime = detectRuntime();
|
|
67
66
|
if (runtime === "node" || runtime === "bun") return "full-threads";
|
|
68
|
-
if (
|
|
67
|
+
if ("Worker" in globalThis) return "full-threads";
|
|
69
68
|
return "single-thread";
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
// src/adapters/node.ts
|
|
73
|
-
import { Worker
|
|
72
|
+
import { Worker } from "worker_threads";
|
|
74
73
|
|
|
75
74
|
// src/bootstrap.ts
|
|
76
75
|
var CHANNEL_PROXY_CODE = `
|
|
@@ -286,7 +285,7 @@ self.postMessage({ type: 'ready' });
|
|
|
286
285
|
var NodeManagedWorker = class {
|
|
287
286
|
worker;
|
|
288
287
|
constructor() {
|
|
289
|
-
this.worker = new
|
|
288
|
+
this.worker = new Worker(NODE_BOOTSTRAP_CODE, { eval: true });
|
|
290
289
|
}
|
|
291
290
|
get id() {
|
|
292
291
|
return this.worker.threadId;
|
|
@@ -332,7 +331,11 @@ var BunManagedWorker = class {
|
|
|
332
331
|
id;
|
|
333
332
|
constructor() {
|
|
334
333
|
this.id = ++workerIdCounter;
|
|
335
|
-
|
|
334
|
+
const WorkerConstructor = globalThis.Worker;
|
|
335
|
+
if (!WorkerConstructor) {
|
|
336
|
+
throw new Error("Bun Worker constructor is not available in this runtime");
|
|
337
|
+
}
|
|
338
|
+
this.worker = new WorkerConstructor(getBootstrapFile());
|
|
336
339
|
}
|
|
337
340
|
postMessage(data) {
|
|
338
341
|
this.worker.postMessage(data);
|
|
@@ -344,27 +347,28 @@ var BunManagedWorker = class {
|
|
|
344
347
|
on(event, handler) {
|
|
345
348
|
if (event === "message") {
|
|
346
349
|
this.worker.addEventListener("message", (e) => {
|
|
350
|
+
;
|
|
347
351
|
handler(e.data);
|
|
348
352
|
});
|
|
349
353
|
} else if (event === "error") {
|
|
350
354
|
this.worker.addEventListener("error", (e) => {
|
|
355
|
+
;
|
|
351
356
|
handler(e.error ?? new Error(e.message));
|
|
352
357
|
});
|
|
353
358
|
} else if (event === "exit") {
|
|
354
359
|
this.worker.addEventListener("close", (e) => {
|
|
360
|
+
;
|
|
355
361
|
handler(e.code ?? 0);
|
|
356
362
|
});
|
|
357
363
|
}
|
|
358
364
|
}
|
|
359
365
|
unref() {
|
|
360
366
|
if ("unref" in this.worker && typeof this.worker.unref === "function") {
|
|
361
|
-
;
|
|
362
367
|
this.worker.unref();
|
|
363
368
|
}
|
|
364
369
|
}
|
|
365
370
|
ref() {
|
|
366
371
|
if ("ref" in this.worker && typeof this.worker.ref === "function") {
|
|
367
|
-
;
|
|
368
372
|
this.worker.ref();
|
|
369
373
|
}
|
|
370
374
|
}
|
|
@@ -463,6 +467,9 @@ function chan(capacity = 0) {
|
|
|
463
467
|
function getChannelById(id) {
|
|
464
468
|
return channelRegistry.get(id);
|
|
465
469
|
}
|
|
470
|
+
function getChannelId(channel) {
|
|
471
|
+
return channel._id;
|
|
472
|
+
}
|
|
466
473
|
|
|
467
474
|
// src/adapters/inline.ts
|
|
468
475
|
var inlineIdCounter = 0;
|
|
@@ -479,15 +486,14 @@ var InlineManagedWorker = class {
|
|
|
479
486
|
this.emit("message", { type: "ready" });
|
|
480
487
|
});
|
|
481
488
|
}
|
|
482
|
-
postMessage(
|
|
489
|
+
postMessage(msg) {
|
|
483
490
|
if (this.terminated) return;
|
|
484
|
-
const msg = data;
|
|
485
491
|
if (msg.type === "execute") {
|
|
486
|
-
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent
|
|
492
|
+
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels);
|
|
487
493
|
} else if (msg.type === "cancel") {
|
|
488
494
|
this.cancelledTasks.add(msg.taskId);
|
|
489
495
|
} else if (msg.type === "channel-result") {
|
|
490
|
-
|
|
496
|
+
return;
|
|
491
497
|
} else if (msg.type === "shutdown") {
|
|
492
498
|
this.terminated = true;
|
|
493
499
|
this.emit("exit", 0);
|
|
@@ -517,7 +523,6 @@ var InlineManagedWorker = class {
|
|
|
517
523
|
}
|
|
518
524
|
}
|
|
519
525
|
buildChannelProxies(channels) {
|
|
520
|
-
const self = this;
|
|
521
526
|
const proxies = {};
|
|
522
527
|
for (const [name, channelId] of Object.entries(channels)) {
|
|
523
528
|
proxies[name] = {
|
|
@@ -539,13 +544,7 @@ var InlineManagedWorker = class {
|
|
|
539
544
|
[Symbol.asyncIterator]() {
|
|
540
545
|
const ch = getChannelById(channelId);
|
|
541
546
|
if (!ch) throw new Error(`Channel ${channelId} not found`);
|
|
542
|
-
return
|
|
543
|
-
async next() {
|
|
544
|
-
const value = await ch.recv();
|
|
545
|
-
if (value === null) return { done: true, value: void 0 };
|
|
546
|
-
return { done: false, value };
|
|
547
|
-
}
|
|
548
|
-
};
|
|
547
|
+
return ch[Symbol.asyncIterator]();
|
|
549
548
|
}
|
|
550
549
|
};
|
|
551
550
|
}
|
|
@@ -893,6 +892,15 @@ var WorkerPool = class {
|
|
|
893
892
|
task2.reject(reason);
|
|
894
893
|
}
|
|
895
894
|
}
|
|
895
|
+
rejectExclusiveTaskForWorker(worker, reason) {
|
|
896
|
+
for (const [taskId, assignedWorker] of this.exclusiveWorkers) {
|
|
897
|
+
if (assignedWorker === worker) {
|
|
898
|
+
this.exclusiveWorkers.delete(taskId);
|
|
899
|
+
this.rejectTask(taskId, reason);
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
896
904
|
// --- Cancellation ---
|
|
897
905
|
cancelTask(taskId) {
|
|
898
906
|
const removed = this.removeFromQueue(taskId);
|
|
@@ -941,6 +949,10 @@ var WorkerPool = class {
|
|
|
941
949
|
}
|
|
942
950
|
this.concurrentQueues[priority] = [];
|
|
943
951
|
}
|
|
952
|
+
for (const [taskId] of this.exclusiveWorkers) {
|
|
953
|
+
this.taskMap.delete(taskId);
|
|
954
|
+
}
|
|
955
|
+
this.exclusiveWorkers.clear();
|
|
944
956
|
for (const [, taskSet] of this.sharedWorkers) {
|
|
945
957
|
for (const taskId of taskSet) {
|
|
946
958
|
this.taskMap.delete(taskId);
|
|
@@ -998,8 +1010,7 @@ var WorkerPool = class {
|
|
|
998
1010
|
this.makeIdle(worker);
|
|
999
1011
|
}
|
|
1000
1012
|
};
|
|
1001
|
-
worker.on("message", (
|
|
1002
|
-
const response = msg;
|
|
1013
|
+
worker.on("message", (response) => {
|
|
1003
1014
|
if (response.type === "ready") {
|
|
1004
1015
|
onReady();
|
|
1005
1016
|
return;
|
|
@@ -1007,12 +1018,7 @@ var WorkerPool = class {
|
|
|
1007
1018
|
this.handleWorkerMessage(worker, response);
|
|
1008
1019
|
});
|
|
1009
1020
|
worker.on("error", (err) => {
|
|
1010
|
-
|
|
1011
|
-
if (w === worker) {
|
|
1012
|
-
this.exclusiveWorkers.delete(taskId);
|
|
1013
|
-
break;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1021
|
+
this.rejectExclusiveTaskForWorker(worker, err);
|
|
1016
1022
|
const taskSet = this.sharedWorkers.get(worker);
|
|
1017
1023
|
if (taskSet) {
|
|
1018
1024
|
for (const taskId of taskSet) {
|
|
@@ -1032,12 +1038,7 @@ var WorkerPool = class {
|
|
|
1032
1038
|
if (idleIdx !== -1) {
|
|
1033
1039
|
this.idleWorkers.splice(idleIdx, 1);
|
|
1034
1040
|
}
|
|
1035
|
-
|
|
1036
|
-
if (w === worker) {
|
|
1037
|
-
this.exclusiveWorkers.delete(taskId);
|
|
1038
|
-
break;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
+
this.rejectExclusiveTaskForWorker(worker, new Error("Worker exited unexpectedly"));
|
|
1041
1042
|
const taskSet = this.sharedWorkers.get(worker);
|
|
1042
1043
|
if (taskSet) {
|
|
1043
1044
|
for (const taskId of taskSet) {
|
|
@@ -1131,11 +1132,7 @@ function spawn(fn, opts) {
|
|
|
1131
1132
|
if (opts?.channels) {
|
|
1132
1133
|
channelMap = {};
|
|
1133
1134
|
for (const [name, ch] of Object.entries(opts.channels)) {
|
|
1134
|
-
|
|
1135
|
-
if (!impl._id) {
|
|
1136
|
-
throw new Error(`Channel "${name}" is not a valid puru channel`);
|
|
1137
|
-
}
|
|
1138
|
-
channelMap[name] = impl._id;
|
|
1135
|
+
channelMap[name] = getChannelId(ch);
|
|
1139
1136
|
}
|
|
1140
1137
|
}
|
|
1141
1138
|
const task2 = {
|
|
@@ -1224,7 +1221,7 @@ var WaitGroup = class {
|
|
|
1224
1221
|
var ErrGroup = class {
|
|
1225
1222
|
tasks = [];
|
|
1226
1223
|
controller = new AbortController();
|
|
1227
|
-
firstError =
|
|
1224
|
+
firstError = null;
|
|
1228
1225
|
hasError = false;
|
|
1229
1226
|
get signal() {
|
|
1230
1227
|
return this.controller.signal;
|
|
@@ -1245,7 +1242,7 @@ var ErrGroup = class {
|
|
|
1245
1242
|
}
|
|
1246
1243
|
async wait() {
|
|
1247
1244
|
const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
|
|
1248
|
-
if (this.hasError) {
|
|
1245
|
+
if (this.hasError && this.firstError) {
|
|
1249
1246
|
throw this.firstError;
|
|
1250
1247
|
}
|
|
1251
1248
|
return settled.map((r) => {
|