@dmop/puru 0.1.5 → 0.1.11

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.cjs CHANGED
@@ -20,12 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CancelledError: () => CancelledError,
24
+ Cond: () => Cond,
25
+ DeadlineExceededError: () => DeadlineExceededError,
23
26
  ErrGroup: () => ErrGroup,
24
27
  Mutex: () => Mutex,
25
28
  Once: () => Once,
29
+ RWMutex: () => RWMutex,
26
30
  Ticker: () => Ticker,
31
+ Timer: () => Timer,
27
32
  WaitGroup: () => WaitGroup,
28
33
  after: () => after,
34
+ background: () => background,
29
35
  chan: () => chan,
30
36
  configure: () => configure,
31
37
  detectCapability: () => detectCapability,
@@ -36,7 +42,11 @@ __export(index_exports, {
36
42
  spawn: () => spawn,
37
43
  stats: () => stats,
38
44
  task: () => task,
39
- ticker: () => ticker
45
+ ticker: () => ticker,
46
+ withCancel: () => withCancel,
47
+ withDeadline: () => withDeadline,
48
+ withTimeout: () => withTimeout,
49
+ withValue: () => withValue
40
50
  });
41
51
  module.exports = __toCommonJS(index_exports);
42
52
 
@@ -98,16 +108,15 @@ function getConfig() {
98
108
 
99
109
  // src/runtime.ts
100
110
  function detectRuntime() {
101
- if (typeof globalThis.Bun !== "undefined") return "bun";
102
- if (typeof globalThis.Deno !== "undefined") return "deno";
103
- if (typeof globalThis.process !== "undefined" && globalThis.process.versions?.node)
104
- return "node";
111
+ if ("Bun" in globalThis) return "bun";
112
+ if ("Deno" in globalThis) return "deno";
113
+ if (typeof globalThis.process !== "undefined" && globalThis.process.versions?.node) return "node";
105
114
  return "browser";
106
115
  }
107
116
  function detectCapability() {
108
117
  const runtime = detectRuntime();
109
118
  if (runtime === "node" || runtime === "bun") return "full-threads";
110
- if (typeof globalThis.Worker !== "undefined") return "full-threads";
119
+ if ("Worker" in globalThis) return "full-threads";
111
120
  return "single-thread";
112
121
  }
113
122
 
@@ -374,7 +383,11 @@ var BunManagedWorker = class {
374
383
  id;
375
384
  constructor() {
376
385
  this.id = ++workerIdCounter;
377
- this.worker = new Worker(getBootstrapFile());
386
+ const WorkerConstructor = globalThis.Worker;
387
+ if (!WorkerConstructor) {
388
+ throw new Error("Bun Worker constructor is not available in this runtime");
389
+ }
390
+ this.worker = new WorkerConstructor(getBootstrapFile());
378
391
  }
379
392
  postMessage(data) {
380
393
  this.worker.postMessage(data);
@@ -386,27 +399,28 @@ var BunManagedWorker = class {
386
399
  on(event, handler) {
387
400
  if (event === "message") {
388
401
  this.worker.addEventListener("message", (e) => {
402
+ ;
389
403
  handler(e.data);
390
404
  });
391
405
  } else if (event === "error") {
392
406
  this.worker.addEventListener("error", (e) => {
407
+ ;
393
408
  handler(e.error ?? new Error(e.message));
394
409
  });
395
410
  } else if (event === "exit") {
396
411
  this.worker.addEventListener("close", (e) => {
412
+ ;
397
413
  handler(e.code ?? 0);
398
414
  });
399
415
  }
400
416
  }
401
417
  unref() {
402
418
  if ("unref" in this.worker && typeof this.worker.unref === "function") {
403
- ;
404
419
  this.worker.unref();
405
420
  }
406
421
  }
407
422
  ref() {
408
423
  if ("ref" in this.worker && typeof this.worker.ref === "function") {
409
- ;
410
424
  this.worker.ref();
411
425
  }
412
426
  }
@@ -435,6 +449,12 @@ var ChannelImpl = class {
435
449
  this.capacity = capacity;
436
450
  channelRegistry.set(this._id, this);
437
451
  }
452
+ get len() {
453
+ return this.buffer.length;
454
+ }
455
+ get cap() {
456
+ return this.capacity;
457
+ }
438
458
  send(value) {
439
459
  if (this.closed) {
440
460
  return Promise.reject(new Error("send on closed channel"));
@@ -488,6 +508,40 @@ var ChannelImpl = class {
488
508
  }
489
509
  this.sendQueue = [];
490
510
  }
511
+ sendOnly() {
512
+ const send = (value) => this.send(value);
513
+ const close = () => this.close();
514
+ const getLen = () => this.len;
515
+ const getCap = () => this.cap;
516
+ return {
517
+ send,
518
+ close,
519
+ get len() {
520
+ return getLen();
521
+ },
522
+ get cap() {
523
+ return getCap();
524
+ }
525
+ };
526
+ }
527
+ recvOnly() {
528
+ const recv = () => this.recv();
529
+ const getLen = () => this.len;
530
+ const getCap = () => this.cap;
531
+ const getIter = () => this[Symbol.asyncIterator]();
532
+ return {
533
+ recv,
534
+ get len() {
535
+ return getLen();
536
+ },
537
+ get cap() {
538
+ return getCap();
539
+ },
540
+ [Symbol.asyncIterator]() {
541
+ return getIter();
542
+ }
543
+ };
544
+ }
491
545
  async *[Symbol.asyncIterator]() {
492
546
  while (true) {
493
547
  const value = await this.recv();
@@ -505,6 +559,9 @@ function chan(capacity = 0) {
505
559
  function getChannelById(id) {
506
560
  return channelRegistry.get(id);
507
561
  }
562
+ function getChannelId(channel) {
563
+ return channel._id;
564
+ }
508
565
 
509
566
  // src/adapters/inline.ts
510
567
  var inlineIdCounter = 0;
@@ -521,15 +578,14 @@ var InlineManagedWorker = class {
521
578
  this.emit("message", { type: "ready" });
522
579
  });
523
580
  }
524
- postMessage(data) {
581
+ postMessage(msg) {
525
582
  if (this.terminated) return;
526
- const msg = data;
527
583
  if (msg.type === "execute") {
528
- this.executeTask(msg.taskId, msg.fnStr, msg.concurrent ?? false, msg.channels);
584
+ this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels);
529
585
  } else if (msg.type === "cancel") {
530
586
  this.cancelledTasks.add(msg.taskId);
531
587
  } else if (msg.type === "channel-result") {
532
- this.emit("message", msg);
588
+ return;
533
589
  } else if (msg.type === "shutdown") {
534
590
  this.terminated = true;
535
591
  this.emit("exit", 0);
@@ -559,7 +615,6 @@ var InlineManagedWorker = class {
559
615
  }
560
616
  }
561
617
  buildChannelProxies(channels) {
562
- const self = this;
563
618
  const proxies = {};
564
619
  for (const [name, channelId] of Object.entries(channels)) {
565
620
  proxies[name] = {
@@ -581,13 +636,7 @@ var InlineManagedWorker = class {
581
636
  [Symbol.asyncIterator]() {
582
637
  const ch = getChannelById(channelId);
583
638
  if (!ch) throw new Error(`Channel ${channelId} not found`);
584
- return {
585
- async next() {
586
- const value = await ch.recv();
587
- if (value === null) return { done: true, value: void 0 };
588
- return { done: false, value };
589
- }
590
- };
639
+ return ch[Symbol.asyncIterator]();
591
640
  }
592
641
  };
593
642
  }
@@ -935,6 +984,15 @@ var WorkerPool = class {
935
984
  task2.reject(reason);
936
985
  }
937
986
  }
987
+ rejectExclusiveTaskForWorker(worker, reason) {
988
+ for (const [taskId, assignedWorker] of this.exclusiveWorkers) {
989
+ if (assignedWorker === worker) {
990
+ this.exclusiveWorkers.delete(taskId);
991
+ this.rejectTask(taskId, reason);
992
+ break;
993
+ }
994
+ }
995
+ }
938
996
  // --- Cancellation ---
939
997
  cancelTask(taskId) {
940
998
  const removed = this.removeFromQueue(taskId);
@@ -983,6 +1041,10 @@ var WorkerPool = class {
983
1041
  }
984
1042
  this.concurrentQueues[priority] = [];
985
1043
  }
1044
+ for (const [taskId] of this.exclusiveWorkers) {
1045
+ this.taskMap.delete(taskId);
1046
+ }
1047
+ this.exclusiveWorkers.clear();
986
1048
  for (const [, taskSet] of this.sharedWorkers) {
987
1049
  for (const taskId of taskSet) {
988
1050
  this.taskMap.delete(taskId);
@@ -1040,8 +1102,7 @@ var WorkerPool = class {
1040
1102
  this.makeIdle(worker);
1041
1103
  }
1042
1104
  };
1043
- worker.on("message", (msg) => {
1044
- const response = msg;
1105
+ worker.on("message", (response) => {
1045
1106
  if (response.type === "ready") {
1046
1107
  onReady();
1047
1108
  return;
@@ -1049,12 +1110,7 @@ var WorkerPool = class {
1049
1110
  this.handleWorkerMessage(worker, response);
1050
1111
  });
1051
1112
  worker.on("error", (err) => {
1052
- for (const [taskId, w] of this.exclusiveWorkers) {
1053
- if (w === worker) {
1054
- this.exclusiveWorkers.delete(taskId);
1055
- break;
1056
- }
1057
- }
1113
+ this.rejectExclusiveTaskForWorker(worker, err);
1058
1114
  const taskSet = this.sharedWorkers.get(worker);
1059
1115
  if (taskSet) {
1060
1116
  for (const taskId of taskSet) {
@@ -1074,12 +1130,7 @@ var WorkerPool = class {
1074
1130
  if (idleIdx !== -1) {
1075
1131
  this.idleWorkers.splice(idleIdx, 1);
1076
1132
  }
1077
- for (const [taskId, w] of this.exclusiveWorkers) {
1078
- if (w === worker) {
1079
- this.exclusiveWorkers.delete(taskId);
1080
- break;
1081
- }
1082
- }
1133
+ this.rejectExclusiveTaskForWorker(worker, new Error("Worker exited unexpectedly"));
1083
1134
  const taskSet = this.sharedWorkers.get(worker);
1084
1135
  if (taskSet) {
1085
1136
  for (const taskId of taskSet) {
@@ -1173,11 +1224,7 @@ function spawn(fn, opts) {
1173
1224
  if (opts?.channels) {
1174
1225
  channelMap = {};
1175
1226
  for (const [name, ch] of Object.entries(opts.channels)) {
1176
- const impl = ch;
1177
- if (!impl._id) {
1178
- throw new Error(`Channel "${name}" is not a valid puru channel`);
1179
- }
1180
- channelMap[name] = impl._id;
1227
+ channelMap[name] = getChannelId(ch);
1181
1228
  }
1182
1229
  }
1183
1230
  const task2 = {
@@ -1203,6 +1250,18 @@ function spawn(fn, opts) {
1203
1250
  }
1204
1251
  }
1205
1252
  };
1253
+ const ctx = opts?.ctx;
1254
+ if (ctx) {
1255
+ if (ctx.signal.aborted) {
1256
+ settled = true;
1257
+ rejectFn(ctx.err ?? new DOMException("Task was cancelled", "AbortError"));
1258
+ return {
1259
+ result,
1260
+ cancel: () => {
1261
+ }
1262
+ };
1263
+ }
1264
+ }
1206
1265
  getPool().submit(task2);
1207
1266
  const cancel = () => {
1208
1267
  if (settled) return;
@@ -1210,6 +1269,14 @@ function spawn(fn, opts) {
1210
1269
  getPool().cancelTask(taskId);
1211
1270
  rejectFn(new DOMException("Task was cancelled", "AbortError"));
1212
1271
  };
1272
+ if (ctx) {
1273
+ const onAbort = () => cancel();
1274
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
1275
+ result.then(
1276
+ () => ctx.signal.removeEventListener("abort", onAbort),
1277
+ () => ctx.signal.removeEventListener("abort", onAbort)
1278
+ );
1279
+ }
1213
1280
  return { result, cancel };
1214
1281
  }
1215
1282
 
@@ -1217,6 +1284,17 @@ function spawn(fn, opts) {
1217
1284
  var WaitGroup = class {
1218
1285
  tasks = [];
1219
1286
  controller = new AbortController();
1287
+ ctx;
1288
+ constructor(ctx) {
1289
+ this.ctx = ctx;
1290
+ if (ctx) {
1291
+ if (ctx.signal.aborted) {
1292
+ this.controller.abort();
1293
+ } else {
1294
+ ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
1295
+ }
1296
+ }
1297
+ }
1220
1298
  /**
1221
1299
  * An `AbortSignal` shared across all tasks in this group.
1222
1300
  * Pass it into spawned functions so they can stop early when `cancel()` is called.
@@ -1233,7 +1311,7 @@ var WaitGroup = class {
1233
1311
  if (this.controller.signal.aborted) {
1234
1312
  throw new Error("WaitGroup has been cancelled");
1235
1313
  }
1236
- const handle = spawn(fn, opts);
1314
+ const handle = spawn(fn, { ...opts, ctx: this.ctx });
1237
1315
  this.tasks.push(handle);
1238
1316
  }
1239
1317
  /**
@@ -1266,28 +1344,77 @@ var WaitGroup = class {
1266
1344
  var ErrGroup = class {
1267
1345
  tasks = [];
1268
1346
  controller = new AbortController();
1269
- firstError = void 0;
1347
+ firstError = null;
1270
1348
  hasError = false;
1349
+ ctx;
1350
+ limit = 0;
1351
+ // 0 = unlimited
1352
+ inFlight = 0;
1353
+ waiting = [];
1354
+ constructor(ctx) {
1355
+ this.ctx = ctx;
1356
+ if (ctx) {
1357
+ if (ctx.signal.aborted) {
1358
+ this.controller.abort();
1359
+ } else {
1360
+ ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
1361
+ }
1362
+ }
1363
+ }
1271
1364
  get signal() {
1272
1365
  return this.controller.signal;
1273
1366
  }
1367
+ /**
1368
+ * Set the maximum number of tasks that can run concurrently.
1369
+ * Like Go's `errgroup.SetLimit()`. Must be called before any `spawn()`.
1370
+ * A value of 0 (default) means unlimited.
1371
+ */
1372
+ setLimit(n) {
1373
+ if (this.tasks.length > 0) {
1374
+ throw new Error("SetLimit must be called before any spawn()");
1375
+ }
1376
+ if (n < 0 || !Number.isInteger(n)) {
1377
+ throw new RangeError("Limit must be a non-negative integer");
1378
+ }
1379
+ this.limit = n;
1380
+ }
1274
1381
  spawn(fn, opts) {
1275
1382
  if (this.controller.signal.aborted) {
1276
1383
  throw new Error("ErrGroup has been cancelled");
1277
1384
  }
1278
- const handle = spawn(fn, opts);
1279
- handle.result.catch((err) => {
1385
+ if (this.limit > 0 && this.inFlight >= this.limit) {
1386
+ const result2 = new Promise((resolve) => {
1387
+ this.waiting.push(resolve);
1388
+ }).then(() => this.doSpawn(fn, opts));
1389
+ this.tasks.push({ result: result2, cancel: () => {
1390
+ } });
1391
+ return;
1392
+ }
1393
+ const result = this.doSpawn(fn, opts);
1394
+ this.tasks.push({ result, cancel: () => {
1395
+ } });
1396
+ }
1397
+ doSpawn(fn, opts) {
1398
+ this.inFlight++;
1399
+ const handle = spawn(fn, { ...opts, ctx: this.ctx });
1400
+ const onSettle = () => {
1401
+ this.inFlight--;
1402
+ const next = this.waiting.shift();
1403
+ if (next) next();
1404
+ };
1405
+ handle.result.then(onSettle, (err) => {
1406
+ onSettle();
1280
1407
  if (!this.hasError) {
1281
1408
  this.hasError = true;
1282
1409
  this.firstError = err;
1283
1410
  this.cancel();
1284
1411
  }
1285
1412
  });
1286
- this.tasks.push(handle);
1413
+ return handle.result;
1287
1414
  }
1288
1415
  async wait() {
1289
1416
  const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
1290
- if (this.hasError) {
1417
+ if (this.hasError && this.firstError) {
1291
1418
  throw this.firstError;
1292
1419
  }
1293
1420
  return settled.map((r) => {
@@ -1339,6 +1466,121 @@ var Mutex = class {
1339
1466
  return this.locked;
1340
1467
  }
1341
1468
  };
1469
+ var RWMutex = class {
1470
+ readers = 0;
1471
+ writing = false;
1472
+ readQueue = [];
1473
+ writeQueue = [];
1474
+ async rLock() {
1475
+ if (!this.writing && this.writeQueue.length === 0) {
1476
+ this.readers++;
1477
+ return;
1478
+ }
1479
+ return new Promise((resolve) => {
1480
+ this.readQueue.push(() => {
1481
+ this.readers++;
1482
+ resolve();
1483
+ });
1484
+ });
1485
+ }
1486
+ rUnlock() {
1487
+ if (this.readers <= 0) {
1488
+ throw new Error("Cannot rUnlock a RWMutex that is not read-locked");
1489
+ }
1490
+ this.readers--;
1491
+ if (this.readers === 0) {
1492
+ this.wakeWriter();
1493
+ }
1494
+ }
1495
+ async lock() {
1496
+ if (!this.writing && this.readers === 0) {
1497
+ this.writing = true;
1498
+ return;
1499
+ }
1500
+ return new Promise((resolve) => {
1501
+ this.writeQueue.push(() => {
1502
+ this.writing = true;
1503
+ resolve();
1504
+ });
1505
+ });
1506
+ }
1507
+ unlock() {
1508
+ if (!this.writing) {
1509
+ throw new Error("Cannot unlock a RWMutex that is not write-locked");
1510
+ }
1511
+ this.writing = false;
1512
+ if (this.readQueue.length > 0) {
1513
+ this.wakeReaders();
1514
+ } else {
1515
+ this.wakeWriter();
1516
+ }
1517
+ }
1518
+ async withRLock(fn) {
1519
+ await this.rLock();
1520
+ try {
1521
+ return await fn();
1522
+ } finally {
1523
+ this.rUnlock();
1524
+ }
1525
+ }
1526
+ async withLock(fn) {
1527
+ await this.lock();
1528
+ try {
1529
+ return await fn();
1530
+ } finally {
1531
+ this.unlock();
1532
+ }
1533
+ }
1534
+ get isLocked() {
1535
+ return this.writing || this.readers > 0;
1536
+ }
1537
+ wakeReaders() {
1538
+ const queue = this.readQueue;
1539
+ this.readQueue = [];
1540
+ for (const wake of queue) {
1541
+ wake();
1542
+ }
1543
+ }
1544
+ wakeWriter() {
1545
+ const next = this.writeQueue.shift();
1546
+ if (next) next();
1547
+ }
1548
+ };
1549
+
1550
+ // src/cond.ts
1551
+ var Cond = class {
1552
+ mu;
1553
+ waiters = [];
1554
+ constructor(mu) {
1555
+ this.mu = mu;
1556
+ }
1557
+ /**
1558
+ * Atomically releases the mutex, suspends the caller until `signal()` or `broadcast()`
1559
+ * is called, then re-acquires the mutex before returning.
1560
+ *
1561
+ * Must be called while holding the mutex.
1562
+ */
1563
+ async wait() {
1564
+ this.mu.unlock();
1565
+ await new Promise((resolve) => {
1566
+ this.waiters.push(resolve);
1567
+ });
1568
+ await this.mu.lock();
1569
+ }
1570
+ /** Wake one waiting task (if any). */
1571
+ signal() {
1572
+ const next = this.waiters.shift();
1573
+ if (next) next();
1574
+ }
1575
+ /** Wake all waiting tasks. */
1576
+ broadcast() {
1577
+ const queue = this.waiters;
1578
+ this.waiters = [];
1579
+ for (const wake of queue) {
1580
+ wake();
1581
+ }
1582
+ }
1583
+ };
1342
1584
 
1343
1585
  // src/once.ts
1344
1586
  var Once = class {
@@ -1377,6 +1619,7 @@ function select(cases, opts) {
1377
1619
  if (settled) return;
1378
1620
  settled = true;
1379
1621
  try {
1622
+ ;
1380
1623
  handler(value);
1381
1624
  resolve();
1382
1625
  } catch (err) {
@@ -1410,6 +1653,7 @@ function select(cases, opts) {
1410
1653
  if (settled) return;
1411
1654
  settled = true;
1412
1655
  try {
1656
+ ;
1413
1657
  handler(value);
1414
1658
  resolve();
1415
1659
  } catch (err) {
@@ -1480,11 +1724,60 @@ function ticker(ms) {
1480
1724
  return new Ticker(ms);
1481
1725
  }
1482
1726
 
1727
+ // src/timer.ts
1728
+ var Timer = class {
1729
+ timer = null;
1730
+ _stopped = false;
1731
+ /** Promise that resolves when the timer fires. Replaced on `reset()`. */
1732
+ channel;
1733
+ constructor(ms) {
1734
+ this.channel = this.schedule(ms);
1735
+ }
1736
+ schedule(ms) {
1737
+ return new Promise((resolve) => {
1738
+ this.timer = setTimeout(() => {
1739
+ this._stopped = true;
1740
+ this.timer = null;
1741
+ resolve();
1742
+ }, ms);
1743
+ if (typeof this.timer === "object" && "unref" in this.timer) {
1744
+ this.timer.unref();
1745
+ }
1746
+ });
1747
+ }
1748
+ /**
1749
+ * Stop the timer. Returns `true` if the timer was pending (stopped before firing),
1750
+ * `false` if it had already fired or was already stopped.
1751
+ *
1752
+ * After stopping, the current `channel` promise will never resolve.
1753
+ */
1754
+ stop() {
1755
+ if (this.timer === null) return false;
1756
+ clearTimeout(this.timer);
1757
+ this.timer = null;
1758
+ this._stopped = true;
1759
+ return true;
1760
+ }
1761
+ /**
1762
+ * Reset the timer to fire after `ms` milliseconds.
1763
+ * If the timer was pending, it is stopped first. Creates a new `channel` promise.
1764
+ */
1765
+ reset(ms) {
1766
+ this.stop();
1767
+ this._stopped = false;
1768
+ this.channel = this.schedule(ms);
1769
+ }
1770
+ /** Whether the timer has fired or been stopped. */
1771
+ get stopped() {
1772
+ return this._stopped;
1773
+ }
1774
+ };
1775
+
1483
1776
  // src/registry.ts
1484
1777
  var taskCounter2 = 0;
1485
1778
  function task(fn) {
1779
+ const fnStr = serializeFunction(fn);
1486
1780
  return (...args) => {
1487
- const fnStr = serializeFunction(fn);
1488
1781
  const serializedArgs = args.map((a) => {
1489
1782
  const json = JSON.stringify(a);
1490
1783
  if (json === void 0) {
@@ -1521,14 +1814,179 @@ function task(fn) {
1521
1814
  return result;
1522
1815
  };
1523
1816
  }
1817
+
1818
+ // src/context.ts
1819
+ var CancelledError = class extends Error {
1820
+ constructor(message = "context cancelled") {
1821
+ super(message);
1822
+ this.name = "CancelledError";
1823
+ }
1824
+ };
1825
+ var DeadlineExceededError = class extends Error {
1826
+ constructor() {
1827
+ super("context deadline exceeded");
1828
+ this.name = "DeadlineExceededError";
1829
+ }
1830
+ };
1831
+ var BaseContext = class {
1832
+ _err = null;
1833
+ controller;
1834
+ parent;
1835
+ constructor(parent) {
1836
+ this.parent = parent;
1837
+ this.controller = new AbortController();
1838
+ if (parent) {
1839
+ if (parent.signal.aborted) {
1840
+ this._err = parent.err ?? new CancelledError();
1841
+ this.controller.abort();
1842
+ } else {
1843
+ parent.signal.addEventListener(
1844
+ "abort",
1845
+ () => {
1846
+ if (!this.controller.signal.aborted) {
1847
+ this._err = parent.err ?? new CancelledError();
1848
+ this.controller.abort();
1849
+ }
1850
+ },
1851
+ { once: true }
1852
+ );
1853
+ }
1854
+ }
1855
+ }
1856
+ get signal() {
1857
+ return this.controller.signal;
1858
+ }
1859
+ get deadline() {
1860
+ return this.parent?.deadline ?? null;
1861
+ }
1862
+ get err() {
1863
+ return this._err;
1864
+ }
1865
+ value(_key) {
1866
+ return this.parent?.value(_key);
1867
+ }
1868
+ done() {
1869
+ if (this.controller.signal.aborted) return Promise.resolve();
1870
+ return new Promise((resolve) => {
1871
+ this.controller.signal.addEventListener("abort", () => resolve(), { once: true });
1872
+ });
1873
+ }
1874
+ };
1875
+ var BackgroundContext = class {
1876
+ _signal = new AbortController().signal;
1877
+ get signal() {
1878
+ return this._signal;
1879
+ }
1880
+ get deadline() {
1881
+ return null;
1882
+ }
1883
+ get err() {
1884
+ return null;
1885
+ }
1886
+ value(_key) {
1887
+ return void 0;
1888
+ }
1889
+ done() {
1890
+ return new Promise(() => {
1891
+ });
1892
+ }
1893
+ };
1894
+ var bg = null;
1895
+ function background() {
1896
+ if (!bg) bg = new BackgroundContext();
1897
+ return bg;
1898
+ }
1899
+ var CancelContext = class extends BaseContext {
1900
+ cancel(reason) {
1901
+ if (!this.controller.signal.aborted) {
1902
+ this._err = new CancelledError(reason ?? "context cancelled");
1903
+ this.controller.abort();
1904
+ }
1905
+ }
1906
+ };
1907
+ function withCancel(parent) {
1908
+ const ctx = new CancelContext(parent);
1909
+ return [ctx, (reason) => ctx.cancel(reason)];
1910
+ }
1911
+ var DeadlineContext = class extends BaseContext {
1912
+ _deadline;
1913
+ timer = null;
1914
+ constructor(parent, deadline) {
1915
+ super(parent);
1916
+ this._deadline = deadline;
1917
+ if (parent.deadline && parent.deadline < deadline) {
1918
+ this._deadline = parent.deadline;
1919
+ }
1920
+ if (this.controller.signal.aborted) {
1921
+ return;
1922
+ }
1923
+ const ms = this._deadline.getTime() - Date.now();
1924
+ if (ms <= 0) {
1925
+ this._err = new DeadlineExceededError();
1926
+ this.controller.abort();
1927
+ } else {
1928
+ this.timer = setTimeout(() => {
1929
+ if (!this.controller.signal.aborted) {
1930
+ this._err = new DeadlineExceededError();
1931
+ this.controller.abort();
1932
+ }
1933
+ }, ms);
1934
+ if (typeof this.timer === "object" && "unref" in this.timer) {
1935
+ this.timer.unref();
1936
+ }
1937
+ }
1938
+ }
1939
+ get deadline() {
1940
+ return this._deadline;
1941
+ }
1942
+ cancel(reason) {
1943
+ if (this.timer !== null) {
1944
+ clearTimeout(this.timer);
1945
+ this.timer = null;
1946
+ }
1947
+ if (!this.controller.signal.aborted) {
1948
+ this._err = new CancelledError(reason ?? "context cancelled");
1949
+ this.controller.abort();
1950
+ }
1951
+ }
1952
+ };
1953
+ function withDeadline(parent, deadline) {
1954
+ const ctx = new DeadlineContext(parent, deadline);
1955
+ return [ctx, (reason) => ctx.cancel(reason)];
1956
+ }
1957
+ function withTimeout(parent, ms) {
1958
+ return withDeadline(parent, new Date(Date.now() + ms));
1959
+ }
1960
+ var ValueContext = class extends BaseContext {
1961
+ key;
1962
+ val;
1963
+ constructor(parent, key, val) {
1964
+ super(parent);
1965
+ this.key = key;
1966
+ this.val = val;
1967
+ }
1968
+ value(key) {
1969
+ if (key === this.key) return this.val;
1970
+ return this.parent?.value(key);
1971
+ }
1972
+ };
1973
+ function withValue(parent, key, value) {
1974
+ return new ValueContext(parent, key, value);
1975
+ }
1524
1976
  // Annotate the CommonJS export names for ESM import in node:
1525
1977
  0 && (module.exports = {
1978
+ CancelledError,
1979
+ Cond,
1980
+ DeadlineExceededError,
1526
1981
  ErrGroup,
1527
1982
  Mutex,
1528
1983
  Once,
1984
+ RWMutex,
1529
1985
  Ticker,
1986
+ Timer,
1530
1987
  WaitGroup,
1531
1988
  after,
1989
+ background,
1532
1990
  chan,
1533
1991
  configure,
1534
1992
  detectCapability,
@@ -1539,6 +1997,10 @@ function task(fn) {
1539
1997
  spawn,
1540
1998
  stats,
1541
1999
  task,
1542
- ticker
2000
+ ticker,
2001
+ withCancel,
2002
+ withDeadline,
2003
+ withTimeout,
2004
+ withValue
1543
2005
  });
1544
2006
  //# sourceMappingURL=index.cjs.map