@jarenjs/core 0.46.5 → 0.56.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 +122 -9
- package/README.md +50 -3
- package/dist/types/async.d.ts +39 -0
- package/dist/types/dates/civil.d.ts +24 -5
- package/dist/types/dates/index.d.ts +2 -0
- package/dist/types/dates/parse.d.ts +47 -0
- package/dist/types/dates/ticks.d.ts +59 -0
- package/dist/types/math/float64.d.ts +13 -0
- package/dist/types/random.d.ts +80 -0
- package/dist/types/series/asof.d.ts +88 -0
- package/dist/types/series/bucket.d.ts +206 -0
- package/dist/types/series/downsample.d.ts +53 -0
- package/dist/types/series/index.d.ts +8 -0
- package/dist/types/series/interval-index.d.ts +47 -0
- package/dist/types/series/interval.d.ts +170 -0
- package/dist/types/series/normalize.d.ts +190 -0
- package/dist/types/series/rolling.d.ts +67 -0
- package/dist/types/series/selector.d.ts +29 -0
- package/dist/types/series/zone.d.ts +59 -0
- package/dist/types/stats.d.ts +73 -0
- package/docs/DATES.md +77 -1
- package/docs/GEO.md +2 -2
- package/docs/SERIES.md +342 -0
- package/package.json +21 -1
- package/src/async.js +78 -0
- package/src/dates/civil.js +136 -41
- package/src/dates/index.js +4 -0
- package/src/dates/parse.js +410 -0
- package/src/dates/ticks.js +184 -0
- package/src/math/float64.js +29 -0
- package/src/random.js +125 -0
- package/src/series/asof.js +276 -0
- package/src/series/bucket.js +542 -0
- package/src/series/downsample.js +343 -0
- package/src/series/index.js +86 -0
- package/src/series/interval-index.js +181 -0
- package/src/series/interval.js +374 -0
- package/src/series/normalize.js +330 -0
- package/src/series/rolling.js +229 -0
- package/src/series/selector.js +104 -0
- package/src/series/zone.js +188 -0
- package/src/stats.js +133 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
export type Sample = {
|
|
2
|
+
/**
|
|
3
|
+
* - Unix epoch milliseconds
|
|
4
|
+
*/
|
|
5
|
+
at: number;
|
|
6
|
+
/**
|
|
7
|
+
* - the reading, or `null` for a
|
|
8
|
+
* measured gap
|
|
9
|
+
*/
|
|
10
|
+
value: number | null;
|
|
11
|
+
};
|
|
12
|
+
export type Interval = {
|
|
13
|
+
/**
|
|
14
|
+
* - inclusive lower bound, epoch milliseconds
|
|
15
|
+
*/
|
|
16
|
+
start: number;
|
|
17
|
+
/**
|
|
18
|
+
* - exclusive upper bound, epoch milliseconds
|
|
19
|
+
*/
|
|
20
|
+
end: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} Sample
|
|
24
|
+
* @property {number} at - Unix epoch milliseconds
|
|
25
|
+
* @property {number | null} value - the reading, or `null` for a
|
|
26
|
+
* measured gap
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} Interval
|
|
30
|
+
* @property {number} start - inclusive lower bound, epoch milliseconds
|
|
31
|
+
* @property {number} end - exclusive upper bound, epoch milliseconds
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* The instant `value` names, in Unix epoch milliseconds.
|
|
35
|
+
*
|
|
36
|
+
* Accepts the two forms a date has in this suite: a finite **number**
|
|
37
|
+
* (already epoch milliseconds, returned unchanged) and a valid **RFC
|
|
38
|
+
* 3339 string**. A full-date reads as UTC midnight, an offset shifts to
|
|
39
|
+
* the instant it names, and `Z` is UTC.
|
|
40
|
+
*
|
|
41
|
+
* Refused, all as `TypeError`: a full-time (`'09:30:00Z'` names no day,
|
|
42
|
+
* and inventing one would be a hidden clock), a date-time without an
|
|
43
|
+
* offset (`'2026-01-01T09:30:00'` names no instant without a zone),
|
|
44
|
+
* `NaN`/`Infinity`, a `Date` object (dates are strings or numbers here,
|
|
45
|
+
* never a wrapper), and anything else.
|
|
46
|
+
*
|
|
47
|
+
* @param {number | string} value
|
|
48
|
+
* @returns {number} epoch milliseconds
|
|
49
|
+
* @throws {TypeError} when `value` names no instant
|
|
50
|
+
* @example
|
|
51
|
+
* toEpoch(0); // 0
|
|
52
|
+
* toEpoch('1970-01-01'); // 0
|
|
53
|
+
* toEpoch('1970-01-01T01:00:00+01:00'); // 0
|
|
54
|
+
*/
|
|
55
|
+
export declare function toEpoch(value: number | string): number;
|
|
56
|
+
/**
|
|
57
|
+
* One instant read out of a row, with the row named when it is not one.
|
|
58
|
+
* "not an RFC 3339 instant" is a puzzle when ten thousand rows were
|
|
59
|
+
* handed in; "row 4172, at: …" is a defect someone can go and look at.
|
|
60
|
+
*
|
|
61
|
+
* @param {any} value - the raw member
|
|
62
|
+
* @param {string} role - the member's name, for the message
|
|
63
|
+
* @param {number} index - the row's position
|
|
64
|
+
* @returns {number} epoch milliseconds
|
|
65
|
+
* @throws {TypeError} when `value` names no instant
|
|
66
|
+
*/
|
|
67
|
+
export declare function epochAt(value: any, role: string, index: number): number;
|
|
68
|
+
/**
|
|
69
|
+
* Sorted canonical samples: every row's instant converted once, its
|
|
70
|
+
* value checked, its other members kept, and the whole ascending by
|
|
71
|
+
* instant.
|
|
72
|
+
*
|
|
73
|
+
* The sort is **stable**, so rows sharing an instant come out in input
|
|
74
|
+
* order — and both are present, because a duplicate instant is two
|
|
75
|
+
* readings, not one row written twice.
|
|
76
|
+
*
|
|
77
|
+
* Each result is a shallow copy of its source row with canonical `at`
|
|
78
|
+
* and `value` members written over it, so a source that spelled its
|
|
79
|
+
* instant `on` keeps `on` too and nothing a caller attached is lost.
|
|
80
|
+
*
|
|
81
|
+
* @param {any[]} rows - the source rows
|
|
82
|
+
* @param {Object} [options]
|
|
83
|
+
* @param {string | ((item: any, index: number) => any)} [options.at]
|
|
84
|
+
* where the instant lives (default `'at'`)
|
|
85
|
+
* @param {string | ((item: any, index: number) => any)} [options.value]
|
|
86
|
+
* where the reading lives (default `'value'`)
|
|
87
|
+
* @returns {(Sample & Record<string, any>)[]} a new array of new records
|
|
88
|
+
* @throws {TypeError} for a non-array, a row that is not an object, an
|
|
89
|
+
* instant that names none, or a value that is neither a finite number
|
|
90
|
+
* nor `null`
|
|
91
|
+
* @example
|
|
92
|
+
* normalizeSeries([{ on: '2026-01-01T00:00:01Z', v: 2 },
|
|
93
|
+
* { on: '2026-01-01T00:00:00Z', v: 1 }],
|
|
94
|
+
* { at: 'on', value: 'v' });
|
|
95
|
+
* // [{ on: '…:00Z', v: 1, at: 1767225600000, value: 1 },
|
|
96
|
+
* // { on: '…:01Z', v: 2, at: 1767225601000, value: 2 }]
|
|
97
|
+
*/
|
|
98
|
+
export declare function normalizeSeries(rows: any[], options?: {
|
|
99
|
+
at?: string | ((item: any, index: number) => any);
|
|
100
|
+
value?: string | ((item: any, index: number) => any);
|
|
101
|
+
}): (Sample & Record<string, any>)[];
|
|
102
|
+
/**
|
|
103
|
+
* The same canonical samples, without the copy when there is nothing to
|
|
104
|
+
* convert.
|
|
105
|
+
*
|
|
106
|
+
* {@link normalizeSeries} always builds a new array of new records,
|
|
107
|
+
* which is the right answer at the door and the wrong one three kernels
|
|
108
|
+
* later: bucketing, rolling and joining all take a series that a caller
|
|
109
|
+
* usually normalized once already, and re-copying a hundred thousand
|
|
110
|
+
* rows per operation costs more than the operation. So this checks
|
|
111
|
+
* instead of converting — one pass, no allocation — and hands the
|
|
112
|
+
* caller's own array straight back when every row is already
|
|
113
|
+
* `{ at: <finite number>, value: <number | null> }` and ascending.
|
|
114
|
+
*
|
|
115
|
+
* Nothing is trusted: a row that fails the check sends the whole array
|
|
116
|
+
* through {@link normalizeSeries}, which refuses it there with the row
|
|
117
|
+
* named. The fast path is a measurement, not a promise.
|
|
118
|
+
*
|
|
119
|
+
* @param {any[]} rows
|
|
120
|
+
* @param {Object} [options]
|
|
121
|
+
* @param {string | ((item: any, index: number) => any)} [options.at]
|
|
122
|
+
* @param {string | ((item: any, index: number) => any)} [options.value]
|
|
123
|
+
* @returns {(Sample & Record<string, any>)[]} `rows` itself, or a new
|
|
124
|
+
* normalized array
|
|
125
|
+
* @throws {TypeError} exactly where {@link normalizeSeries} does
|
|
126
|
+
*/
|
|
127
|
+
export declare function canonicalSeries(rows: any[], options?: {
|
|
128
|
+
at?: string | ((item: any, index: number) => any);
|
|
129
|
+
value?: string | ((item: any, index: number) => any);
|
|
130
|
+
}): (Sample & Record<string, any>)[];
|
|
131
|
+
/**
|
|
132
|
+
* Sorted canonical intervals: every bound converted once, the direction
|
|
133
|
+
* checked, other members kept, and the whole ascending by start.
|
|
134
|
+
*
|
|
135
|
+
* Half-open `[start, end)` with `start < end` is the contract the whole
|
|
136
|
+
* algebra rests on, so an empty (`start === end`), reversed or
|
|
137
|
+
* non-finite interval is refused here rather than producing an answer
|
|
138
|
+
* later that no reader could predict. Duplicates survive — two bookings
|
|
139
|
+
* of the same slot are two bookings — and rows sharing a start keep
|
|
140
|
+
* their input order.
|
|
141
|
+
*
|
|
142
|
+
* @param {any[]} rows - the source rows
|
|
143
|
+
* @param {Object} [options]
|
|
144
|
+
* @param {string | ((item: any, index: number) => any)} [options.start]
|
|
145
|
+
* where the lower bound lives (default `'start'`)
|
|
146
|
+
* @param {string | ((item: any, index: number) => any)} [options.end]
|
|
147
|
+
* where the upper bound lives (default `'end'`)
|
|
148
|
+
* @returns {(Interval & Record<string, any>)[]} a new array of new records
|
|
149
|
+
* @throws {TypeError} for a non-array, a row that is not an object, a
|
|
150
|
+
* bound that names no instant, or `end <= start`
|
|
151
|
+
* @example
|
|
152
|
+
* normalizeIntervals([{ start: '2026-01-01', end: '2026-01-02' }]);
|
|
153
|
+
* // [{ start: 1767225600000, end: 1767312000000 }]
|
|
154
|
+
*/
|
|
155
|
+
export declare function normalizeIntervals(rows: any[], options?: {
|
|
156
|
+
start?: string | ((item: any, index: number) => any);
|
|
157
|
+
end?: string | ((item: any, index: number) => any);
|
|
158
|
+
}): (Interval & Record<string, any>)[];
|
|
159
|
+
/**
|
|
160
|
+
* The index of the first row at or after `at` — the lower end of a
|
|
161
|
+
* half-open cut. `rows` must already be ascending on `key`.
|
|
162
|
+
*
|
|
163
|
+
* This is the whole reason a series is normalized once: a range over a
|
|
164
|
+
* sorted array is two binary searches and a slice, not a pass over
|
|
165
|
+
* everything.
|
|
166
|
+
*
|
|
167
|
+
* @param {any[]} rows - ascending on `key`
|
|
168
|
+
* @param {number} at - epoch milliseconds
|
|
169
|
+
* @param {string} [key] - the member holding the instant (default `'at'`)
|
|
170
|
+
* @returns {number} an index in `[0, rows.length]`
|
|
171
|
+
* @example
|
|
172
|
+
* const lo = lowerBoundTime(samples, start);
|
|
173
|
+
* const hi = lowerBoundTime(samples, end);
|
|
174
|
+
* samples.slice(lo, hi); // every sample in [start, end)
|
|
175
|
+
*/
|
|
176
|
+
export declare function lowerBoundTime(rows: any[], at: number, key?: string): number;
|
|
177
|
+
/**
|
|
178
|
+
* The index of the first row strictly after `at`. `rows` must already
|
|
179
|
+
* be ascending on `key`.
|
|
180
|
+
*
|
|
181
|
+
* The twin of {@link lowerBoundTime}: together they bracket the rows AT
|
|
182
|
+
* an instant (`[lowerBoundTime(rows, t), upperBoundTime(rows, t))`),
|
|
183
|
+
* which is what a duplicate-tolerant as-of has to read.
|
|
184
|
+
*
|
|
185
|
+
* @param {any[]} rows - ascending on `key`
|
|
186
|
+
* @param {number} at - epoch milliseconds
|
|
187
|
+
* @param {string} [key] - the member holding the instant (default `'at'`)
|
|
188
|
+
* @returns {number} an index in `[0, rows.length]`
|
|
189
|
+
*/
|
|
190
|
+
export declare function upperBoundTime(rows: any[], at: number, key?: string): number;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type Sample = import('./normalize.js').Sample;
|
|
2
|
+
/**
|
|
3
|
+
* `rollingSeries`' closed specification: a window measured in time,
|
|
4
|
+
* how much of one counts, the clock a calendar width walks, and where
|
|
5
|
+
* a row keeps its instant and its reading.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ROLLING_MEMBERS: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* A rolling aggregate over a time-width window.
|
|
10
|
+
*
|
|
11
|
+
* Returns one `{ at, value, count }` per input sample, labelled at that
|
|
12
|
+
* sample's own instant, where the window is the half-open span
|
|
13
|
+
* `(at − width, at]`. Two samples at one instant share that window, and
|
|
14
|
+
* so report the same value and count.
|
|
15
|
+
*
|
|
16
|
+
* `count` is the number of **source rows** in the window — duplicates
|
|
17
|
+
* and measured gaps included — and the six value aggregates skip `null`
|
|
18
|
+
* readings, so `value` is `null` exactly when the window had nothing to
|
|
19
|
+
* measure. `aggregate: 'count'` returns that row count as the value.
|
|
20
|
+
*
|
|
21
|
+
* `minPeriods` is the number of source rows the window must hold before
|
|
22
|
+
* a value is reported at all; below it the row is `null` with its real
|
|
23
|
+
* `count`. It counts rows rather than readings, so a window full of
|
|
24
|
+
* measured gaps satisfies it and still reports `null` — which is the
|
|
25
|
+
* honest answer: readings were due, and none of them carried a number.
|
|
26
|
+
*
|
|
27
|
+
* `width` is a fixed duration (`'PT1H'`, `86400000`) or, with a clock,
|
|
28
|
+
* a calendar one (`'P1M'`, and `'P1D'` on a named zone where a day may
|
|
29
|
+
* be 23 or 25 hours long). A calendar window's start is computed by the
|
|
30
|
+
* calendar kernel per output, which is O(1) each — it is never a scan
|
|
31
|
+
* back through the window.
|
|
32
|
+
*
|
|
33
|
+
* @param {any[]} rows - the samples, in any order
|
|
34
|
+
* @param {Object} spec
|
|
35
|
+
* @param {number | string} spec.width - the window's width
|
|
36
|
+
* @param {'sum'|'mean'|'min'|'max'|'first'|'last'|'count'} [spec.aggregate]
|
|
37
|
+
* default `'mean'`
|
|
38
|
+
* @param {number} [spec.minPeriods] - default 1
|
|
39
|
+
* @param {string} [spec.zone] - a named zone, needing `provider`
|
|
40
|
+
* @param {number} [spec.offset] - minutes east of UTC
|
|
41
|
+
* @param {import('./zone.js').ZoneProvider} [spec.provider]
|
|
42
|
+
* @param {'reject'|'earlier'|'later'} [spec.disambiguation]
|
|
43
|
+
* @param {string | ((item: any, index: number) => any)} [spec.at]
|
|
44
|
+
* @param {string | ((item: any, index: number) => any)} [spec.value]
|
|
45
|
+
* @returns {{ at: number, value: number | null, count: number }[]}
|
|
46
|
+
* @throws {TypeError} for a width that is not one positive whole span,
|
|
47
|
+
* an unknown aggregate, a `minPeriods` that is not a positive whole
|
|
48
|
+
* number, or a row that is not a canonical sample
|
|
49
|
+
* @example
|
|
50
|
+
* rollingSeries(readings, { width: 'PT5M', aggregate: 'mean' });
|
|
51
|
+
* rollingSeries(readings, { width: 'PT1M', minPeriods: 60 });
|
|
52
|
+
*/
|
|
53
|
+
export declare function rollingSeries(rows: any[], spec: {
|
|
54
|
+
width: number | string;
|
|
55
|
+
aggregate?: 'sum' | 'mean' | 'min' | 'max' | 'first' | 'last' | 'count';
|
|
56
|
+
minPeriods?: number;
|
|
57
|
+
zone?: string;
|
|
58
|
+
offset?: number;
|
|
59
|
+
provider?: import('./zone.js').ZoneProvider;
|
|
60
|
+
disambiguation?: 'reject' | 'earlier' | 'later';
|
|
61
|
+
at?: string | ((item: any, index: number) => any);
|
|
62
|
+
value?: string | ((item: any, index: number) => any);
|
|
63
|
+
}): {
|
|
64
|
+
at: number;
|
|
65
|
+
value: number | null;
|
|
66
|
+
count: number;
|
|
67
|
+
}[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reader for one member of a source row.
|
|
3
|
+
* @param {string | ((item: any, index: number) => any)} spec - a
|
|
4
|
+
* property name, or a function of the row and its position
|
|
5
|
+
* @param {string} role - the member's name, for the message
|
|
6
|
+
* @returns {(item: any, index: number) => any}
|
|
7
|
+
* @throws {TypeError} when `spec` is neither
|
|
8
|
+
*/
|
|
9
|
+
export declare function selectorOf(spec: string | ((item: any, index: number) => any), role: string): (item: any, index: number) => any;
|
|
10
|
+
/**
|
|
11
|
+
* The row at `index`, confirmed to be something with members to read.
|
|
12
|
+
* @param {any} item
|
|
13
|
+
* @param {number} index
|
|
14
|
+
* @returns {any}
|
|
15
|
+
* @throws {TypeError} when it is not an object
|
|
16
|
+
*/
|
|
17
|
+
export declare function requireRow(item: any, index: number): any;
|
|
18
|
+
/**
|
|
19
|
+
* One specification, confirmed to name only admitted members.
|
|
20
|
+
* @param {any} spec - the caller's specification object
|
|
21
|
+
* @param {readonly string[]} allowed - the closed member list
|
|
22
|
+
* @param {string} kernel - the function's name, for the message
|
|
23
|
+
* @param {string} shape - what the specification is, for the message
|
|
24
|
+
* when it is not an object at all
|
|
25
|
+
* @returns {any} the same object
|
|
26
|
+
* @throws {TypeError} when it is not an object, or names a member the
|
|
27
|
+
* kernel does not admit
|
|
28
|
+
*/
|
|
29
|
+
export declare function requireSpecMembers(spec: any, allowed: readonly string[], kernel: string, shape: string): any;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type ZoneProvider = {
|
|
2
|
+
/**
|
|
3
|
+
* - the wall
|
|
4
|
+
* clock at an instant, as a parts record (`year`, `month` 1-12, `day`,
|
|
5
|
+
* `hours`, `minutes`, `seconds`, and `offset` in minutes east if known)
|
|
6
|
+
*/
|
|
7
|
+
toParts: (epoch: number, zone: string) => any;
|
|
8
|
+
/**
|
|
9
|
+
* - the instant a wall clock names, in epoch milliseconds;
|
|
10
|
+
* non-finite (or a throw) when the local time is ambiguous or does not
|
|
11
|
+
* exist and `disambiguation` does not resolve it
|
|
12
|
+
*/
|
|
13
|
+
toEpoch: (parts: any, zone: string, disambiguation: string) => number;
|
|
14
|
+
};
|
|
15
|
+
export type Clock = {
|
|
16
|
+
/**
|
|
17
|
+
* - what the clock is called, for messages
|
|
18
|
+
*/
|
|
19
|
+
zone: string;
|
|
20
|
+
partsAt: (epoch: number) => any;
|
|
21
|
+
epochOf: (parts: any) => number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The four members every calendar-aware specification carries, so
|
|
25
|
+
* a bucket ladder, a rolling window and a query document all spell
|
|
26
|
+
* the clock the same way.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CLOCK_MEMBERS: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* The clock an operation reads its calendar boundaries on.
|
|
31
|
+
*
|
|
32
|
+
* Defaults to UTC, which needs nothing: no provider, no zone name, no
|
|
33
|
+
* ambiguity. `{ offset }` is a constant number of minutes east and is
|
|
34
|
+
* exact integer arithmetic. `{ zone }` is a name, and a name means
|
|
35
|
+
* nothing without a `provider` — asking for `'Europe/Amsterdam'` with no
|
|
36
|
+
* tzdb is a refusal, never a silent fall back to UTC that is right for
|
|
37
|
+
* eight months of the year.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} [options]
|
|
40
|
+
* @param {string} [options.zone] - an IANA name; `'UTC'` needs no provider
|
|
41
|
+
* @param {number} [options.offset] - minutes east of UTC, for a fixed offset
|
|
42
|
+
* @param {ZoneProvider} [options.provider] - the tzdb, for a named zone
|
|
43
|
+
* @param {'reject' | 'earlier' | 'later'} [options.disambiguation]
|
|
44
|
+
* what a local time that happens twice, or never, resolves to
|
|
45
|
+
* (default `'reject'`)
|
|
46
|
+
* @returns {Clock}
|
|
47
|
+
* @throws {TypeError} for a named zone with no provider, a non-finite
|
|
48
|
+
* offset, an unknown disambiguation, or both a zone and an offset
|
|
49
|
+
* @example
|
|
50
|
+
* resolveClock(); // UTC
|
|
51
|
+
* resolveClock({ offset: 330 }); // +05:30, constant
|
|
52
|
+
* resolveClock({ zone: 'Europe/Amsterdam', provider, disambiguation: 'later' });
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveClock(options?: {
|
|
55
|
+
zone?: string;
|
|
56
|
+
offset?: number;
|
|
57
|
+
provider?: ZoneProvider;
|
|
58
|
+
disambiguation?: 'reject' | 'earlier' | 'later';
|
|
59
|
+
}): Clock;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Descriptive statistics over a sample of numbers: the mean, the
|
|
3
|
+
* sample variance and its root, the median, and a quantile that will
|
|
4
|
+
* not answer until told which quantile it is being asked for.
|
|
5
|
+
*
|
|
6
|
+
* Before this file the suite computed these in two places with two
|
|
7
|
+
* quantile rules — linear interpolation in the query layer's statistics
|
|
8
|
+
* pack, nearest rank in the benchmark harness — and each was the right
|
|
9
|
+
* rule for its consumer: an interpolated percentile is what an analyst
|
|
10
|
+
* expects of `$percentile`, while a benchmark row of eleven readings
|
|
11
|
+
* should not publish a latency nobody measured. Both rules stay; the
|
|
12
|
+
* definitions move here so that there is one of each, and `quantile`
|
|
13
|
+
* makes the caller name its method, because "the 95th percentile" of a
|
|
14
|
+
* small sample is a different number under every one of the seven
|
|
15
|
+
* common definitions and a default would decide silently.
|
|
16
|
+
*
|
|
17
|
+
* Every function answers `undefined` for a sample it cannot summarize —
|
|
18
|
+
* an empty one, or fewer than two values for a variance — rather than
|
|
19
|
+
* `NaN` or `0`: a number that was not measured must not format as one.
|
|
20
|
+
* The caller's array is never reordered; a quantile sorts a copy.
|
|
21
|
+
* Values are numbers by contract and are not checked one by one.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The arithmetic mean.
|
|
25
|
+
* @param {readonly number[]} values
|
|
26
|
+
* @returns {number | undefined} `undefined` for an empty sample
|
|
27
|
+
*/
|
|
28
|
+
export declare function mean(values: readonly number[]): number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The SAMPLE variance, with Bessel's correction (`n − 1`): the sample is
|
|
31
|
+
* taken as drawn from a population it did not enumerate, which is what a
|
|
32
|
+
* benchmark's rounds and a query's rows both are.
|
|
33
|
+
* @param {readonly number[]} values
|
|
34
|
+
* @returns {number | undefined} `undefined` for fewer than two values
|
|
35
|
+
*/
|
|
36
|
+
export declare function variance(values: readonly number[]): number | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The sample standard deviation — the root of {@link variance}.
|
|
39
|
+
* @param {readonly number[]} values
|
|
40
|
+
* @returns {number | undefined} `undefined` where the variance is
|
|
41
|
+
*/
|
|
42
|
+
export declare function stddev(values: readonly number[]): number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The median: the middle value, or the mean of the two middle values
|
|
45
|
+
* when the sample has an even count.
|
|
46
|
+
*
|
|
47
|
+
* This is NOT `quantile(values, 0.5, …)` under either method, and on an
|
|
48
|
+
* even count the three disagree: for `[1, 2, 3, 4]` the median is `2.5`,
|
|
49
|
+
* the nearest-rank p50 is `2`, and the linear p50 is `2.5` only because
|
|
50
|
+
* that sample happens to be evenly spaced. A consumer publishing a "p50"
|
|
51
|
+
* beside a "p95" wants `quantile` with its method named; a consumer
|
|
52
|
+
* asking for the median wants this.
|
|
53
|
+
* @param {readonly number[]} values
|
|
54
|
+
* @returns {number | undefined} `undefined` for an empty sample
|
|
55
|
+
*/
|
|
56
|
+
export declare function median(values: readonly number[]): number | undefined;
|
|
57
|
+
export type QuantileMethod = 'nearest-rank' | 'linear';
|
|
58
|
+
/**
|
|
59
|
+
* The `p`-quantile of a sample, `p` on `[0, 1]`, under a NAMED method.
|
|
60
|
+
*
|
|
61
|
+
* The method is required, not defaulted: on a small sample the common
|
|
62
|
+
* definitions disagree by whole values, and a caller who did not say
|
|
63
|
+
* which one it wanted has published a number it cannot explain.
|
|
64
|
+
* @param {readonly number[]} values
|
|
65
|
+
* @param {number} p - the probability, `0` (the minimum) to `1` (the maximum)
|
|
66
|
+
* @param {{ method: QuantileMethod }} options
|
|
67
|
+
* @returns {number | undefined} `undefined` for an empty sample
|
|
68
|
+
* @throws {TypeError} when `method` is absent or not one of {@link QuantileMethod}
|
|
69
|
+
* @throws {RangeError} when `p` is not a number in `[0, 1]`
|
|
70
|
+
*/
|
|
71
|
+
export declare function quantile(values: readonly number[], p: number, options: {
|
|
72
|
+
method: QuantileMethod;
|
|
73
|
+
}): number | undefined;
|
package/docs/DATES.md
CHANGED
|
@@ -42,6 +42,43 @@ start Monday, per ISO). **Month math clamps**: 31 Jan + 1 month =
|
|
|
42
42
|
28 Feb, which is what makes add and diff behave as inverses in the
|
|
43
43
|
query operators built on this.
|
|
44
44
|
|
|
45
|
+
Two refusals keep those answers honest, both `TypeError`:
|
|
46
|
+
|
|
47
|
+
- **A unit reads a half, and a value may not carry it.** `year` through
|
|
48
|
+
`day` read the calendar; `hour` through `millisecond` read the clock.
|
|
49
|
+
Adding hours to a full-date, adding a day to a full-time, or
|
|
50
|
+
truncating either to the unit it has not got has no answer to give.
|
|
51
|
+
`day` and coarser truncations are the boundary both forms have, so
|
|
52
|
+
`startOfParts` / `endOfParts` of a `day` on a full-time is midnight and
|
|
53
|
+
`23:59:59.999`.
|
|
54
|
+
- **A fraction is a quantity only where it converts exactly.** A
|
|
55
|
+
fractional fixed-width amount becomes whole milliseconds — `1.5 day` is
|
|
56
|
+
thirty-six hours, and needs a value with a clock to land on. A
|
|
57
|
+
fractional calendar unit is refused unless it lands on a whole month:
|
|
58
|
+
half a year is six months, half a month is nothing.
|
|
59
|
+
|
|
60
|
+
## Time-axis ticks — `ticks.js`
|
|
61
|
+
|
|
62
|
+
`niceTimeStep(span, count)` picks the `[unit, amount]` a domain should be
|
|
63
|
+
read in — from 1/5/15/30 seconds and minutes through 1/3/6/12 hours, 1/2
|
|
64
|
+
days, 1/2 weeks and 1/3/6 months, then whole years on the 1/2/5 × 10^k
|
|
65
|
+
ladder, so a millennial domain steps by centuries rather than running out
|
|
66
|
+
of axis. `axisTicksTime(min, max, count)` lays the boundaries down: each
|
|
67
|
+
tick is a multiple of the step's own amount, so an axis reads
|
|
68
|
+
`1850 1900 1950 2000` rather than offsets from wherever the data began.
|
|
69
|
+
Below a second the calendar has nothing to say and the numeric ladder
|
|
70
|
+
answers instead. The chart component re-exports both rather than
|
|
71
|
+
carrying its own copy.
|
|
72
|
+
|
|
73
|
+
`timeTicksEvery(min, max, unit, amount?, options?)` is the same boundary
|
|
74
|
+
rule with the step supplied instead of chosen — what a document that
|
|
75
|
+
declares its own interval needs (Mermaid's Gantt `tickInterval 1week` is
|
|
76
|
+
the one in this repo). `options.weekStart` is an ISO weekday, so a
|
|
77
|
+
document whose weeks begin on Sunday gets Sundays; `options.limit` caps
|
|
78
|
+
the count. `axisTicksTime` is now this function with the ladder in
|
|
79
|
+
front of it, so a chart axis and a timeline cannot disagree about where
|
|
80
|
+
a tick falls.
|
|
81
|
+
|
|
45
82
|
## Durations — `duration.js`
|
|
46
83
|
|
|
47
84
|
`parseDuration('P3DT4H')` — ISO 8601 duration decomposition into
|
|
@@ -65,6 +102,43 @@ ISO tokens work bare; name tokens (`MMMM`, `EEE`, `a`) require a
|
|
|
65
102
|
`names` provider, which is where `@jarenjs/locales` plugs in — the
|
|
66
103
|
kernel ships no month names, so server-rendered output stays
|
|
67
104
|
byte-stable across Node/ICU versions.
|
|
105
|
+
`compileDateLocale(pack).names` is that provider: five arrays (12 months
|
|
106
|
+
wide and short, 7 weekdays wide and short from Sunday, the two day-period
|
|
107
|
+
markers), read once out of a locale pack's own msgids and frozen, so a
|
|
108
|
+
pattern compiled against them costs no lookup per date.
|
|
109
|
+
|
|
110
|
+
## Parsing — `parse.js`
|
|
111
|
+
|
|
112
|
+
`compileDateParser(pattern, names?)` is the formatter's strict inverse
|
|
113
|
+
over the same LDML vocabulary, compiled the same way: the pattern is
|
|
114
|
+
scanned once into a chain of readers, and no regular expression is built
|
|
115
|
+
per compile or per parse. It returns a function from text to the parts
|
|
116
|
+
record `parseRFC3339Parts` produces, so `epochOfRFC3339Parts`,
|
|
117
|
+
`addToParts` and `formatRFC3339Parts` all take its output unchanged, and
|
|
118
|
+
the lexical family follows the pattern — date tokens only give a
|
|
119
|
+
full-date, time tokens only a full-time.
|
|
120
|
+
|
|
121
|
+
Strict means three things, and each of them is the difference between an
|
|
122
|
+
answer and a plausible wrong answer:
|
|
123
|
+
|
|
124
|
+
1. **Full consumption.** `yyyy-MM-dd` does not read `2026-01-02T03:04`.
|
|
125
|
+
2. **Impossible civil dates fail.** `31-02-2014` is `null`, not 3 March.
|
|
126
|
+
3. **Fixed-width tokens are fixed width.** `MM` reads exactly two digits.
|
|
127
|
+
|
|
128
|
+
Malformed *text* is data and comes back `null`; a malformed *pattern* is
|
|
129
|
+
the programmer's error and throws at compile time. The tokens that only
|
|
130
|
+
ever format — `EEEE`, `DDD`, `ww`, `Q` are derived *from* a date rather
|
|
131
|
+
than fields *of* one — throw with a message naming the token and the
|
|
132
|
+
reason, rather than reading nothing in silence. Two tokens are
|
|
133
|
+
documented rather than refused: `yy` reads 00–68 as 2000–2068 and 69–99
|
|
134
|
+
as 1969–1999 (the POSIX pivot), and `h`/`hh` with no `a` in the same
|
|
135
|
+
pattern read as the morning.
|
|
136
|
+
|
|
137
|
+
A consumer whose patterns are **not** LDML adapts its own tokens onto
|
|
138
|
+
this vocabulary rather than handing its spelling over. `@jarenjs/mermaid`
|
|
139
|
+
does exactly that for a Gantt's `dateFormat` (moment's grammar) and
|
|
140
|
+
`axisFormat` (strftime): LDML's `YYYY` is the *week-numbering* year, so
|
|
141
|
+
reading `YYYY-MM-DD` as LDML answers 2019 for 2018-12-31.
|
|
68
142
|
|
|
69
143
|
## Not here
|
|
70
144
|
|
|
@@ -72,7 +146,9 @@ byte-stable across Node/ICU versions.
|
|
|
72
146
|
Node ≥ 24 baseline); the string/number representation is exactly what
|
|
73
147
|
`Temporal.Instant.from()` consumes, so the kernel can delegate
|
|
74
148
|
internally later without a surface change. Relative-time phrasing and
|
|
75
|
-
month-name catalogs are `@jarenjs/locales`' job
|
|
149
|
+
month-name catalogs are `@jarenjs/locales`' job — including the decision
|
|
150
|
+
that a relative phrase is handed its signed amount rather than working
|
|
151
|
+
one out, which is the same "no `now`" rule this kernel keeps; `formatMinimum`/
|
|
76
152
|
`formatMaximum` bound comparison lives in `@jarenjs/formats`; time
|
|
77
153
|
*axes* (charts) and date *controls* (forms) consume this kernel from
|
|
78
154
|
their own packages.
|
package/docs/GEO.md
CHANGED
|
@@ -22,10 +22,10 @@ the *wrong sign* on near-collinear input, which makes containment
|
|
|
22
22
|
contradict itself. `orient2dFast` is the naive form, exported for
|
|
23
23
|
callers that provably do not care. Every winding and containment answer
|
|
24
24
|
in this module rests on this sign; its cost is the deliberate
|
|
25
|
-
point-in-polygon loss on the benchmark page (<!--
|
|
25
|
+
point-in-polygon loss on the benchmark page (<!--fact:geo.pip2000-->0.5×<!--/fact--> against
|
|
26
26
|
Turf at 2000 vertices, kept on purpose). Every loss the kernel carries is
|
|
27
27
|
published in [ARCHITECTURE](../ARCHITECTURE.md), derived from the committed
|
|
28
|
-
measurement: <!--
|
|
28
|
+
measurement: <!--fact:geo.losses-->three rows lose to a rival: point in polygon (2000-vertex) at 0.5× (turf), bounding box (2000-vertex) at 0.9× (turf), index build (100k boxes) at 0.8× (flatbush)<!--/fact-->.
|
|
29
29
|
|
|
30
30
|
## Distance — `distance.js`
|
|
31
31
|
|