@jarenjs/core 0.9.2 → 0.34.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 (121) hide show
  1. package/ARCHITECTURE.md +229 -26
  2. package/README.md +64 -10
  3. package/dist/types/array.d.ts +13 -0
  4. package/dist/types/cache.d.ts +166 -0
  5. package/dist/types/chunk.d.ts +102 -0
  6. package/dist/types/color.d.ts +64 -0
  7. package/dist/types/convert/convert.d.ts +33 -0
  8. package/dist/types/convert/currency.d.ts +47 -0
  9. package/dist/types/convert/index.d.ts +9 -0
  10. package/dist/types/convert/registry.d.ts +55 -0
  11. package/dist/types/dates/civil.d.ts +146 -0
  12. package/dist/types/dates/duration.d.ts +83 -0
  13. package/dist/types/dates/format.d.ts +59 -0
  14. package/dist/types/dates/index.d.ts +4 -0
  15. package/dist/types/{dates.d.ts → dates/rfc3339.d.ts} +57 -3
  16. package/dist/types/errors.d.ts +77 -0
  17. package/dist/types/finance/amortization.d.ts +51 -0
  18. package/dist/types/finance/bond.d.ts +45 -0
  19. package/dist/types/finance/cashflow.d.ts +44 -0
  20. package/dist/types/finance/depreciation.d.ts +26 -0
  21. package/dist/types/finance/index.d.ts +16 -0
  22. package/dist/types/finance/indicators.d.ts +76 -0
  23. package/dist/types/finance/interest.d.ts +44 -0
  24. package/dist/types/finance/returns.d.ts +43 -0
  25. package/dist/types/finance/tvm.d.ts +50 -0
  26. package/dist/types/geo/angle.d.ts +4 -0
  27. package/dist/types/geo/bbox.d.ts +32 -0
  28. package/dist/types/geo/distance.d.ts +67 -0
  29. package/dist/types/geo/geohash.d.ts +63 -0
  30. package/dist/types/geo/geojson.d.ts +108 -0
  31. package/dist/types/geo/index-tree.d.ts +52 -0
  32. package/dist/types/geo/index.d.ts +11 -0
  33. package/dist/types/geo/mercator.d.ts +57 -0
  34. package/dist/types/geo/predicates.d.ts +44 -0
  35. package/dist/types/geo/ring.d.ts +80 -0
  36. package/dist/types/geo/simplify.d.ts +31 -0
  37. package/dist/types/geo/valid.d.ts +24 -0
  38. package/dist/types/geo/wkt.d.ts +16 -0
  39. package/dist/types/math/float64.d.ts +126 -3
  40. package/dist/types/math/format.d.ts +58 -0
  41. package/dist/types/math/index.d.ts +5 -0
  42. package/dist/types/math/mat4.d.ts +60 -0
  43. package/dist/types/math/project.d.ts +54 -0
  44. package/dist/types/math/solve.d.ts +67 -0
  45. package/dist/types/math/word.d.ts +82 -0
  46. package/dist/types/message.d.ts +52 -0
  47. package/dist/types/object.d.ts +151 -0
  48. package/dist/types/scan.d.ts +30 -0
  49. package/dist/types/schema.d.ts +24 -0
  50. package/dist/types/string.d.ts +96 -0
  51. package/dist/types/text/base64.d.ts +6 -4
  52. package/dist/types/text/email.d.ts +0 -1
  53. package/dist/types/text/host.d.ts +39 -8
  54. package/dist/types/text/i18n.d.ts +13 -1
  55. package/dist/types/text/index.d.ts +1 -0
  56. package/dist/types/text/misc.d.ts +15 -1
  57. package/dist/types/text/punycode.d.ts +45 -85
  58. package/docs/CONVERT.md +45 -0
  59. package/docs/DATES.md +78 -0
  60. package/docs/FINANCE.md +59 -0
  61. package/docs/GEO.md +114 -0
  62. package/docs/MATH.md +75 -0
  63. package/package.json +57 -4
  64. package/src/array.js +16 -0
  65. package/src/cache.js +206 -0
  66. package/src/chunk.js +159 -0
  67. package/src/color.js +125 -0
  68. package/src/convert/convert.js +59 -0
  69. package/src/convert/currency.js +74 -0
  70. package/src/convert/index.js +11 -0
  71. package/src/convert/registry.js +213 -0
  72. package/src/dates/civil.js +360 -0
  73. package/src/dates/duration.js +225 -0
  74. package/src/dates/format.js +238 -0
  75. package/src/dates/index.js +30 -0
  76. package/src/dates/rfc3339.js +621 -0
  77. package/src/errors.js +102 -0
  78. package/src/finance/amortization.js +50 -0
  79. package/src/finance/bond.js +100 -0
  80. package/src/finance/cashflow.js +122 -0
  81. package/src/finance/depreciation.js +54 -0
  82. package/src/finance/index.js +27 -0
  83. package/src/finance/indicators.js +207 -0
  84. package/src/finance/interest.js +63 -0
  85. package/src/finance/returns.js +90 -0
  86. package/src/finance/tvm.js +94 -0
  87. package/src/function.js +0 -2
  88. package/src/geo/angle.js +12 -0
  89. package/src/geo/bbox.js +80 -0
  90. package/src/geo/distance.js +155 -0
  91. package/src/geo/geohash.js +224 -0
  92. package/src/geo/geojson.js +361 -0
  93. package/src/geo/index-tree.js +294 -0
  94. package/src/geo/index.js +52 -0
  95. package/src/geo/mercator.js +124 -0
  96. package/src/geo/predicates.js +313 -0
  97. package/src/geo/ring.js +182 -0
  98. package/src/geo/simplify.js +130 -0
  99. package/src/geo/valid.js +142 -0
  100. package/src/geo/wkt.js +262 -0
  101. package/src/math/float64.js +228 -11
  102. package/src/math/format.js +157 -0
  103. package/src/math/index.js +5 -0
  104. package/src/math/mat4.js +131 -0
  105. package/src/math/project.js +49 -0
  106. package/src/math/solve.js +112 -0
  107. package/src/math/word.js +177 -0
  108. package/src/message.js +119 -0
  109. package/src/object.js +329 -0
  110. package/src/scan.js +42 -0
  111. package/src/schema.js +37 -0
  112. package/src/string.js +189 -0
  113. package/src/text/base64.js +15 -52
  114. package/src/text/email.js +0 -5
  115. package/src/text/host.js +515 -140
  116. package/src/text/i18n.js +13 -6
  117. package/src/text/identifiers.js +1 -1
  118. package/src/text/index.js +1 -0
  119. package/src/text/misc.js +81 -5
  120. package/src/text/punycode.js +235 -323
  121. package/src/dates.js +0 -371
package/src/string.js CHANGED
@@ -119,6 +119,7 @@ export function isAsciiString(str) {
119
119
  // Intl.Segmenter can be skipped.
120
120
  // (U+0600-0605, 06DD, 070F, 0890, 0891, 08E2, 110BD, 110CD are the
121
121
  // Prepended_Concatenation_Mark set, which V8 does not expose as \p{...})
122
+ // eslint-disable-next-line no-misleading-character-class -- intentional grapheme-cluster detector: this char class deliberately mixes combining marks, join-controls, emoji modifiers, regional indicators and prepended concatenation marks to fast-path the Intl.Segmenter skip.
122
123
  const COMPLEX_GRAPHEME_REGEX = /[\r\p{M}\p{Join_Control}\p{Emoji_Modifier}\p{Regional_Indicator}\u0600-\u0605\u06DD\u070F\u0890\u0891\u08E2\uFE00-\uFE0F\u1100-\u11FF\uA960-\uA97F\uD7B0-\uD7FF\u{110BD}\u{110CD}\u{E0000}-\u{E007F}]/u;
123
124
 
124
125
  /**
@@ -149,6 +150,22 @@ export function getStringLength(str, useGrapheme = false) {
149
150
  return len;
150
151
  }
151
152
 
153
+ /**
154
+ * Count occurrences of a UTF-16 code unit in a slice of a string.
155
+ * @param {string} str - The string to scan
156
+ * @param {number} code - The char code to count
157
+ * @param {number} [start] - Inclusive start offset (defaults to 0)
158
+ * @param {number} [end] - Exclusive end offset (defaults to full length)
159
+ * @returns {number} Number of occurrences in [start, end)
160
+ */
161
+ export function countCharCode(str, code, start = 0, end = str.length) {
162
+ let n = 0;
163
+ for (let i = start; i < end; ++i)
164
+ if (str.charCodeAt(i) === code)
165
+ n++;
166
+ return n;
167
+ }
168
+
152
169
  /**
153
170
  * Count the Unicode code points of a string (surrogate-pair aware;
154
171
  * a lone surrogate counts as one code point).
@@ -170,6 +187,72 @@ export function countCodePoints(str) {
170
187
  return count;
171
188
  }
172
189
 
190
+ /**
191
+ * Decode a string into its Unicode code points (surrogate-pair aware).
192
+ *
193
+ * `Array.from(str).map(c => c.codePointAt(0))` computes the same thing
194
+ * but allocates a string per character on the way; this reads the code
195
+ * units directly. An unpaired surrogate is kept as its own code point
196
+ * rather than replaced, so the result round-trips through
197
+ * {@link fromCodePoints} and a caller validating text can see the lone
198
+ * surrogate and reject it.
199
+ *
200
+ * @param {string} str - The string to decode
201
+ * @returns {number[]} The code points, in order
202
+ */
203
+ export function toCodePoints(str) {
204
+ const slen = str.length;
205
+ const out = [];
206
+ for (let i = 0; i < slen; i++) {
207
+ const c = str.charCodeAt(i);
208
+ if (c >= 0xD800 && c <= 0xDBFF && i + 1 < slen) {
209
+ const d = str.charCodeAt(i + 1);
210
+ if (d >= 0xDC00 && d <= 0xDFFF) {
211
+ out.push((c - 0xD800) * 0x400 + (d - 0xDC00) + 0x10000);
212
+ i++;
213
+ continue;
214
+ }
215
+ }
216
+ out.push(c);
217
+ }
218
+ return out;
219
+ }
220
+
221
+ /**
222
+ * Fewer code points than this and appending one at a time beats
223
+ * spreading the array into `String.fromCodePoint`, whose argument-list
224
+ * setup then costs more than the concatenations it saves. Measured
225
+ * crossover; the two are within a few percent either side of it.
226
+ */
227
+ const CODE_POINT_SPREAD_MIN = 8;
228
+
229
+ /** Code points per spread, so a large array cannot overflow the stack. */
230
+ const CODE_POINT_SPREAD_CHUNK = 4096;
231
+
232
+ /**
233
+ * Build a string from Unicode code points - the inverse of
234
+ * {@link toCodePoints}.
235
+ *
236
+ * @param {number[]} codePoints - The code points to encode
237
+ * @returns {string} The resulting string
238
+ */
239
+ export function fromCodePoints(codePoints) {
240
+ const len = codePoints.length;
241
+ if (len < CODE_POINT_SPREAD_MIN) {
242
+ let out = '';
243
+ for (let i = 0; i < len; i++)
244
+ out += String.fromCodePoint(codePoints[i]);
245
+ return out;
246
+ }
247
+ if (len <= CODE_POINT_SPREAD_CHUNK)
248
+ return String.fromCodePoint(...codePoints);
249
+
250
+ let out = '';
251
+ for (let i = 0; i < len; i += CODE_POINT_SPREAD_CHUNK)
252
+ out += String.fromCodePoint(...codePoints.slice(i, i + CODE_POINT_SPREAD_CHUNK));
253
+ return out;
254
+ }
255
+
173
256
  /**
174
257
  * Compare two strings by Unicode scalar values (code points), per
175
258
  * RFC 9535 section 2.3.5.2.2. This differs from JavaScript's native
@@ -192,3 +275,109 @@ export function compareCodePoints(a, b) {
192
275
  // differing code units at i can never decode to equal code points
193
276
  return a.codePointAt(i) < b.codePointAt(i) ? -1 : 1;
194
277
  }
278
+
279
+ /** FNV-1a 32-bit offset basis — the seed a fresh hash starts from. */
280
+ export const FNV1A_OFFSET_BASIS = 0x811c9dc5;
281
+
282
+ /**
283
+ * FNV-1a 32-bit hash of a string, as an unsigned 32-bit number. Not
284
+ * cryptographic — a stable, fast content fingerprint.
285
+ *
286
+ * Pass `seed` to continue an existing hash, which is what lets a caller
287
+ * fold a chunk stream or walk a tree without concatenating the pieces
288
+ * first: `fnv1a(b, fnv1a(a))` equals `fnv1a(a + b)`. Callers that just want
289
+ * a fingerprint string should use {@link hashContent} instead, so the whole
290
+ * suite agrees on one encoding.
291
+ *
292
+ * @param {string} str
293
+ * @param {number} [seed] running hash to continue, unsigned 32-bit
294
+ * @returns {number} unsigned 32-bit hash
295
+ */
296
+ export function fnv1a(str, seed = FNV1A_OFFSET_BASIS) {
297
+ let hash = seed;
298
+ for (let i = 0; i < str.length; i++) {
299
+ hash ^= str.charCodeAt(i);
300
+ hash = (hash * 0x01000193) >>> 0;
301
+ }
302
+ return hash;
303
+ }
304
+
305
+ /**
306
+ * FNV-1a 32-bit hash of a string, returned as an unsigned base-36 string
307
+ * (at most 7 chars). Not cryptographic — a stable, fast content
308
+ * fingerprint for cache keys and reconciliation keys. The suite's single
309
+ * content-hash primitive: equal content produces the same fingerprint
310
+ * (and therefore the same vnode `key`/memo key) everywhere downstream, so
311
+ * do NOT invent a second hash — {@link fnv1a} is the one mixing step.
312
+ *
313
+ * @param {string} str
314
+ * @returns {string}
315
+ */
316
+ export function hashContent(str) {
317
+ return fnv1a(str).toString(36);
318
+ }
319
+
320
+ /**
321
+ * Convert a camelCase identifier to kebab-case by inserting a hyphen
322
+ * before each ASCII uppercase letter and lower-casing it
323
+ * (`fontFamily` → `font-family`). Leaves already-hyphenated or
324
+ * all-lowercase input unchanged.
325
+ *
326
+ * @param {string} s
327
+ * @returns {string}
328
+ */
329
+ export function kebabCase(s) {
330
+ return s.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
331
+ }
332
+
333
+ /** One code point that a slug keeps: a letter, a digit, or a combining
334
+ * mark (which belongs to the letter in front of it). Everything else
335
+ * outside ASCII is punctuation, a symbol or an emoji, and is dropped. */
336
+ const SLUG_KEEP = /[\p{L}\p{N}\p{M}]/u;
337
+
338
+ /**
339
+ * Convert heading text into a URL fragment the way GitHub does, so one
340
+ * committed document anchors identically on GitHub, in an editor preview
341
+ * and in a renderer that uses this: lower-case, drop punctuation and
342
+ * symbols, and turn each whitespace character into a hyphen.
343
+ *
344
+ * `-` and `_` survive, as do non-ASCII letters and digits
345
+ * (`Ünicode Wörks` → `ünicode-wörks`); a `§` or an em dash does not,
346
+ * because it is a symbol rather than a letter. Text that reduces to
347
+ * nothing (`***`) yields `''` — what an empty slug means is the caller's
348
+ * decision, not this function's.
349
+ *
350
+ * This is the suite's ONLY slug implementation: a second one would drift
351
+ * from the first and break the promise above. Whitespace is converted one
352
+ * character at a time, not per run, because that is what GitHub does and
353
+ * the resulting `--` is part of the fragment a reader may already have
354
+ * bookmarked.
355
+ *
356
+ * @example
357
+ * slugify('Hello, World!'); // 'hello-world'
358
+ * slugify('§ 3.1 — Setup'); // '-31--setup'
359
+ *
360
+ * @param {string} text
361
+ * @returns {string}
362
+ */
363
+ export function slugify(text) {
364
+ let out = '';
365
+ for (let i = 0; i < text.length; i++) {
366
+ const code = text.charCodeAt(i);
367
+ if (code < 0x80) {
368
+ if (code >= 0x61 && code <= 0x7a) out += text[i]; // a-z
369
+ else if (code >= 0x41 && code <= 0x5a) out += String.fromCharCode(code + 32); // A-Z
370
+ else if (code >= 0x30 && code <= 0x39) out += text[i]; // 0-9
371
+ else if (code === 0x2d || code === 0x5f) out += text[i]; // - _
372
+ else if (code === 0x20 || (code >= 0x09 && code <= 0x0d)) out += '-';
373
+ continue;
374
+ }
375
+ // Outside ASCII a slug decision needs the whole code point, so an
376
+ // astral pair is read (and skipped over) as one character.
377
+ const point = /** @type {number} */ (text.codePointAt(i));
378
+ const char = String.fromCodePoint(point);
379
+ if (point > 0xffff) i++;
380
+ if (SLUG_KEEP.test(char)) out += char.toLowerCase();
381
+ }
382
+ return out;
383
+ }
@@ -1,54 +1,17 @@
1
- const CONST_REGEXP_BASE64 = /^(?:[a-zA-Z0-9+\/]{4})*(?:|(?:[a-zA-Z0-9+\/]{3}=)|(?:[a-zA-Z0-9+\/]{2}==)|(?:[a-zA-Z0-9+\/]{1}===))$/;
2
- export function isValidBase64Full(str) {
3
- return CONST_REGEXP_BASE64.test(str);
4
- }
5
-
6
- // Base64 validation regex (RFC 4648)
7
- const BASE64_REGEX_SHORT = /^[A-Za-z0-9+/]*={0,2}$/;
8
-
9
- export function isValidBase64Old(str) {
10
- // Check length is valid for base64 (multiple of 4)
11
- if (str.length % 4 !== 0) return false;
12
- // Check characters are valid base64
13
- if (!BASE64_REGEX_SHORT.test(str)) return false;
14
- return true;
15
- }
16
-
17
- const BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})?$/;
18
-
1
+ //@ts-check
2
+
3
+ // Base64 (RFC 4648 section 4): groups of four characters from the
4
+ // standard alphabet, with the final group optionally padded to four by
5
+ // one or two "=". Three padding characters, or padding anywhere but at
6
+ // the end, are not Base64 - which is what separates this from the
7
+ // length-and-charset approximations it is easy to reach for.
8
+ const CONST_REGEXP_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})?$/;
9
+
10
+ /**
11
+ * Validate a Base64-encoded string (RFC 4648).
12
+ * @param {string} str - The candidate encoding
13
+ * @returns {boolean} True when the string is well-formed Base64
14
+ */
19
15
  export function isValidBase64(str) {
20
- return BASE64_REGEX.test(str);
21
- }
22
-
23
- // Fast base64 validation - inline for performance
24
- // Valid base64 chars: A-Z (65-90), a-z (97-122), 0-9 (48-57), + (43), / (47)
25
- export function isValidBase64Fast(str) {
26
- const len = str.length;
27
- if (len === 0) return true;
28
-
29
- // Check length is valid for base64 (must be multiple of 4)
30
- if (len % 4 !== 0) return false;
31
-
32
- // Check all characters are valid base64 (A-Z, a-z, 0-9, +, /, =)
33
- for (let i = 0; i < len; i++) {
34
- const code = str.charCodeAt(i);
35
- // A-Z, a-z, 0-9, +, /, =
36
- if (!((code >= 65 && code <= 90) ||
37
- (code >= 97 && code <= 122) ||
38
- (code >= 48 && code <= 57) ||
39
- code === 43 || code === 47 || code === 61)) {
40
- return false;
41
- }
42
- }
43
-
44
- // Check padding rules
45
- // Valid endings: xxxx (no padding), xxx= (1 padding), xx== (2 padding)
46
- const c1 = str.charCodeAt(len - 2);
47
- const c2 = str.charCodeAt(len - 1);
48
-
49
- // If second to last is =, last must also be =
50
- if (c1 === 61 && c2 !== 61) return false;
51
-
52
- return true;
16
+ return CONST_REGEXP_BASE64.test(str);
53
17
  }
54
-
package/src/text/email.js CHANGED
@@ -50,11 +50,6 @@ export function isValidEmail(str) {
50
50
  && isValidEmailDomain(str.slice(at + 1));
51
51
  }
52
52
 
53
- const CONST_REGEXP_EMAIL_FULL = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
54
- export function isValidEmailFull(str) {
55
- return CONST_REGEXP_EMAIL_FULL.test(str);
56
- }
57
-
58
53
  const CONST_REGEXP_IDNEMAIL = /^[^@]+@[^@]+\.[^@]+$/;
59
54
  export function isValidIdnEmail(str) {
60
55
  return CONST_REGEXP_IDNEMAIL.test(str);