@jarenjs/core 0.9.2 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/ARCHITECTURE.md +229 -26
  2. package/README.md +64 -10
  3. package/dist/types/array.d.ts +13 -0
  4. package/dist/types/cache.d.ts +166 -0
  5. package/dist/types/chunk.d.ts +102 -0
  6. package/dist/types/color.d.ts +64 -0
  7. package/dist/types/convert/convert.d.ts +33 -0
  8. package/dist/types/convert/currency.d.ts +47 -0
  9. package/dist/types/convert/index.d.ts +9 -0
  10. package/dist/types/convert/registry.d.ts +55 -0
  11. package/dist/types/dates/civil.d.ts +146 -0
  12. package/dist/types/dates/duration.d.ts +83 -0
  13. package/dist/types/dates/format.d.ts +59 -0
  14. package/dist/types/dates/index.d.ts +4 -0
  15. package/dist/types/{dates.d.ts → dates/rfc3339.d.ts} +57 -3
  16. package/dist/types/errors.d.ts +77 -0
  17. package/dist/types/finance/amortization.d.ts +51 -0
  18. package/dist/types/finance/bond.d.ts +45 -0
  19. package/dist/types/finance/cashflow.d.ts +44 -0
  20. package/dist/types/finance/depreciation.d.ts +26 -0
  21. package/dist/types/finance/index.d.ts +16 -0
  22. package/dist/types/finance/indicators.d.ts +76 -0
  23. package/dist/types/finance/interest.d.ts +44 -0
  24. package/dist/types/finance/returns.d.ts +43 -0
  25. package/dist/types/finance/tvm.d.ts +50 -0
  26. package/dist/types/geo/angle.d.ts +4 -0
  27. package/dist/types/geo/bbox.d.ts +32 -0
  28. package/dist/types/geo/distance.d.ts +67 -0
  29. package/dist/types/geo/geohash.d.ts +63 -0
  30. package/dist/types/geo/geojson.d.ts +108 -0
  31. package/dist/types/geo/index-tree.d.ts +52 -0
  32. package/dist/types/geo/index.d.ts +11 -0
  33. package/dist/types/geo/mercator.d.ts +57 -0
  34. package/dist/types/geo/predicates.d.ts +44 -0
  35. package/dist/types/geo/ring.d.ts +80 -0
  36. package/dist/types/geo/simplify.d.ts +31 -0
  37. package/dist/types/geo/valid.d.ts +24 -0
  38. package/dist/types/geo/wkt.d.ts +16 -0
  39. package/dist/types/math/float64.d.ts +126 -3
  40. package/dist/types/math/format.d.ts +58 -0
  41. package/dist/types/math/index.d.ts +5 -0
  42. package/dist/types/math/mat4.d.ts +60 -0
  43. package/dist/types/math/project.d.ts +54 -0
  44. package/dist/types/math/solve.d.ts +67 -0
  45. package/dist/types/math/word.d.ts +82 -0
  46. package/dist/types/message.d.ts +52 -0
  47. package/dist/types/object.d.ts +151 -0
  48. package/dist/types/scan.d.ts +30 -0
  49. package/dist/types/schema.d.ts +24 -0
  50. package/dist/types/string.d.ts +96 -0
  51. package/dist/types/text/base64.d.ts +6 -4
  52. package/dist/types/text/email.d.ts +0 -1
  53. package/dist/types/text/host.d.ts +39 -8
  54. package/dist/types/text/i18n.d.ts +13 -1
  55. package/dist/types/text/index.d.ts +1 -0
  56. package/dist/types/text/misc.d.ts +15 -1
  57. package/dist/types/text/punycode.d.ts +45 -85
  58. package/docs/CONVERT.md +45 -0
  59. package/docs/DATES.md +78 -0
  60. package/docs/FINANCE.md +59 -0
  61. package/docs/GEO.md +114 -0
  62. package/docs/MATH.md +75 -0
  63. package/package.json +57 -4
  64. package/src/array.js +16 -0
  65. package/src/cache.js +206 -0
  66. package/src/chunk.js +159 -0
  67. package/src/color.js +125 -0
  68. package/src/convert/convert.js +59 -0
  69. package/src/convert/currency.js +74 -0
  70. package/src/convert/index.js +11 -0
  71. package/src/convert/registry.js +213 -0
  72. package/src/dates/civil.js +360 -0
  73. package/src/dates/duration.js +225 -0
  74. package/src/dates/format.js +238 -0
  75. package/src/dates/index.js +30 -0
  76. package/src/dates/rfc3339.js +621 -0
  77. package/src/errors.js +102 -0
  78. package/src/finance/amortization.js +50 -0
  79. package/src/finance/bond.js +100 -0
  80. package/src/finance/cashflow.js +122 -0
  81. package/src/finance/depreciation.js +54 -0
  82. package/src/finance/index.js +27 -0
  83. package/src/finance/indicators.js +207 -0
  84. package/src/finance/interest.js +63 -0
  85. package/src/finance/returns.js +90 -0
  86. package/src/finance/tvm.js +94 -0
  87. package/src/function.js +0 -2
  88. package/src/geo/angle.js +12 -0
  89. package/src/geo/bbox.js +80 -0
  90. package/src/geo/distance.js +155 -0
  91. package/src/geo/geohash.js +224 -0
  92. package/src/geo/geojson.js +361 -0
  93. package/src/geo/index-tree.js +294 -0
  94. package/src/geo/index.js +52 -0
  95. package/src/geo/mercator.js +124 -0
  96. package/src/geo/predicates.js +313 -0
  97. package/src/geo/ring.js +182 -0
  98. package/src/geo/simplify.js +130 -0
  99. package/src/geo/valid.js +142 -0
  100. package/src/geo/wkt.js +262 -0
  101. package/src/math/float64.js +228 -11
  102. package/src/math/format.js +157 -0
  103. package/src/math/index.js +5 -0
  104. package/src/math/mat4.js +131 -0
  105. package/src/math/project.js +49 -0
  106. package/src/math/solve.js +112 -0
  107. package/src/math/word.js +177 -0
  108. package/src/message.js +119 -0
  109. package/src/object.js +329 -0
  110. package/src/scan.js +42 -0
  111. package/src/schema.js +37 -0
  112. package/src/string.js +189 -0
  113. package/src/text/base64.js +15 -52
  114. package/src/text/email.js +0 -5
  115. package/src/text/host.js +515 -140
  116. package/src/text/i18n.js +13 -6
  117. package/src/text/identifiers.js +1 -1
  118. package/src/text/index.js +1 -0
  119. package/src/text/misc.js +81 -5
  120. package/src/text/punycode.js +235 -323
  121. package/src/dates.js +0 -371
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @file The suite's one bounded-cache primitive. Before this file the
3
+ * FIFO-512 delete-oldest map was written five times (the query engine's
4
+ * string cache, the JSONPath query cache, forms' three pointer caches)
5
+ * with a sixth divergent flush-all variant in forms' regex cache and a
6
+ * true LRU in the view projection memo. One implementation, one policy:
7
+ *
8
+ * - **LRU with recency refresh**: a `get` hit re-inserts the entry, so
9
+ * the evicted entry is the least recently USED, not the oldest
10
+ * inserted. (FIFO versus LRU was never result-observable at any call
11
+ * site — both bound memory; LRU keeps hot entries hotter.)
12
+ * - **Evict at `size >= limit` before inserting a new key**, so the
13
+ * cache never holds more than `limit` entries. (The old sites
14
+ * disagreed between `>=`-before and `>`-after; the capacity is the
15
+ * same, the invariant here is simply "never above `limit`".)
16
+ * - `undefined` is the miss sentinel: a cache MUST NOT store
17
+ * `undefined` as a value (store `null` for "computed, negative" —
18
+ * the regex cache does exactly that).
19
+ *
20
+ * The identity axis is {@link createWeakCache}: reference-keyed,
21
+ * unbounded by design because a WeakMap is bounded by its keys'
22
+ * lifetimes — an entry dies with its key. Compose the two for
23
+ * "per-registry bounded caches" (a WeakMap of bounded caches).
24
+ *
25
+ * The VALUE axis is {@link createSemanticCache}: keyed by what a
26
+ * plain-data value IS rather than by a fingerprint of it, for the caches
27
+ * whose entries carry meaning (a compiled query, a query plan, a
28
+ * registered function body) and where serving the wrong entry is wrong
29
+ * data rather than a slow repaint.
30
+ */
31
+ export type BoundedCache<K, V> = {
32
+ /**
33
+ * - Lookup; a hit refreshes
34
+ * recency. `undefined` means miss.
35
+ */
36
+ get: (key: K) => V | undefined;
37
+ /**
38
+ * - Insert or refresh; evicts
39
+ * the least recently used entry when the cache is full.
40
+ */
41
+ set: (key: K, value: V) => void;
42
+ /**
43
+ * - Lookup
44
+ * or compute-and-insert in one step.
45
+ */
46
+ getOrCreate: (key: K, create: (key: K) => V) => V;
47
+ /**
48
+ * - Drop every entry.
49
+ */
50
+ clear: () => void;
51
+ /**
52
+ * - Current entry count.
53
+ */
54
+ size: () => number;
55
+ };
56
+ /**
57
+ * @template K, V
58
+ * @typedef {object} BoundedCache
59
+ * @property {(key: K) => V | undefined} get - Lookup; a hit refreshes
60
+ * recency. `undefined` means miss.
61
+ * @property {(key: K, value: V) => void} set - Insert or refresh; evicts
62
+ * the least recently used entry when the cache is full.
63
+ * @property {(key: K, create: (key: K) => V) => V} getOrCreate - Lookup
64
+ * or compute-and-insert in one step.
65
+ * @property {() => void} clear - Drop every entry.
66
+ * @property {() => number} size - Current entry count.
67
+ */
68
+ /**
69
+ * A bounded, string-or-value-keyed LRU cache.
70
+ * @template K, V
71
+ * @param {number} limit - Maximum number of retained entries (> 0)
72
+ * @returns {BoundedCache<K, V>}
73
+ */
74
+ export declare function createBoundedCache<K, V>(limit: number): BoundedCache<K, V>;
75
+ export type SemanticCache<V> = {
76
+ /**
77
+ * - Lookup by structural
78
+ * identity. `undefined` means miss — including "this value cannot be
79
+ * keyed", which is a permanent miss, never someone else's entry.
80
+ */
81
+ get: (value: any) => V | undefined;
82
+ /**
83
+ * - Insert or refresh.
84
+ * Returns whether the entry was retained — `false` means the value
85
+ * could not be keyed, which a caller comparing sizes to detect an
86
+ * eviction must not mistake for one.
87
+ */
88
+ set: (value: any, entry: V) => boolean;
89
+ /**
90
+ * - Lookup or compute-and-insert in one step. `create`
91
+ * receives the identity, or `null` when the value was unkeyable and
92
+ * the result will NOT be retained.
93
+ */
94
+ getOrCreate: (value: any, create: (identity: string | null) => V) => V;
95
+ /**
96
+ * - Drop every entry.
97
+ */
98
+ clear: () => void;
99
+ /**
100
+ * - Current entry count.
101
+ */
102
+ size: () => number;
103
+ };
104
+ /**
105
+ * @template V
106
+ * @typedef {object} SemanticCache
107
+ * @property {(value: any) => V | undefined} get - Lookup by structural
108
+ * identity. `undefined` means miss — including "this value cannot be
109
+ * keyed", which is a permanent miss, never someone else's entry.
110
+ * @property {(value: any, entry: V) => boolean} set - Insert or refresh.
111
+ * Returns whether the entry was retained — `false` means the value
112
+ * could not be keyed, which a caller comparing sizes to detect an
113
+ * eviction must not mistake for one.
114
+ * @property {(value: any, create: (identity: string | null) => V) => V}
115
+ * getOrCreate - Lookup or compute-and-insert in one step. `create`
116
+ * receives the identity, or `null` when the value was unkeyable and
117
+ * the result will NOT be retained.
118
+ * @property {() => void} clear - Drop every entry.
119
+ * @property {() => number} size - Current entry count.
120
+ */
121
+ /**
122
+ * A bounded cache keyed by a value's COLLISION-FREE structural identity
123
+ * ({@link semanticKey}), for entries whose reuse decides a result.
124
+ *
125
+ * Two guarantees a fingerprint-keyed cache cannot give:
126
+ *
127
+ * - **Distinct inputs never share an entry.** The key is the whole
128
+ * deterministic serialization, so a hash collision cannot make one
129
+ * document's compiled semantics answer for another's.
130
+ * - **An unkeyable input is a permanent miss, never a wrong hit.** A
131
+ * value carrying a cycle, a function or a class instance cannot be
132
+ * keyed injectively, so it is computed afresh every time and never
133
+ * retained. Correct and slow beats fast and wrong; callers that want
134
+ * to *reject* such input should validate before asking the cache.
135
+ *
136
+ * Compose with a discriminating tuple when an entry depends on more than
137
+ * the document — `cache.getOrCreate([document, dialect, strict], …)`
138
+ * keys the whole tuple, which a `${a}|${b}` string concatenation cannot
139
+ * do injectively once `a` may itself contain the separator.
140
+ * @template V
141
+ * @param {number} limit - Maximum number of retained entries (> 0)
142
+ * @returns {SemanticCache<V>}
143
+ */
144
+ export declare function createSemanticCache<V>(limit: number): SemanticCache<V>;
145
+ export type WeakCache<K extends object, V> = {
146
+ get: (key: K) => V | undefined;
147
+ set: (key: K, value: V) => void;
148
+ getOrCreate: (key: K, create: (key: K) => V) => V;
149
+ };
150
+ /**
151
+ * @template {object} K
152
+ * @template V
153
+ * @typedef {object} WeakCache
154
+ * @property {(key: K) => V | undefined} get
155
+ * @property {(key: K, value: V) => void} set
156
+ * @property {(key: K, create: (key: K) => V) => V} getOrCreate
157
+ */
158
+ /**
159
+ * The identity axis: a reference-keyed cache whose entries live exactly
160
+ * as long as their keys. No bound, deliberately — the bound is the
161
+ * caller's ownership of the key objects.
162
+ * @template {object} K
163
+ * @template V
164
+ * @returns {WeakCache<K, V>}
165
+ */
166
+ export declare function createWeakCache<K extends object, V>(): WeakCache<K, V>;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Cutting text down to size: the four operations every consumer of a
3
+ * too-large value needs, in one place.
4
+ *
5
+ * They live together because they are one decision made four ways —
6
+ * *how much of this do I carry?* `sizeOf` answers what it costs,
7
+ * `excerpt` shows a line of it, `truncate` cuts it at a boundary and
8
+ * says so, and `chunkText` splits it into pieces that can each be
9
+ * carried, addressed and fetched on their own.
10
+ *
11
+ * Two rules hold across all four:
12
+ *
13
+ * - **Characters, not bytes and not tokens.** Tokens are
14
+ * provider-private and bytes depend on an encoding nobody has agreed
15
+ * on at this layer; characters are deterministic, and a budget stated
16
+ * in them holds exactly.
17
+ * - **A cut is always visible.** `excerpt` appends an ellipsis and
18
+ * `truncate` a named marker, so nothing downstream — a reader, a
19
+ * model, a diff — mistakes a cut tail for the end of the content.
20
+ *
21
+ * Nothing here allocates a copy it does not return: `chunkText` slices,
22
+ * and a caller streaming a corpus can write each piece and drop it.
23
+ */
24
+ /**
25
+ * The size of a value as the characters it will occupy in a request: a
26
+ * string is its own length, anything else is the length of its JSON
27
+ * encoding. That is the one size rule the suite budgets against — an
28
+ * agent's history budget, a slot's metadata, a digest's cap — and it is
29
+ * exact rather than an estimate, which is what makes those budgets hold
30
+ * to the character instead of approximately.
31
+ * @param {any} value
32
+ * @returns {number}
33
+ */
34
+ export declare function sizeOf(value: any): number;
35
+ /**
36
+ * A one-line excerpt: whitespace collapsed to single spaces, trimmed,
37
+ * hard-capped, with an ellipsis when anything was dropped. Total over
38
+ * `null`/`undefined`/non-strings, because it is called on content a
39
+ * provider or a store may have left absent.
40
+ * @param {any} text
41
+ * @param {number} max - characters kept, before the ellipsis
42
+ * @returns {string}
43
+ */
44
+ export declare function excerpt(text: any, max: number): string;
45
+ /**
46
+ * A hard cut with a named suffix, preserving newlines — what an
47
+ * oversized result or an over-long generated block is reduced to. The
48
+ * suffix is part of the contract: a cut that looked like an ending would
49
+ * be read as one.
50
+ * @param {string} text
51
+ * @param {number} max
52
+ * @param {string} [marker] - the suffix appended when a cut happened
53
+ * @returns {string}
54
+ */
55
+ export declare function truncate(text: string, max: number, marker?: string): string;
56
+ export type ChunkStrategy = 'size' | 'line' | 'separator';
57
+ /**
58
+ * How a text is cut into pieces.
59
+ *
60
+ * - `size` — fixed character windows. The only strategy that will split
61
+ * a word, and the only one whose piece count is exactly predictable.
62
+ * - `line` — whole lines, grouped up to `size`. A line longer than
63
+ * `size` becomes its own oversized piece rather than being split:
64
+ * breaking a line is the one thing a line strategy may not do.
65
+ * - `separator` — units delimited by `separator` (paragraphs, records),
66
+ * grouped up to `size`, separators preserved between units in a
67
+ * piece. Same rule for an oversized unit.
68
+ *
69
+ * @typedef {'size' | 'line' | 'separator'} ChunkStrategy
70
+ */
71
+ /**
72
+ * Split a text into addressable pieces.
73
+ *
74
+ * Deterministic and total: the same text and the same options always
75
+ * produce the same pieces, with the same offsets, which is what lets a
76
+ * caller name a piece by its index and re-derive that name later instead
77
+ * of storing a mapping. Empty text produces no pieces at all — an empty
78
+ * piece is not a thing anyone wants an address for.
79
+ *
80
+ * `overlap` (character strategy only) repeats the tail of each piece at
81
+ * the head of the next, so a match spanning a cut is still found whole
82
+ * in one piece. It costs storage proportional to `overlap / size` and is
83
+ * off by default.
84
+ *
85
+ * @param {string} text
86
+ * @param {{ strategy?: ChunkStrategy, size?: number, overlap?: number,
87
+ * separator?: string }} [options]
88
+ * @returns {Array<{ index: number, start: number, end: number, text: string }>}
89
+ * `start`/`end` are offsets into the original text, so a piece can be
90
+ * located in the source it came from.
91
+ */
92
+ export declare function chunkText(text: string, options?: {
93
+ strategy?: ChunkStrategy;
94
+ size?: number;
95
+ overlap?: number;
96
+ separator?: string;
97
+ }): Array<{
98
+ index: number;
99
+ start: number;
100
+ end: number;
101
+ text: string;
102
+ }>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @file Pure color helpers — dependency-free numeric math over color
3
+ * strings, with no view or DOM knowledge. The one home for the suite's
4
+ * color primitives (currently the hex `#rrggbb` interpolation used by the
5
+ * calc surface shader).
6
+ */
7
+ /**
8
+ * Linear-interpolate two `#rrggbb` hex colors channel-by-channel and
9
+ * return the result as `#rrggbb`. `t` is the blend factor (0 → `a`,
10
+ * 1 → `b`); values outside `[0, 1]` extrapolate.
11
+ *
12
+ * @param {string} a a `#rrggbb` hex color
13
+ * @param {string} b a `#rrggbb` hex color
14
+ * @param {number} t blend factor
15
+ * @returns {string} the interpolated `#rrggbb` color
16
+ */
17
+ export declare function lerpColor(a: string, b: string, t: number): string;
18
+ /**
19
+ * WCAG relative luminance of a `#rrggbb` hex color: 0 for black, 1 for
20
+ * white. The sRGB channels are linearized (the 2.4-exponent transfer
21
+ * curve with its linear toe) and weighted per ITU-R BT.709. Use it to
22
+ * pick a legible ink over an arbitrary fill (light ink below ~0.4,
23
+ * dark ink above).
24
+ *
25
+ * @param {string} hex a `#rrggbb` hex color
26
+ * @returns {number} relative luminance in [0, 1]
27
+ */
28
+ export declare function relativeLuminance(hex: string): number;
29
+ /**
30
+ * A legible ink for text set INSIDE a concrete fill: near-black on light
31
+ * fills, white on dark ones.
32
+ *
33
+ * Keyed off the fill's luminance, **not** the theme. That distinction is the
34
+ * whole point: a fill the author named is a constant, so it does not follow
35
+ * light/dark — and if the ink does, a light fill under a dark theme ends up
36
+ * with light text on it and the label disappears.
37
+ *
38
+ * The choice is made by comparing the two candidates' actual WCAG contrast
39
+ * rather than by a luminance threshold. A threshold is subtly wrong in the
40
+ * mid-tones: at 0.4 a fill like `#87b496` took dark ink and landed at 2.33:1,
41
+ * well under AA, because the crossover between the two inks is near 0.179 and
42
+ * not where a round number puts it. Picking the better of two is optimal by
43
+ * construction, so the worst case over the whole RGB cube is ~4.6:1 — which
44
+ * clears AA with nothing to tune.
45
+ * @param {string} fillHex - The `#rgb` or `#rrggbb` fill under the text
46
+ * @returns {string} `#1f2020` or `#ffffff`
47
+ */
48
+ export declare function inkFor(fillHex: string): string;
49
+ /**
50
+ * The WCAG contrast ratio between two relative luminances, from 1 (identical)
51
+ * to 21 (black on white).
52
+ * @param {number} a
53
+ * @param {number} b
54
+ * @returns {number}
55
+ */
56
+ export declare function contrastOf(a: number, b: number): number;
57
+ /**
58
+ * Whether a CSS color value is a concrete hex this module can reason about.
59
+ * A `var(...)`, a named color or a function is not: it may resolve to
60
+ * anything at paint time, so the caller must fall back to its theme.
61
+ * @param {string} value
62
+ * @returns {boolean}
63
+ */
64
+ export declare function isHexColor(value: string): boolean;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @file The static conversion engine. Pure, single-pass,
3
+ * no per-call allocation. Rejects cross-dimension conversions with a
4
+ * clear error.
5
+ */
6
+ /**
7
+ * Convert `value` from unit `fromId` to unit `toId` (same dimension).
8
+ * Affine through the dimension base: `base = value*factor + offset`,
9
+ * `target = (base - offset')/factor'`.
10
+ *
11
+ * @param {number} value
12
+ * @param {string} fromId
13
+ * @param {string} toId
14
+ * @returns {number}
15
+ */
16
+ export declare function convert(value: number, fromId: string, toId: string): number;
17
+ /**
18
+ * The unit list for a dimension (in registry order).
19
+ * @param {string} dimension
20
+ * @returns {import('./registry.js').Unit[]}
21
+ */
22
+ export declare function unitsOf(dimension: string): import('./registry.js').Unit[];
23
+ /**
24
+ * All dimension names.
25
+ * @returns {string[]}
26
+ */
27
+ export declare function dimensions(): string[];
28
+ /**
29
+ * Look up the dimension a unit belongs to (or `null`).
30
+ * @param {string} unitId
31
+ * @returns {string|null}
32
+ */
33
+ export declare function dimensionOf(unitId: string): string | null;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @file The pure currency-conversion primitive.
3
+ * `convertCurrency` is deterministic and side-effect
4
+ * free: the caller supplies the `{ code: rate }` table; core NEVER
5
+ * fetches it. This lives in core because the dividing line is **purity**,
6
+ * not constant-vs-variable factors — this function is exactly as pure as
7
+ * the fixed-factor path, it just receives its factors as an argument.
8
+ * Fetching the table is the impure half and stays in the app component.
9
+ *
10
+ * Rate convention: `rateTable[code]` is the value of ONE unit of `code`
11
+ * expressed in the table's base currency (so the base currency has rate
12
+ * 1). To convert `value` from → to:
13
+ *
14
+ * result = value * rate[from] / rate[to]
15
+ */
16
+ export type RateTable = {
17
+ /**
18
+ * the base currency code (informational)
19
+ */
20
+ base?: string;
21
+ /**
22
+ * code → value-in-base
23
+ */
24
+ rates: Record<string, number>;
25
+ /**
26
+ * timestamp of the table (informational)
27
+ */
28
+ at?: number | string;
29
+ };
30
+ /**
31
+ * Convert an amount between currencies using a supplied rate table.
32
+ * Throws a `RangeError` when either code is missing from the table (the
33
+ * component decides how to fall back — a static table, a last-good cache).
34
+ *
35
+ * @param {number} value
36
+ * @param {string} from
37
+ * @param {string} to
38
+ * @param {RateTable | Record<string, number>} rateTable
39
+ * @returns {number}
40
+ */
41
+ export declare function convertCurrency(value: number, from: string, to: string, rateTable: RateTable | Record<string, number>): number;
42
+ /**
43
+ * The currency codes available in a rate table.
44
+ * @param {RateTable | Record<string, number>} rateTable
45
+ * @returns {string[]}
46
+ */
47
+ export declare function currenciesOf(rateTable: RateTable | Record<string, number>): string[];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @file `@jarenjs/core/convert` — pure, deterministic quantity
3
+ * conversion. Fixed-factor dimensional conversion (`convert`) plus the
4
+ * pure rate-table primitive `convertCurrency` (the caller supplies the
5
+ * table; core never fetches). Zero runtime dependencies.
6
+ */
7
+ export { DIMENSIONS, UNIT_INDEX } from './registry.js';
8
+ export { convert, unitsOf, dimensions, dimensionOf } from './convert.js';
9
+ export { convertCurrency, currenciesOf } from './currency.js';
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file Unit registry. Fixed-factor unit definitions per
3
+ * **dimension**. Conversion is affine through the dimension's base unit:
4
+ *
5
+ * base = value * factor + offset
6
+ * target = (base - offset') / factor'
7
+ *
8
+ * Most dimensions are linear (`offset` absent = 0); **temperature** is
9
+ * affine (°C/°F/K/°R). Digital storage ships both binary (KiB/MiB, 1024)
10
+ * and decimal (KB/MB, 1000) prefixes.
11
+ *
12
+ * Number-base conversion (HEX/DEC/OCT/BIN) is intentionally NOT here — it
13
+ * is `@jarenjs/core/math/word.js` (`toBase`/`fromBase`), which the
14
+ * converter delegates to. Fuel-economy (mpg ↔ L/100km) is non-affine and
15
+ * likewise out of this data-driven model.
16
+ */
17
+ export type Unit = {
18
+ /**
19
+ * unique unit id
20
+ */
21
+ id: string;
22
+ /**
23
+ * display symbol
24
+ */
25
+ symbol: string;
26
+ /**
27
+ * multiply to reach the base unit
28
+ */
29
+ factor: number;
30
+ /**
31
+ * affine offset toward the base unit
32
+ */
33
+ offset?: number;
34
+ };
35
+ /**
36
+ * @typedef {object} Unit
37
+ * @property {string} id unique unit id
38
+ * @property {string} symbol display symbol
39
+ * @property {number} factor multiply to reach the base unit
40
+ * @property {number} [offset] affine offset toward the base unit
41
+ */
42
+ /** @type {Record<string, { base: string, units: Unit[] }>} */
43
+ export declare const DIMENSIONS: Record<string, {
44
+ base: string;
45
+ units: Unit[];
46
+ }>;
47
+ /**
48
+ * Reverse index: unit id → `{ dimension, unit }`. Built once at module
49
+ * load; ids are unique across dimensions.
50
+ * @type {Map<string, { dimension: string, unit: Unit }>}
51
+ */
52
+ export declare const UNIT_INDEX: Map<string, {
53
+ dimension: string;
54
+ unit: Unit;
55
+ }>;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Whether a proleptic Gregorian year is a leap year.
3
+ * @param {number} year
4
+ * @returns {boolean}
5
+ */
6
+ export declare function isLeapYear(year: number): boolean;
7
+ /**
8
+ * Length of a month in days.
9
+ * @param {number} year
10
+ * @param {number} month - 1-12
11
+ * @returns {number}
12
+ */
13
+ export declare function daysInMonth(year: number, month: number): number;
14
+ /**
15
+ * Days since 1970-01-01 for a civil date (Howard Hinnant's algorithm).
16
+ * Exact for every proleptic Gregorian date; no `Date`, no allocation.
17
+ * @param {number} y - year
18
+ * @param {number} m - month 1-12
19
+ * @param {number} d - day 1-31
20
+ * @returns {number} day number, negative before 1970
21
+ */
22
+ export declare function daysFromCivil(y: number, m: number, d: number): number;
23
+ /**
24
+ * The inverse of {@link daysFromCivil}: a civil date from a day number.
25
+ * @param {number} z - days since 1970-01-01
26
+ * @returns {{ year: number, month: number, day: number }}
27
+ */
28
+ export declare function civilFromDays(z: number): {
29
+ year: number;
30
+ month: number;
31
+ day: number;
32
+ };
33
+ /**
34
+ * Day of the week for a day number: 0 = Sunday … 6 = Saturday, matching
35
+ * `Date.prototype.getUTCDay`.
36
+ * @param {number} z - days since 1970-01-01
37
+ * @returns {number} 0-6
38
+ */
39
+ export declare function weekdayFromDays(z: number): number;
40
+ /**
41
+ * ISO 8601 weekday: 1 = Monday … 7 = Sunday.
42
+ * @param {number} z - days since 1970-01-01
43
+ * @returns {number} 1-7
44
+ */
45
+ export declare function isoWeekdayFromDays(z: number): number;
46
+ /**
47
+ * Day of the year, 1-based (1-366).
48
+ * @param {{ year: number, month: number, day: number }} parts
49
+ * @returns {number}
50
+ */
51
+ export declare function dayOfYear(parts: {
52
+ year: number;
53
+ month: number;
54
+ day: number;
55
+ }): number;
56
+ /**
57
+ * Calendar quarter, 1-4.
58
+ * @param {{ month: number }} parts
59
+ * @returns {number}
60
+ */
61
+ export declare function quarterOfYear(parts: {
62
+ month: number;
63
+ }): number;
64
+ /**
65
+ * ISO 8601 week-numbering week and its year. The ISO year is not always
66
+ * the calendar year: 2027-01-01 is a Friday and belongs to week 53 of
67
+ * 2026, so the pair has to be returned together.
68
+ * @param {{ year: number, month: number, day: number }} parts
69
+ * @returns {{ year: number, week: number }}
70
+ */
71
+ export declare function isoWeekOfYear(parts: {
72
+ year: number;
73
+ month: number;
74
+ day: number;
75
+ }): {
76
+ year: number;
77
+ week: number;
78
+ };
79
+ /**
80
+ * The parts record of an instant, the inverse of
81
+ * `epochOfRFC3339Parts` (rfc3339.js).
82
+ *
83
+ * `offset` selects the wall clock the fields are read on: 0 (the
84
+ * default) gives UTC, 120 gives the clock in `+02:00`. The returned
85
+ * record carries that offset, so rendering it back with
86
+ * `formatRFC3339Parts` yields the same instant spelled in that zone.
87
+ *
88
+ * @param {number} ms - milliseconds since 1970-01-01T00:00:00Z
89
+ * @param {number} [offset] - minutes east of UTC to read the clock in
90
+ * @returns {object} a parts record, always with a time half
91
+ */
92
+ export declare function partsFromEpoch(ms: number, offset?: number): object;
93
+ /**
94
+ * The calendar units every unit-taking function in this module accepts.
95
+ * Ordered coarse to fine, which is the order `startOfParts` truncates in.
96
+ */
97
+ export declare const DATE_UNITS: readonly string[];
98
+ /**
99
+ * Whether a string names a calendar unit.
100
+ * @param {any} unit
101
+ * @returns {boolean}
102
+ */
103
+ export declare function isDateUnit(unit: any): boolean;
104
+ /**
105
+ * Milliseconds in a fixed-width unit, or 0 for the calendar units
106
+ * (`month`, `quarter`, `year`) that do not have one.
107
+ * @param {string} unit
108
+ * @returns {number}
109
+ */
110
+ export declare function fixedUnitMs(unit: string): number;
111
+ /**
112
+ * Add a signed amount of calendar units to a parts record, returning a
113
+ * new one. The input is never mutated and its lexical shape is kept: a
114
+ * full-date stays a full-date, and a value keeps its own UTC offset
115
+ * rather than being normalized.
116
+ *
117
+ * Month and year arithmetic **clamps** to the end of the target month —
118
+ * 2026-01-31 plus one month is 2026-02-28 — which is the rule every
119
+ * mainstream date library uses, because the alternative (overflowing
120
+ * into March) makes `add(1, 'month')` non-monotonic.
121
+ *
122
+ * @param {object} parts - a parts record from `parseRFC3339Parts`
123
+ * @param {number} amount - signed count, may be fractional only for
124
+ * fixed-width units (a fractional month has no meaning)
125
+ * @param {string} unit - a {@link DATE_UNITS} member
126
+ * @returns {object} a new parts record
127
+ */
128
+ export declare function addToParts(parts: object, amount: number, unit: string): object;
129
+ /**
130
+ * Truncate a parts record to the start of a calendar unit, returning a
131
+ * new one. `week` starts on Monday (ISO 8601).
132
+ * @param {object} parts - a parts record
133
+ * @param {string} unit - a {@link DATE_UNITS} member
134
+ * @returns {object} a new parts record
135
+ */
136
+ export declare function startOfParts(parts: object, unit: string): object;
137
+ /**
138
+ * The last representable instant inside a calendar unit: the start of
139
+ * the next unit less one millisecond. A value with no time half is
140
+ * truncated to the unit's last *day* instead, so a full-date stays a
141
+ * full-date.
142
+ * @param {object} parts - a parts record
143
+ * @param {string} unit - a {@link DATE_UNITS} member
144
+ * @returns {object} a new parts record
145
+ */
146
+ export declare function endOfParts(parts: object, unit: string): object;