@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/ARCHITECTURE.md
CHANGED
|
@@ -95,38 +95,70 @@ flowchart TB
|
|
|
95
95
|
TextIndex["text/index.js"]
|
|
96
96
|
Basic["basic.js<br/>Alpha, numeric, hex"]
|
|
97
97
|
Email["email.js<br/>Email validation"]
|
|
98
|
-
Host["host.js<br/>URL, IP, hostname"]
|
|
98
|
+
Host["host.js<br/>URL, IP, hostname, URI/IRI"]
|
|
99
99
|
Identifiers["identifiers.js<br/>UUID, GUID"]
|
|
100
100
|
Base64["base64.js<br/>Base64 validation"]
|
|
101
|
-
Punycode["punycode.js<br/>
|
|
101
|
+
Punycode["punycode.js<br/>Punycode codec + IDNA"]
|
|
102
102
|
I18n["i18n.js<br/>Unicode category checks"]
|
|
103
|
+
IRegexp["iregexp.js<br/>I-Regexp (RFC 9485)"]
|
|
104
|
+
TextMisc["misc.js<br/>Assorted text testers"]
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
subgraph DateModule["Date Processing"]
|
|
106
|
-
|
|
108
|
+
DatesIndex["dates/index.js"]
|
|
109
|
+
DatesRfc["rfc3339.js<br/>RFC 3339 / ISO 8601"]
|
|
110
|
+
DatesCivil["civil.js<br/>Gregorian day-number math"]
|
|
111
|
+
DatesFormat["format.js<br/>LDML pattern compiler"]
|
|
112
|
+
DatesDuration["duration.js<br/>ISO 8601 durations"]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
subgraph GeoModule["Spatial"]
|
|
116
|
+
GeoIndex["geo/index.js"]
|
|
117
|
+
GeoPred["predicates.js<br/>Robust orientation"]
|
|
118
|
+
GeoDist["distance.js<br/>Great-circle measurement"]
|
|
119
|
+
GeoRing["ring.js<br/>Closure, winding, area, containment"]
|
|
120
|
+
GeoBbox["bbox.js<br/>Bounding boxes"]
|
|
121
|
+
GeoHash["geohash.js<br/>Base-32 cells"]
|
|
122
|
+
GeoJson["geojson.js<br/>Typed-geometry traversal"]
|
|
123
|
+
GeoValid["valid.js + wkt.js<br/>Validity testers"]
|
|
124
|
+
GeoTree["index-tree.js<br/>Packed-Hilbert box index"]
|
|
125
|
+
GeoMerc["mercator.js + simplify.js<br/>Projection out, simplification"]
|
|
107
126
|
end
|
|
108
127
|
|
|
109
128
|
subgraph MathModules["Mathematics"]
|
|
110
129
|
MathIndex["math/index.js"]
|
|
111
130
|
Int32Math["int32.js<br/>Fixed-point math"]
|
|
112
|
-
Float64Math["float64.js<br/>Float64 utilities"]
|
|
131
|
+
Float64Math["float64.js<br/>Float64 utilities, remap, clamp01"]
|
|
113
132
|
Vec2I32["vec2i32.js<br/>2D integer vectors"]
|
|
114
133
|
Vec2F64["vec2f64.js<br/>2D float vectors"]
|
|
115
134
|
Vec3F64["vec3f64.js<br/>3D float vectors"]
|
|
135
|
+
Mat4Math["mat4.js<br/>4x4 matrices"]
|
|
136
|
+
ProjectMath["project.js<br/>3D to 2D projection"]
|
|
137
|
+
SolveMath["solve.js<br/>Numeric root finders"]
|
|
138
|
+
WordMath["word.js<br/>BigInt fixed-width words"]
|
|
139
|
+
FormatMath["format.js<br/>Number format/parse"]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
subgraph DomainModules["Domain Kernels"]
|
|
143
|
+
ConvertIndex["convert/index.js<br/>Unit and currency conversion"]
|
|
144
|
+
FinanceIndex["finance/index.js<br/>TVM, bonds, cash flow, returns"]
|
|
116
145
|
end
|
|
117
146
|
|
|
118
147
|
subgraph FunctionModule["Function Utilities"]
|
|
119
148
|
FunctionUtil["function.js<br/>trueThat, falseThat"]
|
|
120
149
|
end
|
|
121
150
|
|
|
122
|
-
subgraph
|
|
123
|
-
StringUtil["string.js<br/>RegExp, grapheme counting"]
|
|
151
|
+
subgraph StringModules["String & Scanning"]
|
|
152
|
+
StringUtil["string.js<br/>RegExp, grapheme counting, fnv1a"]
|
|
153
|
+
ScanUtil["scan.js<br/>Char-code classes for parsers"]
|
|
154
|
+
MessageUtil["message.js<br/>Message template/catalog compiler"]
|
|
155
|
+
ColorUtil["color.js<br/>Mixing, relative luminance"]
|
|
124
156
|
end
|
|
125
157
|
end
|
|
126
158
|
|
|
127
159
|
CoreModule --> ScalarModules
|
|
128
160
|
CoreModule --> CollectionModules
|
|
129
|
-
CoreModule -->
|
|
161
|
+
CoreModule --> StringModules
|
|
130
162
|
CoreModule --> FunctionModule
|
|
131
163
|
|
|
132
164
|
TextIndex --> Basic
|
|
@@ -136,18 +168,41 @@ flowchart TB
|
|
|
136
168
|
TextIndex --> Base64
|
|
137
169
|
TextIndex --> Punycode
|
|
138
170
|
TextIndex --> I18n
|
|
171
|
+
TextIndex --> IRegexp
|
|
172
|
+
TextIndex --> TextMisc
|
|
139
173
|
|
|
140
174
|
MathIndex --> Int32Math
|
|
141
175
|
MathIndex --> Float64Math
|
|
142
176
|
MathIndex --> Vec2I32
|
|
143
177
|
MathIndex --> Vec2F64
|
|
144
178
|
MathIndex --> Vec3F64
|
|
179
|
+
MathIndex --> Mat4Math
|
|
180
|
+
MathIndex --> ProjectMath
|
|
181
|
+
MathIndex --> SolveMath
|
|
182
|
+
MathIndex --> WordMath
|
|
183
|
+
MathIndex --> FormatMath
|
|
184
|
+
|
|
185
|
+
DatesIndex --> DatesRfc
|
|
186
|
+
DatesIndex --> DatesCivil
|
|
187
|
+
DatesIndex --> DatesFormat
|
|
188
|
+
DatesIndex --> DatesDuration
|
|
189
|
+
|
|
190
|
+
GeoIndex --> GeoPred
|
|
191
|
+
GeoIndex --> GeoDist
|
|
192
|
+
GeoIndex --> GeoRing
|
|
193
|
+
GeoIndex --> GeoBbox
|
|
194
|
+
GeoIndex --> GeoHash
|
|
195
|
+
GeoIndex --> GeoJson
|
|
196
|
+
GeoIndex --> GeoValid
|
|
197
|
+
GeoIndex --> GeoTree
|
|
198
|
+
GeoIndex --> GeoMerc
|
|
145
199
|
|
|
146
200
|
style CorePackage fill:#e1f5fe
|
|
147
201
|
style CoreModule fill:#bbdefb
|
|
148
202
|
style TextModules fill:#c8e6c9
|
|
149
203
|
style MathModules fill:#ffccbc
|
|
150
204
|
style DateModule fill:#fff9c4
|
|
205
|
+
style GeoModule fill:#b2dfdb
|
|
151
206
|
```
|
|
152
207
|
|
|
153
208
|
---
|
|
@@ -206,7 +261,7 @@ flowchart TB
|
|
|
206
261
|
| `@jarenjs/refs` | None | Schema reference data |
|
|
207
262
|
| `jarenjs` (root) | All packages | Public API aggregation |
|
|
208
263
|
|
|
209
|
-
> **Note:** For broader Jaren architecture, see the root [`ARCHITECTURE.md`](../../ARCHITECTURE.md). For development guides, see [`HOWTO.md`](../../HOWTO.md).
|
|
264
|
+
> **Note:** For broader Jaren architecture, see the root [`ARCHITECTURE.md`](../../docs/ARCHITECTURE.md). For development guides, see [`HOWTO.md`](../../docs/HOWTO.md).
|
|
210
265
|
|
|
211
266
|
---
|
|
212
267
|
|
|
@@ -383,9 +438,41 @@ getStringLength("👨👩👧👦", false); // 11 (code units)
|
|
|
383
438
|
getStringLength("👨👩👧👦", true); // 1 (grapheme cluster)
|
|
384
439
|
```
|
|
385
440
|
|
|
386
|
-
### 5. Date Module (`dates
|
|
387
|
-
|
|
388
|
-
RFC 3339 and ISO 8601
|
|
441
|
+
### 5. Date Module (`dates/`)
|
|
442
|
+
|
|
443
|
+
RFC 3339 and ISO 8601 validation, plus the suite's calendar kernel.
|
|
444
|
+
|
|
445
|
+
**Dates are not a type here.** They are the two forms JSON already has — an
|
|
446
|
+
RFC 3339 **string** (lexical, what documents, schemas, forms and TOML
|
|
447
|
+
contain) and **epoch milliseconds** (arithmetic, what a chart plots). A
|
|
448
|
+
wrapper object, even an immutable one, could not be a query-engine item, a
|
|
449
|
+
JSON Patch target or part of app state; it is why `canonicalizeJson` turns a
|
|
450
|
+
`Date` into `{}`. The parts record from `parseRFC3339Parts` is the working
|
|
451
|
+
intermediate and is plain data, never an opaque handle.
|
|
452
|
+
|
|
453
|
+
| Module | Owns |
|
|
454
|
+
|---|---|
|
|
455
|
+
| `rfc3339.js` | validation, lexical decomposition, epoch conversion |
|
|
456
|
+
| `civil.js` | proleptic Gregorian arithmetic over integers |
|
|
457
|
+
| `format.js` | LDML pattern → compiled formatter |
|
|
458
|
+
| `duration.js` | ISO 8601 duration decomposition and date arithmetic |
|
|
459
|
+
|
|
460
|
+
Calendar math goes through day numbers (`daysFromCivil`/`civilFromDays`),
|
|
461
|
+
never through `Date`: the conversions are ~10 integer operations and allocate
|
|
462
|
+
nothing, where an allocate-mutate-read `Date` round trip costs about 165× as
|
|
463
|
+
much for the same answer. For the same reason every `getDateTypeOf*` getter
|
|
464
|
+
has a `getEpochOf*` twin — the same validity check and the same engine parse,
|
|
465
|
+
returning epoch milliseconds instead of a `Date` — so a consumer comparing
|
|
466
|
+
against precomputed bounds (the `formatMinimum`/`formatMaximum` validators)
|
|
467
|
+
allocates nothing per value. Formatting is the two-stage compiler again — a
|
|
468
|
+
pattern is scanned once into a chain of appenders, which measured ~4.5×
|
|
469
|
+
against re-scanning it per call.
|
|
470
|
+
|
|
471
|
+
Locale-dependent presentation (month and weekday names, meridiem, relative
|
|
472
|
+
phrasing) is deliberately **not** here, so this package stays zero-dependency
|
|
473
|
+
and free of data that drifts per language: `compileDateFormat` takes a names
|
|
474
|
+
provider, and a pattern using `MMMM`/`EEEE`/`a` without one is a compile
|
|
475
|
+
error rather than a silent English fallback.
|
|
389
476
|
|
|
390
477
|
```mermaid
|
|
391
478
|
flowchart TB
|
|
@@ -425,11 +512,130 @@ import {
|
|
|
425
512
|
CONST_TICKS_HOUR, // 3600000
|
|
426
513
|
CONST_TICKS_DAY, // 86400000
|
|
427
514
|
CONST_RFC3339_DAYS, // Days per month array
|
|
428
|
-
CONST_RFC3339_REGEX_ISDATE, // Date regex
|
|
429
|
-
CONST_RFC3339_REGEX_ISTIME, // Time regex
|
|
430
515
|
} from '@jarenjs/core/dates';
|
|
431
516
|
```
|
|
432
517
|
|
|
518
|
+
**Working with the calendar:**
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
import {
|
|
522
|
+
parseRFC3339Parts, addToParts, startOfParts, compileDateFormat,
|
|
523
|
+
} from '@jarenjs/core/dates';
|
|
524
|
+
|
|
525
|
+
const parts = parseRFC3339Parts('2026-01-31');
|
|
526
|
+
addToParts(parts, 1, 'month'); // 2026-02-28 — month math clamps
|
|
527
|
+
startOfParts(parts, 'week'); // the Monday of that week (ISO 8601)
|
|
528
|
+
|
|
529
|
+
const fmt = compileDateFormat("yyyy-'W'ww"); // compile once…
|
|
530
|
+
fmt(parts); // …call many: '2026-W05'
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### 5b. Geo Module (`geo/`)
|
|
534
|
+
|
|
535
|
+
The spatial kernel. As with dates, **there is no geometry type**: the
|
|
536
|
+
representation is GeoJSON ([RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946)),
|
|
537
|
+
whose positions are `[longitude, latitude]` arrays and whose rings are arrays
|
|
538
|
+
of those. They are already JSON items, so they stay patchable, schema-checkable
|
|
539
|
+
and addressable; a wrapper class would break all three. Every function here
|
|
540
|
+
takes plain numbers and plain arrays, so a `Polygon`'s `coordinates` can be
|
|
541
|
+
passed straight in without the `type` discriminator being involved.
|
|
542
|
+
|
|
543
|
+
| Module | Owns |
|
|
544
|
+
|---|---|
|
|
545
|
+
| `predicates.js` | robust orientation — the sign every spatial test rests on |
|
|
546
|
+
| `distance.js` | great-circle measurement on the WGS 84 sphere |
|
|
547
|
+
| `ring.js` | ring closure, winding, area and containment |
|
|
548
|
+
| `bbox.js` | bounding boxes, the cheap half of every spatial test |
|
|
549
|
+
| `geohash.js` | the base-32 cell encoding, and its validity tester |
|
|
550
|
+
| `geojson.js` | the one layer that knows the `type` discriminator |
|
|
551
|
+
| `valid.js` | `isValidGeoJson` — the one-call structural judgment (rings must close), the boolean twin of the schema artifacts in `@jarenjs/json` |
|
|
552
|
+
| `wkt.js` | `isValidWkt` — a strict validity tester for Well-Known Text |
|
|
553
|
+
| `index-tree.js` | a static packed-Hilbert box index for spatial joins |
|
|
554
|
+
| `mercator.js` | Web Mercator, the projection *out* for anything that draws |
|
|
555
|
+
| `simplify.js` | Douglas-Peucker — dropping the vertices a drawing cannot show |
|
|
556
|
+
|
|
557
|
+
The three validity testers back the `geoFormats` group in
|
|
558
|
+
[`@jarenjs/formats`](../formats) (`geohash`, `wkt`, `geojson`).
|
|
559
|
+
|
|
560
|
+
Two decisions carry the module. **Orientation is computed exactly**, through
|
|
561
|
+
Shewchuk's adaptive precision arithmetic: a naive floating-point determinant
|
|
562
|
+
returns the *wrong sign* on near-collinear input, which makes a containment
|
|
563
|
+
test contradict itself and a clipper emit self-intersecting output. The test
|
|
564
|
+
suite pins real longitude/latitude fixtures on the San Francisco–Los Angeles
|
|
565
|
+
line where the naive form reports "collinear" and the truth is one side or the
|
|
566
|
+
other. The cheap filter runs first, so the exact path costs nothing until it is
|
|
567
|
+
needed.
|
|
568
|
+
|
|
569
|
+
**Distance is spherical, and the planar shortcut is refused.** A degree of
|
|
570
|
+
longitude spans ~111 km at the equator and ~68 km at 52°N, so a Euclidean norm
|
|
571
|
+
on raw degrees is 64% wrong over 1 km at Dutch latitudes. `haversineDistance`
|
|
572
|
+
is the answer (<0.5% anywhere); `equirectDistance` is the *screening* form
|
|
573
|
+
(0.02% at 430 km, 12.4% intercontinentally — a few multiply-adds against
|
|
574
|
+
haversine's transcendentals) for rejecting candidates before the real test —
|
|
575
|
+
the same build-then-probe shape the query engine's hash join uses. The
|
|
576
|
+
ellipsoid is deliberately not modelled.
|
|
577
|
+
|
|
578
|
+
RFC 7946 removed coordinate-reference-system support and mandates WGS 84, so
|
|
579
|
+
there is no SRID table and no reprojection: conformance removes the need rather
|
|
580
|
+
than an omission hiding it.
|
|
581
|
+
|
|
582
|
+
**Measured against the field** (`npm run benchmark:geo`, Node v22.22.2). Every
|
|
583
|
+
scenario asserts result equivalence before any timing, and the harness refuses
|
|
584
|
+
to print a table if the engines disagree: distance, area, length and bounding
|
|
585
|
+
box come out *bit-identical* to Turf, containment agrees on a 400-point sweep,
|
|
586
|
+
and the index returns exactly Flatbush's answer on 200 queries. Ratios are the
|
|
587
|
+
rival's time over this kernel's, so above 1 means Jaren is faster.
|
|
588
|
+
|
|
589
|
+
| scenario | Jaren | rival | ratio |
|
|
590
|
+
|---|---|---|---|
|
|
591
|
+
| distance, two positions | 26.2 ns | 128.5 ns (turf) | **4.9×** |
|
|
592
|
+
| distance vs an ellipsoidal library | 52.3 ns | 648.4 ns (geolib) | **12.4×** |
|
|
593
|
+
| line length, 500 positions | 20.5 µs | 63.6 µs (turf) | **3.1×** |
|
|
594
|
+
| point in polygon, 12 vertices | 228 ns | 327 ns (turf) | **1.4×** |
|
|
595
|
+
| bounding box, 2000 vertices | 15.3 µs | 18.1 µs (turf) | **1.2×** |
|
|
596
|
+
| polygon area, 2000 vertices | 18.8 µs | 20.4 µs (turf) | **1.1×** |
|
|
597
|
+
| centroid, 2000 vertices | 18.4 µs | 19.1 µs (turf) | 1.0× |
|
|
598
|
+
| index build, 100k boxes | 12.2 ms | 12.4 ms (flatbush) | 1.0× |
|
|
599
|
+
| index probe, 100k boxes | 529 ns | 529 ns (flatbush) | 1.0× |
|
|
600
|
+
| point in polygon, 2000 vertices | 8.2 µs | 3.7 µs (turf) | **0.5×** |
|
|
601
|
+
|
|
602
|
+
One row is a loss, and it is kept on purpose. **Point-in-polygon on a large
|
|
603
|
+
ring runs at about half Turf's speed** because every edge that could matter
|
|
604
|
+
goes through the exact orientation predicate, where Turf uses naive
|
|
605
|
+
floating-point arithmetic. That is the trade this module exists to make — it
|
|
606
|
+
is the difference between a containment test that is right on near-collinear
|
|
607
|
+
input and one that is merely fast. Cheap straddle/span tests already skip the
|
|
608
|
+
predicate on edges that cannot affect the answer, which took this from 0.2×
|
|
609
|
+
to 0.5×; the rest is the predicate itself.
|
|
610
|
+
|
|
611
|
+
Two former losses closed, and both closures carry a lesson about profiling
|
|
612
|
+
before fixing:
|
|
613
|
+
|
|
614
|
+
- **Index build** was 2.6× behind Flatbush, and the assumed cause — the leaf
|
|
615
|
+
sort permuting the four-wide bounds rows on every swap — turned out to be
|
|
616
|
+
wrong: fixing it moved nothing. A phase profile put ~60% of the build in
|
|
617
|
+
the *Hilbert distance* computation, a 16-step bisection loop with a float
|
|
618
|
+
division per step. The classical loop is now the bit-parallel transform
|
|
619
|
+
(identical values, verified exhaustively at the corners and over 200k
|
|
620
|
+
pseudo-random points), the sort orders a `Uint32Array` permutation with an
|
|
621
|
+
insertion-sort cutoff, and the bounds are written once, already in leaf
|
|
622
|
+
order. Build and probe are both level with Flatbush now.
|
|
623
|
+
- **Centroid** walked the same `eachPosition` as `bboxOf` yet lost where
|
|
624
|
+
`bboxOf` won. The difference was the callback body: accumulating doubles
|
|
625
|
+
into *closure variables* writes a boxed heap number per `+=` — two per
|
|
626
|
+
vertex — where `bboxOf`'s comparisons rarely write at all. The accumulators
|
|
627
|
+
are a small `Float64Array` now (raw double stores, no boxing), and the row
|
|
628
|
+
is level with Turf.
|
|
629
|
+
|
|
630
|
+
```javascript
|
|
631
|
+
import { orient2d, haversineDistance, ringWinding, geohashEncode } from '@jarenjs/core/geo';
|
|
632
|
+
|
|
633
|
+
orient2d(0, 0, 1, 0, 0, 1); // > 0 — counter-clockwise, exactly
|
|
634
|
+
haversineDistance(4.9041, 52.3676, 2.3522, 48.8566); // 429_862 m
|
|
635
|
+
ringWinding([[0,0],[1,0],[1,1],[0,1],[0,0]]); // 1 — RFC 7946 exterior ring
|
|
636
|
+
geohashEncode(4.9041, 52.3676, 5); // 'u173z' — a string, so $starts-with is proximity
|
|
637
|
+
```
|
|
638
|
+
|
|
433
639
|
### 6. Text Module (`text/`)
|
|
434
640
|
|
|
435
641
|
Comprehensive string format validation organized by domain.
|
|
@@ -457,12 +663,11 @@ flowchart TB
|
|
|
457
663
|
|
|
458
664
|
subgraph EmailModule["email.js"]
|
|
459
665
|
Email["isValidEmail()<br/>Basic RFC 5322"]
|
|
460
|
-
EmailFull["isValidEmailFull()<br/>Comprehensive"]
|
|
461
666
|
IDNEmail["isValidIdnEmail()<br/>Internationalized"]
|
|
462
667
|
end
|
|
463
668
|
|
|
464
669
|
subgraph HostModule["host.js"]
|
|
465
|
-
URL["isValidUrl()
|
|
670
|
+
URL["isValidUrl()<br/>Web-scheme URI"]
|
|
466
671
|
URI["isValidUri() / isValidUriFull()"]
|
|
467
672
|
IPv4["isValidIPv4()"]
|
|
468
673
|
IPv6["isValidIPv6()"]
|
|
@@ -471,16 +676,14 @@ flowchart TB
|
|
|
471
676
|
end
|
|
472
677
|
|
|
473
678
|
subgraph Base64Module["base64.js"]
|
|
474
|
-
B64["isValidBase64()<br/>
|
|
475
|
-
B64Fast["isValidBase64Fast()<br/>Optimized"]
|
|
476
|
-
B64Full["isValidBase64Full()<br/>Strict padding"]
|
|
679
|
+
B64["isValidBase64()<br/>RFC 4648"]
|
|
477
680
|
end
|
|
478
681
|
|
|
479
682
|
subgraph PunycodeModule["punycode.js"]
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
683
|
+
Encode["punycodeEncode()<br/>One label, RFC 3492"]
|
|
684
|
+
Decode["punycodeDecode()<br/>One label, RFC 3492"]
|
|
685
|
+
ToASCII["domainToASCII()<br/>Whole name → ACE"]
|
|
686
|
+
ToUnicode["domainToUnicode()<br/>ACE → Unicode"]
|
|
484
687
|
end
|
|
485
688
|
|
|
486
689
|
subgraph I18nModule["i18n.js"]
|
|
@@ -490,13 +693,13 @@ flowchart TB
|
|
|
490
693
|
Hebrew["isHebrew()"]
|
|
491
694
|
Arabic["isArabicIndicDigit()"]
|
|
492
695
|
CJK["isHiragana() / isKatakana() / isHan()"]
|
|
493
|
-
Contextual["checkContextualRules()<br/>IDN label rules"]
|
|
696
|
+
Contextual["checkContextualRules()<br/>IDN label rules, over code points"]
|
|
494
697
|
end
|
|
495
698
|
|
|
496
699
|
subgraph Misc["misc.js"]
|
|
497
700
|
ISBN["ISBN-10 / ISBN-13 validation"]
|
|
498
701
|
Country["isValidCountryAlpha2()<br/>ISO 3166-1"]
|
|
499
|
-
IBAN["isValidIBAN()"]
|
|
702
|
+
IBAN["isValidIBAN()<br/>ISO 13616 + MOD 97-10"]
|
|
500
703
|
end
|
|
501
704
|
end
|
|
502
705
|
```
|
|
@@ -531,7 +734,7 @@ flowchart TB
|
|
|
531
734
|
Standard["Standard math<br/>sqrt, pow, sin, cos"]
|
|
532
735
|
GCD["GCD calculation"]
|
|
533
736
|
InverseSqrt["Fast inverse square root<br/>(Quake III algorithm)"]
|
|
534
|
-
Interpolation["
|
|
737
|
+
Interpolation["Normalization<br/>norm (free export: remap)"]
|
|
535
738
|
end
|
|
536
739
|
|
|
537
740
|
subgraph Vec2I32["vec2i32.js"]
|
|
@@ -797,4 +1000,4 @@ export function isNumberType(data) {
|
|
|
797
1000
|
|
|
798
1001
|
When contributing, remember: this package is used by `@jarenjs/validate` and `@jarenjs/formats`. Changes here have downstream effects. Maintain backward compatibility, optimize for performance, and keep the API predictable.
|
|
799
1002
|
|
|
800
|
-
For questions about the broader architecture, see the root [`ARCHITECTURE.md`](../../ARCHITECTURE.md). For development workflows, see [`HOWTO.md`](../../HOWTO.md).
|
|
1003
|
+
For questions about the broader architecture, see the root [`ARCHITECTURE.md`](../../docs/ARCHITECTURE.md). For development workflows, see [`HOWTO.md`](../../docs/HOWTO.md).
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jarenjs/core
|
|
2
2
|
|
|
3
|
-
The zero-dependency foundation of [Jaren](https://github.com/jklarenbeek/jarenjs). Everything the
|
|
3
|
+
The zero-dependency foundation of [Jaren](https://github.com/jklarenbeek/jarenjs). Everything the rest of the suite is built on lives here — type guards, Unicode-aware string handling, a large text-validation toolbox, number range helpers, fixed-point and vector math, the calendar kernel, the spatial kernel, the message-catalog compiler, unit/currency conversion and a finance library.
|
|
4
4
|
|
|
5
5
|
None of it depends on JSON Schema: every module can be used standalone in any JavaScript project.
|
|
6
6
|
|
|
@@ -10,18 +10,25 @@ None of it depends on JSON Schema: every module can be used standalone in any Ja
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| `@jarenjs/core` | type guards and getters (`isStringType`, `isObjectClass`, `getIntegerType`, ...) |
|
|
12
12
|
| `@jarenjs/core/array` | array helpers (`isUniqueArray`, `getUniqueArray`, `includesAll`, ...) |
|
|
13
|
-
| `@jarenjs/core/object` | deep equality (`equalsDeep`, JSON-only `equalsJson`), map/set merging |
|
|
14
|
-
| `@jarenjs/core/string` | Unicode string helpers (`countCodePoints`, `compareCodePoints`, ...), cached regex compilation |
|
|
13
|
+
| `@jarenjs/core/object` | deep equality (`equalsDeep`, JSON-only `equalsJson`), the `isJsonObject` predicate, `__proto__`-safe `setObjectMember`, `deepFreeze`, map/set merging |
|
|
14
|
+
| `@jarenjs/core/string` | Unicode string helpers (`countCodePoints`, `compareCodePoints`, ...), cached regex compilation, the suite's one content hash (`fnv1a` and the `hashContent` fingerprint over it) and `kebabCase` |
|
|
15
|
+
| `@jarenjs/core/cache` | the bounded LRU (`createBoundedCache`), the reference-keyed `createWeakCache`, and `createSemanticCache` — keyed by what a value IS, for caches whose entries decide a result |
|
|
16
|
+
| `@jarenjs/core/chunk` | cutting a value down to size: `sizeOf` (the suite's one size rule — a string is its length, anything else its JSON), `excerpt`, `truncate`, and `chunkText` by size, line or separator with offsets that locate a piece in its source |
|
|
15
17
|
| `@jarenjs/core/scan` | char-code constants and predicates for recursive-descent parsers |
|
|
18
|
+
| `@jarenjs/core/message` | the message template/catalog compiler shared by the validator and the form layer |
|
|
19
|
+
| `@jarenjs/core/color` | pure color math (`lerpColor` — hex `#rrggbb` interpolation) |
|
|
16
20
|
| `@jarenjs/core/number` | boolean/number/integer coercion helpers (`isIntishType`, ...) |
|
|
17
21
|
| `@jarenjs/core/integer` | `int8` ... `uint64` ranges and validators |
|
|
18
22
|
| `@jarenjs/core/float` | `float16` ... `float64` constants, validators, increment/decrement |
|
|
19
23
|
| `@jarenjs/core/bigint` | bigint helpers (`BigInt_min`, `BigInt_MinMax`, ...) |
|
|
20
|
-
| `@jarenjs/core/dates` | RFC 3339 / ISO 8601 date
|
|
24
|
+
| `@jarenjs/core/dates` | RFC 3339 / ISO 8601 validation, plus the calendar kernel: integer date arithmetic, compiled formatting, durations |
|
|
25
|
+
| `@jarenjs/core/geo` | the spatial kernel over GeoJSON: robust orientation, great-circle measurement, rings, bounding boxes, geohash, GeoJSON/WKT validity, a packed-Hilbert box index, Web Mercator and Douglas-Peucker simplification |
|
|
21
26
|
| `@jarenjs/core/text` | text validators: emails, hostnames, IPs, URIs/IRIs, UUIDs, punycode, ... |
|
|
22
|
-
| `@jarenjs/core/math` | int32/float64 math and 2D/3D vector classes |
|
|
27
|
+
| `@jarenjs/core/math` | int32/float64 math and 2D/3D vector classes; the linear `remap` and unit-interval `clamp01` |
|
|
28
|
+
| `@jarenjs/core/finance` | zero-dependency finance/trading formulas: TVM, cash flow, amortization, interest, depreciation, bonds, technical indicators, returns/risk |
|
|
29
|
+
| `@jarenjs/core/convert` | pure deterministic quantity conversion: affine dimensional units and rate-table currency |
|
|
23
30
|
|
|
24
|
-
Deep imports work too (`@jarenjs/core/text/email`, `@jarenjs/core/math/vec2f64`, ...).
|
|
31
|
+
Deep imports work too (`@jarenjs/core/text/email`, `@jarenjs/core/math/vec2f64`, `@jarenjs/core/finance/tvm`, ...).
|
|
25
32
|
|
|
26
33
|
## Strings and Unicode
|
|
27
34
|
|
|
@@ -51,13 +58,60 @@ isValidIPv6('::ffff:192.168.0.1'); // true
|
|
|
51
58
|
isValidUriTemplate('/users{/id}{?q}'); // true (RFC 6570)
|
|
52
59
|
```
|
|
53
60
|
|
|
54
|
-
Grouped by file: `email` (RFC 5321 + internationalized addresses), `host` (hostnames, IDN hostnames, IPv4/IPv6, MAC, URI/URL/IRI and references, URI templates), `identifiers` (UUID, GUID, C/HTML/CSS identifiers), `iregexp` (I-Regexp / RFC 9485 validation and translation to `RegExp`: `isValidIRegexp`, `compileIRegexp`), `base64`, `basic` (alpha, numeric, hex, web colors), `misc` (ISBN-10/13, ISO 3166 country codes, IBAN), `i18n` (Unicode script tests and IDNA context checks) and
|
|
61
|
+
Grouped by file: `email` (RFC 5321 + internationalized addresses), `host` (hostnames, IDN hostnames, IPv4/IPv6, MAC, URI/URL/IRI and references, URI templates), `identifiers` (UUID, GUID, C/HTML/CSS identifiers), `iregexp` (I-Regexp / RFC 9485 validation and translation to `RegExp`: `isValidIRegexp`, `compileIRegexp`), `base64`, `basic` (alpha, numeric, hex, web colors), `misc` (ISBN-10/13, ISO 3166 country codes, ISO 13616 IBAN including its MOD 97-10 check digits), `i18n` (Unicode script tests and IDNA context checks) and `punycode` (the RFC 3492 codec, `punycodeEncode`/`punycodeDecode`, plus the domain-level `domainToASCII`/`domainToUnicode`).
|
|
62
|
+
|
|
63
|
+
`host` answers `uri`, `uri-reference`, `iri` and `iri-reference` from one character-code scanner: RFC 3987 is RFC 3986 with the unreserved class widened by `ucschar` and `iprivate` admitted in the query, so both grammars are the same walk with one flag. That is what makes every URI an IRI by construction rather than by coincidence.
|
|
55
64
|
|
|
56
65
|
## Dates, numbers and math
|
|
57
66
|
|
|
58
|
-
- `dates` — RFC 3339 date/time/date-time validation and parsing
|
|
67
|
+
- `dates` — RFC 3339 date/time/date-time validation and parsing (leap years and month lengths included), the more lenient ISO date-time forms, and the suite's calendar kernel: proleptic Gregorian arithmetic over integer day numbers (`addToParts`, `startOfParts`, `endOfParts` — month math clamps, so 31 Jan plus a month is 28 Feb), ISO 8601 duration decomposition, and `compileDateFormat`, which turns an LDML pattern into a formatter once instead of re-scanning it per call. Dates stay JSON: an RFC 3339 string or epoch milliseconds, never a wrapper object. Locale names live in `@jarenjs/locales`, so a pattern needing `MMMM` takes a names provider. The full module reference is [docs/DATES.md](./docs/DATES.md).
|
|
59
68
|
- `integer`/`float`/`bigint` — range constants and validators for every fixed-width type from `int8` to `uint64` and `float16` to `float64`, including float increment/decrement in representable steps.
|
|
60
|
-
- `math` — asm.js-style typed math (`Int32`, `Float64`) and vector classes (`Vec2i32`, `Vec2f64`, `Vec3f64`) with a fast integer sine approximation.
|
|
69
|
+
- `math` — asm.js-style typed math (`Int32`, `Float64`) and vector classes (`Vec2i32`, `Vec2f64`, `Vec3f64`) with a fast integer sine approximation; reference in [docs/MATH.md](./docs/MATH.md).
|
|
70
|
+
|
|
71
|
+
## Geospatial
|
|
72
|
+
|
|
73
|
+
`@jarenjs/core/geo` is the suite's spatial kernel, and its representation is GeoJSON itself ([RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946)) rather than a geometry class: positions are `[longitude, latitude]` arrays, a `Polygon`'s `coordinates` *is* an array of rings, so every value stays a plain JSON item — patchable, schema-checkable, addressable by pointer and path. RFC 7946 fixes the coordinate system to WGS 84, so there is no SRID table and no reprojection *in*; the one projection that exists goes *out*, for drawing.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import {
|
|
77
|
+
orient2d, haversineDistance, ringWinding, bboxOf,
|
|
78
|
+
geohashEncode, isValidWkt, createBboxIndex,
|
|
79
|
+
} from '@jarenjs/core/geo';
|
|
80
|
+
|
|
81
|
+
orient2d(0, 0, 1, 0, 0, 1); // > 0 — counter-clockwise, exactly
|
|
82
|
+
haversineDistance(4.9041, 52.3676, 2.3522, 48.8566); // 429_862 m (Amsterdam–Paris)
|
|
83
|
+
ringWinding([[0,0],[1,0],[1,1],[0,1],[0,0]]); // 1 — an RFC 7946 exterior ring
|
|
84
|
+
bboxOf({ type: 'Polygon', coordinates: [[[4,52],[5,52],[5,53],[4,52]]] }); // [4, 52, 5, 53]
|
|
85
|
+
geohashEncode(4.9041, 52.3676, 5); // 'u173z' — a string, so a prefix test is proximity
|
|
86
|
+
isValidWkt('POINT (4.9041 52.3676)'); // true — strict ISO 19125 grammar
|
|
87
|
+
|
|
88
|
+
const index = createBboxIndex(regions.map(bboxOf)); // packed-Hilbert, build once
|
|
89
|
+
for (const i of index.search(...bboxOf(point))) confirm(regions[i]); // candidates, then the exact test
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Three design decisions carry the module:
|
|
93
|
+
|
|
94
|
+
- **Orientation is computed exactly** (Shewchuk's adaptive predicates): a naive floating-point determinant returns the *wrong sign* on near-collinear input, which makes containment contradict itself. Every ring winding and point-in-polygon answer rests on this sign, and the deliberate cost is on the [benchmark page](https://jklarenbeek.github.io/jarenjs/#/benchmarks?suite=geo).
|
|
95
|
+
- **Measurement is spherical, drawing is projected, and the two never mix.** A Euclidean norm on raw degrees is 64% wrong over 1 km at Dutch latitudes, so `haversineDistance`/`sphericalRingArea` work on the sphere (`equirectDistance` is the cheap screening form for rejecting candidates first), while `projectMercator`/`fitMercator` and `simplifyLine`/`simplifyRing` exist for renderers — never measure on a projected coordinate.
|
|
96
|
+
- **Validity is a separate concern from traversal.** `eachPosition`, `bboxOf`, `centroidOf` and friends measure without judging; `isValidGeoJson` (structure plus the ring closure a JSON Schema provably cannot express), `isValidWkt` and `isValidGeohash` are the one-call judgments that back the `geoFormats` group in [`@jarenjs/formats`](../formats), next to the full GeoJSON meta-schema artifacts in [`@jarenjs/json`](../json).
|
|
97
|
+
|
|
98
|
+
The spatial query operators (`$distance`, `$within`, `$geohash`, spatial joins over the box index) live in the query engine in [`@jarenjs/json`](../json); the streaming map chart that draws a FeatureCollection with bounded memory lives in [`@jarenjs/charts`](../../components/charts). The full module reference is [docs/GEO.md](./docs/GEO.md).
|
|
99
|
+
|
|
100
|
+
## Messages
|
|
101
|
+
|
|
102
|
+
`@jarenjs/core/message` is the template/catalog compiler behind the validator's uniform error messages and the form layer's labels — msgid plus parameters in, rendered text out, so every message is translatable by swapping a catalog ([`@jarenjs/locales`](../locales) ships eleven language packs over it):
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
import { compileMessageTemplate, compileMessageCatalog } from '@jarenjs/core/message';
|
|
106
|
+
|
|
107
|
+
const t = compileMessageTemplate('must be {comparison} {limit}');
|
|
108
|
+
t({ comparison: '>=', limit: 18 }); // 'must be >= 18'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Units, currency and finance
|
|
112
|
+
|
|
113
|
+
- `convert` — pure, deterministic quantity conversion over a registry of affine dimensional units (`convert(1, 'nmi', 'km')` → `1.852`; dimensions include length, area, volume, mass, temperature, time, speed, pressure, energy, angle and more, with `unitsOf`/`dimensionOf` for discovery). Currency is rate-table based: rates come in as data, never from a network call inside this package. Reference in [docs/CONVERT.md](./docs/CONVERT.md).
|
|
114
|
+
- `finance` — zero-dependency finance and trading formulas grouped by file: `tvm` (`pmt(0.05/12, 360, 250000)` → `-1342.05`, plus `fv`/`pv`/`nper`/`rate`), `cashflow` (NPV/IRR), `amortization`, `interest`, `depreciation`, `bond`, `returns` (returns and risk measures) and `indicators` (SMA/EMA/WMA, MACD, RSI, Bollinger bands — windowed values pad with `null` until the window fills, so outputs align with inputs). Reference in [docs/FINANCE.md](./docs/FINANCE.md).
|
|
61
115
|
|
|
62
116
|
## JSON addressing standards
|
|
63
117
|
|
|
@@ -65,4 +119,4 @@ The JSON addressing and query standards — JSON validation helpers, JSON Pointe
|
|
|
65
119
|
|
|
66
120
|
## Development
|
|
67
121
|
|
|
68
|
-
Unit tests live in `test/core/` at the repository root (`npm run test:core`). This package's internals are described in its own [ARCHITECTURE](./ARCHITECTURE.md) document; see the repository [README](../../README.md) and [ARCHITECTURE](../../ARCHITECTURE.md) for the monorepo picture, and the [ROADMAP](../../ROADMAP.md) for planned work.
|
|
122
|
+
Unit tests live in `test/core/` at the repository root (`npm run test:core`). This package's internals are described in its own [ARCHITECTURE](./ARCHITECTURE.md) document, with per-module references under [docs/](./docs/) (`MATH`, `CONVERT`, `FINANCE`, `DATES`, `GEO`); see the repository [README](../../README.md) and [ARCHITECTURE](../../docs/ARCHITECTURE.md) for the monorepo picture, and the [ROADMAP](../../docs/ROADMAP.md) for planned work.
|
package/dist/types/array.d.ts
CHANGED
|
@@ -26,3 +26,16 @@ export declare function isUniqueArray(arr: any[]): boolean;
|
|
|
26
26
|
* @returns {boolean} True if all 'values' are included in 'arr', false otherwise.
|
|
27
27
|
*/
|
|
28
28
|
export declare function includesAll(arr: any[], values: any[]): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The value when it is one of `allowed`, otherwise `fallback`. The point
|
|
31
|
+
* is untrusted input: a value restored from storage, a share link or a
|
|
32
|
+
* stale state slice can be anything, and a UI that switches on it directly
|
|
33
|
+
* renders nothing at all for a name it does not know. Narrowing to a known
|
|
34
|
+
* set turns that blank into the fallback.
|
|
35
|
+
* @template T
|
|
36
|
+
* @param {unknown} value - The candidate, from anywhere.
|
|
37
|
+
* @param {readonly T[]} allowed - The values that may pass through.
|
|
38
|
+
* @param {T} fallback - Used when `value` is not one of `allowed`.
|
|
39
|
+
* @returns {T}
|
|
40
|
+
*/
|
|
41
|
+
export declare function pickAllowed<T>(value: unknown, allowed: readonly T[], fallback: T): T;
|