@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.js CHANGED
@@ -397,6 +397,12 @@ var ChannelImpl = class {
397
397
  this.capacity = capacity;
398
398
  channelRegistry.set(this._id, this);
399
399
  }
400
+ get len() {
401
+ return this.buffer.length;
402
+ }
403
+ get cap() {
404
+ return this.capacity;
405
+ }
400
406
  send(value) {
401
407
  if (this.closed) {
402
408
  return Promise.reject(new Error("send on closed channel"));
@@ -450,6 +456,40 @@ var ChannelImpl = class {
450
456
  }
451
457
  this.sendQueue = [];
452
458
  }
459
+ sendOnly() {
460
+ const send = (value) => this.send(value);
461
+ const close = () => this.close();
462
+ const getLen = () => this.len;
463
+ const getCap = () => this.cap;
464
+ return {
465
+ send,
466
+ close,
467
+ get len() {
468
+ return getLen();
469
+ },
470
+ get cap() {
471
+ return getCap();
472
+ }
473
+ };
474
+ }
475
+ recvOnly() {
476
+ const recv = () => this.recv();
477
+ const getLen = () => this.len;
478
+ const getCap = () => this.cap;
479
+ const getIter = () => this[Symbol.asyncIterator]();
480
+ return {
481
+ recv,
482
+ get len() {
483
+ return getLen();
484
+ },
485
+ get cap() {
486
+ return getCap();
487
+ },
488
+ [Symbol.asyncIterator]() {
489
+ return getIter();
490
+ }
491
+ };
492
+ }
453
493
  async *[Symbol.asyncIterator]() {
454
494
  while (true) {
455
495
  const value = await this.recv();
@@ -1158,6 +1198,18 @@ function spawn(fn, opts) {
1158
1198
  }
1159
1199
  }
1160
1200
  };
1201
+ const ctx = opts?.ctx;
1202
+ if (ctx) {
1203
+ if (ctx.signal.aborted) {
1204
+ settled = true;
1205
+ rejectFn(ctx.err ?? new DOMException("Task was cancelled", "AbortError"));
1206
+ return {
1207
+ result,
1208
+ cancel: () => {
1209
+ }
1210
+ };
1211
+ }
1212
+ }
1161
1213
  getPool().submit(task2);
1162
1214
  const cancel = () => {
1163
1215
  if (settled) return;
@@ -1165,6 +1217,14 @@ function spawn(fn, opts) {
1165
1217
  getPool().cancelTask(taskId);
1166
1218
  rejectFn(new DOMException("Task was cancelled", "AbortError"));
1167
1219
  };
1220
+ if (ctx) {
1221
+ const onAbort = () => cancel();
1222
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
1223
+ result.then(
1224
+ () => ctx.signal.removeEventListener("abort", onAbort),
1225
+ () => ctx.signal.removeEventListener("abort", onAbort)
1226
+ );
1227
+ }
1168
1228
  return { result, cancel };
1169
1229
  }
1170
1230
 
@@ -1172,6 +1232,17 @@ function spawn(fn, opts) {
1172
1232
  var WaitGroup = class {
1173
1233
  tasks = [];
1174
1234
  controller = new AbortController();
1235
+ ctx;
1236
+ constructor(ctx) {
1237
+ this.ctx = ctx;
1238
+ if (ctx) {
1239
+ if (ctx.signal.aborted) {
1240
+ this.controller.abort();
1241
+ } else {
1242
+ ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
1243
+ }
1244
+ }
1245
+ }
1175
1246
  /**
1176
1247
  * An `AbortSignal` shared across all tasks in this group.
1177
1248
  * Pass it into spawned functions so they can stop early when `cancel()` is called.
@@ -1188,7 +1259,7 @@ var WaitGroup = class {
1188
1259
  if (this.controller.signal.aborted) {
1189
1260
  throw new Error("WaitGroup has been cancelled");
1190
1261
  }
1191
- const handle = spawn(fn, opts);
1262
+ const handle = spawn(fn, { ...opts, ctx: this.ctx });
1192
1263
  this.tasks.push(handle);
1193
1264
  }
1194
1265
  /**
@@ -1223,22 +1294,71 @@ var ErrGroup = class {
1223
1294
  controller = new AbortController();
1224
1295
  firstError = null;
1225
1296
  hasError = false;
1297
+ ctx;
1298
+ limit = 0;
1299
+ // 0 = unlimited
1300
+ inFlight = 0;
1301
+ waiting = [];
1302
+ constructor(ctx) {
1303
+ this.ctx = ctx;
1304
+ if (ctx) {
1305
+ if (ctx.signal.aborted) {
1306
+ this.controller.abort();
1307
+ } else {
1308
+ ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
1309
+ }
1310
+ }
1311
+ }
1226
1312
  get signal() {
1227
1313
  return this.controller.signal;
1228
1314
  }
1315
+ /**
1316
+ * Set the maximum number of tasks that can run concurrently.
1317
+ * Like Go's `errgroup.SetLimit()`. Must be called before any `spawn()`.
1318
+ * A value of 0 (default) means unlimited.
1319
+ */
1320
+ setLimit(n) {
1321
+ if (this.tasks.length > 0) {
1322
+ throw new Error("SetLimit must be called before any spawn()");
1323
+ }
1324
+ if (n < 0 || !Number.isInteger(n)) {
1325
+ throw new RangeError("Limit must be a non-negative integer");
1326
+ }
1327
+ this.limit = n;
1328
+ }
1229
1329
  spawn(fn, opts) {
1230
1330
  if (this.controller.signal.aborted) {
1231
1331
  throw new Error("ErrGroup has been cancelled");
1232
1332
  }
1233
- const handle = spawn(fn, opts);
1234
- handle.result.catch((err) => {
1333
+ if (this.limit > 0 && this.inFlight >= this.limit) {
1334
+ const result2 = new Promise((resolve) => {
1335
+ this.waiting.push(resolve);
1336
+ }).then(() => this.doSpawn(fn, opts));
1337
+ this.tasks.push({ result: result2, cancel: () => {
1338
+ } });
1339
+ return;
1340
+ }
1341
+ const result = this.doSpawn(fn, opts);
1342
+ this.tasks.push({ result, cancel: () => {
1343
+ } });
1344
+ }
1345
+ doSpawn(fn, opts) {
1346
+ this.inFlight++;
1347
+ const handle = spawn(fn, { ...opts, ctx: this.ctx });
1348
+ const onSettle = () => {
1349
+ this.inFlight--;
1350
+ const next = this.waiting.shift();
1351
+ if (next) next();
1352
+ };
1353
+ handle.result.then(onSettle, (err) => {
1354
+ onSettle();
1235
1355
  if (!this.hasError) {
1236
1356
  this.hasError = true;
1237
1357
  this.firstError = err;
1238
1358
  this.cancel();
1239
1359
  }
1240
1360
  });
1241
- this.tasks.push(handle);
1361
+ return handle.result;
1242
1362
  }
1243
1363
  async wait() {
1244
1364
  const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
@@ -1294,6 +1414,121 @@ var Mutex = class {
1294
1414
  return this.locked;
1295
1415
  }
1296
1416
  };
1417
+ var RWMutex = class {
1418
+ readers = 0;
1419
+ writing = false;
1420
+ readQueue = [];
1421
+ writeQueue = [];
1422
+ async rLock() {
1423
+ if (!this.writing && this.writeQueue.length === 0) {
1424
+ this.readers++;
1425
+ return;
1426
+ }
1427
+ return new Promise((resolve) => {
1428
+ this.readQueue.push(() => {
1429
+ this.readers++;
1430
+ resolve();
1431
+ });
1432
+ });
1433
+ }
1434
+ rUnlock() {
1435
+ if (this.readers <= 0) {
1436
+ throw new Error("Cannot rUnlock a RWMutex that is not read-locked");
1437
+ }
1438
+ this.readers--;
1439
+ if (this.readers === 0) {
1440
+ this.wakeWriter();
1441
+ }
1442
+ }
1443
+ async lock() {
1444
+ if (!this.writing && this.readers === 0) {
1445
+ this.writing = true;
1446
+ return;
1447
+ }
1448
+ return new Promise((resolve) => {
1449
+ this.writeQueue.push(() => {
1450
+ this.writing = true;
1451
+ resolve();
1452
+ });
1453
+ });
1454
+ }
1455
+ unlock() {
1456
+ if (!this.writing) {
1457
+ throw new Error("Cannot unlock a RWMutex that is not write-locked");
1458
+ }
1459
+ this.writing = false;
1460
+ if (this.readQueue.length > 0) {
1461
+ this.wakeReaders();
1462
+ } else {
1463
+ this.wakeWriter();
1464
+ }
1465
+ }
1466
+ async withRLock(fn) {
1467
+ await this.rLock();
1468
+ try {
1469
+ return await fn();
1470
+ } finally {
1471
+ this.rUnlock();
1472
+ }
1473
+ }
1474
+ async withLock(fn) {
1475
+ await this.lock();
1476
+ try {
1477
+ return await fn();
1478
+ } finally {
1479
+ this.unlock();
1480
+ }
1481
+ }
1482
+ get isLocked() {
1483
+ return this.writing || this.readers > 0;
1484
+ }
1485
+ wakeReaders() {
1486
+ const queue = this.readQueue;
1487
+ this.readQueue = [];
1488
+ for (const wake of queue) {
1489
+ wake();
1490
+ }
1491
+ }
1492
+ wakeWriter() {
1493
+ const next = this.writeQueue.shift();
1494
+ if (next) next();
1495
+ }
1496
+ };
1497
+
1498
+ // src/cond.ts
1499
+ var Cond = class {
1500
+ mu;
1501
+ waiters = [];
1502
+ constructor(mu) {
1503
+ this.mu = mu;
1504
+ }
1505
+ /**
1506
+ * Atomically releases the mutex, suspends the caller until `signal()` or `broadcast()`
1507
+ * is called, then re-acquires the mutex before returning.
1508
+ *
1509
+ * Must be called while holding the mutex.
1510
+ */
1511
+ async wait() {
1512
+ this.mu.unlock();
1513
+ await new Promise((resolve) => {
1514
+ this.waiters.push(resolve);
1515
+ });
1516
+ await this.mu.lock();
1517
+ }
1518
+ /** Wake one waiting task (if any). */
1519
+ signal() {
1520
+ const next = this.waiters.shift();
1521
+ if (next) next();
1522
+ }
1523
+ /** Wake all waiting tasks. */
1524
+ broadcast() {
1525
+ const queue = this.waiters;
1526
+ this.waiters = [];
1527
+ for (const wake of queue) {
1528
+ wake();
1529
+ }
1530
+ }
1531
+ };
1297
1532
 
1298
1533
  // src/once.ts
1299
1534
  var Once = class {
@@ -1332,6 +1567,7 @@ function select(cases, opts) {
1332
1567
  if (settled) return;
1333
1568
  settled = true;
1334
1569
  try {
1570
+ ;
1335
1571
  handler(value);
1336
1572
  resolve();
1337
1573
  } catch (err) {
@@ -1365,6 +1601,7 @@ function select(cases, opts) {
1365
1601
  if (settled) return;
1366
1602
  settled = true;
1367
1603
  try {
1604
+ ;
1368
1605
  handler(value);
1369
1606
  resolve();
1370
1607
  } catch (err) {
@@ -1435,11 +1672,60 @@ function ticker(ms) {
1435
1672
  return new Ticker(ms);
1436
1673
  }
1437
1674
 
1675
+ // src/timer.ts
1676
+ var Timer = class {
1677
+ timer = null;
1678
+ _stopped = false;
1679
+ /** Promise that resolves when the timer fires. Replaced on `reset()`. */
1680
+ channel;
1681
+ constructor(ms) {
1682
+ this.channel = this.schedule(ms);
1683
+ }
1684
+ schedule(ms) {
1685
+ return new Promise((resolve) => {
1686
+ this.timer = setTimeout(() => {
1687
+ this._stopped = true;
1688
+ this.timer = null;
1689
+ resolve();
1690
+ }, ms);
1691
+ if (typeof this.timer === "object" && "unref" in this.timer) {
1692
+ this.timer.unref();
1693
+ }
1694
+ });
1695
+ }
1696
+ /**
1697
+ * Stop the timer. Returns `true` if the timer was pending (stopped before firing),
1698
+ * `false` if it had already fired or was already stopped.
1699
+ *
1700
+ * After stopping, the current `channel` promise will never resolve.
1701
+ */
1702
+ stop() {
1703
+ if (this.timer === null) return false;
1704
+ clearTimeout(this.timer);
1705
+ this.timer = null;
1706
+ this._stopped = true;
1707
+ return true;
1708
+ }
1709
+ /**
1710
+ * Reset the timer to fire after `ms` milliseconds.
1711
+ * If the timer was pending, it is stopped first. Creates a new `channel` promise.
1712
+ */
1713
+ reset(ms) {
1714
+ this.stop();
1715
+ this._stopped = false;
1716
+ this.channel = this.schedule(ms);
1717
+ }
1718
+ /** Whether the timer has fired or been stopped. */
1719
+ get stopped() {
1720
+ return this._stopped;
1721
+ }
1722
+ };
1723
+
1438
1724
  // src/registry.ts
1439
1725
  var taskCounter2 = 0;
1440
1726
  function task(fn) {
1727
+ const fnStr = serializeFunction(fn);
1441
1728
  return (...args) => {
1442
- const fnStr = serializeFunction(fn);
1443
1729
  const serializedArgs = args.map((a) => {
1444
1730
  const json = JSON.stringify(a);
1445
1731
  if (json === void 0) {
@@ -1476,13 +1762,178 @@ function task(fn) {
1476
1762
  return result;
1477
1763
  };
1478
1764
  }
1765
+
1766
+ // src/context.ts
1767
+ var CancelledError = class extends Error {
1768
+ constructor(message = "context cancelled") {
1769
+ super(message);
1770
+ this.name = "CancelledError";
1771
+ }
1772
+ };
1773
+ var DeadlineExceededError = class extends Error {
1774
+ constructor() {
1775
+ super("context deadline exceeded");
1776
+ this.name = "DeadlineExceededError";
1777
+ }
1778
+ };
1779
+ var BaseContext = class {
1780
+ _err = null;
1781
+ controller;
1782
+ parent;
1783
+ constructor(parent) {
1784
+ this.parent = parent;
1785
+ this.controller = new AbortController();
1786
+ if (parent) {
1787
+ if (parent.signal.aborted) {
1788
+ this._err = parent.err ?? new CancelledError();
1789
+ this.controller.abort();
1790
+ } else {
1791
+ parent.signal.addEventListener(
1792
+ "abort",
1793
+ () => {
1794
+ if (!this.controller.signal.aborted) {
1795
+ this._err = parent.err ?? new CancelledError();
1796
+ this.controller.abort();
1797
+ }
1798
+ },
1799
+ { once: true }
1800
+ );
1801
+ }
1802
+ }
1803
+ }
1804
+ get signal() {
1805
+ return this.controller.signal;
1806
+ }
1807
+ get deadline() {
1808
+ return this.parent?.deadline ?? null;
1809
+ }
1810
+ get err() {
1811
+ return this._err;
1812
+ }
1813
+ value(_key) {
1814
+ return this.parent?.value(_key);
1815
+ }
1816
+ done() {
1817
+ if (this.controller.signal.aborted) return Promise.resolve();
1818
+ return new Promise((resolve) => {
1819
+ this.controller.signal.addEventListener("abort", () => resolve(), { once: true });
1820
+ });
1821
+ }
1822
+ };
1823
+ var BackgroundContext = class {
1824
+ _signal = new AbortController().signal;
1825
+ get signal() {
1826
+ return this._signal;
1827
+ }
1828
+ get deadline() {
1829
+ return null;
1830
+ }
1831
+ get err() {
1832
+ return null;
1833
+ }
1834
+ value(_key) {
1835
+ return void 0;
1836
+ }
1837
+ done() {
1838
+ return new Promise(() => {
1839
+ });
1840
+ }
1841
+ };
1842
+ var bg = null;
1843
+ function background() {
1844
+ if (!bg) bg = new BackgroundContext();
1845
+ return bg;
1846
+ }
1847
+ var CancelContext = class extends BaseContext {
1848
+ cancel(reason) {
1849
+ if (!this.controller.signal.aborted) {
1850
+ this._err = new CancelledError(reason ?? "context cancelled");
1851
+ this.controller.abort();
1852
+ }
1853
+ }
1854
+ };
1855
+ function withCancel(parent) {
1856
+ const ctx = new CancelContext(parent);
1857
+ return [ctx, (reason) => ctx.cancel(reason)];
1858
+ }
1859
+ var DeadlineContext = class extends BaseContext {
1860
+ _deadline;
1861
+ timer = null;
1862
+ constructor(parent, deadline) {
1863
+ super(parent);
1864
+ this._deadline = deadline;
1865
+ if (parent.deadline && parent.deadline < deadline) {
1866
+ this._deadline = parent.deadline;
1867
+ }
1868
+ if (this.controller.signal.aborted) {
1869
+ return;
1870
+ }
1871
+ const ms = this._deadline.getTime() - Date.now();
1872
+ if (ms <= 0) {
1873
+ this._err = new DeadlineExceededError();
1874
+ this.controller.abort();
1875
+ } else {
1876
+ this.timer = setTimeout(() => {
1877
+ if (!this.controller.signal.aborted) {
1878
+ this._err = new DeadlineExceededError();
1879
+ this.controller.abort();
1880
+ }
1881
+ }, ms);
1882
+ if (typeof this.timer === "object" && "unref" in this.timer) {
1883
+ this.timer.unref();
1884
+ }
1885
+ }
1886
+ }
1887
+ get deadline() {
1888
+ return this._deadline;
1889
+ }
1890
+ cancel(reason) {
1891
+ if (this.timer !== null) {
1892
+ clearTimeout(this.timer);
1893
+ this.timer = null;
1894
+ }
1895
+ if (!this.controller.signal.aborted) {
1896
+ this._err = new CancelledError(reason ?? "context cancelled");
1897
+ this.controller.abort();
1898
+ }
1899
+ }
1900
+ };
1901
+ function withDeadline(parent, deadline) {
1902
+ const ctx = new DeadlineContext(parent, deadline);
1903
+ return [ctx, (reason) => ctx.cancel(reason)];
1904
+ }
1905
+ function withTimeout(parent, ms) {
1906
+ return withDeadline(parent, new Date(Date.now() + ms));
1907
+ }
1908
+ var ValueContext = class extends BaseContext {
1909
+ key;
1910
+ val;
1911
+ constructor(parent, key, val) {
1912
+ super(parent);
1913
+ this.key = key;
1914
+ this.val = val;
1915
+ }
1916
+ value(key) {
1917
+ if (key === this.key) return this.val;
1918
+ return this.parent?.value(key);
1919
+ }
1920
+ };
1921
+ function withValue(parent, key, value) {
1922
+ return new ValueContext(parent, key, value);
1923
+ }
1479
1924
  export {
1925
+ CancelledError,
1926
+ Cond,
1927
+ DeadlineExceededError,
1480
1928
  ErrGroup,
1481
1929
  Mutex,
1482
1930
  Once,
1931
+ RWMutex,
1483
1932
  Ticker,
1933
+ Timer,
1484
1934
  WaitGroup,
1485
1935
  after,
1936
+ background,
1486
1937
  chan,
1487
1938
  configure,
1488
1939
  detectCapability,
@@ -1493,6 +1944,10 @@ export {
1493
1944
  spawn,
1494
1945
  stats,
1495
1946
  task,
1496
- ticker
1947
+ ticker,
1948
+ withCancel,
1949
+ withDeadline,
1950
+ withTimeout,
1951
+ withValue
1497
1952
  };
1498
1953
  //# sourceMappingURL=index.js.map