@forthtilliath/ts-kit 0.10.0 → 0.12.0

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.
Files changed (129) hide show
  1. package/README.md +100 -22
  2. package/dist/array/flattenDeep.d.ts +7 -5
  3. package/dist/array/flattenDeep.d.ts.map +1 -1
  4. package/dist/array/flattenDeep.js +8 -5
  5. package/dist/array/index.d.ts +1 -0
  6. package/dist/array/index.d.ts.map +1 -1
  7. package/dist/array/index.js +1 -0
  8. package/dist/array/shuffle.d.ts +14 -0
  9. package/dist/array/shuffle.d.ts.map +1 -0
  10. package/dist/array/shuffle.js +20 -0
  11. package/dist/async/abortReason.d.ts +9 -0
  12. package/dist/async/abortReason.d.ts.map +1 -0
  13. package/dist/async/abortReason.js +11 -0
  14. package/dist/async/memoize.d.ts +2 -18
  15. package/dist/async/memoize.d.ts.map +1 -1
  16. package/dist/async/memoize.js +4 -25
  17. package/dist/async/retry.d.ts +15 -3
  18. package/dist/async/retry.d.ts.map +1 -1
  19. package/dist/async/retry.js +15 -11
  20. package/dist/async/sleep.d.ts +2 -1
  21. package/dist/async/sleep.d.ts.map +1 -1
  22. package/dist/async/sleep.js +23 -2
  23. package/dist/async/withTimeout.d.ts +11 -4
  24. package/dist/async/withTimeout.d.ts.map +1 -1
  25. package/dist/async/withTimeout.js +31 -9
  26. package/dist/csv/escapeCsvField.d.ts +21 -0
  27. package/dist/csv/escapeCsvField.d.ts.map +1 -0
  28. package/dist/csv/escapeCsvField.js +28 -0
  29. package/dist/csv/formatCsvNumber.d.ts +14 -0
  30. package/dist/csv/formatCsvNumber.d.ts.map +1 -0
  31. package/dist/csv/formatCsvNumber.js +15 -0
  32. package/dist/csv/index.d.ts +2 -0
  33. package/dist/csv/index.d.ts.map +1 -1
  34. package/dist/csv/index.js +2 -0
  35. package/dist/date/dateInTimeZone.d.ts +22 -0
  36. package/dist/date/dateInTimeZone.d.ts.map +1 -0
  37. package/dist/date/dateInTimeZone.js +35 -0
  38. package/dist/date/daysUntil.d.ts +15 -0
  39. package/dist/date/daysUntil.d.ts.map +1 -0
  40. package/dist/date/daysUntil.js +22 -0
  41. package/dist/date/formatRecentDate.d.ts +22 -0
  42. package/dist/date/formatRecentDate.d.ts.map +1 -0
  43. package/dist/date/formatRecentDate.js +46 -0
  44. package/dist/date/index.d.ts +7 -0
  45. package/dist/date/index.d.ts.map +1 -1
  46. package/dist/date/index.js +7 -0
  47. package/dist/date/mondayFirstWeekday.d.ts +12 -0
  48. package/dist/date/mondayFirstWeekday.d.ts.map +1 -0
  49. package/dist/date/mondayFirstWeekday.js +15 -0
  50. package/dist/date/parseIsoDate.d.ts +12 -0
  51. package/dist/date/parseIsoDate.d.ts.map +1 -0
  52. package/dist/date/parseIsoDate.js +13 -0
  53. package/dist/date/parseSqliteDate.d.ts +13 -0
  54. package/dist/date/parseSqliteDate.d.ts.map +1 -0
  55. package/dist/date/parseSqliteDate.js +17 -0
  56. package/dist/date/todayInTimeZone.d.ts +14 -0
  57. package/dist/date/todayInTimeZone.d.ts.map +1 -0
  58. package/dist/date/todayInTimeZone.js +15 -0
  59. package/dist/files/downloadText.d.ts.map +1 -1
  60. package/dist/files/downloadText.js +9 -1
  61. package/dist/files/formatFileTimestamp.d.ts +22 -0
  62. package/dist/files/formatFileTimestamp.d.ts.map +1 -0
  63. package/dist/files/formatFileTimestamp.js +20 -0
  64. package/dist/files/index.d.ts +1 -0
  65. package/dist/files/index.d.ts.map +1 -1
  66. package/dist/files/index.js +1 -0
  67. package/dist/function/index.d.ts +2 -0
  68. package/dist/function/index.d.ts.map +1 -0
  69. package/dist/function/index.js +1 -0
  70. package/dist/function/memoize.d.ts +34 -0
  71. package/dist/function/memoize.d.ts.map +1 -0
  72. package/dist/function/memoize.js +50 -0
  73. package/dist/ics/escapeIcsText.d.ts +12 -0
  74. package/dist/ics/escapeIcsText.d.ts.map +1 -0
  75. package/dist/ics/escapeIcsText.js +17 -0
  76. package/dist/ics/foldIcsLine.d.ts +15 -0
  77. package/dist/ics/foldIcsLine.d.ts.map +1 -0
  78. package/dist/ics/foldIcsLine.js +40 -0
  79. package/dist/ics/icsTimestamp.d.ts +10 -0
  80. package/dist/ics/icsTimestamp.d.ts.map +1 -0
  81. package/dist/ics/icsTimestamp.js +11 -0
  82. package/dist/ics/index.d.ts +6 -0
  83. package/dist/ics/index.d.ts.map +1 -0
  84. package/dist/ics/index.js +5 -0
  85. package/dist/ics/toIcsDate.d.ts +11 -0
  86. package/dist/ics/toIcsDate.d.ts.map +1 -0
  87. package/dist/ics/toIcsDate.js +12 -0
  88. package/dist/ics/toIcsDateTime.d.ts +13 -0
  89. package/dist/ics/toIcsDateTime.d.ts.map +1 -0
  90. package/dist/ics/toIcsDateTime.js +16 -0
  91. package/dist/index.d.ts +2 -1
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +2 -1
  94. package/dist/maths/average.d.ts +2 -7
  95. package/dist/maths/average.d.ts.map +1 -1
  96. package/dist/maths/average.js +4 -10
  97. package/dist/maths/sum.d.ts +2 -7
  98. package/dist/maths/sum.d.ts.map +1 -1
  99. package/dist/maths/sum.js +4 -9
  100. package/dist/number/average.d.ts +13 -0
  101. package/dist/number/average.d.ts.map +1 -0
  102. package/dist/number/average.js +15 -0
  103. package/dist/number/formatBytes.d.ts.map +1 -1
  104. package/dist/number/formatBytes.js +6 -2
  105. package/dist/number/formatCsvNumber.d.ts +2 -13
  106. package/dist/number/formatCsvNumber.d.ts.map +1 -1
  107. package/dist/number/formatCsvNumber.js +4 -15
  108. package/dist/number/index.d.ts +2 -0
  109. package/dist/number/index.d.ts.map +1 -1
  110. package/dist/number/index.js +2 -0
  111. package/dist/number/sum.d.ts +10 -0
  112. package/dist/number/sum.d.ts.map +1 -0
  113. package/dist/number/sum.js +11 -0
  114. package/dist/object/deepEqual.d.ts +6 -0
  115. package/dist/object/deepEqual.d.ts.map +1 -1
  116. package/dist/object/deepEqual.js +45 -11
  117. package/dist/string/escapeCsvField.d.ts +2 -13
  118. package/dist/string/escapeCsvField.d.ts.map +1 -1
  119. package/dist/string/escapeCsvField.js +4 -17
  120. package/dist/string/escapeHtml.d.ts +6 -4
  121. package/dist/string/escapeHtml.d.ts.map +1 -1
  122. package/dist/string/escapeHtml.js +8 -5
  123. package/dist/string/pluralize.d.ts +30 -7
  124. package/dist/string/pluralize.d.ts.map +1 -1
  125. package/dist/string/pluralize.js +48 -15
  126. package/dist/version/compareVersions.d.ts +8 -1
  127. package/dist/version/compareVersions.d.ts.map +1 -1
  128. package/dist/version/compareVersions.js +63 -9
  129. package/package.json +7 -6
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Caches the result of `fn` per argument tuple (JSON-stringified by
3
+ * default).
4
+ *
5
+ * A returned promise that rejects is evicted from the cache, so a failed
6
+ * async call is retried on the next invocation instead of failing forever.
7
+ *
8
+ * @param fn - The function to memoize.
9
+ * @param getKeyOrOptions - A key function, or `{ getKey, maxSize }`.
10
+ * @returns A memoized version of `fn`, with a `cache` map exposed.
11
+ * @example
12
+ * const square = memoize((n: number) => expensiveSquare(n));
13
+ * square(4); // computed
14
+ * square(4); // cached
15
+ *
16
+ * const getUser = memoize(fetchUser, { maxSize: 100 });
17
+ */
18
+ export function memoize(fn, getKeyOrOptions = {}) {
19
+ const { getKey = (...args) => JSON.stringify(args), maxSize = Infinity, } = typeof getKeyOrOptions === "function"
20
+ ? { getKey: getKeyOrOptions }
21
+ : getKeyOrOptions;
22
+ const cache = new Map();
23
+ const memoized = ((...args) => {
24
+ const key = getKey(...args);
25
+ if (cache.has(key)) {
26
+ const cached = cache.get(key);
27
+ // Re-insert to mark it as the most recently used.
28
+ cache.delete(key);
29
+ cache.set(key, cached);
30
+ return cached;
31
+ }
32
+ const result = fn(...args);
33
+ cache.set(key, result);
34
+ if (cache.size > maxSize) {
35
+ const oldestKey = cache.keys().next().value;
36
+ if (oldestKey !== undefined)
37
+ cache.delete(oldestKey);
38
+ }
39
+ if (result instanceof Promise) {
40
+ result.catch(() => {
41
+ // Only if it's still this call's promise (not a newer one).
42
+ if (cache.get(key) === result)
43
+ cache.delete(key);
44
+ });
45
+ }
46
+ return result;
47
+ });
48
+ memoized.cache = cache;
49
+ return memoized;
50
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Escapes a value for an iCalendar (RFC 5545) TEXT property — `SUMMARY`,
3
+ * `DESCRIPTION`, `LOCATION`…: backslashes, semicolons and commas get a
4
+ * backslash, line breaks become a literal `\n`.
5
+ *
6
+ * @param text - The raw text.
7
+ * @returns The text, safe to put after `SUMMARY:` and the like.
8
+ * @example
9
+ * escapeIcsText("Matin; puis soir, à 20h"); // => "Matin\\; puis soir\\, à 20h"
10
+ */
11
+ export declare function escapeIcsText(text: string): string;
12
+ //# sourceMappingURL=escapeIcsText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escapeIcsText.d.ts","sourceRoot":"","sources":["../../src/ics/escapeIcsText.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Escapes a value for an iCalendar (RFC 5545) TEXT property — `SUMMARY`,
3
+ * `DESCRIPTION`, `LOCATION`…: backslashes, semicolons and commas get a
4
+ * backslash, line breaks become a literal `\n`.
5
+ *
6
+ * @param text - The raw text.
7
+ * @returns The text, safe to put after `SUMMARY:` and the like.
8
+ * @example
9
+ * escapeIcsText("Matin; puis soir, à 20h"); // => "Matin\\; puis soir\\, à 20h"
10
+ */
11
+ export function escapeIcsText(text) {
12
+ return text
13
+ .replace(/\\/g, "\\\\")
14
+ .replace(/;/g, "\\;")
15
+ .replace(/,/g, "\\,")
16
+ .replace(/\r?\n/g, "\\n");
17
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Folds an iCalendar (RFC 5545) content line longer than 75 **octets**: the
3
+ * rest goes on continuation lines (CRLF + one space), each 75 octets at most
4
+ * including that space. Counts UTF-8 bytes, not characters — an accented
5
+ * line folded at 75 characters would overflow — and never splits a
6
+ * multi-byte character.
7
+ *
8
+ * @param line - One unfolded content line, without its line break.
9
+ * @param maxOctets - Octets per line. @defaultValue 75
10
+ * @returns The line, folded with `\r\n ` where needed.
11
+ * @example
12
+ * foldIcsLine(`DESCRIPTION:${"é".repeat(40)}`); // => 2 lines
13
+ */
14
+ export declare function foldIcsLine(line: string, maxOctets?: number): string;
15
+ //# sourceMappingURL=foldIcsLine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldIcsLine.d.ts","sourceRoot":"","sources":["../../src/ics/foldIcsLine.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAK,GAAG,MAAM,CAgBhE"}
@@ -0,0 +1,40 @@
1
+ /** Octets taken by one code point in UTF-8. */
2
+ function utf8Length(codePoint) {
3
+ if (codePoint < 0x80)
4
+ return 1;
5
+ if (codePoint < 0x800)
6
+ return 2;
7
+ if (codePoint < 0x10000)
8
+ return 3;
9
+ return 4;
10
+ }
11
+ /**
12
+ * Folds an iCalendar (RFC 5545) content line longer than 75 **octets**: the
13
+ * rest goes on continuation lines (CRLF + one space), each 75 octets at most
14
+ * including that space. Counts UTF-8 bytes, not characters — an accented
15
+ * line folded at 75 characters would overflow — and never splits a
16
+ * multi-byte character.
17
+ *
18
+ * @param line - One unfolded content line, without its line break.
19
+ * @param maxOctets - Octets per line. @defaultValue 75
20
+ * @returns The line, folded with `\r\n ` where needed.
21
+ * @example
22
+ * foldIcsLine(`DESCRIPTION:${"é".repeat(40)}`); // => 2 lines
23
+ */
24
+ export function foldIcsLine(line, maxOctets = 75) {
25
+ const parts = [];
26
+ let current = "";
27
+ let size = 0;
28
+ for (const char of line) {
29
+ const bytes = utf8Length(char.codePointAt(0) ?? 0);
30
+ if (size + bytes > maxOctets) {
31
+ parts.push(current);
32
+ current = " ";
33
+ size = 1;
34
+ }
35
+ current += char;
36
+ size += bytes;
37
+ }
38
+ parts.push(current);
39
+ return parts.join("\r\n");
40
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * An iCalendar UTC `DATE-TIME` (`YYYYMMDDTHHMMSSZ`), as `DTSTAMP` expects.
3
+ *
4
+ * @param date - The instant. @defaultValue now
5
+ * @returns The instant in UTC, as `YYYYMMDDTHHMMSSZ`.
6
+ * @example
7
+ * icsTimestamp(new Date("2026-07-14T08:05:09.123Z")); // => "20260714T080509Z"
8
+ */
9
+ export declare function icsTimestamp(date?: Date): string;
10
+ //# sourceMappingURL=icsTimestamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icsTimestamp.d.ts","sourceRoot":"","sources":["../../src/ics/icsTimestamp.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * An iCalendar UTC `DATE-TIME` (`YYYYMMDDTHHMMSSZ`), as `DTSTAMP` expects.
3
+ *
4
+ * @param date - The instant. @defaultValue now
5
+ * @returns The instant in UTC, as `YYYYMMDDTHHMMSSZ`.
6
+ * @example
7
+ * icsTimestamp(new Date("2026-07-14T08:05:09.123Z")); // => "20260714T080509Z"
8
+ */
9
+ export function icsTimestamp(date = new Date()) {
10
+ return `${date.toISOString().replace(/[-:]/g, "").slice(0, 15)}Z`;
11
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./escapeIcsText.js";
2
+ export * from "./foldIcsLine.js";
3
+ export * from "./icsTimestamp.js";
4
+ export * from "./toIcsDate.js";
5
+ export * from "./toIcsDateTime.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ics/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./escapeIcsText.js";
2
+ export * from "./foldIcsLine.js";
3
+ export * from "./icsTimestamp.js";
4
+ export * from "./toIcsDate.js";
5
+ export * from "./toIcsDateTime.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * An iCalendar `DATE` value (`YYYYMMDD`) from a `YYYY-MM-DD` calendar date,
3
+ * for all-day events (`DTSTART;VALUE=DATE:…`).
4
+ *
5
+ * @param isoDate - A calendar date, `YYYY-MM-DD`.
6
+ * @returns The date as `YYYYMMDD`.
7
+ * @example
8
+ * toIcsDate("2026-07-14"); // => "20260714"
9
+ */
10
+ export declare function toIcsDate(isoDate: string): string;
11
+ //# sourceMappingURL=toIcsDate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toIcsDate.d.ts","sourceRoot":"","sources":["../../src/ics/toIcsDate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * An iCalendar `DATE` value (`YYYYMMDD`) from a `YYYY-MM-DD` calendar date,
3
+ * for all-day events (`DTSTART;VALUE=DATE:…`).
4
+ *
5
+ * @param isoDate - A calendar date, `YYYY-MM-DD`.
6
+ * @returns The date as `YYYYMMDD`.
7
+ * @example
8
+ * toIcsDate("2026-07-14"); // => "20260714"
9
+ */
10
+ export function toIcsDate(isoDate) {
11
+ return isoDate.replace(/-/g, "");
12
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * An iCalendar floating local `DATE-TIME` value (`YYYYMMDDTHHMMSS`, no `Z`)
3
+ * from a calendar date and a wall-clock time — the calendar app shows it at
4
+ * that time in its own time zone.
5
+ *
6
+ * @param isoDate - A calendar date, `YYYY-MM-DD`.
7
+ * @param time - A time, `H:MM` or `HH:MM`.
8
+ * @returns The date-time as `YYYYMMDDTHHMMSS`.
9
+ * @example
10
+ * toIcsDateTime("2026-07-14", "6:30"); // => "20260714T063000"
11
+ */
12
+ export declare function toIcsDateTime(isoDate: string, time: string): string;
13
+ //# sourceMappingURL=toIcsDateTime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toIcsDateTime.d.ts","sourceRoot":"","sources":["../../src/ics/toIcsDateTime.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnE"}
@@ -0,0 +1,16 @@
1
+ import { toIcsDate } from "./toIcsDate.js";
2
+ /**
3
+ * An iCalendar floating local `DATE-TIME` value (`YYYYMMDDTHHMMSS`, no `Z`)
4
+ * from a calendar date and a wall-clock time — the calendar app shows it at
5
+ * that time in its own time zone.
6
+ *
7
+ * @param isoDate - A calendar date, `YYYY-MM-DD`.
8
+ * @param time - A time, `H:MM` or `HH:MM`.
9
+ * @returns The date-time as `YYYYMMDDTHHMMSS`.
10
+ * @example
11
+ * toIcsDateTime("2026-07-14", "6:30"); // => "20260714T063000"
12
+ */
13
+ export function toIcsDateTime(isoDate, time) {
14
+ const [hours = "0", minutes = "0"] = time.split(":");
15
+ return `${toIcsDate(isoDate)}T${hours.padStart(2, "0")}${minutes.padStart(2, "0")}00`;
16
+ }
package/dist/index.d.ts CHANGED
@@ -4,10 +4,11 @@ export * from "./classes/index.js";
4
4
  export * from "./csv/index.js";
5
5
  export * from "./date/index.js";
6
6
  export * from "./files/index.js";
7
+ export * from "./function/index.js";
8
+ export * from "./ics/index.js";
7
9
  export * from "./id/index.js";
8
10
  export * from "./image/index.js";
9
11
  export * from "./markdown/index.js";
10
- export * from "./maths/index.js";
11
12
  export * from "./number/index.js";
12
13
  export * from "./object/index.js";
13
14
  export * from "./string/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -9,10 +9,11 @@ export * from "./classes/index.js";
9
9
  export * from "./csv/index.js";
10
10
  export * from "./date/index.js";
11
11
  export * from "./files/index.js";
12
+ export * from "./function/index.js";
13
+ export * from "./ics/index.js";
12
14
  export * from "./id/index.js";
13
15
  export * from "./image/index.js";
14
16
  export * from "./markdown/index.js";
15
- export * from "./maths/index.js";
16
17
  export * from "./number/index.js";
17
18
  export * from "./object/index.js";
18
19
  export * from "./string/index.js";
@@ -1,8 +1,3 @@
1
- /**
2
- * Calculates the average of all numbers in an array.
3
- *
4
- * @param {number[]} arr - The array of numbers to average.
5
- * @return {number} The average of all numbers in the array.
6
- */
7
- export declare function avg(arr: number[]): number;
1
+ /** @deprecated Import from `@forthtilliath/ts-kit/number/average` instead. */
2
+ export { avg } from "../number/average.js";
8
3
  //# sourceMappingURL=average.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"average.d.ts","sourceRoot":"","sources":["../../src/maths/average.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzC"}
1
+ {"version":3,"file":"average.d.ts","sourceRoot":"","sources":["../../src/maths/average.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC"}
@@ -1,10 +1,4 @@
1
- import { sum } from "./sum.js";
2
- /**
3
- * Calculates the average of all numbers in an array.
4
- *
5
- * @param {number[]} arr - The array of numbers to average.
6
- * @return {number} The average of all numbers in the array.
7
- */
8
- export function avg(arr) {
9
- return sum(arr) / arr.length;
10
- }
1
+ // `maths/` duplicated `number/`: `avg` moved there. Re-exported so the
2
+ // `@forthtilliath/ts-kit/maths/average` deep import keeps working.
3
+ /** @deprecated Import from `@forthtilliath/ts-kit/number/average` instead. */
4
+ export { avg } from "../number/average.js";
@@ -1,8 +1,3 @@
1
- /**
2
- * Calculates the sum of all numbers in an array.
3
- *
4
- * @param {number[]} arr - The array of numbers to sum.
5
- * @return {number} The sum of all numbers in the array.
6
- */
7
- export declare function sum(arr: number[]): number;
1
+ /** @deprecated Import from `@forthtilliath/ts-kit/number/sum` instead. */
2
+ export { sum } from "../number/sum.js";
8
3
  //# sourceMappingURL=sum.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sum.d.ts","sourceRoot":"","sources":["../../src/maths/sum.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzC"}
1
+ {"version":3,"file":"sum.d.ts","sourceRoot":"","sources":["../../src/maths/sum.ts"],"names":[],"mappings":"AAGA,0EAA0E;AAC1E,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/maths/sum.js CHANGED
@@ -1,9 +1,4 @@
1
- /**
2
- * Calculates the sum of all numbers in an array.
3
- *
4
- * @param {number[]} arr - The array of numbers to sum.
5
- * @return {number} The sum of all numbers in the array.
6
- */
7
- export function sum(arr) {
8
- return arr.reduce((total, n) => total + n, 0);
9
- }
1
+ // `maths/` duplicated `number/`: `sum` moved there. Re-exported so the
2
+ // `@forthtilliath/ts-kit/maths/sum` deep import keeps working.
3
+ /** @deprecated Import from `@forthtilliath/ts-kit/number/sum` instead. */
4
+ export { sum } from "../number/sum.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Calculates the average (arithmetic mean) of all numbers in an array.
3
+ *
4
+ * @param arr - The numbers to average.
5
+ * @returns The average, or `NaN` for an empty array — there's no meaningful
6
+ * average of nothing, and `0` would be indistinguishable from a real one
7
+ * (same as lodash's `mean`). Check `arr.length` first if you need a fallback.
8
+ * @example
9
+ * avg([1, 2, 3]); // => 2
10
+ * avg([]); // => NaN
11
+ */
12
+ export declare function avg(arr: readonly number[]): number;
13
+ //# sourceMappingURL=average.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"average.d.ts","sourceRoot":"","sources":["../../src/number/average.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAElD"}
@@ -0,0 +1,15 @@
1
+ import { sum } from "./sum.js";
2
+ /**
3
+ * Calculates the average (arithmetic mean) of all numbers in an array.
4
+ *
5
+ * @param arr - The numbers to average.
6
+ * @returns The average, or `NaN` for an empty array — there's no meaningful
7
+ * average of nothing, and `0` would be indistinguishable from a real one
8
+ * (same as lodash's `mean`). Check `arr.length` first if you need a fallback.
9
+ * @example
10
+ * avg([1, 2, 3]); // => 2
11
+ * avg([]); // => NaN
12
+ */
13
+ export function avg(arr) {
14
+ return sum(arr) / arr.length;
15
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"formatBytes.d.ts","sourceRoot":"","sources":["../../src/number/formatBytes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAW/D"}
1
+ {"version":3,"file":"formatBytes.d.ts","sourceRoot":"","sources":["../../src/number/formatBytes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAkB/D"}
@@ -12,8 +12,12 @@ const UNITS = ["B", "KB", "MB", "GB", "TB", "PB"];
12
12
  export function formatBytes(bytes, decimals = 2) {
13
13
  if (bytes === 0)
14
14
  return "0 B";
15
- const exponent = Math.min(Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024)), UNITS.length - 1);
15
+ // Clamped at 0 so a fraction of a byte stays in "B" (a negative exponent
16
+ // has no unit).
17
+ const exponent = Math.max(0, Math.min(Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024)), UNITS.length - 1));
16
18
  const value = bytes / 1024 ** exponent;
17
- const formatted = value.toFixed(Math.max(decimals, 0)).replace(/\.?0+$/, "");
19
+ // Round-tripping through Number drops trailing decimal zeros ("1.50" →
20
+ // "1.5") without touching the integer part ("100" stays "100").
21
+ const formatted = String(Number(value.toFixed(Math.max(decimals, 0))));
18
22
  return `${formatted} ${UNITS[exponent]}`;
19
23
  }
@@ -1,14 +1,3 @@
1
- /**
2
- * Formats a number as a raw value (no unit suffix) for use in a spreadsheet
3
- * (sum, average...), with a comma decimal separator as expected by
4
- * French-locale spreadsheets instead of JS's dot.
5
- *
6
- * @param value - The number to format.
7
- * @param decimals - Number of decimal places. Defaults to `1`.
8
- * @returns The formatted number, comma as decimal separator.
9
- * @example
10
- * formatCsvNumber(1.5); // => "1,5"
11
- * formatCsvNumber(12.345, 2); // => "12,35"
12
- */
13
- export declare function formatCsvNumber(value: number, decimals?: number): string;
1
+ /** @deprecated Import from `@forthtilliath/ts-kit/csv/formatCsvNumber` instead. */
2
+ export { formatCsvNumber } from "../csv/formatCsvNumber.js";
14
3
  //# sourceMappingURL=formatCsvNumber.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatCsvNumber.d.ts","sourceRoot":"","sources":["../../src/number/formatCsvNumber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAEnE"}
1
+ {"version":3,"file":"formatCsvNumber.d.ts","sourceRoot":"","sources":["../../src/number/formatCsvNumber.ts"],"names":[],"mappings":"AAGA,mFAAmF;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC"}
@@ -1,15 +1,4 @@
1
- /**
2
- * Formats a number as a raw value (no unit suffix) for use in a spreadsheet
3
- * (sum, average...), with a comma decimal separator as expected by
4
- * French-locale spreadsheets instead of JS's dot.
5
- *
6
- * @param value - The number to format.
7
- * @param decimals - Number of decimal places. Defaults to `1`.
8
- * @returns The formatted number, comma as decimal separator.
9
- * @example
10
- * formatCsvNumber(1.5); // => "1,5"
11
- * formatCsvNumber(12.345, 2); // => "12,35"
12
- */
13
- export function formatCsvNumber(value, decimals = 1) {
14
- return value.toFixed(decimals).replace(".", ",");
15
- }
1
+ // CSV helpers now live together in `csv/`. Re-exported so the
2
+ // `@forthtilliath/ts-kit/number/formatCsvNumber` deep import keeps working.
3
+ /** @deprecated Import from `@forthtilliath/ts-kit/csv/formatCsvNumber` instead. */
4
+ export { formatCsvNumber } from "../csv/formatCsvNumber.js";
@@ -1,7 +1,9 @@
1
+ export * from "./average.js";
1
2
  export * from "./clamp.js";
2
3
  export * from "./formatBytes.js";
3
4
  export * from "./formatCsvNumber.js";
4
5
  export * from "./formatDuration.js";
5
6
  export * from "./padNumber.js";
6
7
  export * from "./round.js";
8
+ export * from "./sum.js";
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/number/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/number/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC"}
@@ -1,6 +1,8 @@
1
+ export * from "./average.js";
1
2
  export * from "./clamp.js";
2
3
  export * from "./formatBytes.js";
3
4
  export * from "./formatCsvNumber.js";
4
5
  export * from "./formatDuration.js";
5
6
  export * from "./padNumber.js";
6
7
  export * from "./round.js";
8
+ export * from "./sum.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Calculates the sum of all numbers in an array.
3
+ *
4
+ * @param arr - The numbers to add up.
5
+ * @returns Their sum, `0` for an empty array.
6
+ * @example
7
+ * sum([1, 2, 3]); // => 6
8
+ */
9
+ export declare function sum(arr: readonly number[]): number;
10
+ //# sourceMappingURL=sum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sum.d.ts","sourceRoot":"","sources":["../../src/number/sum.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAElD"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Calculates the sum of all numbers in an array.
3
+ *
4
+ * @param arr - The numbers to add up.
5
+ * @returns Their sum, `0` for an empty array.
6
+ * @example
7
+ * sum([1, 2, 3]); // => 6
8
+ */
9
+ export function sum(arr) {
10
+ return arr.reduce((total, n) => total + n, 0);
11
+ }
@@ -1,12 +1,18 @@
1
1
  /**
2
2
  * Recursively compares two values for structural equality.
3
3
  *
4
+ * Both values must share the same prototype (a plain object never equals an
5
+ * array or a class instance). `Date`s are compared by timestamp, `RegExp`s by
6
+ * source and flags, `Map`s by keys (identity) then values (deeply), and
7
+ * `Set`s by membership (identity, like `Set.prototype.has`).
8
+ *
4
9
  * @param a - The first value.
5
10
  * @param b - The second value.
6
11
  * @returns `true` if `a` and `b` are deeply equal.
7
12
  * @example
8
13
  * deepEqual({ a: [1, 2] }, { a: [1, 2] }); // => true
9
14
  * deepEqual({ a: 1 }, { a: 2 }); // => false
15
+ * deepEqual(new Date(0), new Date(0)); // => true
10
16
  */
11
17
  export declare function deepEqual(a: unknown, b: unknown): boolean;
12
18
  //# sourceMappingURL=deepEqual.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deepEqual.d.ts","sourceRoot":"","sources":["../../src/object/deepEqual.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAwBzD"}
1
+ {"version":3,"file":"deepEqual.d.ts","sourceRoot":"","sources":["../../src/object/deepEqual.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAwDzD"}
@@ -1,31 +1,65 @@
1
1
  /**
2
2
  * Recursively compares two values for structural equality.
3
3
  *
4
+ * Both values must share the same prototype (a plain object never equals an
5
+ * array or a class instance). `Date`s are compared by timestamp, `RegExp`s by
6
+ * source and flags, `Map`s by keys (identity) then values (deeply), and
7
+ * `Set`s by membership (identity, like `Set.prototype.has`).
8
+ *
4
9
  * @param a - The first value.
5
10
  * @param b - The second value.
6
11
  * @returns `true` if `a` and `b` are deeply equal.
7
12
  * @example
8
13
  * deepEqual({ a: [1, 2] }, { a: [1, 2] }); // => true
9
14
  * deepEqual({ a: 1 }, { a: 2 }); // => false
15
+ * deepEqual(new Date(0), new Date(0)); // => true
10
16
  */
11
17
  export function deepEqual(a, b) {
12
18
  if (Object.is(a, b))
13
19
  return true;
14
- if (typeof a !== typeof b || a === null || b === null)
20
+ if (typeof a !== "object" ||
21
+ typeof b !== "object" ||
22
+ a === null ||
23
+ b === null ||
24
+ Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
15
25
  return false;
16
- if (Array.isArray(a) || Array.isArray(b)) {
17
- if (!Array.isArray(a) || !Array.isArray(b))
26
+ }
27
+ if (a instanceof Date) {
28
+ return Object.is(a.getTime(), b.getTime());
29
+ }
30
+ if (a instanceof RegExp) {
31
+ const other = b;
32
+ return a.source === other.source && a.flags === other.flags;
33
+ }
34
+ if (a instanceof Map) {
35
+ const other = b;
36
+ if (a.size !== other.size)
18
37
  return false;
19
- if (a.length !== b.length)
38
+ for (const [key, value] of a) {
39
+ if (!other.has(key) || !deepEqual(value, other.get(key)))
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ if (a instanceof Set) {
45
+ const other = b;
46
+ if (a.size !== other.size)
20
47
  return false;
21
- return a.every((item, index) => deepEqual(item, b[index]));
48
+ for (const value of a) {
49
+ if (!other.has(value))
50
+ return false;
51
+ }
52
+ return true;
22
53
  }
23
- if (typeof a === "object" && typeof b === "object") {
24
- const aKeys = Object.keys(a);
25
- const bKeys = Object.keys(b);
26
- if (aKeys.length !== bKeys.length)
54
+ if (Array.isArray(a)) {
55
+ const other = b;
56
+ if (a.length !== other.length)
27
57
  return false;
28
- return aKeys.every((key) => deepEqual(a[key], b[key]));
58
+ return a.every((item, index) => deepEqual(item, other[index]));
29
59
  }
30
- return false;
60
+ const aKeys = Object.keys(a);
61
+ if (aKeys.length !== Object.keys(b).length)
62
+ return false;
63
+ return aKeys.every((key) => Object.prototype.hasOwnProperty.call(b, key) &&
64
+ deepEqual(a[key], b[key]));
31
65
  }