@dmop/puru 0.1.10 → 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
 
@@ -439,6 +449,12 @@ var ChannelImpl = class {
439
449
  this.capacity = capacity;
440
450
  channelRegistry.set(this._id, this);
441
451
  }
452
+ get len() {
453
+ return this.buffer.length;
454
+ }
455
+ get cap() {
456
+ return this.capacity;
457
+ }
442
458
  send(value) {
443
459
  if (this.closed) {
444
460
  return Promise.reject(new Error("send on closed channel"));
@@ -492,6 +508,40 @@ var ChannelImpl = class {
492
508
  }
493
509
  this.sendQueue = [];
494
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
+ }
495
545
  async *[Symbol.asyncIterator]() {
496
546
  while (true) {
497
547
  const value = await this.recv();
@@ -1200,6 +1250,18 @@ function spawn(fn, opts) {
1200
1250
  }
1201
1251
  }
1202
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
+ }
1203
1265
  getPool().submit(task2);
1204
1266
  const cancel = () => {
1205
1267
  if (settled) return;
@@ -1207,6 +1269,14 @@ function spawn(fn, opts) {
1207
1269
  getPool().cancelTask(taskId);
1208
1270
  rejectFn(new DOMException("Task was cancelled", "AbortError"));
1209
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
+ }
1210
1280
  return { result, cancel };
1211
1281
  }
1212
1282
 
@@ -1214,6 +1284,17 @@ function spawn(fn, opts) {
1214
1284
  var WaitGroup = class {
1215
1285
  tasks = [];
1216
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
+ }
1217
1298
  /**
1218
1299
  * An `AbortSignal` shared across all tasks in this group.
1219
1300
  * Pass it into spawned functions so they can stop early when `cancel()` is called.
@@ -1230,7 +1311,7 @@ var WaitGroup = class {
1230
1311
  if (this.controller.signal.aborted) {
1231
1312
  throw new Error("WaitGroup has been cancelled");
1232
1313
  }
1233
- const handle = spawn(fn, opts);
1314
+ const handle = spawn(fn, { ...opts, ctx: this.ctx });
1234
1315
  this.tasks.push(handle);
1235
1316
  }
1236
1317
  /**
@@ -1265,22 +1346,71 @@ var ErrGroup = class {
1265
1346
  controller = new AbortController();
1266
1347
  firstError = null;
1267
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
+ }
1268
1364
  get signal() {
1269
1365
  return this.controller.signal;
1270
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
+ }
1271
1381
  spawn(fn, opts) {
1272
1382
  if (this.controller.signal.aborted) {
1273
1383
  throw new Error("ErrGroup has been cancelled");
1274
1384
  }
1275
- const handle = spawn(fn, opts);
1276
- 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();
1277
1407
  if (!this.hasError) {
1278
1408
  this.hasError = true;
1279
1409
  this.firstError = err;
1280
1410
  this.cancel();
1281
1411
  }
1282
1412
  });
1283
- this.tasks.push(handle);
1413
+ return handle.result;
1284
1414
  }
1285
1415
  async wait() {
1286
1416
  const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
@@ -1336,6 +1466,121 @@ var Mutex = class {
1336
1466
  return this.locked;
1337
1467
  }
1338
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
+ };
1339
1584
 
1340
1585
  // src/once.ts
1341
1586
  var Once = class {
@@ -1374,6 +1619,7 @@ function select(cases, opts) {
1374
1619
  if (settled) return;
1375
1620
  settled = true;
1376
1621
  try {
1622
+ ;
1377
1623
  handler(value);
1378
1624
  resolve();
1379
1625
  } catch (err) {
@@ -1407,6 +1653,7 @@ function select(cases, opts) {
1407
1653
  if (settled) return;
1408
1654
  settled = true;
1409
1655
  try {
1656
+ ;
1410
1657
  handler(value);
1411
1658
  resolve();
1412
1659
  } catch (err) {
@@ -1477,11 +1724,60 @@ function ticker(ms) {
1477
1724
  return new Ticker(ms);
1478
1725
  }
1479
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
+
1480
1776
  // src/registry.ts
1481
1777
  var taskCounter2 = 0;
1482
1778
  function task(fn) {
1779
+ const fnStr = serializeFunction(fn);
1483
1780
  return (...args) => {
1484
- const fnStr = serializeFunction(fn);
1485
1781
  const serializedArgs = args.map((a) => {
1486
1782
  const json = JSON.stringify(a);
1487
1783
  if (json === void 0) {
@@ -1518,14 +1814,179 @@ function task(fn) {
1518
1814
  return result;
1519
1815
  };
1520
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
+ }
1521
1976
  // Annotate the CommonJS export names for ESM import in node:
1522
1977
  0 && (module.exports = {
1978
+ CancelledError,
1979
+ Cond,
1980
+ DeadlineExceededError,
1523
1981
  ErrGroup,
1524
1982
  Mutex,
1525
1983
  Once,
1984
+ RWMutex,
1526
1985
  Ticker,
1986
+ Timer,
1527
1987
  WaitGroup,
1528
1988
  after,
1989
+ background,
1529
1990
  chan,
1530
1991
  configure,
1531
1992
  detectCapability,
@@ -1536,6 +1997,10 @@ function task(fn) {
1536
1997
  spawn,
1537
1998
  stats,
1538
1999
  task,
1539
- ticker
2000
+ ticker,
2001
+ withCancel,
2002
+ withDeadline,
2003
+ withTimeout,
2004
+ withValue
1540
2005
  });
1541
2006
  //# sourceMappingURL=index.cjs.map