@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.
- 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/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.
|
package/docs/FINANCE.md
ADDED
|
@@ -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.
|
|
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": ">=
|
|
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
|
+
}
|