radashi 12.5.1 → 12.6.1

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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  <a href="https://app.codecov.io/gh/radashi-org/radashi/tree/main/src"><img src="https://img.shields.io/codecov/c/github/radashi-org/radashi?logo=codecov" alt="Codecov" /></a>
10
10
  <a href="https://biomejs.dev/"><img src="https://img.shields.io/badge/code_style-biome.js-blue?logo=biome" alt="Code Style: Biome.js" /></a>
11
11
  <a href="https://github.com/radashi-org/radashi/discussions"><img src="https://img.shields.io/github/discussions/radashi-org/radashi?logo=github" alt="GitHub Discussions" /></a>
12
- <a href="https://app.gitter.im/#/room/#radashi:gitter.im"><img src="https://badges.gitter.im/join_chat.svg" alt="Gitter.im" /></a>
12
+ <a href="https://jsr.io/@radashi-org/radashi"><img src="https://jsr.io/badges/@radashi-org/radashi" alt="JSR.io" /></a>
13
13
  <p align="center">
14
14
  <span>English</span> | <a href="./README-pt_br.md">Português</a>
15
15
  </p>
@@ -100,13 +100,13 @@ deno add jsr:@radashi-org/radashi
100
100
 
101
101
  ## FAQ
102
102
 
103
- - **“I need XYZ, but Radashi doesn't have it.”**
103
+ - **“I need XYZ, but Radashi doesn't have it.”**
104
104
  If you have a need not met by our current set of functions, we want to hear about it. [Start a discussion](https://github.com/orgs/radashi-org/discussions/new?category=ideas) so we can explore the idea together!
105
105
 
106
- - **What does “community first” mean exactly?**
106
+ - **What does “community first” mean exactly?**
107
107
  It means putting the community's needs first, leaning towards adding support for popular use cases, as opposed to being strictly minimalist. As such, your feedback is very welcome and we value your perspective. Specifically, we want you to [contribute your viewpoint](https://github.com/orgs/radashi-org/discussions/categories/rfcs?discussions_q=is%3Aopen+category%3ARFCs) to discussions in our RFCs category.
108
108
 
109
- - **Are my contributions welcome?**
109
+ - **Are my contributions welcome?**
110
110
  Yes! Pull requests are encouraged, but please keep them small and focused. Sweeping changes are discouraged and won't be merged (unless the rationale's been thoroughly discussed).
111
111
 
112
112
  Please review _“The ethos of Radashi”_ before submitting a pull request:
@@ -115,35 +115,35 @@ deno add jsr:@radashi-org/radashi
115
115
  <img src="https://github.com/radashi-org/radashi/raw/main/.github/img/ethos-button.png" alt="The ethos of Radashi" width="250px" />
116
116
  </a>
117
117
 
118
- - **Can I help you maintain this?**
118
+ - **Can I help you maintain this?**
119
119
  Yes! I'll add you as a contributor to the repository. You can review pull requests and help with triage. With time, you may earn the ability to merge approved PRs.
120
120
 
121
121
  <a href="https://github.com/orgs/radashi-org/discussions/4">
122
122
  <img src="https://github.com/radashi-org/radashi/raw/main/.github/img/apply-button.png" alt="Apply to join the Radashi team" width="250px" />
123
123
  </a>
124
124
 
125
- - **Is backwards compatibility a goal?**
125
+ - **Is backwards compatibility a goal?**
126
126
  Yes! We want the transition from `radash` to this library to be smooth. If you're coming from Radash, we recommend installing `radashi@^12`. This version will continue to receive backported fixes even after Radashi v13 is released. You can upgrade to the latest major version when you're ready.
127
127
 
128
- - **Automatic releases**
128
+ - **Automatic releases**
129
129
  To ensure contributions are quickly rolled out, we have the following automatic processes:
130
130
 
131
- - **Patch releases**
131
+ - **Patch releases**
132
132
  Whenever the `main` branch receives a `^fix:` commit, a patch release is immediately published to NPM.
133
133
 
134
- - **Beta releases**
134
+ - **Beta releases**
135
135
  Pull requests that add a new feature can be labeled with `prerelease` by a maintainer. This triggers a GitHub workflow that attempts to copy the PR into the `beta` branch. If that succeeds, a beta release is immediately published to NPM.
136
136
 
137
137
  Installing `radashi@beta` will always fetch the latest beta release. Although the name "beta" may suggest unstable code, PRs need tests to be eligible for a prerelease.
138
138
 
139
139
  Beta releases provide quick access to new features without waiting for a regular release cycle. They're also an opportunity for the community to provide feedback before the feature is released to the `main` branch.
140
140
 
141
- - **"Next" releases**
141
+ - **"Next" releases**
142
142
  Pull requests with breaking changes can also be labeled with `prerelease` by a maintainer. In this case, the PR is copied into the `next` branch. If that succeeds, a "next" release is published to NPM.
143
143
 
144
144
  Installing `radashi@next` will always fetch the latest "next" release.
145
145
 
146
- - **Release cycle**
146
+ - **Release cycle**
147
147
  Radashi is expected to release a new minor or major version on a monthly basis, but releases are not on a strict schedule. Pre-releases are available for testing and feedback before the final release.
148
148
 
149
149
  <img src="https://github.com/radashi-org/radashi/raw/main/.github/img/rule.png" width="100%" />
package/dist/radashi.cjs CHANGED
@@ -132,17 +132,15 @@ function fork(array, condition) {
132
132
 
133
133
  // src/array/group.ts
134
134
  function group(array, getGroupId) {
135
- return array.reduce(
136
- (acc, item) => {
137
- const groupId = getGroupId(item);
138
- if (!acc[groupId]) {
139
- acc[groupId] = [];
140
- }
141
- acc[groupId].push(item);
142
- return acc;
143
- },
144
- {}
145
- );
135
+ const groups = /* @__PURE__ */ Object.create(null);
136
+ array.forEach((item, index) => {
137
+ const groupId = getGroupId(item, index);
138
+ if (!groups[groupId]) {
139
+ groups[groupId] = [];
140
+ }
141
+ groups[groupId].push(item);
142
+ });
143
+ return groups;
146
144
  }
147
145
 
148
146
  // src/array/intersects.ts
@@ -404,19 +402,6 @@ function zipToObject(keys2, values) {
404
402
  );
405
403
  }
406
404
 
407
- // src/async/AggregateError.ts
408
- var AggregateErrorOrPolyfill = /* @__PURE__ */ (() => globalThis.AggregateError ?? class AggregateError extends Error {
409
- constructor(errors = []) {
410
- var _a, _b;
411
- super();
412
- const name = ((_a = errors.find((e) => e.name)) == null ? void 0 : _a.name) ?? "";
413
- this.name = `AggregateError(${name}...)`;
414
- this.message = `AggregateError with ${errors.length} errors`;
415
- this.stack = ((_b = errors.find((e) => e.stack)) == null ? void 0 : _b.stack) ?? this.stack;
416
- this.errors = errors;
417
- }
418
- })();
419
-
420
405
  // src/async/all.ts
421
406
  async function all(input) {
422
407
  const errors = [];
@@ -549,6 +534,20 @@ async function parallel(options, array, func) {
549
534
  return results.map((r) => r.result);
550
535
  }
551
536
 
537
+ // src/async/queueByKey.ts
538
+ function queueByKey(asyncFn, keyFn) {
539
+ const queues = /* @__PURE__ */ new Map();
540
+ return async (...args) => {
541
+ const key = keyFn(...args);
542
+ const next = () => asyncFn(...args);
543
+ const queue = (queues.get(key) || Promise.resolve()).then(next, next);
544
+ queues.set(key, queue);
545
+ const cleanup = () => queues.get(key) === queue && queues.delete(key);
546
+ queue.then(cleanup, cleanup);
547
+ return queue;
548
+ };
549
+ }
550
+
552
551
  // src/async/reduce.ts
553
552
  async function reduce(array, reducer, initialValue) {
554
553
  if (!array) {
@@ -603,14 +602,6 @@ function sleep(milliseconds) {
603
602
  return new Promise((res) => setTimeout(res, milliseconds));
604
603
  }
605
604
 
606
- // src/async/TimeoutError.ts
607
- var TimeoutError = class extends Error {
608
- constructor(message) {
609
- super(message ?? "Operation timed out");
610
- this.name = "TimeoutError";
611
- }
612
- };
613
-
614
605
  // src/async/timeout.ts
615
606
  function timeout(ms, error) {
616
607
  return new Promise(
@@ -787,6 +778,17 @@ function partob(fn, argObj) {
787
778
  return (restObj) => fn({ ...argObj, ...restObj });
788
779
  }
789
780
 
781
+ // src/curry/promiseChain.ts
782
+ function promiseChain(...funcs) {
783
+ return async (...args) => {
784
+ let result = await funcs[0](...args);
785
+ for (let i = 1; i < funcs.length; i++) {
786
+ result = await funcs[i](result);
787
+ }
788
+ return result;
789
+ };
790
+ }
791
+
790
792
  // src/curry/proxied.ts
791
793
  function proxied(handler) {
792
794
  return new Proxy(
@@ -898,6 +900,16 @@ function min(array, getter) {
898
900
  return array.reduce((a, b) => get2(a) < get2(b) ? a : b);
899
901
  }
900
902
 
903
+ // src/number/parseDuration.ts
904
+ function parseDuration(duration, options) {
905
+ return new DurationParser(options).parse(duration);
906
+ }
907
+
908
+ // src/number/parseQuantity.ts
909
+ function parseQuantity(quantity, options) {
910
+ return new QuantityParser(options).parse(quantity);
911
+ }
912
+
901
913
  // src/number/range.ts
902
914
  function* range(startOrLength, end, valueOrMapper = (i) => i, step = 1) {
903
915
  const mapper = isFunction(valueOrMapper) ? valueOrMapper : () => valueOrMapper;
@@ -1369,6 +1381,192 @@ function upperize(obj) {
1369
1381
  return mapKeys(obj, (k) => k.toUpperCase());
1370
1382
  }
1371
1383
 
1384
+ // src/oop/AggregateError.ts
1385
+ var AggregateErrorOrPolyfill = /* @__PURE__ */ (() => globalThis.AggregateError ?? class AggregateError extends Error {
1386
+ constructor(errors = []) {
1387
+ var _a, _b;
1388
+ super();
1389
+ const name = ((_a = errors.find((e) => e.name)) == null ? void 0 : _a.name) ?? "";
1390
+ this.name = `AggregateError(${name}...)`;
1391
+ this.message = `AggregateError with ${errors.length} errors`;
1392
+ this.stack = ((_b = errors.find((e) => e.stack)) == null ? void 0 : _b.stack) ?? this.stack;
1393
+ this.errors = errors;
1394
+ }
1395
+ })();
1396
+
1397
+ // src/oop/QuantityParser.ts
1398
+ var QuantityParser = class {
1399
+ constructor({ units, short }) {
1400
+ this.units = units;
1401
+ this.short = short;
1402
+ }
1403
+ /**
1404
+ * Parse a quantity string into its numeric value
1405
+ *
1406
+ * @throws {Error} If the quantity string is invalid or contains an unknown unit
1407
+ */
1408
+ parse(quantity) {
1409
+ var _a;
1410
+ const match = quantity.match(/^(-?\d+(?:\.\d+)?) ?(\w+)?s?$/);
1411
+ if (!match) {
1412
+ throw new Error(`Invalid quantity, cannot parse: ${quantity}`);
1413
+ }
1414
+ let unit = match[2];
1415
+ unit = ((_a = this.short) == null ? void 0 : _a[unit]) || unit;
1416
+ const count = Number.parseFloat(match[1]);
1417
+ if (Math.abs(count) > 1 && unit.endsWith("s")) {
1418
+ unit = unit.substring(0, unit.length - 1);
1419
+ }
1420
+ if (!this.units[unit]) {
1421
+ throw new Error(
1422
+ `Invalid unit: ${unit}, makes sure it is one of: ${Object.keys(this.units).join(", ")}`
1423
+ );
1424
+ }
1425
+ return count * this.units[unit];
1426
+ }
1427
+ };
1428
+
1429
+ // src/oop/DurationParser.ts
1430
+ var _DurationParser = class _DurationParser extends QuantityParser {
1431
+ constructor(options) {
1432
+ super({
1433
+ units: {
1434
+ ..._DurationParser.units,
1435
+ ...options == null ? void 0 : options.units
1436
+ },
1437
+ short: {
1438
+ ..._DurationParser.shortUnits,
1439
+ ...options == null ? void 0 : options.short
1440
+ }
1441
+ });
1442
+ }
1443
+ };
1444
+ _DurationParser.units = {
1445
+ week: 6048e5,
1446
+ day: 864e5,
1447
+ hour: 36e5,
1448
+ minute: 6e4,
1449
+ second: 1e3,
1450
+ millisecond: 1
1451
+ };
1452
+ _DurationParser.shortUnits = {
1453
+ w: "week",
1454
+ d: "day",
1455
+ h: "hour",
1456
+ m: "minute",
1457
+ s: "second",
1458
+ ms: "millisecond"
1459
+ };
1460
+ var DurationParser = _DurationParser;
1461
+
1462
+ // src/oop/Semaphore.ts
1463
+ var SemaphorePermit = class {
1464
+ constructor(semaphore, request, weight) {
1465
+ this.semaphore = semaphore;
1466
+ this.request = request;
1467
+ this.weight = weight;
1468
+ }
1469
+ /**
1470
+ * Releases this permit back to the semaphore, allowing another
1471
+ * operation to acquire it.
1472
+ */
1473
+ release() {
1474
+ this.semaphore.release(this);
1475
+ this.release = noop;
1476
+ }
1477
+ };
1478
+ var Semaphore = class {
1479
+ /**
1480
+ * Creates a new semaphore with the specified capacity.
1481
+ * @param maxCapacity Maximum number of permits that can be issued simultaneously
1482
+ */
1483
+ constructor(maxCapacity) {
1484
+ this.maxCapacity = maxCapacity;
1485
+ this.queue = [];
1486
+ if (maxCapacity <= 0) {
1487
+ throw new Error("maxCapacity must be > 0");
1488
+ }
1489
+ this.capacity = maxCapacity;
1490
+ }
1491
+ /**
1492
+ * Number of pending acquisition requests.
1493
+ */
1494
+ get queueLength() {
1495
+ return this.queue.length;
1496
+ }
1497
+ /**
1498
+ * Acquires a permit from this semaphore, waiting if necessary until
1499
+ * one becomes available.
1500
+ * @param options.signal - The signal to abort the acquisition
1501
+ * @param options.weight - The weight of the permit to acquire
1502
+ * @returns A promise that resolves to a permit when one is
1503
+ * available
1504
+ */
1505
+ async acquire({
1506
+ signal,
1507
+ weight = 1
1508
+ } = {}) {
1509
+ if (weight <= 0) {
1510
+ throw new Error("weight must be > 0");
1511
+ }
1512
+ if (weight > this.maxCapacity) {
1513
+ throw new Error("weight must be \u2264 maxCapacity");
1514
+ }
1515
+ const request = withResolvers();
1516
+ const permit = new SemaphorePermit(this, request, weight);
1517
+ if (signal) {
1518
+ const abort = () => {
1519
+ const index = this.queue.indexOf(permit);
1520
+ if (index >= 0) {
1521
+ this.queue.splice(index, 1);
1522
+ request.reject(signal.reason);
1523
+ }
1524
+ };
1525
+ signal.addEventListener("abort", abort);
1526
+ const cleanup = () => {
1527
+ signal.removeEventListener("abort", abort);
1528
+ };
1529
+ request.promise.then(cleanup, cleanup);
1530
+ }
1531
+ if (this.capacity < weight) {
1532
+ this.queue.push(permit);
1533
+ await request.promise;
1534
+ } else {
1535
+ this.capacity -= weight;
1536
+ }
1537
+ return permit;
1538
+ }
1539
+ /**
1540
+ * Rejects all pending acquisition requests.
1541
+ */
1542
+ reject(error) {
1543
+ this.acquire = () => Promise.reject(error);
1544
+ this.queue.forEach((permit) => permit.request.reject(error));
1545
+ this.queue = [];
1546
+ }
1547
+ /**
1548
+ * Releases a permit back to the semaphore, increasing capacity and
1549
+ * potentially fulfilling a pending acquisition request.
1550
+ */
1551
+ release(permit) {
1552
+ this.capacity += permit.weight;
1553
+ const nextPermit = this.queue[0];
1554
+ if (nextPermit && this.capacity >= nextPermit.weight) {
1555
+ this.capacity -= nextPermit.weight;
1556
+ this.queue.shift();
1557
+ nextPermit.request.resolve();
1558
+ }
1559
+ }
1560
+ };
1561
+
1562
+ // src/oop/TimeoutError.ts
1563
+ var TimeoutError = class extends Error {
1564
+ constructor(message) {
1565
+ super(message ?? "Operation timed out");
1566
+ this.name = "TimeoutError";
1567
+ }
1568
+ };
1569
+
1372
1570
  // src/random/draw.ts
1373
1571
  function draw(array) {
1374
1572
  const max2 = array.length;
@@ -1526,6 +1724,19 @@ function dedent(text, ...values) {
1526
1724
  return output.replace(/^[ \t]*\n|\n[ \t]*$/g, "");
1527
1725
  }
1528
1726
 
1727
+ // src/string/escapeHTML.ts
1728
+ var htmlCharacters = /[&<>"']/g;
1729
+ var replacements = {
1730
+ "&": "&amp;",
1731
+ "<": "&lt;",
1732
+ ">": "&gt;",
1733
+ '"': "&quot;",
1734
+ "'": "&#39;"
1735
+ };
1736
+ function escapeHTML(input) {
1737
+ return input.replace(htmlCharacters, (char) => replacements[char]);
1738
+ }
1739
+
1529
1740
  // src/string/pascal.ts
1530
1741
  function pascal(str) {
1531
1742
  if (!str) {
@@ -1636,6 +1847,13 @@ function trim(str, charsToTrim = " ") {
1636
1847
  return str.replace(regex, "");
1637
1848
  }
1638
1849
 
1850
+ // src/typed/assert.ts
1851
+ function assert(condition, message) {
1852
+ if (!condition) {
1853
+ throw message instanceof Error ? message : new Error(message ?? "Assertion failed");
1854
+ }
1855
+ }
1856
+
1639
1857
  // src/typed/isArray.ts
1640
1858
  var isArray = /* @__PURE__ */ (() => Array.isArray)();
1641
1859
 
@@ -1851,11 +2069,16 @@ function isWeakSet(value) {
1851
2069
 
1852
2070
  exports.AggregateError = AggregateErrorOrPolyfill;
1853
2071
  exports.DefaultCloningStrategy = DefaultCloningStrategy;
2072
+ exports.DurationParser = DurationParser;
1854
2073
  exports.FastCloningStrategy = FastCloningStrategy;
2074
+ exports.QuantityParser = QuantityParser;
2075
+ exports.Semaphore = Semaphore;
2076
+ exports.SemaphorePermit = SemaphorePermit;
1855
2077
  exports.TimeoutError = TimeoutError;
1856
2078
  exports.all = all;
1857
2079
  exports.alphabetical = alphabetical;
1858
2080
  exports.always = always;
2081
+ exports.assert = assert;
1859
2082
  exports.assign = assign;
1860
2083
  exports.boil = boil;
1861
2084
  exports.callable = callable;
@@ -1882,6 +2105,7 @@ exports.dedent = dedent;
1882
2105
  exports.defer = defer;
1883
2106
  exports.diff = diff;
1884
2107
  exports.draw = draw;
2108
+ exports.escapeHTML = escapeHTML;
1885
2109
  exports.filterKey = filterKey;
1886
2110
  exports.first = first;
1887
2111
  exports.flat = flat;
@@ -1948,12 +2172,16 @@ exports.objectify = objectify;
1948
2172
  exports.omit = omit;
1949
2173
  exports.once = once;
1950
2174
  exports.parallel = parallel;
2175
+ exports.parseDuration = parseDuration;
2176
+ exports.parseQuantity = parseQuantity;
1951
2177
  exports.partial = partial;
1952
2178
  exports.partob = partob;
1953
2179
  exports.pascal = pascal;
1954
2180
  exports.pick = pick;
1955
2181
  exports.pluck = pluck;
2182
+ exports.promiseChain = promiseChain;
1956
2183
  exports.proxied = proxied;
2184
+ exports.queueByKey = queueByKey;
1957
2185
  exports.random = random;
1958
2186
  exports.range = range;
1959
2187
  exports.reduce = reduce;