@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
|
@@ -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;
|