@jarenjs/core 0.9.2 → 0.34.2

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/docs/DATES.md ADDED
@@ -0,0 +1,78 @@
1
+ # `@jarenjs/core/dates`
2
+
3
+ The calendar kernel. Two constraints shape everything in it. **There is
4
+ no date type**: a date is an RFC 3339 *string* (lexical, interchange) or
5
+ *epoch milliseconds* (arithmetic) — both already JSON items — never a
6
+ wrapper object. And **there is no `now`**: the current instant enters as
7
+ data (an app effect, an external), never as a hidden read, so everything
8
+ here is pure and cacheable. Locale names live in `@jarenjs/locales`; the
9
+ kernel itself is locale-free. Import the barrel (`@jarenjs/core/dates`)
10
+ or a single module.
11
+
12
+ ## Lexical layer — `rfc3339.js`
13
+
14
+ Validation: `isDateOnlyRFC3339`, `isTimeOnlyRFC3339`,
15
+ `isDateTimeRFC3339` (strict RFC 3339 — an offset is required), and the
16
+ lenient ISO twins `isValidISODateTime` / `isValidISOTime` (offset
17
+ optional — what HTML's `datetime-local` control emits). Leap years and
18
+ month lengths are checked, not just shape.
19
+
20
+ Parsing: `parseRFC3339Parts(str)` → a flat **parts** record
21
+ `{year, month, day, hours, minutes, seconds, offset}`. Absent fields are
22
+ `-1` sentinels; `offset` is minutes east of UTC, `0` for `Z` and `null`
23
+ when the string carried none. The inverse is `formatRFC3339Parts(parts)`
24
+ (round-trips the offset). `epochOfRFC3339Parts(parts)` → epoch ms: a
25
+ date-only reads as UTC midnight, an offset shifts to its instant, and a
26
+ time-only has **no** instant — it returns `NaN` rather than inventing a
27
+ day. `getDateTypeOf*` variants produce a `Date` for callers that want
28
+ one; `isDateType` / `isDateishType` classify values.
29
+
30
+ ## Calendar arithmetic — `civil.js`
31
+
32
+ Proleptic Gregorian math over **integer day numbers** (Howard Hinnant's
33
+ civil-days algorithm — no `Date`, no timezone, no DST edge):
34
+ `daysFromCivil(y, m, d)` ⇄ `civilFromDays(z)`, `weekdayFromDays` /
35
+ `isoWeekdayFromDays`, `isLeapYear`, `daysInMonth`, `dayOfYear`,
36
+ `quarterOfYear`, `isoWeekOfYear` (returns `{year, week}` — the ISO week
37
+ year is not the calendar year at the edges), `partsFromEpoch(ms, offset)`.
38
+
39
+ `addToParts(parts, amount, unit)`, `startOfParts`, `endOfParts` over
40
+ `DATE_UNITS` (`year`…`millisecond`, plus `quarter` and `week`; weeks
41
+ start Monday, per ISO). **Month math clamps**: 31 Jan + 1 month =
42
+ 28 Feb, which is what makes add and diff behave as inverses in the
43
+ query operators built on this.
44
+
45
+ ## Durations — `duration.js`
46
+
47
+ `parseDuration('P3DT4H')` — ISO 8601 duration decomposition into
48
+ `{negative, years, months, weeks, days, hours, minutes, seconds}`
49
+ (the boolean twin `isValidDuration` lives in `rfc3339.js` with the
50
+ other format testers). The load-bearing distinction is
51
+ `isFixedDuration(parts)`: days and smaller are a fixed millisecond span
52
+ (`durationToMs`), while years and months are **calendar** units whose
53
+ length depends on where they land — those go through
54
+ `addDuration(dateParts, duration, sign)`, which applies the clamping
55
+ month math. `monthsBetween(from, to)` is the calendar diff.
56
+
57
+ ## Formatting — `format.js`
58
+
59
+ `compileDateFormat(pattern, names?)` compiles an
60
+ [LDML](https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns)
61
+ pattern (`'yyyy-MM-dd HH:mm'`, quoted literals included) into a
62
+ formatter **once**, instead of re-scanning the pattern per call — the
63
+ same two-stage shape as every other compiler in the suite. Numeric and
64
+ ISO tokens work bare; name tokens (`MMMM`, `EEE`, `a`) require a
65
+ `names` provider, which is where `@jarenjs/locales` plugs in — the
66
+ kernel ships no month names, so server-rendered output stays
67
+ byte-stable across Node/ICU versions.
68
+
69
+ ## Not here
70
+
71
+ `Temporal` is deliberately not a dependency (it is not in this repo's
72
+ Node ≥ 24 baseline); the string/number representation is exactly what
73
+ `Temporal.Instant.from()` consumes, so the kernel can delegate
74
+ internally later without a surface change. Relative-time phrasing and
75
+ month-name catalogs are `@jarenjs/locales`' job; `formatMinimum`/
76
+ `formatMaximum` bound comparison lives in `@jarenjs/formats`; time
77
+ *axes* (charts) and date *controls* (forms) consume this kernel from
78
+ their own packages.
@@ -0,0 +1,59 @@
1
+ # `@jarenjs/core/finance`
2
+
3
+ A first-class subpackage of finance & trading **formulas** — pure,
4
+ zero-dependency (imports only `@jarenjs/core/math`), reusable by any
5
+ package. The math lives here; orchestration (which variable to solve,
6
+ input forms, display) belongs to the consumer. Import the barrel
7
+ (`@jarenjs/core/finance`) or a single module (`@jarenjs/core/finance/tvm`).
8
+
9
+ Sign convention (TVM/cash-flow): money **in** is positive, money **out**
10
+ negative — the standard Excel / HP-12C model.
11
+
12
+ ## Time Value of Money — `tvm.js`
13
+
14
+ `pmt`, `pv`, `fv`, `nper`, `rate`. The five obey one identity; each solves
15
+ for its own variable. Rates are per period; `type` is 0 (ordinary
16
+ annuity, payment at period end) or 1 (annuity-due, begin). `rate` is
17
+ iterative (`math/solve`).
18
+
19
+ ## Cash flow — `cashflow.js`
20
+
21
+ `npv(rate, cashflows)`, `irr(cashflows, guess?)`, `mirr(cashflows,
22
+ financeRate, reinvestRate)`, and date-indexed `xnpv(rate, cashflows,
23
+ dates)` / `xirr(cashflows, dates, guess?)` (dates as `Date` or ms). IRR
24
+ uses Newton with a bisection fallback.
25
+
26
+ ## Amortization — `amortization.js`
27
+
28
+ `amortizationSchedule(principal, rate, nper, { payment?, type? })` →
29
+ rows of `{ period, payment, interest, principal, balance }`; the last row
30
+ clears rounding residue to a zero balance.
31
+
32
+ ## Interest — `interest.js`
33
+
34
+ `simpleInterest`, `compoundAmount`, `compoundInterest`,
35
+ `nominalToEffective` (APR→APY/EAR), `effectiveToNominal`,
36
+ `continuousCompound`.
37
+
38
+ ## Depreciation — `depreciation.js`
39
+
40
+ `straightLine`, `decliningBalance` (factor 2 = double-declining, floored
41
+ at salvage), `sumOfYearsDigits` — each returns per-period amounts.
42
+
43
+ ## Bonds — `bond.js`
44
+
45
+ `bondPrice`, `bondYTM` (iterative), `macaulayDuration`,
46
+ `modifiedDuration`, `convexity`. Basic day-count (level coupons, whole
47
+ periods); exotic conventions are out of scope.
48
+
49
+ ## Technical indicators — `indicators.js`
50
+
51
+ `sma`, `ema`, `wma`, `macd` (line + signal + histogram), `rsi`,
52
+ `bollinger`, `stochastic`, `atr`, `roc`. Each returns an array **aligned**
53
+ to the input (same length) with `null` in warm-up positions.
54
+ `Float64Array` inputs are accepted.
55
+
56
+ ## Returns & risk — `returns.js`
57
+
58
+ `cagr`, `holdingPeriodReturn`, `returnsOf` (price series → return series),
59
+ `volatility` (sample stddev), `sharpe`, `maxDrawdown`.
package/docs/GEO.md ADDED
@@ -0,0 +1,114 @@
1
+ # `@jarenjs/core/geo`
2
+
3
+ The spatial kernel. **There is no geometry type**: the representation is
4
+ GeoJSON ([RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946)) —
5
+ positions are `[longitude, latitude]` arrays, a `Polygon`'s `coordinates`
6
+ IS an array of rings — so every value stays a plain JSON item: patchable,
7
+ schema-checkable, addressable by pointer and path. RFC 7946 fixes the
8
+ coordinate system to WGS 84 and removed CRS support, so there is no SRID
9
+ table and no reprojection *in*; the one projection goes *out*, for
10
+ drawing. Import the barrel (`@jarenjs/core/geo`) or a single module.
11
+
12
+ The rule that keeps the module honest: **never measure on a projected
13
+ coordinate.** Measurement is spherical; Mercator exists for renderers.
14
+
15
+ ## Robust orientation — `predicates.js`
16
+
17
+ `orient2d(ax, ay, bx, by, cx, cy)` — which side of the line a→b point c
18
+ lies on, with an **exactly correct sign** (Shewchuk's adaptive-precision
19
+ arithmetic; a cheap filter first, the exact path only when the
20
+ determinant is too close to zero to trust). A naive determinant returns
21
+ the *wrong sign* on near-collinear input, which makes containment
22
+ contradict itself. `orient2dFast` is the naive form, exported for
23
+ callers that provably do not care. Every winding and containment answer
24
+ in this module rests on this sign; its cost is the deliberate
25
+ point-in-polygon loss on the benchmark page.
26
+
27
+ ## Distance — `distance.js`
28
+
29
+ Spherical, on the IUGG mean radius (`EARTH_RADIUS` = 6 371 008.8 m). A
30
+ Euclidean norm on raw degrees is 64% wrong over 1 km at 52°N, so the
31
+ planar shortcut does not exist here. `haversineDistance` (<0.5%
32
+ anywhere) is the answer; `equirectDistance` (0.02% at 430 km, 12.4%
33
+ intercontinentally, a few multiply-adds) is the *screening* form for
34
+ rejecting candidates before the real test. Also `initialBearing`,
35
+ `destinationPoint`, `lineLength(positions)`. The ellipsoid is
36
+ deliberately not modelled.
37
+
38
+ ## Rings — `ring.js`
39
+
40
+ `isRingClosed`, `ringSignedArea` (shoelace over `orient2d` terms —
41
+ the *sign* is the point), `ringWinding` (1 = counter-clockwise, the
42
+ RFC 7946 exterior convention), `sphericalRingArea` (m², spherical
43
+ excess), `pointInRing` / `pointInPolygon` (even-odd rule, boundary
44
+ counts as inside, exact predicate per crossing).
45
+
46
+ ## Boxes — `bbox.js` + `index-tree.js`
47
+
48
+ `bboxOfPositions`, `bboxIntersects`, `bboxContains`, `bboxUnion` —
49
+ `[west, south, east, north]` arrays, writable straight into a
50
+ document's `bbox` member. `createBboxIndex(boxes)` packs a **static**
51
+ Hilbert R-tree into flat typed arrays (no per-node objects; the Hilbert
52
+ distance is the bit-parallel transform): `search()` returns *candidate*
53
+ indexes — box overlap is necessary, never sufficient, so a caller
54
+ confirms each candidate with the exact test. Null entries index as
55
+ never-matching, keeping positions aligned with the caller's array.
56
+ Build and probe are level with Flatbush (`npm run benchmark:geo`; the
57
+ table lives in [ARCHITECTURE](../ARCHITECTURE.md)).
58
+
59
+ ## GeoJSON traversal — `geojson.js`
60
+
61
+ The one layer that reads the `type` discriminator; every function takes
62
+ a bare position, a geometry, a Feature or a collection. `eachPosition`,
63
+ `positionsOf`, `bboxOf`, `geometryOf`, `geometryArea` (exterior minus
64
+ holes), `geometryLength`, `centroidOf` (vertex mean, not center of
65
+ mass), `containsPosition`, `geoDistance` (between representative
66
+ positions), `ringsClosed`. Nothing here validates — malformed input
67
+ yields null or 0 — because judgment lives one module over.
68
+
69
+ ## Validity — `valid.js`, `wkt.js`, `geohash.js`
70
+
71
+ The one-call judgments backing the `geoFormats` group in
72
+ `@jarenjs/formats`:
73
+
74
+ - `isValidGeoJson(value)` — structure per type, positions of 2–3
75
+ numbers inside WGS 84 bounds, and **every ring closed** — the
76
+ invariant a JSON Schema provably cannot express. The shallow twin of
77
+ the meta-schema artifacts in `@jarenjs/json`, which locate failures
78
+ and (via `$query`) also check winding.
79
+ - `isValidWkt(text)` — strict ISO 19125 grammar: seven tags, `Z`/`M`/
80
+ `ZM` modifiers (unmodified accepts 2 or 3 coordinates, as PostGIS
81
+ does), consistent counts, closed rings, `EMPTY`, no surrounding text.
82
+ - `isValidGeohash(hash)` — non-empty, lowercase base-32 alphabet.
83
+
84
+ ## Geohash — `geohash.js`
85
+
86
+ `geohashEncode(lon, lat, precision)`, `geohashDecode`, `geohashBounds`,
87
+ `geohashCellSize`, `geohashNeighbours`. A geohash is a **string**, so it
88
+ needs no new vocabulary anywhere: proximity is a prefix test, bucketing
89
+ is grouping on a substring, and a sorted index over the hash is a
90
+ spatial index. Cells are not equal-area and neighbours can straddle a
91
+ cell edge — use `geohashNeighbours` for boundary-safe proximity, and
92
+ never as a distance.
93
+
94
+ ## Drawing — `mercator.js` + `simplify.js`
95
+
96
+ `projectMercator(lon, lat)` → unit square, y growing southward
97
+ (screen order); latitude clamps at `MERCATOR_MAX_LAT` (±85.051129°).
98
+ `unprojectMercator` inverts; `fitMercator(bbox, aspect)` fits a
99
+ geographic box into the unit square **without distorting aspect** (the
100
+ spare room becomes margin — stretching is how maps look wrong).
101
+ `simplifyLine` / `simplifyRing` are iterative Douglas-Peucker;
102
+ endpoints always survive so a ring stays closed, and a ring that would
103
+ drop below four positions is returned unsimplified rather than
104
+ degenerate.
105
+
106
+ ## Not here
107
+
108
+ Spatial *query operators* (`$distance`, `$within`, `$bbox-intersects`,
109
+ `$centroid`, `$geohash`, index-screened spatial joins) live in the
110
+ query engine of `@jarenjs/json` (QUERY-FORMAT §8.14); the GeoJSON
111
+ meta-schema artifacts live in `@jarenjs/json/schemas`; the map chart
112
+ and its bounded-memory streaming accumulator live in `@jarenjs/charts`.
113
+ Overlay operations (union/intersection/buffer) are deliberately absent
114
+ — see the ROADMAP.
package/docs/MATH.md ADDED
@@ -0,0 +1,75 @@
1
+ # `@jarenjs/core/math`
2
+
3
+ Zero-dependency numeric primitives. Static methods on `Float64`/`Int32`/
4
+ `Vec2f64`/`Vec3f64`, free `mathf64_*`/`mathi32_*` aliases and constants,
5
+ plus the newer subpath modules below. Import the barrel
6
+ (`@jarenjs/core/math`) or a single module (`@jarenjs/core/math/word`).
7
+
8
+ ## Transcendental completeness — `float64.js`
9
+
10
+ Constants `mathf64_E`, `mathf64_LN2`, `mathf64_LN10`, `mathf64_PHI`.
11
+ Free aliases `mathf64_log`, `mathf64_log2`, `mathf64_log10`, `mathf64_exp`,
12
+ `mathf64_expm1`, `mathf64_tan`, `mathf64_acos`, `mathf64_atan`,
13
+ `mathf64_sinh`, `mathf64_cosh`, `mathf64_tanh`, `mathf64_cbrt`,
14
+ `mathf64_hypot`, `mathf64_sign`.
15
+
16
+ Statics on `Float64`: `factorial(n)` (exact for integers, `gamma(n+1)`
17
+ otherwise), `gamma(x)` (Lanczos, whole real line), `hypot(...args)`,
18
+ `roundTo(value, digits)` (decimal places),
19
+ `roundToPrecision(value, digits)` (significant figures — the numeric
20
+ value of `toPrecision`, without its exponential string formatting),
21
+ `nthroot(x, n)` (odd roots of negatives), `sign(x)`,
22
+ `logBase(base, x)`, `cosHp(r)` (a high-precision polynomial cosine).
23
+
24
+ ### Range remapping — `remap`
25
+
26
+ The free export `remap(v, smin, smax, dmin, dmax)` is the one linear remap of
27
+ `v` from `[smin, smax]` to `[dmin, dmax]`: `dmin + t·(dmax - dmin)` with
28
+ `t = (v - smin)/(smax - smin)`. A degenerate source range (`smax === smin`)
29
+ collapses to `dmin` instead of dividing by zero, so a constant-valued axis still
30
+ maps to a drawable coordinate, and an inverted destination range works, which is
31
+ what screen-space y-flips need. Use it for any value/screen interpolation.
32
+
33
+ `Float64.norm(value, min, max)` is the bare normalization to `[0, 1]` that
34
+ `remap` performs internally; reach for it when you want the fraction itself
35
+ rather than a mapped coordinate.
36
+
37
+ ## Word math — `word.js`
38
+
39
+ BigInt fixed-width integers for programmer-calculator mode. `toWord(value,
40
+ bits, signed)` masks and sign-extends into an 8/16/32/64-bit word;
41
+ `wAnd/wOr/wXor/wNot/wShl/wShr/wRol/wRor/wMod` operate at a width;
42
+ `toBase(int, radix, {group, sep, pad, upper})` and `fromBase(str, radix)`
43
+ (prefix-aware: `0x`/`0o`/`0b`) are the radix string I/O.
44
+
45
+ ## Root finders — `solve.js`
46
+
47
+ Domain-free `newtonRaphson(f, df, x0, opts)`, `bisect(f, a, b, opts)`,
48
+ `secant(f, x0, x1, opts)`. Each returns `{ root, iterations, converged }`
49
+ and never throws on non-convergence. `@jarenjs/core/finance` builds
50
+ `irr`/`rate` on top.
51
+
52
+ ## 3D kernel — `mat4.js` + `project.js`
53
+
54
+ `Mat4` (column-major `Float64Array(16)`): `identity`, `multiply`,
55
+ `rotationX/Y/Z`, `translation`, `scaling`, `ortho`, `perspective`,
56
+ `transformPoint`. `project3dTo2d(point3, mat, viewport)` maps a point to
57
+ SVG pixels (Y flipped); `surfaceNormal(a, b, c)` returns the face normal
58
+ as a `Vec3f64`. Reusable for any 3D projection (the x·y·z plotter is the
59
+ first consumer; Mermaid 3D is a candidate — see ROADMAP).
60
+
61
+ ## Number formatting — `format.js`
62
+
63
+ `formatNumber(value, { notation:'auto'|'fixed'|'sci'|'eng', precision,
64
+ group, radix, decimal })` and radix-aware `parseNumber(str, { radix })`
65
+ (recognizes `0x`/`0o`/`0b` prefixes, grouping separators and scientific
66
+ notation; returns `NaN` on failure). Radix I/O delegates to `word.js`.
67
+
68
+ > **`notation: 'auto'` is not a drop-in for a fixed-unit readout.** The auto
69
+ > branch escapes to exponential once `abs(value) >= 1e21` or
70
+ > `abs(value) < 1e-6`, so `formatNumber(v, { precision: 3 })` is *not*
71
+ > interchangeable with `Float64.roundToPrecision(v, 3)`. A caller that
72
+ > appends its own unit — `"0.0509 ms"` — wants the value never to become
73
+ > `5.09e-5`, and must either pass `notation: 'fixed'` or round to
74
+ > significant figures itself. The website's timing helpers and the chart
75
+ > tick labels take the second route, via `roundToPrecision`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/core",
3
3
  "private": false,
4
- "version": "0.9.2",
4
+ "version": "0.34.2",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./dist/types/index.d.ts",
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "src/",
11
11
  "dist/types/",
12
+ "docs/",
12
13
  "ARCHITECTURE.md"
13
14
  ],
14
15
  "description": "Jaren Core Functions",
@@ -20,7 +21,7 @@
20
21
  },
21
22
  "license": "MIT",
22
23
  "engines": {
23
- "node": ">=22"
24
+ "node": ">=24"
24
25
  },
25
26
  "publishConfig": {
26
27
  "access": "public",
@@ -43,14 +44,42 @@
43
44
  "types": "./dist/types/bigint.d.ts",
44
45
  "default": "./src/bigint.js"
45
46
  },
47
+ "./cache": {
48
+ "types": "./dist/types/cache.d.ts",
49
+ "default": "./src/cache.js"
50
+ },
51
+ "./chunk": {
52
+ "types": "./dist/types/chunk.d.ts",
53
+ "default": "./src/chunk.js"
54
+ },
55
+ "./color": {
56
+ "types": "./dist/types/color.d.ts",
57
+ "default": "./src/color.js"
58
+ },
46
59
  "./dates": {
47
- "types": "./dist/types/dates.d.ts",
48
- "default": "./src/dates.js"
60
+ "types": "./dist/types/dates/index.d.ts",
61
+ "default": "./src/dates/index.js"
62
+ },
63
+ "./errors": {
64
+ "types": "./dist/types/errors.d.ts",
65
+ "default": "./src/errors.js"
66
+ },
67
+ "./dates/*": {
68
+ "types": "./dist/types/dates/*.d.ts",
69
+ "default": "./src/dates/*.js"
49
70
  },
50
71
  "./float": {
51
72
  "types": "./dist/types/float.d.ts",
52
73
  "default": "./src/float.js"
53
74
  },
75
+ "./geo": {
76
+ "types": "./dist/types/geo/index.d.ts",
77
+ "default": "./src/geo/index.js"
78
+ },
79
+ "./geo/*": {
80
+ "types": "./dist/types/geo/*.d.ts",
81
+ "default": "./src/geo/*.js"
82
+ },
54
83
  "./function": {
55
84
  "types": "./dist/types/function.d.ts",
56
85
  "default": "./src/function.js"
@@ -59,6 +88,10 @@
59
88
  "types": "./dist/types/integer.d.ts",
60
89
  "default": "./src/integer.js"
61
90
  },
91
+ "./message": {
92
+ "types": "./dist/types/message.d.ts",
93
+ "default": "./src/message.js"
94
+ },
62
95
  "./number": {
63
96
  "types": "./dist/types/number.d.ts",
64
97
  "default": "./src/number.js"
@@ -71,6 +104,10 @@
71
104
  "types": "./dist/types/scan.d.ts",
72
105
  "default": "./src/scan.js"
73
106
  },
107
+ "./schema": {
108
+ "types": "./dist/types/schema.d.ts",
109
+ "default": "./src/schema.js"
110
+ },
74
111
  "./string": {
75
112
  "types": "./dist/types/string.d.ts",
76
113
  "default": "./src/string.js"
@@ -91,6 +128,22 @@
91
128
  "types": "./dist/types/math/*.d.ts",
92
129
  "default": "./src/math/*.js"
93
130
  },
131
+ "./finance": {
132
+ "types": "./dist/types/finance/index.d.ts",
133
+ "default": "./src/finance/index.js"
134
+ },
135
+ "./finance/*": {
136
+ "types": "./dist/types/finance/*.d.ts",
137
+ "default": "./src/finance/*.js"
138
+ },
139
+ "./convert": {
140
+ "types": "./dist/types/convert/index.d.ts",
141
+ "default": "./src/convert/index.js"
142
+ },
143
+ "./convert/*": {
144
+ "types": "./dist/types/convert/*.d.ts",
145
+ "default": "./src/convert/*.js"
146
+ },
94
147
  "./package.json": "./package.json"
95
148
  },
96
149
  "scripts": {
package/src/array.js CHANGED
@@ -55,3 +55,19 @@ export function isUniqueArray(arr) {
55
55
  export function includesAll(arr, values) {
56
56
  return values.every(v => arr.includes(v));
57
57
  }
58
+
59
+ /**
60
+ * The value when it is one of `allowed`, otherwise `fallback`. The point
61
+ * is untrusted input: a value restored from storage, a share link or a
62
+ * stale state slice can be anything, and a UI that switches on it directly
63
+ * renders nothing at all for a name it does not know. Narrowing to a known
64
+ * set turns that blank into the fallback.
65
+ * @template T
66
+ * @param {unknown} value - The candidate, from anywhere.
67
+ * @param {readonly T[]} allowed - The values that may pass through.
68
+ * @param {T} fallback - Used when `value` is not one of `allowed`.
69
+ * @returns {T}
70
+ */
71
+ export function pickAllowed(value, allowed, fallback) {
72
+ return allowed.includes(/** @type {T} */ (value)) ? /** @type {T} */ (value) : fallback;
73
+ }
package/src/cache.js ADDED
@@ -0,0 +1,206 @@
1
+ //@ts-check
2
+ /**
3
+ * @file The suite's one bounded-cache primitive. Before this file the
4
+ * FIFO-512 delete-oldest map was written five times (the query engine's
5
+ * string cache, the JSONPath query cache, forms' three pointer caches)
6
+ * with a sixth divergent flush-all variant in forms' regex cache and a
7
+ * true LRU in the view projection memo. One implementation, one policy:
8
+ *
9
+ * - **LRU with recency refresh**: a `get` hit re-inserts the entry, so
10
+ * the evicted entry is the least recently USED, not the oldest
11
+ * inserted. (FIFO versus LRU was never result-observable at any call
12
+ * site — both bound memory; LRU keeps hot entries hotter.)
13
+ * - **Evict at `size >= limit` before inserting a new key**, so the
14
+ * cache never holds more than `limit` entries. (The old sites
15
+ * disagreed between `>=`-before and `>`-after; the capacity is the
16
+ * same, the invariant here is simply "never above `limit`".)
17
+ * - `undefined` is the miss sentinel: a cache MUST NOT store
18
+ * `undefined` as a value (store `null` for "computed, negative" —
19
+ * the regex cache does exactly that).
20
+ *
21
+ * The identity axis is {@link createWeakCache}: reference-keyed,
22
+ * unbounded by design because a WeakMap is bounded by its keys'
23
+ * lifetimes — an entry dies with its key. Compose the two for
24
+ * "per-registry bounded caches" (a WeakMap of bounded caches).
25
+ *
26
+ * The VALUE axis is {@link createSemanticCache}: keyed by what a
27
+ * plain-data value IS rather than by a fingerprint of it, for the caches
28
+ * whose entries carry meaning (a compiled query, a query plan, a
29
+ * registered function body) and where serving the wrong entry is wrong
30
+ * data rather than a slow repaint.
31
+ */
32
+
33
+ import { semanticKey } from './object.js';
34
+
35
+ /**
36
+ * @template K, V
37
+ * @typedef {object} BoundedCache
38
+ * @property {(key: K) => V | undefined} get - Lookup; a hit refreshes
39
+ * recency. `undefined` means miss.
40
+ * @property {(key: K, value: V) => void} set - Insert or refresh; evicts
41
+ * the least recently used entry when the cache is full.
42
+ * @property {(key: K, create: (key: K) => V) => V} getOrCreate - Lookup
43
+ * or compute-and-insert in one step.
44
+ * @property {() => void} clear - Drop every entry.
45
+ * @property {() => number} size - Current entry count.
46
+ */
47
+
48
+ /**
49
+ * A bounded, string-or-value-keyed LRU cache.
50
+ * @template K, V
51
+ * @param {number} limit - Maximum number of retained entries (> 0)
52
+ * @returns {BoundedCache<K, V>}
53
+ */
54
+ export function createBoundedCache(limit) {
55
+ /** @type {Map<K, V>} */
56
+ const map = new Map();
57
+
58
+ /** @type {BoundedCache<K, V>['get']} */
59
+ function get(key) {
60
+ const value = map.get(key);
61
+ if (value !== undefined) {
62
+ // Refresh recency: Map iteration order is insertion order, so
63
+ // delete + set moves the entry to the back of the eviction queue.
64
+ map.delete(key);
65
+ map.set(key, value);
66
+ }
67
+ return value;
68
+ }
69
+
70
+ /** @type {BoundedCache<K, V>['set']} */
71
+ function set(key, value) {
72
+ if (map.has(key)) map.delete(key);
73
+ else if (map.size >= limit) map.delete(map.keys().next().value);
74
+ map.set(key, value);
75
+ }
76
+
77
+ /** @type {BoundedCache<K, V>['getOrCreate']} */
78
+ function getOrCreate(key, create) {
79
+ let value = get(key);
80
+ if (value === undefined) {
81
+ value = create(key);
82
+ set(key, value);
83
+ }
84
+ return value;
85
+ }
86
+
87
+ return { get, set, getOrCreate, clear: () => map.clear(), size: () => map.size };
88
+ }
89
+
90
+ /**
91
+ * @template V
92
+ * @typedef {object} SemanticCache
93
+ * @property {(value: any) => V | undefined} get - Lookup by structural
94
+ * identity. `undefined` means miss — including "this value cannot be
95
+ * keyed", which is a permanent miss, never someone else's entry.
96
+ * @property {(value: any, entry: V) => boolean} set - Insert or refresh.
97
+ * Returns whether the entry was retained — `false` means the value
98
+ * could not be keyed, which a caller comparing sizes to detect an
99
+ * eviction must not mistake for one.
100
+ * @property {(value: any, create: (identity: string | null) => V) => V}
101
+ * getOrCreate - Lookup or compute-and-insert in one step. `create`
102
+ * receives the identity, or `null` when the value was unkeyable and
103
+ * the result will NOT be retained.
104
+ * @property {() => void} clear - Drop every entry.
105
+ * @property {() => number} size - Current entry count.
106
+ */
107
+
108
+ /**
109
+ * A bounded cache keyed by a value's COLLISION-FREE structural identity
110
+ * ({@link semanticKey}), for entries whose reuse decides a result.
111
+ *
112
+ * Two guarantees a fingerprint-keyed cache cannot give:
113
+ *
114
+ * - **Distinct inputs never share an entry.** The key is the whole
115
+ * deterministic serialization, so a hash collision cannot make one
116
+ * document's compiled semantics answer for another's.
117
+ * - **An unkeyable input is a permanent miss, never a wrong hit.** A
118
+ * value carrying a cycle, a function or a class instance cannot be
119
+ * keyed injectively, so it is computed afresh every time and never
120
+ * retained. Correct and slow beats fast and wrong; callers that want
121
+ * to *reject* such input should validate before asking the cache.
122
+ *
123
+ * Compose with a discriminating tuple when an entry depends on more than
124
+ * the document — `cache.getOrCreate([document, dialect, strict], …)`
125
+ * keys the whole tuple, which a `${a}|${b}` string concatenation cannot
126
+ * do injectively once `a` may itself contain the separator.
127
+ * @template V
128
+ * @param {number} limit - Maximum number of retained entries (> 0)
129
+ * @returns {SemanticCache<V>}
130
+ */
131
+ export function createSemanticCache(limit) {
132
+ /** @type {BoundedCache<string, V>} */
133
+ const cache = createBoundedCache(limit);
134
+
135
+ /** @param {any} value @returns {string | null} */
136
+ const identify = (value) => {
137
+ try {
138
+ return semanticKey(value);
139
+ }
140
+ catch {
141
+ return null; // unkeyable: a permanent miss
142
+ }
143
+ };
144
+
145
+ /** @type {SemanticCache<V>['get']} */
146
+ function get(value) {
147
+ const identity = identify(value);
148
+ return identity === null ? undefined : cache.get(identity);
149
+ }
150
+
151
+ /** @type {SemanticCache<V>['set']} */
152
+ function set(value, entry) {
153
+ const identity = identify(value);
154
+ if (identity === null) return false;
155
+ cache.set(identity, entry);
156
+ return true;
157
+ }
158
+
159
+ /** @type {SemanticCache<V>['getOrCreate']} */
160
+ function getOrCreate(value, create) {
161
+ const identity = identify(value);
162
+ if (identity === null) return create(null);
163
+ let entry = cache.get(identity);
164
+ if (entry === undefined) {
165
+ entry = create(identity);
166
+ cache.set(identity, entry);
167
+ }
168
+ return entry;
169
+ }
170
+
171
+ return { get, set, getOrCreate, clear: () => cache.clear(), size: () => cache.size() };
172
+ }
173
+
174
+ /**
175
+ * @template {object} K
176
+ * @template V
177
+ * @typedef {object} WeakCache
178
+ * @property {(key: K) => V | undefined} get
179
+ * @property {(key: K, value: V) => void} set
180
+ * @property {(key: K, create: (key: K) => V) => V} getOrCreate
181
+ */
182
+
183
+ /**
184
+ * The identity axis: a reference-keyed cache whose entries live exactly
185
+ * as long as their keys. No bound, deliberately — the bound is the
186
+ * caller's ownership of the key objects.
187
+ * @template {object} K
188
+ * @template V
189
+ * @returns {WeakCache<K, V>}
190
+ */
191
+ export function createWeakCache() {
192
+ /** @type {WeakMap<K, V>} */
193
+ const map = new WeakMap();
194
+
195
+ /** @type {WeakCache<K, V>['getOrCreate']} */
196
+ function getOrCreate(key, create) {
197
+ let value = map.get(key);
198
+ if (value === undefined) {
199
+ value = create(key);
200
+ map.set(key, value);
201
+ }
202
+ return value;
203
+ }
204
+
205
+ return { get: (key) => map.get(key), set: (key, value) => map.set(key, value), getOrCreate };
206
+ }