@dmop/puru 0.1.4 → 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/dist/index.js CHANGED
@@ -56,21 +56,20 @@ function getConfig() {
56
56
 
57
57
  // src/runtime.ts
58
58
  function detectRuntime() {
59
- if (typeof globalThis.Bun !== "undefined") return "bun";
60
- if (typeof globalThis.Deno !== "undefined") return "deno";
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 (typeof globalThis.Worker !== "undefined") return "full-threads";
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 as Worker2 } from "worker_threads";
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 Worker2(NODE_BOOTSTRAP_CODE, { eval: true });
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
- this.worker = new Worker(getBootstrapFile());
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
  }
@@ -376,9 +380,11 @@ var BunWorkerAdapter = class {
376
380
  };
377
381
 
378
382
  // src/channel.ts
383
+ var CLOSED = /* @__PURE__ */ Symbol("puru.channel.closed");
379
384
  var channelIdCounter = 0;
380
385
  var channelRegistry = /* @__PURE__ */ new Map();
381
386
  var ChannelImpl = class {
387
+ // constraint: can't create channels of nullable type
382
388
  /** @internal */
383
389
  _id;
384
390
  buffer = [];
@@ -427,14 +433,16 @@ var ChannelImpl = class {
427
433
  return Promise.resolve(null);
428
434
  }
429
435
  return new Promise((resolve) => {
430
- this.recvQueue.push({ resolve });
436
+ this.recvQueue.push({
437
+ resolve: (v) => resolve(v === CLOSED ? null : v)
438
+ });
431
439
  });
432
440
  }
433
441
  close() {
434
442
  if (this.closed) return;
435
443
  this.closed = true;
436
444
  for (const receiver of this.recvQueue) {
437
- receiver.resolve(null);
445
+ receiver.resolve(CLOSED);
438
446
  }
439
447
  this.recvQueue = [];
440
448
  for (const sender of this.sendQueue) {
@@ -459,6 +467,9 @@ function chan(capacity = 0) {
459
467
  function getChannelById(id) {
460
468
  return channelRegistry.get(id);
461
469
  }
470
+ function getChannelId(channel) {
471
+ return channel._id;
472
+ }
462
473
 
463
474
  // src/adapters/inline.ts
464
475
  var inlineIdCounter = 0;
@@ -475,15 +486,14 @@ var InlineManagedWorker = class {
475
486
  this.emit("message", { type: "ready" });
476
487
  });
477
488
  }
478
- postMessage(data) {
489
+ postMessage(msg) {
479
490
  if (this.terminated) return;
480
- const msg = data;
481
491
  if (msg.type === "execute") {
482
- this.executeTask(msg.taskId, msg.fnStr, msg.concurrent ?? false, msg.channels);
492
+ this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels);
483
493
  } else if (msg.type === "cancel") {
484
494
  this.cancelledTasks.add(msg.taskId);
485
495
  } else if (msg.type === "channel-result") {
486
- this.emit("message", msg);
496
+ return;
487
497
  } else if (msg.type === "shutdown") {
488
498
  this.terminated = true;
489
499
  this.emit("exit", 0);
@@ -513,7 +523,6 @@ var InlineManagedWorker = class {
513
523
  }
514
524
  }
515
525
  buildChannelProxies(channels) {
516
- const self = this;
517
526
  const proxies = {};
518
527
  for (const [name, channelId] of Object.entries(channels)) {
519
528
  proxies[name] = {
@@ -535,13 +544,7 @@ var InlineManagedWorker = class {
535
544
  [Symbol.asyncIterator]() {
536
545
  const ch = getChannelById(channelId);
537
546
  if (!ch) throw new Error(`Channel ${channelId} not found`);
538
- return {
539
- async next() {
540
- const value = await ch.recv();
541
- if (value === null) return { done: true, value: void 0 };
542
- return { done: false, value };
543
- }
544
- };
547
+ return ch[Symbol.asyncIterator]();
545
548
  }
546
549
  };
547
550
  }
@@ -620,14 +623,14 @@ var WorkerPool = class {
620
623
  this.adapter = adapter;
621
624
  }
622
625
  // --- Queue helpers ---
623
- enqueue(task) {
624
- this.queues[task.priority].push(task);
626
+ enqueue(task2) {
627
+ this.queues[task2.priority].push(task2);
625
628
  }
626
629
  dequeue() {
627
630
  return this.queues.high.shift() ?? this.queues.normal.shift() ?? this.queues.low.shift();
628
631
  }
629
- enqueueConcurrent(task) {
630
- this.concurrentQueues[task.priority].push(task);
632
+ enqueueConcurrent(task2) {
633
+ this.concurrentQueues[task2.priority].push(task2);
631
634
  }
632
635
  dequeueConcurrent() {
633
636
  return this.concurrentQueues.high.shift() ?? this.concurrentQueues.normal.shift() ?? this.concurrentQueues.low.shift();
@@ -653,73 +656,73 @@ var WorkerPool = class {
653
656
  return void 0;
654
657
  }
655
658
  // --- Submit ---
656
- submit(task) {
659
+ submit(task2) {
657
660
  if (this.draining) {
658
- task.reject(new Error("Pool is shutting down"));
661
+ task2.reject(new Error("Pool is shutting down"));
659
662
  return;
660
663
  }
661
- if (task.concurrent) {
662
- this.submitConcurrent(task);
664
+ if (task2.concurrent) {
665
+ this.submitConcurrent(task2);
663
666
  } else {
664
- this.submitExclusive(task);
667
+ this.submitExclusive(task2);
665
668
  }
666
669
  }
667
- submitExclusive(task) {
670
+ submitExclusive(task2) {
668
671
  const worker = this.idleWorkers.pop();
669
672
  if (worker) {
670
- this.dispatch(worker, task);
673
+ this.dispatch(worker, task2);
671
674
  return;
672
675
  }
673
676
  const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
674
677
  if (totalWorkers < this.config.maxThreads) {
675
678
  this.pendingWorkerCount++;
676
- this.pendingTasksForWorkers.push(task);
679
+ this.pendingTasksForWorkers.push(task2);
677
680
  this.createAndReadyWorker();
678
681
  return;
679
682
  }
680
- this.enqueue(task);
683
+ this.enqueue(task2);
681
684
  }
682
- submitConcurrent(task) {
685
+ submitConcurrent(task2) {
683
686
  for (const [worker, tasks] of this.sharedWorkers) {
684
687
  if (tasks.size < this.config.concurrency) {
685
- this.dispatchConcurrent(worker, task);
688
+ this.dispatchConcurrent(worker, task2);
686
689
  return;
687
690
  }
688
691
  }
689
692
  const idleWorker = this.idleWorkers.pop();
690
693
  if (idleWorker) {
691
- this.dispatchConcurrent(idleWorker, task);
694
+ this.dispatchConcurrent(idleWorker, task2);
692
695
  return;
693
696
  }
694
697
  const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
695
698
  if (totalWorkers < this.config.maxThreads) {
696
699
  this.pendingWorkerCount++;
697
- this.pendingTasksForWorkers.push(task);
700
+ this.pendingTasksForWorkers.push(task2);
698
701
  this.createAndReadyWorker();
699
702
  return;
700
703
  }
701
- this.enqueueConcurrent(task);
704
+ this.enqueueConcurrent(task2);
702
705
  }
703
706
  // --- Dispatch ---
704
- dispatch(worker, task) {
707
+ dispatch(worker, task2) {
705
708
  const timer = this.idleTimers.get(worker);
706
709
  if (timer) {
707
710
  clearTimeout(timer);
708
711
  this.idleTimers.delete(worker);
709
712
  }
710
713
  worker.ref();
711
- this.exclusiveWorkers.set(task.id, worker);
712
- this.taskMap.set(task.id, task);
714
+ this.exclusiveWorkers.set(task2.id, worker);
715
+ this.taskMap.set(task2.id, task2);
713
716
  const msg = {
714
717
  type: "execute",
715
- taskId: task.id,
716
- fnStr: task.fnStr,
718
+ taskId: task2.id,
719
+ fnStr: task2.fnStr,
717
720
  concurrent: false,
718
- channels: task.channels
721
+ channels: task2.channels
719
722
  };
720
723
  worker.postMessage(msg);
721
724
  }
722
- dispatchConcurrent(worker, task) {
725
+ dispatchConcurrent(worker, task2) {
723
726
  const timer = this.idleTimers.get(worker);
724
727
  if (timer) {
725
728
  clearTimeout(timer);
@@ -729,14 +732,14 @@ var WorkerPool = class {
729
732
  if (!this.sharedWorkers.has(worker)) {
730
733
  this.sharedWorkers.set(worker, /* @__PURE__ */ new Set());
731
734
  }
732
- this.sharedWorkers.get(worker).add(task.id);
733
- this.taskMap.set(task.id, task);
735
+ this.sharedWorkers.get(worker).add(task2.id);
736
+ this.taskMap.set(task2.id, task2);
734
737
  const msg = {
735
738
  type: "execute",
736
- taskId: task.id,
737
- fnStr: task.fnStr,
739
+ taskId: task2.id,
740
+ fnStr: task2.fnStr,
738
741
  concurrent: true,
739
- channels: task.channels
742
+ channels: task2.channels
740
743
  };
741
744
  worker.postMessage(msg);
742
745
  }
@@ -763,6 +766,9 @@ var WorkerPool = class {
763
766
  const taskId = response.taskId;
764
767
  const err = new Error(response.message);
765
768
  if (response.stack) err.stack = response.stack;
769
+ if (response.message.match(/^ReferenceError:/) || response.message.match(/ is not defined$/)) {
770
+ err.message += "\n Hint: functions passed to spawn() cannot access variables from the enclosing scope. Inline all required values directly in the function body, or pass them via the channels option.";
771
+ }
766
772
  this.rejectTask(taskId, err);
767
773
  if (this.exclusiveWorkers.has(taskId)) {
768
774
  this.exclusiveWorkers.delete(taskId);
@@ -871,19 +877,28 @@ var WorkerPool = class {
871
877
  }
872
878
  // --- Task resolution ---
873
879
  resolveTask(taskId, value) {
874
- const task = this.taskMap.get(taskId);
875
- if (task) {
880
+ const task2 = this.taskMap.get(taskId);
881
+ if (task2) {
876
882
  this.taskMap.delete(taskId);
877
883
  this.totalCompleted++;
878
- task.resolve(value);
884
+ task2.resolve(value);
879
885
  }
880
886
  }
881
887
  rejectTask(taskId, reason) {
882
- const task = this.taskMap.get(taskId);
883
- if (task) {
888
+ const task2 = this.taskMap.get(taskId);
889
+ if (task2) {
884
890
  this.taskMap.delete(taskId);
885
891
  this.totalFailed++;
886
- task.reject(reason);
892
+ task2.reject(reason);
893
+ }
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
+ }
887
902
  }
888
903
  }
889
904
  // --- Cancellation ---
@@ -923,17 +938,21 @@ var WorkerPool = class {
923
938
  async drain() {
924
939
  this.draining = true;
925
940
  for (const priority of ["high", "normal", "low"]) {
926
- for (const task of this.queues[priority]) {
927
- task.reject(new Error("Pool is shutting down"));
941
+ for (const task2 of this.queues[priority]) {
942
+ task2.reject(new Error("Pool is shutting down"));
928
943
  }
929
944
  this.queues[priority] = [];
930
945
  }
931
946
  for (const priority of ["high", "normal", "low"]) {
932
- for (const task of this.concurrentQueues[priority]) {
933
- task.reject(new Error("Pool is shutting down"));
947
+ for (const task2 of this.concurrentQueues[priority]) {
948
+ task2.reject(new Error("Pool is shutting down"));
934
949
  }
935
950
  this.concurrentQueues[priority] = [];
936
951
  }
952
+ for (const [taskId] of this.exclusiveWorkers) {
953
+ this.taskMap.delete(taskId);
954
+ }
955
+ this.exclusiveWorkers.clear();
937
956
  for (const [, taskSet] of this.sharedWorkers) {
938
957
  for (const taskId of taskSet) {
939
958
  this.taskMap.delete(taskId);
@@ -957,10 +976,10 @@ var WorkerPool = class {
957
976
  while (true) {
958
977
  const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
959
978
  if (totalWorkers >= maxThreads) break;
960
- const task = this.dequeue() ?? this.dequeueConcurrent();
961
- if (!task) break;
979
+ const task2 = this.dequeue() ?? this.dequeueConcurrent();
980
+ if (!task2) break;
962
981
  this.pendingWorkerCount++;
963
- this.pendingTasksForWorkers.push(task);
982
+ this.pendingTasksForWorkers.push(task2);
964
983
  this.createAndReadyWorker();
965
984
  }
966
985
  while (this.allWorkers.size > maxThreads && this.idleWorkers.length > 0) {
@@ -980,19 +999,18 @@ var WorkerPool = class {
980
999
  const onReady = () => {
981
1000
  this.pendingWorkerCount--;
982
1001
  this.allWorkers.add(worker);
983
- const task = this.pendingTasksForWorkers.shift();
984
- if (task) {
985
- if (task.concurrent) {
986
- this.dispatchConcurrent(worker, task);
1002
+ const task2 = this.pendingTasksForWorkers.shift();
1003
+ if (task2) {
1004
+ if (task2.concurrent) {
1005
+ this.dispatchConcurrent(worker, task2);
987
1006
  } else {
988
- this.dispatch(worker, task);
1007
+ this.dispatch(worker, task2);
989
1008
  }
990
1009
  } else {
991
1010
  this.makeIdle(worker);
992
1011
  }
993
1012
  };
994
- worker.on("message", (msg) => {
995
- const response = msg;
1013
+ worker.on("message", (response) => {
996
1014
  if (response.type === "ready") {
997
1015
  onReady();
998
1016
  return;
@@ -1000,12 +1018,7 @@ var WorkerPool = class {
1000
1018
  this.handleWorkerMessage(worker, response);
1001
1019
  });
1002
1020
  worker.on("error", (err) => {
1003
- for (const [taskId, w] of this.exclusiveWorkers) {
1004
- if (w === worker) {
1005
- this.exclusiveWorkers.delete(taskId);
1006
- break;
1007
- }
1008
- }
1021
+ this.rejectExclusiveTaskForWorker(worker, err);
1009
1022
  const taskSet = this.sharedWorkers.get(worker);
1010
1023
  if (taskSet) {
1011
1024
  for (const taskId of taskSet) {
@@ -1025,12 +1038,7 @@ var WorkerPool = class {
1025
1038
  if (idleIdx !== -1) {
1026
1039
  this.idleWorkers.splice(idleIdx, 1);
1027
1040
  }
1028
- for (const [taskId, w] of this.exclusiveWorkers) {
1029
- if (w === worker) {
1030
- this.exclusiveWorkers.delete(taskId);
1031
- break;
1032
- }
1033
- }
1041
+ this.rejectExclusiveTaskForWorker(worker, new Error("Worker exited unexpectedly"));
1034
1042
  const taskSet = this.sharedWorkers.get(worker);
1035
1043
  if (taskSet) {
1036
1044
  for (const taskId of taskSet) {
@@ -1100,6 +1108,12 @@ function stats() {
1100
1108
  function resize(maxThreads) {
1101
1109
  getPool().resize(maxThreads);
1102
1110
  }
1111
+ async function shutdown() {
1112
+ if (poolInstance) {
1113
+ await poolInstance.drain();
1114
+ poolInstance = null;
1115
+ }
1116
+ }
1103
1117
 
1104
1118
  // src/spawn.ts
1105
1119
  var taskCounter = 0;
@@ -1118,14 +1132,10 @@ function spawn(fn, opts) {
1118
1132
  if (opts?.channels) {
1119
1133
  channelMap = {};
1120
1134
  for (const [name, ch] of Object.entries(opts.channels)) {
1121
- const impl = ch;
1122
- if (!impl._id) {
1123
- throw new Error(`Channel "${name}" is not a valid puru channel`);
1124
- }
1125
- channelMap[name] = impl._id;
1135
+ channelMap[name] = getChannelId(ch);
1126
1136
  }
1127
1137
  }
1128
- const task = {
1138
+ const task2 = {
1129
1139
  id: taskId,
1130
1140
  fnStr,
1131
1141
  priority: opts?.priority ?? "normal",
@@ -1148,7 +1158,7 @@ function spawn(fn, opts) {
1148
1158
  }
1149
1159
  }
1150
1160
  };
1151
- getPool().submit(task);
1161
+ getPool().submit(task2);
1152
1162
  const cancel = () => {
1153
1163
  if (settled) return;
1154
1164
  settled = true;
@@ -1162,9 +1172,18 @@ function spawn(fn, opts) {
1162
1172
  var WaitGroup = class {
1163
1173
  tasks = [];
1164
1174
  controller = new AbortController();
1175
+ /**
1176
+ * An `AbortSignal` shared across all tasks in this group.
1177
+ * Pass it into spawned functions so they can stop early when `cancel()` is called.
1178
+ */
1165
1179
  get signal() {
1166
1180
  return this.controller.signal;
1167
1181
  }
1182
+ /**
1183
+ * Spawns a function on a worker thread and adds it to the group.
1184
+ *
1185
+ * @throws If the group has already been cancelled.
1186
+ */
1168
1187
  spawn(fn, opts) {
1169
1188
  if (this.controller.signal.aborted) {
1170
1189
  throw new Error("WaitGroup has been cancelled");
@@ -1172,16 +1191,28 @@ var WaitGroup = class {
1172
1191
  const handle = spawn(fn, opts);
1173
1192
  this.tasks.push(handle);
1174
1193
  }
1194
+ /**
1195
+ * Waits for all tasks to complete successfully.
1196
+ * Rejects as soon as any task throws.
1197
+ */
1175
1198
  async wait() {
1176
1199
  return Promise.all(this.tasks.map((t) => t.result));
1177
1200
  }
1201
+ /**
1202
+ * Waits for all tasks to settle (fulfilled or rejected) and returns each outcome.
1203
+ * Never rejects — inspect each `PromiseSettledResult` to handle failures individually.
1204
+ */
1178
1205
  async waitSettled() {
1179
1206
  return Promise.allSettled(this.tasks.map((t) => t.result));
1180
1207
  }
1208
+ /**
1209
+ * Cancels all tasks in the group and signals the shared `AbortSignal`.
1210
+ * Already-settled tasks are unaffected.
1211
+ */
1181
1212
  cancel() {
1182
1213
  this.controller.abort();
1183
- for (const task of this.tasks) {
1184
- task.cancel();
1214
+ for (const task2 of this.tasks) {
1215
+ task2.cancel();
1185
1216
  }
1186
1217
  }
1187
1218
  };
@@ -1190,7 +1221,7 @@ var WaitGroup = class {
1190
1221
  var ErrGroup = class {
1191
1222
  tasks = [];
1192
1223
  controller = new AbortController();
1193
- firstError = void 0;
1224
+ firstError = null;
1194
1225
  hasError = false;
1195
1226
  get signal() {
1196
1227
  return this.controller.signal;
@@ -1211,7 +1242,7 @@ var ErrGroup = class {
1211
1242
  }
1212
1243
  async wait() {
1213
1244
  const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
1214
- if (this.hasError) {
1245
+ if (this.hasError && this.firstError) {
1215
1246
  throw this.firstError;
1216
1247
  }
1217
1248
  return settled.map((r) => {
@@ -1221,8 +1252,8 @@ var ErrGroup = class {
1221
1252
  }
1222
1253
  cancel() {
1223
1254
  this.controller.abort();
1224
- for (const task of this.tasks) {
1225
- task.cancel();
1255
+ for (const task2 of this.tasks) {
1256
+ task2.cancel();
1226
1257
  }
1227
1258
  }
1228
1259
  };
@@ -1367,7 +1398,7 @@ var Ticker = class {
1367
1398
  if (this.resolve) {
1368
1399
  const r = this.resolve;
1369
1400
  this.resolve = null;
1370
- r();
1401
+ r(true);
1371
1402
  }
1372
1403
  }, ms);
1373
1404
  if (this.interval.unref) this.interval.unref();
@@ -1375,7 +1406,11 @@ var Ticker = class {
1375
1406
  async tick() {
1376
1407
  if (this.stopped) return false;
1377
1408
  return new Promise((resolve) => {
1378
- this.resolve = () => resolve(true);
1409
+ this.resolve = resolve;
1410
+ if (this.stopped) {
1411
+ this.resolve = null;
1412
+ resolve(false);
1413
+ }
1379
1414
  });
1380
1415
  }
1381
1416
  stop() {
@@ -1387,7 +1422,7 @@ var Ticker = class {
1387
1422
  if (this.resolve) {
1388
1423
  const r = this.resolve;
1389
1424
  this.resolve = null;
1390
- r();
1425
+ r(false);
1391
1426
  }
1392
1427
  }
1393
1428
  async *[Symbol.asyncIterator]() {
@@ -1401,54 +1436,45 @@ function ticker(ms) {
1401
1436
  }
1402
1437
 
1403
1438
  // src/registry.ts
1404
- var registry = /* @__PURE__ */ new Map();
1405
1439
  var taskCounter2 = 0;
1406
- function register(name, fn) {
1407
- if (registry.has(name)) {
1408
- throw new Error(`Task "${name}" is already registered`);
1409
- }
1410
- registry.set(name, fn);
1411
- }
1412
- function run(name, ...args) {
1413
- const fn = registry.get(name);
1414
- if (!fn) {
1415
- throw new Error(`Task "${name}" is not registered. Call register() first.`);
1416
- }
1417
- const fnStr = serializeFunction(fn);
1418
- const serializedArgs = args.map((a) => {
1419
- const json = JSON.stringify(a);
1420
- if (json === void 0) {
1421
- throw new TypeError(
1422
- `Argument of type ${typeof a} is not JSON-serializable. run() args must be JSON-serializable (no undefined, functions, symbols, or BigInt).`
1423
- );
1424
- }
1425
- return json;
1426
- });
1427
- const wrapperStr = `() => (${fnStr})(${serializedArgs.join(", ")})`;
1428
- const taskId = `reg_${++taskCounter2}`;
1429
- const spawnStack = new Error().stack;
1430
- let resolveFn;
1431
- let rejectFn;
1432
- const result = new Promise((resolve, reject) => {
1433
- resolveFn = resolve;
1434
- rejectFn = reject;
1435
- });
1436
- const task = {
1437
- id: taskId,
1438
- fnStr: wrapperStr,
1439
- priority: "normal",
1440
- concurrent: false,
1441
- resolve: (value) => resolveFn(value),
1442
- reject: (reason) => {
1443
- if (reason instanceof Error && spawnStack) {
1444
- const callerLine = spawnStack.split("\n").slice(2).join("\n");
1445
- reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
1440
+ function task(fn) {
1441
+ return (...args) => {
1442
+ const fnStr = serializeFunction(fn);
1443
+ const serializedArgs = args.map((a) => {
1444
+ const json = JSON.stringify(a);
1445
+ if (json === void 0) {
1446
+ throw new TypeError(
1447
+ `Argument of type ${typeof a} is not JSON-serializable. task() args must be JSON-serializable (no undefined, functions, symbols, or BigInt).`
1448
+ );
1446
1449
  }
1447
- rejectFn(reason);
1448
- }
1450
+ return json;
1451
+ });
1452
+ const wrapperStr = `() => (${fnStr})(${serializedArgs.join(", ")})`;
1453
+ const taskId = `task_${++taskCounter2}`;
1454
+ const spawnStack = new Error().stack;
1455
+ let resolveFn;
1456
+ let rejectFn;
1457
+ const result = new Promise((resolve, reject) => {
1458
+ resolveFn = resolve;
1459
+ rejectFn = reject;
1460
+ });
1461
+ const taskObj = {
1462
+ id: taskId,
1463
+ fnStr: wrapperStr,
1464
+ priority: "normal",
1465
+ concurrent: false,
1466
+ resolve: (value) => resolveFn(value),
1467
+ reject: (reason) => {
1468
+ if (reason instanceof Error && spawnStack) {
1469
+ const callerLine = spawnStack.split("\n").slice(2).join("\n");
1470
+ reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
1471
+ }
1472
+ rejectFn(reason);
1473
+ }
1474
+ };
1475
+ getPool().submit(taskObj);
1476
+ return result;
1449
1477
  };
1450
- getPool().submit(task);
1451
- return result;
1452
1478
  }
1453
1479
  export {
1454
1480
  ErrGroup,
@@ -1461,12 +1487,12 @@ export {
1461
1487
  configure,
1462
1488
  detectCapability,
1463
1489
  detectRuntime,
1464
- register,
1465
1490
  resize,
1466
- run,
1467
1491
  select,
1492
+ shutdown,
1468
1493
  spawn,
1469
1494
  stats,
1495
+ task,
1470
1496
  ticker
1471
1497
  };
1472
1498
  //# sourceMappingURL=index.js.map