@dmop/puru 0.1.3 → 0.1.5

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
@@ -376,9 +376,11 @@ var BunWorkerAdapter = class {
376
376
  };
377
377
 
378
378
  // src/channel.ts
379
+ var CLOSED = /* @__PURE__ */ Symbol("puru.channel.closed");
379
380
  var channelIdCounter = 0;
380
381
  var channelRegistry = /* @__PURE__ */ new Map();
381
382
  var ChannelImpl = class {
383
+ // constraint: can't create channels of nullable type
382
384
  /** @internal */
383
385
  _id;
384
386
  buffer = [];
@@ -427,14 +429,16 @@ var ChannelImpl = class {
427
429
  return Promise.resolve(null);
428
430
  }
429
431
  return new Promise((resolve) => {
430
- this.recvQueue.push({ resolve });
432
+ this.recvQueue.push({
433
+ resolve: (v) => resolve(v === CLOSED ? null : v)
434
+ });
431
435
  });
432
436
  }
433
437
  close() {
434
438
  if (this.closed) return;
435
439
  this.closed = true;
436
440
  for (const receiver of this.recvQueue) {
437
- receiver.resolve(null);
441
+ receiver.resolve(CLOSED);
438
442
  }
439
443
  this.recvQueue = [];
440
444
  for (const sender of this.sendQueue) {
@@ -620,14 +624,14 @@ var WorkerPool = class {
620
624
  this.adapter = adapter;
621
625
  }
622
626
  // --- Queue helpers ---
623
- enqueue(task) {
624
- this.queues[task.priority].push(task);
627
+ enqueue(task2) {
628
+ this.queues[task2.priority].push(task2);
625
629
  }
626
630
  dequeue() {
627
631
  return this.queues.high.shift() ?? this.queues.normal.shift() ?? this.queues.low.shift();
628
632
  }
629
- enqueueConcurrent(task) {
630
- this.concurrentQueues[task.priority].push(task);
633
+ enqueueConcurrent(task2) {
634
+ this.concurrentQueues[task2.priority].push(task2);
631
635
  }
632
636
  dequeueConcurrent() {
633
637
  return this.concurrentQueues.high.shift() ?? this.concurrentQueues.normal.shift() ?? this.concurrentQueues.low.shift();
@@ -653,73 +657,73 @@ var WorkerPool = class {
653
657
  return void 0;
654
658
  }
655
659
  // --- Submit ---
656
- submit(task) {
660
+ submit(task2) {
657
661
  if (this.draining) {
658
- task.reject(new Error("Pool is shutting down"));
662
+ task2.reject(new Error("Pool is shutting down"));
659
663
  return;
660
664
  }
661
- if (task.concurrent) {
662
- this.submitConcurrent(task);
665
+ if (task2.concurrent) {
666
+ this.submitConcurrent(task2);
663
667
  } else {
664
- this.submitExclusive(task);
668
+ this.submitExclusive(task2);
665
669
  }
666
670
  }
667
- submitExclusive(task) {
671
+ submitExclusive(task2) {
668
672
  const worker = this.idleWorkers.pop();
669
673
  if (worker) {
670
- this.dispatch(worker, task);
674
+ this.dispatch(worker, task2);
671
675
  return;
672
676
  }
673
677
  const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
674
678
  if (totalWorkers < this.config.maxThreads) {
675
679
  this.pendingWorkerCount++;
676
- this.pendingTasksForWorkers.push(task);
680
+ this.pendingTasksForWorkers.push(task2);
677
681
  this.createAndReadyWorker();
678
682
  return;
679
683
  }
680
- this.enqueue(task);
684
+ this.enqueue(task2);
681
685
  }
682
- submitConcurrent(task) {
686
+ submitConcurrent(task2) {
683
687
  for (const [worker, tasks] of this.sharedWorkers) {
684
688
  if (tasks.size < this.config.concurrency) {
685
- this.dispatchConcurrent(worker, task);
689
+ this.dispatchConcurrent(worker, task2);
686
690
  return;
687
691
  }
688
692
  }
689
693
  const idleWorker = this.idleWorkers.pop();
690
694
  if (idleWorker) {
691
- this.dispatchConcurrent(idleWorker, task);
695
+ this.dispatchConcurrent(idleWorker, task2);
692
696
  return;
693
697
  }
694
698
  const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
695
699
  if (totalWorkers < this.config.maxThreads) {
696
700
  this.pendingWorkerCount++;
697
- this.pendingTasksForWorkers.push(task);
701
+ this.pendingTasksForWorkers.push(task2);
698
702
  this.createAndReadyWorker();
699
703
  return;
700
704
  }
701
- this.enqueueConcurrent(task);
705
+ this.enqueueConcurrent(task2);
702
706
  }
703
707
  // --- Dispatch ---
704
- dispatch(worker, task) {
708
+ dispatch(worker, task2) {
705
709
  const timer = this.idleTimers.get(worker);
706
710
  if (timer) {
707
711
  clearTimeout(timer);
708
712
  this.idleTimers.delete(worker);
709
713
  }
710
714
  worker.ref();
711
- this.exclusiveWorkers.set(task.id, worker);
712
- this.taskMap.set(task.id, task);
715
+ this.exclusiveWorkers.set(task2.id, worker);
716
+ this.taskMap.set(task2.id, task2);
713
717
  const msg = {
714
718
  type: "execute",
715
- taskId: task.id,
716
- fnStr: task.fnStr,
719
+ taskId: task2.id,
720
+ fnStr: task2.fnStr,
717
721
  concurrent: false,
718
- channels: task.channels
722
+ channels: task2.channels
719
723
  };
720
724
  worker.postMessage(msg);
721
725
  }
722
- dispatchConcurrent(worker, task) {
726
+ dispatchConcurrent(worker, task2) {
723
727
  const timer = this.idleTimers.get(worker);
724
728
  if (timer) {
725
729
  clearTimeout(timer);
@@ -729,14 +733,14 @@ var WorkerPool = class {
729
733
  if (!this.sharedWorkers.has(worker)) {
730
734
  this.sharedWorkers.set(worker, /* @__PURE__ */ new Set());
731
735
  }
732
- this.sharedWorkers.get(worker).add(task.id);
733
- this.taskMap.set(task.id, task);
736
+ this.sharedWorkers.get(worker).add(task2.id);
737
+ this.taskMap.set(task2.id, task2);
734
738
  const msg = {
735
739
  type: "execute",
736
- taskId: task.id,
737
- fnStr: task.fnStr,
740
+ taskId: task2.id,
741
+ fnStr: task2.fnStr,
738
742
  concurrent: true,
739
- channels: task.channels
743
+ channels: task2.channels
740
744
  };
741
745
  worker.postMessage(msg);
742
746
  }
@@ -763,6 +767,9 @@ var WorkerPool = class {
763
767
  const taskId = response.taskId;
764
768
  const err = new Error(response.message);
765
769
  if (response.stack) err.stack = response.stack;
770
+ if (response.message.match(/^ReferenceError:/) || response.message.match(/ is not defined$/)) {
771
+ 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.";
772
+ }
766
773
  this.rejectTask(taskId, err);
767
774
  if (this.exclusiveWorkers.has(taskId)) {
768
775
  this.exclusiveWorkers.delete(taskId);
@@ -871,19 +878,19 @@ var WorkerPool = class {
871
878
  }
872
879
  // --- Task resolution ---
873
880
  resolveTask(taskId, value) {
874
- const task = this.taskMap.get(taskId);
875
- if (task) {
881
+ const task2 = this.taskMap.get(taskId);
882
+ if (task2) {
876
883
  this.taskMap.delete(taskId);
877
884
  this.totalCompleted++;
878
- task.resolve(value);
885
+ task2.resolve(value);
879
886
  }
880
887
  }
881
888
  rejectTask(taskId, reason) {
882
- const task = this.taskMap.get(taskId);
883
- if (task) {
889
+ const task2 = this.taskMap.get(taskId);
890
+ if (task2) {
884
891
  this.taskMap.delete(taskId);
885
892
  this.totalFailed++;
886
- task.reject(reason);
893
+ task2.reject(reason);
887
894
  }
888
895
  }
889
896
  // --- Cancellation ---
@@ -923,14 +930,14 @@ var WorkerPool = class {
923
930
  async drain() {
924
931
  this.draining = true;
925
932
  for (const priority of ["high", "normal", "low"]) {
926
- for (const task of this.queues[priority]) {
927
- task.reject(new Error("Pool is shutting down"));
933
+ for (const task2 of this.queues[priority]) {
934
+ task2.reject(new Error("Pool is shutting down"));
928
935
  }
929
936
  this.queues[priority] = [];
930
937
  }
931
938
  for (const priority of ["high", "normal", "low"]) {
932
- for (const task of this.concurrentQueues[priority]) {
933
- task.reject(new Error("Pool is shutting down"));
939
+ for (const task2 of this.concurrentQueues[priority]) {
940
+ task2.reject(new Error("Pool is shutting down"));
934
941
  }
935
942
  this.concurrentQueues[priority] = [];
936
943
  }
@@ -957,10 +964,10 @@ var WorkerPool = class {
957
964
  while (true) {
958
965
  const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
959
966
  if (totalWorkers >= maxThreads) break;
960
- const task = this.dequeue() ?? this.dequeueConcurrent();
961
- if (!task) break;
967
+ const task2 = this.dequeue() ?? this.dequeueConcurrent();
968
+ if (!task2) break;
962
969
  this.pendingWorkerCount++;
963
- this.pendingTasksForWorkers.push(task);
970
+ this.pendingTasksForWorkers.push(task2);
964
971
  this.createAndReadyWorker();
965
972
  }
966
973
  while (this.allWorkers.size > maxThreads && this.idleWorkers.length > 0) {
@@ -980,12 +987,12 @@ var WorkerPool = class {
980
987
  const onReady = () => {
981
988
  this.pendingWorkerCount--;
982
989
  this.allWorkers.add(worker);
983
- const task = this.pendingTasksForWorkers.shift();
984
- if (task) {
985
- if (task.concurrent) {
986
- this.dispatchConcurrent(worker, task);
990
+ const task2 = this.pendingTasksForWorkers.shift();
991
+ if (task2) {
992
+ if (task2.concurrent) {
993
+ this.dispatchConcurrent(worker, task2);
987
994
  } else {
988
- this.dispatch(worker, task);
995
+ this.dispatch(worker, task2);
989
996
  }
990
997
  } else {
991
998
  this.makeIdle(worker);
@@ -1100,6 +1107,12 @@ function stats() {
1100
1107
  function resize(maxThreads) {
1101
1108
  getPool().resize(maxThreads);
1102
1109
  }
1110
+ async function shutdown() {
1111
+ if (poolInstance) {
1112
+ await poolInstance.drain();
1113
+ poolInstance = null;
1114
+ }
1115
+ }
1103
1116
 
1104
1117
  // src/spawn.ts
1105
1118
  var taskCounter = 0;
@@ -1125,7 +1138,7 @@ function spawn(fn, opts) {
1125
1138
  channelMap[name] = impl._id;
1126
1139
  }
1127
1140
  }
1128
- const task = {
1141
+ const task2 = {
1129
1142
  id: taskId,
1130
1143
  fnStr,
1131
1144
  priority: opts?.priority ?? "normal",
@@ -1148,7 +1161,7 @@ function spawn(fn, opts) {
1148
1161
  }
1149
1162
  }
1150
1163
  };
1151
- getPool().submit(task);
1164
+ getPool().submit(task2);
1152
1165
  const cancel = () => {
1153
1166
  if (settled) return;
1154
1167
  settled = true;
@@ -1162,9 +1175,18 @@ function spawn(fn, opts) {
1162
1175
  var WaitGroup = class {
1163
1176
  tasks = [];
1164
1177
  controller = new AbortController();
1178
+ /**
1179
+ * An `AbortSignal` shared across all tasks in this group.
1180
+ * Pass it into spawned functions so they can stop early when `cancel()` is called.
1181
+ */
1165
1182
  get signal() {
1166
1183
  return this.controller.signal;
1167
1184
  }
1185
+ /**
1186
+ * Spawns a function on a worker thread and adds it to the group.
1187
+ *
1188
+ * @throws If the group has already been cancelled.
1189
+ */
1168
1190
  spawn(fn, opts) {
1169
1191
  if (this.controller.signal.aborted) {
1170
1192
  throw new Error("WaitGroup has been cancelled");
@@ -1172,16 +1194,28 @@ var WaitGroup = class {
1172
1194
  const handle = spawn(fn, opts);
1173
1195
  this.tasks.push(handle);
1174
1196
  }
1197
+ /**
1198
+ * Waits for all tasks to complete successfully.
1199
+ * Rejects as soon as any task throws.
1200
+ */
1175
1201
  async wait() {
1176
1202
  return Promise.all(this.tasks.map((t) => t.result));
1177
1203
  }
1204
+ /**
1205
+ * Waits for all tasks to settle (fulfilled or rejected) and returns each outcome.
1206
+ * Never rejects — inspect each `PromiseSettledResult` to handle failures individually.
1207
+ */
1178
1208
  async waitSettled() {
1179
1209
  return Promise.allSettled(this.tasks.map((t) => t.result));
1180
1210
  }
1211
+ /**
1212
+ * Cancels all tasks in the group and signals the shared `AbortSignal`.
1213
+ * Already-settled tasks are unaffected.
1214
+ */
1181
1215
  cancel() {
1182
1216
  this.controller.abort();
1183
- for (const task of this.tasks) {
1184
- task.cancel();
1217
+ for (const task2 of this.tasks) {
1218
+ task2.cancel();
1185
1219
  }
1186
1220
  }
1187
1221
  };
@@ -1221,8 +1255,8 @@ var ErrGroup = class {
1221
1255
  }
1222
1256
  cancel() {
1223
1257
  this.controller.abort();
1224
- for (const task of this.tasks) {
1225
- task.cancel();
1258
+ for (const task2 of this.tasks) {
1259
+ task2.cancel();
1226
1260
  }
1227
1261
  }
1228
1262
  };
@@ -1367,7 +1401,7 @@ var Ticker = class {
1367
1401
  if (this.resolve) {
1368
1402
  const r = this.resolve;
1369
1403
  this.resolve = null;
1370
- r();
1404
+ r(true);
1371
1405
  }
1372
1406
  }, ms);
1373
1407
  if (this.interval.unref) this.interval.unref();
@@ -1375,7 +1409,11 @@ var Ticker = class {
1375
1409
  async tick() {
1376
1410
  if (this.stopped) return false;
1377
1411
  return new Promise((resolve) => {
1378
- this.resolve = () => resolve(true);
1412
+ this.resolve = resolve;
1413
+ if (this.stopped) {
1414
+ this.resolve = null;
1415
+ resolve(false);
1416
+ }
1379
1417
  });
1380
1418
  }
1381
1419
  stop() {
@@ -1387,7 +1425,7 @@ var Ticker = class {
1387
1425
  if (this.resolve) {
1388
1426
  const r = this.resolve;
1389
1427
  this.resolve = null;
1390
- r();
1428
+ r(false);
1391
1429
  }
1392
1430
  }
1393
1431
  async *[Symbol.asyncIterator]() {
@@ -1401,54 +1439,45 @@ function ticker(ms) {
1401
1439
  }
1402
1440
 
1403
1441
  // src/registry.ts
1404
- var registry = /* @__PURE__ */ new Map();
1405
1442
  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;
1443
+ function task(fn) {
1444
+ return (...args) => {
1445
+ const fnStr = serializeFunction(fn);
1446
+ const serializedArgs = args.map((a) => {
1447
+ const json = JSON.stringify(a);
1448
+ if (json === void 0) {
1449
+ throw new TypeError(
1450
+ `Argument of type ${typeof a} is not JSON-serializable. task() args must be JSON-serializable (no undefined, functions, symbols, or BigInt).`
1451
+ );
1446
1452
  }
1447
- rejectFn(reason);
1448
- }
1453
+ return json;
1454
+ });
1455
+ const wrapperStr = `() => (${fnStr})(${serializedArgs.join(", ")})`;
1456
+ const taskId = `task_${++taskCounter2}`;
1457
+ const spawnStack = new Error().stack;
1458
+ let resolveFn;
1459
+ let rejectFn;
1460
+ const result = new Promise((resolve, reject) => {
1461
+ resolveFn = resolve;
1462
+ rejectFn = reject;
1463
+ });
1464
+ const taskObj = {
1465
+ id: taskId,
1466
+ fnStr: wrapperStr,
1467
+ priority: "normal",
1468
+ concurrent: false,
1469
+ resolve: (value) => resolveFn(value),
1470
+ reject: (reason) => {
1471
+ if (reason instanceof Error && spawnStack) {
1472
+ const callerLine = spawnStack.split("\n").slice(2).join("\n");
1473
+ reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
1474
+ }
1475
+ rejectFn(reason);
1476
+ }
1477
+ };
1478
+ getPool().submit(taskObj);
1479
+ return result;
1449
1480
  };
1450
- getPool().submit(task);
1451
- return result;
1452
1481
  }
1453
1482
  export {
1454
1483
  ErrGroup,
@@ -1461,12 +1490,12 @@ export {
1461
1490
  configure,
1462
1491
  detectCapability,
1463
1492
  detectRuntime,
1464
- register,
1465
1493
  resize,
1466
- run,
1467
1494
  select,
1495
+ shutdown,
1468
1496
  spawn,
1469
1497
  stats,
1498
+ task,
1470
1499
  ticker
1471
1500
  };
1472
1501
  //# sourceMappingURL=index.js.map