@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.
- package/ARCHITECTURE.md +229 -26
- package/README.md +64 -10
- package/dist/types/array.d.ts +13 -0
- package/dist/types/cache.d.ts +166 -0
- package/dist/types/chunk.d.ts +102 -0
- package/dist/types/color.d.ts +64 -0
- package/dist/types/convert/convert.d.ts +33 -0
- package/dist/types/convert/currency.d.ts +47 -0
- package/dist/types/convert/index.d.ts +9 -0
- package/dist/types/convert/registry.d.ts +55 -0
- package/dist/types/dates/civil.d.ts +146 -0
- package/dist/types/dates/duration.d.ts +83 -0
- package/dist/types/dates/format.d.ts +59 -0
- package/dist/types/dates/index.d.ts +4 -0
- package/dist/types/{dates.d.ts → dates/rfc3339.d.ts} +57 -3
- package/dist/types/errors.d.ts +77 -0
- package/dist/types/finance/amortization.d.ts +51 -0
- package/dist/types/finance/bond.d.ts +45 -0
- package/dist/types/finance/cashflow.d.ts +44 -0
- package/dist/types/finance/depreciation.d.ts +26 -0
- package/dist/types/finance/index.d.ts +16 -0
- package/dist/types/finance/indicators.d.ts +76 -0
- package/dist/types/finance/interest.d.ts +44 -0
- package/dist/types/finance/returns.d.ts +43 -0
- package/dist/types/finance/tvm.d.ts +50 -0
- package/dist/types/geo/angle.d.ts +4 -0
- package/dist/types/geo/bbox.d.ts +32 -0
- package/dist/types/geo/distance.d.ts +67 -0
- package/dist/types/geo/geohash.d.ts +63 -0
- package/dist/types/geo/geojson.d.ts +108 -0
- package/dist/types/geo/index-tree.d.ts +52 -0
- package/dist/types/geo/index.d.ts +11 -0
- package/dist/types/geo/mercator.d.ts +57 -0
- package/dist/types/geo/predicates.d.ts +44 -0
- package/dist/types/geo/ring.d.ts +80 -0
- package/dist/types/geo/simplify.d.ts +31 -0
- package/dist/types/geo/valid.d.ts +24 -0
- package/dist/types/geo/wkt.d.ts +16 -0
- package/dist/types/math/float64.d.ts +126 -3
- package/dist/types/math/format.d.ts +58 -0
- package/dist/types/math/index.d.ts +5 -0
- package/dist/types/math/mat4.d.ts +60 -0
- package/dist/types/math/project.d.ts +54 -0
- package/dist/types/math/solve.d.ts +67 -0
- package/dist/types/math/word.d.ts +82 -0
- package/dist/types/message.d.ts +52 -0
- package/dist/types/object.d.ts +151 -0
- package/dist/types/scan.d.ts +30 -0
- package/dist/types/schema.d.ts +24 -0
- package/dist/types/string.d.ts +96 -0
- package/dist/types/text/base64.d.ts +6 -4
- package/dist/types/text/email.d.ts +0 -1
- package/dist/types/text/host.d.ts +39 -8
- package/dist/types/text/i18n.d.ts +13 -1
- package/dist/types/text/index.d.ts +1 -0
- package/dist/types/text/misc.d.ts +15 -1
- package/dist/types/text/punycode.d.ts +45 -85
- package/docs/CONVERT.md +45 -0
- package/docs/DATES.md +78 -0
- package/docs/FINANCE.md +59 -0
- package/docs/GEO.md +114 -0
- package/docs/MATH.md +75 -0
- package/package.json +57 -4
- package/src/array.js +16 -0
- package/src/cache.js +206 -0
- package/src/chunk.js +159 -0
- package/src/color.js +125 -0
- package/src/convert/convert.js +59 -0
- package/src/convert/currency.js +74 -0
- package/src/convert/index.js +11 -0
- package/src/convert/registry.js +213 -0
- package/src/dates/civil.js +360 -0
- package/src/dates/duration.js +225 -0
- package/src/dates/format.js +238 -0
- package/src/dates/index.js +30 -0
- package/src/dates/rfc3339.js +621 -0
- package/src/errors.js +102 -0
- package/src/finance/amortization.js +50 -0
- package/src/finance/bond.js +100 -0
- package/src/finance/cashflow.js +122 -0
- package/src/finance/depreciation.js +54 -0
- package/src/finance/index.js +27 -0
- package/src/finance/indicators.js +207 -0
- package/src/finance/interest.js +63 -0
- package/src/finance/returns.js +90 -0
- package/src/finance/tvm.js +94 -0
- package/src/function.js +0 -2
- package/src/geo/angle.js +12 -0
- package/src/geo/bbox.js +80 -0
- package/src/geo/distance.js +155 -0
- package/src/geo/geohash.js +224 -0
- package/src/geo/geojson.js +361 -0
- package/src/geo/index-tree.js +294 -0
- package/src/geo/index.js +52 -0
- package/src/geo/mercator.js +124 -0
- package/src/geo/predicates.js +313 -0
- package/src/geo/ring.js +182 -0
- package/src/geo/simplify.js +130 -0
- package/src/geo/valid.js +142 -0
- package/src/geo/wkt.js +262 -0
- package/src/math/float64.js +228 -11
- package/src/math/format.js +157 -0
- package/src/math/index.js +5 -0
- package/src/math/mat4.js +131 -0
- package/src/math/project.js +49 -0
- package/src/math/solve.js +112 -0
- package/src/math/word.js +177 -0
- package/src/message.js +119 -0
- package/src/object.js +329 -0
- package/src/scan.js +42 -0
- package/src/schema.js +37 -0
- package/src/string.js +189 -0
- package/src/text/base64.js +15 -52
- package/src/text/email.js +0 -5
- package/src/text/host.js +515 -140
- package/src/text/i18n.js +13 -6
- package/src/text/identifiers.js +1 -1
- package/src/text/index.js +1 -0
- package/src/text/misc.js +81 -5
- package/src/text/punycode.js +235 -323
- 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
|
+
}
|
package/src/text/base64.js
CHANGED
|
@@ -1,54 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// Base64
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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);
|