pond-ts 0.21.0 → 0.23.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/CHANGELOG.md +133 -1
- package/dist/batch/operators/map.d.ts +14 -2
- package/dist/batch/operators/map.js +35 -3
- package/dist/batch/partitioned-time-series.d.ts +4 -18
- package/dist/batch/time-series.d.ts +13 -22
- package/dist/batch/time-series.js +104 -27
- package/dist/batch/validate.js +2 -2
- package/dist/cjs-fallback.cjs +15 -0
- package/dist/columnar/index.d.ts +1 -1
- package/dist/columnar/index.js +1 -1
- package/dist/columnar/view.d.ts +25 -1
- package/dist/columnar/view.js +26 -0
- package/dist/index.d.ts +1 -1
- package/dist/live/live-aggregation.d.ts +2 -2
- package/dist/live/live-partitioned-series.d.ts +8 -31
- package/dist/live/live-rolling-aggregation.d.ts +2 -2
- package/dist/live/live-series.d.ts +5 -10
- package/dist/live/live-series.js +2 -1
- package/dist/live/live-view.d.ts +5 -10
- package/dist/live/live-view.js +1 -0
- package/dist/reducers/stdev.js +81 -59
- package/dist/schema/aggregate.d.ts +162 -64
- package/dist/schema/events.d.ts +10 -0
- package/dist/schema/index.d.ts +1 -2
- package/dist/schema/index.js +8 -0
- package/dist/schema/reduce.d.ts +34 -4
- package/dist/schema/rolling.d.ts +1 -1
- package/package.json +4 -3
- package/dist/batch/aggregate-columns.d.ts.map +0 -1
- package/dist/batch/aggregate-columns.js.map +0 -1
- package/dist/batch/index.d.ts.map +0 -1
- package/dist/batch/index.js.map +0 -1
- package/dist/batch/json.d.ts.map +0 -1
- package/dist/batch/json.js.map +0 -1
- package/dist/batch/operators/collapse.d.ts.map +0 -1
- package/dist/batch/operators/collapse.js.map +0 -1
- package/dist/batch/operators/cumulative.d.ts.map +0 -1
- package/dist/batch/operators/cumulative.js.map +0 -1
- package/dist/batch/operators/diff-rate.d.ts.map +0 -1
- package/dist/batch/operators/diff-rate.js.map +0 -1
- package/dist/batch/operators/fill.d.ts.map +0 -1
- package/dist/batch/operators/fill.js.map +0 -1
- package/dist/batch/operators/map.d.ts.map +0 -1
- package/dist/batch/operators/map.js.map +0 -1
- package/dist/batch/operators/shift.d.ts.map +0 -1
- package/dist/batch/operators/shift.js.map +0 -1
- package/dist/batch/partitioned-time-series.d.ts.map +0 -1
- package/dist/batch/partitioned-time-series.js.map +0 -1
- package/dist/batch/time-series.d.ts.map +0 -1
- package/dist/batch/time-series.js.map +0 -1
- package/dist/batch/validate.d.ts.map +0 -1
- package/dist/batch/validate.js.map +0 -1
- package/dist/column.d.ts.map +0 -1
- package/dist/column.js.map +0 -1
- package/dist/columnar/array-column.d.ts.map +0 -1
- package/dist/columnar/array-column.js.map +0 -1
- package/dist/columnar/builder.d.ts.map +0 -1
- package/dist/columnar/builder.js.map +0 -1
- package/dist/columnar/chunked-column.d.ts.map +0 -1
- package/dist/columnar/chunked-column.js.map +0 -1
- package/dist/columnar/column.d.ts.map +0 -1
- package/dist/columnar/column.js.map +0 -1
- package/dist/columnar/concat.d.ts.map +0 -1
- package/dist/columnar/concat.js.map +0 -1
- package/dist/columnar/index.d.ts.map +0 -1
- package/dist/columnar/index.js.map +0 -1
- package/dist/columnar/key-column.d.ts.map +0 -1
- package/dist/columnar/key-column.js.map +0 -1
- package/dist/columnar/ring-buffer.d.ts.map +0 -1
- package/dist/columnar/ring-buffer.js.map +0 -1
- package/dist/columnar/scatter.d.ts.map +0 -1
- package/dist/columnar/scatter.js.map +0 -1
- package/dist/columnar/store.d.ts.map +0 -1
- package/dist/columnar/store.js.map +0 -1
- package/dist/columnar/string-column.d.ts.map +0 -1
- package/dist/columnar/string-column.js.map +0 -1
- package/dist/columnar/types.d.ts.map +0 -1
- package/dist/columnar/types.js.map +0 -1
- package/dist/columnar/validity.d.ts.map +0 -1
- package/dist/columnar/validity.js.map +0 -1
- package/dist/columnar/view.d.ts.map +0 -1
- package/dist/columnar/view.js.map +0 -1
- package/dist/core/calendar.d.ts.map +0 -1
- package/dist/core/calendar.js.map +0 -1
- package/dist/core/duration.d.ts.map +0 -1
- package/dist/core/duration.js.map +0 -1
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js.map +0 -1
- package/dist/core/event.d.ts.map +0 -1
- package/dist/core/event.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/core/interval.d.ts.map +0 -1
- package/dist/core/interval.js.map +0 -1
- package/dist/core/temporal.d.ts.map +0 -1
- package/dist/core/temporal.js.map +0 -1
- package/dist/core/time-range.d.ts.map +0 -1
- package/dist/core/time-range.js.map +0 -1
- package/dist/core/time.d.ts.map +0 -1
- package/dist/core/time.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/live/index.d.ts.map +0 -1
- package/dist/live/index.js.map +0 -1
- package/dist/live/live-aggregation.d.ts.map +0 -1
- package/dist/live/live-aggregation.js.map +0 -1
- package/dist/live/live-chunked-storage.d.ts.map +0 -1
- package/dist/live/live-chunked-storage.js.map +0 -1
- package/dist/live/live-fused-rolling.d.ts.map +0 -1
- package/dist/live/live-fused-rolling.js.map +0 -1
- package/dist/live/live-history.d.ts.map +0 -1
- package/dist/live/live-history.js.map +0 -1
- package/dist/live/live-partitioned-fused-rolling.d.ts.map +0 -1
- package/dist/live/live-partitioned-fused-rolling.js.map +0 -1
- package/dist/live/live-partitioned-series.d.ts.map +0 -1
- package/dist/live/live-partitioned-series.js.map +0 -1
- package/dist/live/live-partitioned-sync-rolling.d.ts.map +0 -1
- package/dist/live/live-partitioned-sync-rolling.js.map +0 -1
- package/dist/live/live-reduce.d.ts.map +0 -1
- package/dist/live/live-reduce.js.map +0 -1
- package/dist/live/live-rolling-aggregation.d.ts.map +0 -1
- package/dist/live/live-rolling-aggregation.js.map +0 -1
- package/dist/live/live-series.d.ts.map +0 -1
- package/dist/live/live-series.js.map +0 -1
- package/dist/live/live-storage.d.ts.map +0 -1
- package/dist/live/live-storage.js.map +0 -1
- package/dist/live/live-view.d.ts.map +0 -1
- package/dist/live/live-view.js.map +0 -1
- package/dist/live/series-store.d.ts.map +0 -1
- package/dist/live/series-store.js.map +0 -1
- package/dist/live/triggers.d.ts.map +0 -1
- package/dist/live/triggers.js.map +0 -1
- package/dist/reducers/avg.d.ts.map +0 -1
- package/dist/reducers/avg.js.map +0 -1
- package/dist/reducers/count.d.ts.map +0 -1
- package/dist/reducers/count.js.map +0 -1
- package/dist/reducers/difference.d.ts.map +0 -1
- package/dist/reducers/difference.js.map +0 -1
- package/dist/reducers/first.d.ts.map +0 -1
- package/dist/reducers/first.js.map +0 -1
- package/dist/reducers/index.d.ts.map +0 -1
- package/dist/reducers/index.js.map +0 -1
- package/dist/reducers/keep.d.ts.map +0 -1
- package/dist/reducers/keep.js.map +0 -1
- package/dist/reducers/last.d.ts.map +0 -1
- package/dist/reducers/last.js.map +0 -1
- package/dist/reducers/max.d.ts.map +0 -1
- package/dist/reducers/max.js.map +0 -1
- package/dist/reducers/median.d.ts.map +0 -1
- package/dist/reducers/median.js.map +0 -1
- package/dist/reducers/min.d.ts.map +0 -1
- package/dist/reducers/min.js.map +0 -1
- package/dist/reducers/percentile.d.ts.map +0 -1
- package/dist/reducers/percentile.js.map +0 -1
- package/dist/reducers/rolling.d.ts.map +0 -1
- package/dist/reducers/rolling.js.map +0 -1
- package/dist/reducers/samples.d.ts.map +0 -1
- package/dist/reducers/samples.js.map +0 -1
- package/dist/reducers/stdev.d.ts.map +0 -1
- package/dist/reducers/stdev.js.map +0 -1
- package/dist/reducers/sum.d.ts.map +0 -1
- package/dist/reducers/sum.js.map +0 -1
- package/dist/reducers/top.d.ts.map +0 -1
- package/dist/reducers/top.js.map +0 -1
- package/dist/reducers/types.d.ts.map +0 -1
- package/dist/reducers/types.js.map +0 -1
- package/dist/reducers/unique.d.ts.map +0 -1
- package/dist/reducers/unique.js.map +0 -1
- package/dist/schema/aggregate.d.ts.map +0 -1
- package/dist/schema/aggregate.js.map +0 -1
- package/dist/schema/diff.d.ts.map +0 -1
- package/dist/schema/diff.js.map +0 -1
- package/dist/schema/events.d.ts.map +0 -1
- package/dist/schema/events.js.map +0 -1
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/join.d.ts.map +0 -1
- package/dist/schema/join.js.map +0 -1
- package/dist/schema/json.d.ts.map +0 -1
- package/dist/schema/json.js.map +0 -1
- package/dist/schema/public.d.ts.map +0 -1
- package/dist/schema/public.js.map +0 -1
- package/dist/schema/reduce.d.ts.map +0 -1
- package/dist/schema/reduce.js.map +0 -1
- package/dist/schema/reshape.d.ts.map +0 -1
- package/dist/schema/reshape.js.map +0 -1
- package/dist/schema/rolling.d.ts.map +0 -1
- package/dist/schema/rolling.js.map +0 -1
- package/dist/schema/series.d.ts.map +0 -1
- package/dist/schema/series.js.map +0 -1
- package/dist/sequence/bounded-sequence.d.ts.map +0 -1
- package/dist/sequence/bounded-sequence.js.map +0 -1
- package/dist/sequence/index.d.ts.map +0 -1
- package/dist/sequence/index.js.map +0 -1
- package/dist/sequence/sample.d.ts.map +0 -1
- package/dist/sequence/sample.js.map +0 -1
- package/dist/sequence/sequence.d.ts.map +0 -1
- package/dist/sequence/sequence.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
7
7
|
file covers both packages. Pre-1.0: minor bumps may include new features and
|
|
8
8
|
type-level changes; patch bumps are strictly additive.
|
|
9
9
|
|
|
10
|
-
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.
|
|
10
|
+
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.23.0...HEAD
|
|
11
|
+
[0.23.0]: https://github.com/pjm17971/pond-ts/compare/v0.22.0...v0.23.0
|
|
12
|
+
[0.22.0]: https://github.com/pjm17971/pond-ts/compare/v0.21.0...v0.22.0
|
|
11
13
|
[0.21.0]: https://github.com/pjm17971/pond-ts/compare/v0.20.0...v0.21.0
|
|
12
14
|
[0.20.0]: https://github.com/pjm17971/pond-ts/compare/v0.19.0...v0.20.0
|
|
13
15
|
[0.19.0]: https://github.com/pjm17971/pond-ts/compare/v0.18.0...v0.19.0
|
|
@@ -15,6 +17,136 @@ type-level changes; patch bumps are strictly additive.
|
|
|
15
17
|
|
|
16
18
|
## [Unreleased]
|
|
17
19
|
|
|
20
|
+
## [0.23.0] — 2026-06-13
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`new TimeSeries({ …, sort: true })` (and `TimeSeries.fromJSON`) sort rows by
|
|
25
|
+
key on construction.** Pond requires rows in non-decreasing key order and
|
|
26
|
+
throws otherwise; `sort: true` accepts unsorted input (messy CSVs, merged
|
|
27
|
+
sources) and sorts it for you instead of forcing a manual pre-sort. The sort
|
|
28
|
+
is **stable** — rows with equal keys keep their input order — matching what
|
|
29
|
+
`TimeSeries.fromEvents` already does. The out-of-order error now names the
|
|
30
|
+
option. (Audit v2 §5 F3.)
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **CommonJS consumers now get a clear error instead of
|
|
35
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`.** Both `pond-ts` and `@pond-ts/react`
|
|
36
|
+
add a `require` condition to their `exports["."]` map pointing at a tiny
|
|
37
|
+
shipped CJS stub that throws an ESM-only message naming `import` as the
|
|
38
|
+
fix. The packages remain ES-module-only; this only improves the error a
|
|
39
|
+
`require('pond-ts')` caller sees. (Audit v2 §5 F6/F7/F9/F10/F11)
|
|
40
|
+
- **Published tarballs no longer ship `*.js.map` / `*.d.ts.map` source
|
|
41
|
+
maps.** The maps referenced a `../src` tree that was never included in
|
|
42
|
+
the tarball (`files: ["dist", …]`), so they were dead weight (~⅓ of the
|
|
43
|
+
unpacked size). A `prepack` step now strips them from the published
|
|
44
|
+
artifact for both packages; local `npm run build` still emits them, so
|
|
45
|
+
in-repo debugging is unaffected. (Audit v2 §5 F6/F7/F9/F10/F11)
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **Shipped `.d.ts` now type-check under `skipLibCheck: false`.** The internal
|
|
50
|
+
`EMITS_EVICT` marker symbol was `@internal` (stripped from the emitted
|
|
51
|
+
`series.d.ts`) but still referenced by un-stripped public declarations — a
|
|
52
|
+
by-name re-export in `schema/index.d.ts` and the `[EMITS_EVICT]` brand members
|
|
53
|
+
on `LiveSeries` / `LiveView` — leaving dangling references that broke strict
|
|
54
|
+
consumer builds with **TS2305**. Those references are now `@internal` too, so
|
|
55
|
+
the symbol is fully stripped from the published types; runtime behavior is
|
|
56
|
+
unchanged. (Audit v2 §5 F2.)
|
|
57
|
+
- **`TimeSeries.at(-1)` counts from the end**, matching `LiveSeries.at` and
|
|
58
|
+
`Array.prototype.at` (it previously returned `undefined` for any negative
|
|
59
|
+
index). Deep underflow (e.g. `at(-100)` on a 3-event series) still returns
|
|
60
|
+
`undefined`, and the non-integer / `NaN` guard is unchanged. (Audit v2 §5 F8.)
|
|
61
|
+
- **Docs: corrected `Time.asString()` (does not exist), the missing
|
|
62
|
+
`aggregate`/`materialize` → `pivotByGroup` rekey pointer, and an
|
|
63
|
+
inaccurate `rolling().value()` return-type example.** The getting-started
|
|
64
|
+
example now calls `event.key().toDate().toISOString()`; the aggregation
|
|
65
|
+
and reshape pages note that interval-keyed output must be rekeyed with
|
|
66
|
+
`.asTime({ at: 'begin' })` before a time-keyed transform like
|
|
67
|
+
`pivotByGroup` (whose runtime error now says so too); the rolling page
|
|
68
|
+
documents `value()` as `Record<string, ColumnValue | undefined>`.
|
|
69
|
+
(Audit v2 §5 F6/F7/F9/F10/F11)
|
|
70
|
+
- **Mixed shorthand + `{ from, using }` mappings now keep every output
|
|
71
|
+
column in the result type (Audit v2 §5 F1).** Calling
|
|
72
|
+
`aggregate` / `rolling` / `reduce` with a mapping that mixes the
|
|
73
|
+
shorthand form (`cpu: 'avg'`) and the spec form
|
|
74
|
+
(`cpu_p95: { from: 'cpu', using: 'p95' }`) in one call — the
|
|
75
|
+
docs-blessed pattern — previously resolved to the shorthand overload
|
|
76
|
+
and **silently dropped every spec-keyed output column from the result
|
|
77
|
+
type** (`event.get('cpu_p95')` failed to compile with `TS2345`), even
|
|
78
|
+
though the runtime emitted the column. The two overloads
|
|
79
|
+
(`AggregateMap` shorthand + `AggregateOutputMap` spec) are now
|
|
80
|
+
collapsed into one unified mapping shape whose result schema dispatches
|
|
81
|
+
per output key, so all columns survive and each narrows to its
|
|
82
|
+
reducer's output kind. Runtime behavior is unchanged — this is a
|
|
83
|
+
types-only fix plus the tests that should have caught it.
|
|
84
|
+
- **The unified mapping keeps the shorthand compile-time guards.** A
|
|
85
|
+
shorthand reducer is still kind-checked against its source column
|
|
86
|
+
(`host: 'avg'` on a `string` column stays a compile error), and a bare
|
|
87
|
+
reducer on a key that is not a source column (`ghost: 'avg'` — a typo
|
|
88
|
+
the runtime rejects with "unknown source column") is now a compile
|
|
89
|
+
error too. Spec keys (`{ from, using }`) remain free output names.
|
|
90
|
+
Inline mapping literals get full validation; values pre-widened to
|
|
91
|
+
`AggregateMap<S>` and broad-schema (`TimeSeries<SeriesSchema>`)
|
|
92
|
+
callers keep the permissive shape. `AggregateOutputMap` is retained
|
|
93
|
+
as a back-compat alias of `AggregateMap`.
|
|
94
|
+
|
|
95
|
+
## [0.22.0] — 2026-06-12
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- **`asTime` / `asTimeRange` / `asInterval` are now column-native.** They
|
|
100
|
+
reinterpret the key's kind (a "rekey") straight off the existing key's
|
|
101
|
+
`begin` / `end` buffers instead of materializing events — value columns pass
|
|
102
|
+
through by reference. `asTimeRange` and `asTime` with `begin` / `end` reuse
|
|
103
|
+
the key buffer zero-copy (≈ **9×** faster on a build → rekey → read pipeline);
|
|
104
|
+
`asTime({ at: 'center' })` adds one midpoint pass; `asInterval` builds the
|
|
105
|
+
label column (string → `StringColumn`, number → `Float64Column`, inferred
|
|
106
|
+
from the first label and required consistent across rows). `asTime` with
|
|
107
|
+
`center` / `end` throws if anchoring a source with overlapping extents would
|
|
108
|
+
produce a non-monotonic time axis (preserving the prior validation — `begin`
|
|
109
|
+
is always sorted and is exempt).
|
|
110
|
+
- **Breaking: `asInterval`'s label function now receives the interval's
|
|
111
|
+
`TimeRange` (its `[begin, end]` extent) and index — not the whole `Event`.**
|
|
112
|
+
The canonical form is unchanged: `series.asInterval(range => range.begin())`
|
|
113
|
+
works exactly as before (both `Event` and `TimeRange` expose `begin()` /
|
|
114
|
+
`end()`). Only a label fn that read a _value column_ off the event (e.g.
|
|
115
|
+
`event => event.get('label')`) needs rewriting — compute the label before
|
|
116
|
+
`asInterval`, or derive it from the extent. The constant form
|
|
117
|
+
(`asInterval('bucket')` / `asInterval(42)`) is unaffected. (Pre-1.0 minor;
|
|
118
|
+
this is the change that lets the function form stay on the columnar path.)
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- **`mapColumns` rejects a non-finite numeric result at write.** A mapper on a
|
|
123
|
+
`number` column that returns `NaN` or `±Infinity` now throws a `RangeError`,
|
|
124
|
+
consistent with construction intake (which already rejects non-finite
|
|
125
|
+
numbers). Previously the value was packed into the column, where the reduce
|
|
126
|
+
fast path and the row path could disagree on the same bucket (e.g.
|
|
127
|
+
`aggregate('min')` returning a different result depending on which path ran).
|
|
128
|
+
A stored `NaN` is still a defined value the mapper sees — map it to a finite
|
|
129
|
+
number, or to `undefined` (missing), to clean it. (Closes a hole introduced
|
|
130
|
+
alongside `mapColumns` in 0.21.0.)
|
|
131
|
+
- **`aggregate('stdev')` is now numerically stable and path-independent.** The
|
|
132
|
+
bucketed row path (`bucketState`) used a one-pass `sq/n − mean²` accumulator
|
|
133
|
+
that cancels catastrophically on near-equal large-magnitude values —
|
|
134
|
+
returning `0` (e.g. `[1e10, 1e10+1, 1e10+2, 1e10+3]` → `0` instead of
|
|
135
|
+
`≈1.118`), or a negative variance whose `sqrt` is `NaN` that the validating
|
|
136
|
+
constructor then rejected with a throw. Because the columnar fast path is
|
|
137
|
+
all-or-nothing, an unrelated mapping (e.g. a `count` over a string column)
|
|
138
|
+
could silently flip the _same_ series' stdev. All three batch paths (`reduce`,
|
|
139
|
+
`reduceColumn`, `bucketState`) now share **one Welford recurrence** — O(1) per
|
|
140
|
+
element, no buffer (so the live aggregation path that shares `bucketState`
|
|
141
|
+
stays O(1)), `m2 ≥ 0` by construction — so they agree regardless of magnitude.
|
|
142
|
+
(Even the prior two-pass `Σv/n`-then-deviations drifted ~8.7% from the true
|
|
143
|
+
value at `2^52`, where the summed mean rounds — so unifying on Welford, not
|
|
144
|
+
two-pass, was necessary.) **Correction:** 0.21.0's columnar `aggregate()` fast
|
|
145
|
+
path (#186) was described as "signature + semantics unchanged", but it did
|
|
146
|
+
change released `stdev` output for fast-path-qualifying aggregates; this fix
|
|
147
|
+
makes every path agree. (`rolling`/`smooth` stdev keep the one-pass form for
|
|
148
|
+
now — a separate, deferred item.)
|
|
149
|
+
|
|
18
150
|
## [0.21.0] — 2026-06-11
|
|
19
151
|
|
|
20
152
|
### Added
|
|
@@ -19,8 +19,17 @@ export type ColumnMapper = (value: unknown) => unknown;
|
|
|
19
19
|
* Semantics:
|
|
20
20
|
* - **Missing cells carry.** The mapper is called only on defined
|
|
21
21
|
* values; a missing (`undefined`) cell stays missing (the mapper is
|
|
22
|
-
* not invoked).
|
|
23
|
-
*
|
|
22
|
+
* not invoked).
|
|
23
|
+
* - **Numeric results must be finite.** For a `number` column, a mapper
|
|
24
|
+
* result of `NaN` or `±Infinity` throws a `RangeError` at write —
|
|
25
|
+
* matching construction intake (`assertCellKind`), which rejects
|
|
26
|
+
* non-finite numbers. This keeps packed numeric columns NaN-free, so
|
|
27
|
+
* the columnar fast-path and the row-path reducers can never diverge
|
|
28
|
+
* on the same bucket (the bug audit v2 §1.3 reproduced). A stored
|
|
29
|
+
* `NaN` *is* a defined value, so the mapper is still invoked on it —
|
|
30
|
+
* use that to clean it (map `NaN` to a finite number, or to
|
|
31
|
+
* `undefined` for a missing cell). (Array columns are not
|
|
32
|
+
* element-checked here; they don't feed the numeric reducers.)
|
|
24
33
|
* - **Same kind, schema-stable.** The mapper returns the column's own
|
|
25
34
|
* kind (the method's type enforces `(value: T) => T`), so the output
|
|
26
35
|
* column keeps its kind and the schema is unchanged. The result is
|
|
@@ -36,6 +45,9 @@ export type ColumnMapper = (value: unknown) => unknown;
|
|
|
36
45
|
* same-kind builder can't store: `columnFromValuesByKind` coerces it
|
|
37
46
|
* to missing, so the cell reads back as a gap the declared schema may
|
|
38
47
|
* not advertise. Both are type-illegal inputs, not handled specially.
|
|
48
|
+
* (A non-finite *number* from a numeric mapper is the one same-kind
|
|
49
|
+
* case that throws rather than coerces — it would corrupt the packed
|
|
50
|
+
* column, not read back as a gap.)
|
|
39
51
|
*/
|
|
40
52
|
export declare function mapOp<S extends SeriesSchema>(store: ColumnarStore<S>, schema: S, spec: ReadonlyMap<string, ColumnMapper>): {
|
|
41
53
|
store: ColumnarStore<S>;
|
|
@@ -32,8 +32,17 @@ function columnFromValuesByKind(kind, values) {
|
|
|
32
32
|
* Semantics:
|
|
33
33
|
* - **Missing cells carry.** The mapper is called only on defined
|
|
34
34
|
* values; a missing (`undefined`) cell stays missing (the mapper is
|
|
35
|
-
* not invoked).
|
|
36
|
-
*
|
|
35
|
+
* not invoked).
|
|
36
|
+
* - **Numeric results must be finite.** For a `number` column, a mapper
|
|
37
|
+
* result of `NaN` or `±Infinity` throws a `RangeError` at write —
|
|
38
|
+
* matching construction intake (`assertCellKind`), which rejects
|
|
39
|
+
* non-finite numbers. This keeps packed numeric columns NaN-free, so
|
|
40
|
+
* the columnar fast-path and the row-path reducers can never diverge
|
|
41
|
+
* on the same bucket (the bug audit v2 §1.3 reproduced). A stored
|
|
42
|
+
* `NaN` *is* a defined value, so the mapper is still invoked on it —
|
|
43
|
+
* use that to clean it (map `NaN` to a finite number, or to
|
|
44
|
+
* `undefined` for a missing cell). (Array columns are not
|
|
45
|
+
* element-checked here; they don't feed the numeric reducers.)
|
|
37
46
|
* - **Same kind, schema-stable.** The mapper returns the column's own
|
|
38
47
|
* kind (the method's type enforces `(value: T) => T`), so the output
|
|
39
48
|
* column keeps its kind and the schema is unchanged. The result is
|
|
@@ -49,6 +58,9 @@ function columnFromValuesByKind(kind, values) {
|
|
|
49
58
|
* same-kind builder can't store: `columnFromValuesByKind` coerces it
|
|
50
59
|
* to missing, so the cell reads back as a gap the declared schema may
|
|
51
60
|
* not advertise. Both are type-illegal inputs, not handled specially.
|
|
61
|
+
* (A non-finite *number* from a numeric mapper is the one same-kind
|
|
62
|
+
* case that throws rather than coerces — it would corrupt the packed
|
|
63
|
+
* column, not read back as a gap.)
|
|
52
64
|
*/
|
|
53
65
|
export function mapOp(store, schema, spec) {
|
|
54
66
|
const n = store.length;
|
|
@@ -65,10 +77,30 @@ export function mapOp(store, schema, spec) {
|
|
|
65
77
|
if (col === undefined)
|
|
66
78
|
continue;
|
|
67
79
|
const kind = colKind.get(name);
|
|
80
|
+
// Numeric columns reject a non-finite (NaN / ±Infinity) mapper result at
|
|
81
|
+
// write, matching construction intake (assertCellKind). A packed NaN would
|
|
82
|
+
// otherwise be unreachable-by-assumption for the reduce kernels and diverge
|
|
83
|
+
// the fast path from the row path (audit v2 §1.3). Hoisted out of the loop
|
|
84
|
+
// so non-numeric columns pay nothing.
|
|
85
|
+
const rejectNonFinite = kind === 'number';
|
|
68
86
|
const out = new Array(n);
|
|
69
87
|
for (let i = 0; i < n; i += 1) {
|
|
70
88
|
const v = col.read(i);
|
|
71
|
-
|
|
89
|
+
if (v === undefined) {
|
|
90
|
+
out[i] = undefined;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const mapped = fn(v);
|
|
94
|
+
if (rejectNonFinite &&
|
|
95
|
+
typeof mapped === 'number' &&
|
|
96
|
+
!Number.isFinite(mapped)) {
|
|
97
|
+
throw new RangeError(`mapColumns: the mapper for column '${name}' returned a non-finite ` +
|
|
98
|
+
`number (${mapped}) at row ${i}. Numeric columns reject NaN and ` +
|
|
99
|
+
`±Infinity at write, consistent with intake — a packed NaN would ` +
|
|
100
|
+
`diverge the fast-path and row-path reducers on the same data. ` +
|
|
101
|
+
`Map to a finite number, or to undefined for a missing cell.`);
|
|
102
|
+
}
|
|
103
|
+
out[i] = mapped;
|
|
72
104
|
}
|
|
73
105
|
result = withColumnReplaced(result, name, columnFromValuesByKind(kind, out));
|
|
74
106
|
}
|
|
@@ -4,8 +4,7 @@ import { Sequence } from '../sequence/sequence.js';
|
|
|
4
4
|
import type { DurationInput } from '../core/duration.js';
|
|
5
5
|
import type { TemporalLike } from '../core/temporal.js';
|
|
6
6
|
import type { BatchSampleStrategy } from '../sequence/sample.js';
|
|
7
|
-
import type {
|
|
8
|
-
import type { AggregateOutputMapResultSchema, RollingOutputMapSchema } from '../schema/index.js';
|
|
7
|
+
import type { AggregateSchema, AlignSchema, BaselineSchema, DedupeKeep, DiffSchema, EventDataForSchema, FillMapping, FillStrategy, MaterializeSchema, NumericColumnNameForSchema, RollingAlignment, RollingSchema, SeriesSchema, SmoothAppendSchema, SmoothMethod, SmoothSchema, ValidatedAggregateMap } from '../schema/index.js';
|
|
9
8
|
type SequenceLike = Sequence | BoundedSequence;
|
|
10
9
|
type AlignMethod = 'hold' | 'linear';
|
|
11
10
|
type AlignSample = 'begin' | 'center' | 'end';
|
|
@@ -247,26 +246,16 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
247
246
|
range?: TemporalLike;
|
|
248
247
|
}): PartitionedTimeSeries<MaterializeSchema<S>, K>;
|
|
249
248
|
/** Per-partition `rolling`. See {@link TimeSeries.rolling}. */
|
|
250
|
-
rolling<const Mapping extends
|
|
249
|
+
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(window: DurationInput, mapping: Mapping, options?: {
|
|
251
250
|
alignment?: RollingAlignment;
|
|
252
251
|
minSamples?: number;
|
|
253
252
|
}): PartitionedTimeSeries<RollingSchema<S, Mapping>, K>;
|
|
254
|
-
rolling<const Mapping extends
|
|
255
|
-
alignment?: RollingAlignment;
|
|
256
|
-
minSamples?: number;
|
|
257
|
-
}): PartitionedTimeSeries<RollingOutputMapSchema<S, Mapping>, K>;
|
|
258
|
-
rolling<const Mapping extends AggregateMap<S>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
253
|
+
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
259
254
|
alignment?: RollingAlignment;
|
|
260
255
|
sample?: AlignSample;
|
|
261
256
|
range?: TemporalLike;
|
|
262
257
|
minSamples?: number;
|
|
263
258
|
}): PartitionedTimeSeries<AggregateSchema<S, Mapping>, K>;
|
|
264
|
-
rolling<const Mapping extends AggregateOutputMap<S>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
265
|
-
alignment?: RollingAlignment;
|
|
266
|
-
sample?: AlignSample;
|
|
267
|
-
range?: TemporalLike;
|
|
268
|
-
minSamples?: number;
|
|
269
|
-
}): PartitionedTimeSeries<AggregateOutputMapResultSchema<S, Mapping>, K>;
|
|
270
259
|
/** Per-partition `smooth`. See {@link TimeSeries.smooth}. */
|
|
271
260
|
smooth<const Target extends NumericColumnNameForSchema<S>, const Output extends string | undefined = undefined>(column: Target, method: SmoothMethod, options: {
|
|
272
261
|
alpha: number;
|
|
@@ -319,12 +308,9 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
319
308
|
/** Per-partition `shift`. See {@link TimeSeries.shift}. */
|
|
320
309
|
shift<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], n: number): PartitionedTimeSeries<DiffSchema<S, Target>, K>;
|
|
321
310
|
/** Per-partition `aggregate`. See {@link TimeSeries.aggregate}. */
|
|
322
|
-
aggregate<const Mapping extends
|
|
311
|
+
aggregate<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, mapping: Mapping, options?: {
|
|
323
312
|
range?: TemporalLike;
|
|
324
313
|
}): PartitionedTimeSeries<AggregateSchema<S, Mapping>, K>;
|
|
325
|
-
aggregate<const Mapping extends AggregateOutputMap<S>>(sequence: SequenceLike, mapping: Mapping, options?: {
|
|
326
|
-
range?: TemporalLike;
|
|
327
|
-
}): PartitionedTimeSeries<AggregateOutputMapResultSchema<S, Mapping>, K>;
|
|
328
314
|
}
|
|
329
315
|
export {};
|
|
330
316
|
//# sourceMappingURL=partitioned-time-series.d.ts.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { AlignSchema, MaterializeSchema, ArrayAggregateAppendSchema, ArrayAggregateReplaceSchema, ArrayColumnNameForSchema, ArrayExplodeAppendSchema, ArrayExplodeReplaceSchema, BaselineSchema, AggregateReducer,
|
|
2
|
-
import type { AggregateOutputMapResultSchema, RollingOutputMapSchema } from '../schema/index.js';
|
|
1
|
+
import type { AlignSchema, MaterializeSchema, ArrayAggregateAppendSchema, ArrayAggregateReplaceSchema, ArrayColumnNameForSchema, ArrayExplodeAppendSchema, ArrayExplodeReplaceSchema, BaselineSchema, AggregateReducer, AggregateSchema, CollapseSchema, EventDataForSchema, EventForSchema, FirstColKind, IntervalKeyedSchema, JsonRowFormat, JoinManySchema, JoinSchema, JoinType, NumericColumnNameForSchema, NormalizedObjectRow, NormalizedRowForSchema, PivotByGroupSchema, PointRowForSchema, PrefixedJoinManySchema, PrefixedJoinSchema, ReduceResult, RenameMap, ValidatedAggregateMap } from '../schema/index.js';
|
|
3
2
|
import type { RenameSchema, RollingAlignment, RollingSchema, ColumnValue, DedupeKeep, DiffSchema, FillMapping, FillStrategy, ScalarKind, ScalarValue, SmoothMethod, SmoothAppendSchema, SmoothSchema, SelectSchema, SeriesSchema, TimeKeyedSchema, TimeSeriesJsonInput, TimeSeriesInput, TimeRangeKeyedSchema, ValueColumnKindForName, ValueColumnNameForSchema, ValueColumnsForSchema } from '../schema/index.js';
|
|
4
3
|
import { BoundedSequence } from '../sequence/bounded-sequence.js';
|
|
5
4
|
import { type TimeZoneOptions } from '../core/calendar.js';
|
|
@@ -77,6 +76,12 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
77
76
|
*/
|
|
78
77
|
static fromJSON<S extends SeriesSchema>(input: TimeSeriesJsonInput<S> & {
|
|
79
78
|
parse?: TimeZoneOptions;
|
|
79
|
+
/**
|
|
80
|
+
* Sort rows by key before construction (off by default; see
|
|
81
|
+
* `TimeSeriesInput.sort`). Useful when reviving a wire payload whose
|
|
82
|
+
* rows aren't guaranteed sorted — avoids a manual pre-sort.
|
|
83
|
+
*/
|
|
84
|
+
sort?: boolean;
|
|
80
85
|
}): TimeSeries<S>;
|
|
81
86
|
/**
|
|
82
87
|
* Example: `TimeSeries.fromEvents(events, { schema, name })`.
|
|
@@ -337,9 +342,9 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
337
342
|
}): TimeSeries<TimeKeyedSchema<S>>;
|
|
338
343
|
/** Example: `series.asTimeRange()`. Converts the series key type to `"timeRange"` while preserving each event extent. */
|
|
339
344
|
asTimeRange(): TimeSeries<TimeRangeKeyedSchema<S>>;
|
|
340
|
-
/** Example: `series.asInterval(
|
|
345
|
+
/** Example: `series.asInterval(range => range.begin())`. Converts the series key type to `"interval"` while preserving each event extent and supplying interval labels. */
|
|
341
346
|
asInterval(value: IntervalValue): TimeSeries<IntervalKeyedSchema<S>>;
|
|
342
|
-
asInterval(value: (
|
|
347
|
+
asInterval(value: (range: TimeRange, index: number) => IntervalValue): TimeSeries<IntervalKeyedSchema<S>>;
|
|
343
348
|
/**
|
|
344
349
|
* Example: `left.join(right, { type: "left" })`.
|
|
345
350
|
* Performs an exact-key join of two series with the same key kind.
|
|
@@ -497,12 +502,9 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
497
502
|
* `series.partitionBy(col).aggregate(seq, mapping).collect()` to
|
|
498
503
|
* aggregate per entity. See {@link TimeSeries.partitionBy}.
|
|
499
504
|
*/
|
|
500
|
-
aggregate<const Mapping extends
|
|
505
|
+
aggregate<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, mapping: Mapping, options?: {
|
|
501
506
|
range?: TemporalLike;
|
|
502
507
|
}): TimeSeries<AggregateSchema<S, Mapping>>;
|
|
503
|
-
aggregate<const Mapping extends AggregateOutputMap<S>>(sequence: SequenceLike, mapping: Mapping, options?: {
|
|
504
|
-
range?: TemporalLike;
|
|
505
|
-
}): TimeSeries<AggregateOutputMapResultSchema<S, Mapping>>;
|
|
506
508
|
/**
|
|
507
509
|
* Example: `series.reduce("value", "avg")`.
|
|
508
510
|
* Collapses the entire series to a single scalar value using the specified reducer.
|
|
@@ -516,8 +518,7 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
516
518
|
* a plain value or record.
|
|
517
519
|
*/
|
|
518
520
|
reduce(column: ValueColumnsForSchema<S>[number]['name'], reducer: AggregateReducer): ColumnValue | undefined;
|
|
519
|
-
reduce<const Mapping extends
|
|
520
|
-
reduce<const Mapping extends AggregateOutputMap<S>>(mapping: Mapping): ReduceResult<S, Mapping>;
|
|
521
|
+
reduce<const Mapping extends ValidatedAggregateMap<S, Mapping>>(mapping: Mapping): ReduceResult<S, Mapping>;
|
|
521
522
|
/**
|
|
522
523
|
* Example: `series.groupBy("host")`.
|
|
523
524
|
* Partitions the series into groups keyed by the distinct values of a payload column.
|
|
@@ -900,26 +901,16 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
900
901
|
* `series.partitionBy(col).rolling(...).collect()` to scope per
|
|
901
902
|
* entity. See {@link TimeSeries.partitionBy}.
|
|
902
903
|
*/
|
|
903
|
-
rolling<const Mapping extends
|
|
904
|
+
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(window: DurationInput, mapping: Mapping, options?: {
|
|
904
905
|
alignment?: RollingAlignment;
|
|
905
906
|
minSamples?: number;
|
|
906
907
|
}): TimeSeries<RollingSchema<S, Mapping>>;
|
|
907
|
-
rolling<const Mapping extends
|
|
908
|
-
alignment?: RollingAlignment;
|
|
909
|
-
minSamples?: number;
|
|
910
|
-
}): TimeSeries<RollingOutputMapSchema<S, Mapping>>;
|
|
911
|
-
rolling<const Mapping extends AggregateMap<S>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
908
|
+
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
912
909
|
alignment?: RollingAlignment;
|
|
913
910
|
sample?: AlignSample;
|
|
914
911
|
range?: TemporalLike;
|
|
915
912
|
minSamples?: number;
|
|
916
913
|
}): TimeSeries<AggregateSchema<S, Mapping>>;
|
|
917
|
-
rolling<const Mapping extends AggregateOutputMap<S>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
918
|
-
alignment?: RollingAlignment;
|
|
919
|
-
sample?: AlignSample;
|
|
920
|
-
range?: TemporalLike;
|
|
921
|
-
minSamples?: number;
|
|
922
|
-
}): TimeSeries<AggregateOutputMapResultSchema<S, Mapping>>;
|
|
923
914
|
/**
|
|
924
915
|
* Example: `series.smooth("value", "ema", { alpha: 0.2 })`.
|
|
925
916
|
* Applies a smoothing transform to one numeric payload column while preserving the original key
|
|
@@ -12,7 +12,7 @@ import { TimeRange } from '../core/time-range.js';
|
|
|
12
12
|
import { compareEventKeys } from '../core/temporal.js';
|
|
13
13
|
import { PartitionedTimeSeries } from './partitioned-time-series.js';
|
|
14
14
|
import { Sequence } from '../sequence/sequence.js';
|
|
15
|
-
import { withColumnsRenamed, withColumnsSelected, withRowRange, } from '../columnar/index.js';
|
|
15
|
+
import { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, float64ColumnFromArray, stringColumnFromArray, withColumnsRenamed, withColumnsSelected, withKeyColumn, withRowRange, } from '../columnar/index.js';
|
|
16
16
|
import { SeriesStore } from '../live/series-store.js';
|
|
17
17
|
import { parseDuration } from '../core/duration.js';
|
|
18
18
|
import { resolveReducer, } from '../reducers/index.js';
|
|
@@ -468,6 +468,7 @@ export class TimeSeries {
|
|
|
468
468
|
name: input.name,
|
|
469
469
|
schema: input.schema,
|
|
470
470
|
rows: parseJsonRows(input.schema, input.rows, input.parse),
|
|
471
|
+
sort: input.sort ?? false,
|
|
471
472
|
});
|
|
472
473
|
}
|
|
473
474
|
/**
|
|
@@ -587,13 +588,21 @@ export class TimeSeries {
|
|
|
587
588
|
}
|
|
588
589
|
else {
|
|
589
590
|
this.schema = Object.freeze(input.schema.slice());
|
|
591
|
+
// `{ sort: true }` sorts unsorted input by key before intake (otherwise
|
|
592
|
+
// the intake's non-decreasing-order check throws). Stable, so rows with
|
|
593
|
+
// equal keys keep their input order; same key comparator as
|
|
594
|
+
// `fromEvents` / `concat`. Copies the array — the caller's `rows` is not
|
|
595
|
+
// mutated.
|
|
596
|
+
const rows = input.sort
|
|
597
|
+
? [...input.rows].sort((a, b) => compareEventKeys(toKey(a[0]), toKey(b[0])))
|
|
598
|
+
: input.rows;
|
|
590
599
|
// `SeriesStore.fromValidatedRows` runs the column-native
|
|
591
600
|
// intake (`validateAndNormalizeColumnar`) — same validation
|
|
592
601
|
// rules as the pre-2a row-shape `validateAndNormalize` but
|
|
593
602
|
// writes directly into columnar buffers without allocating
|
|
594
603
|
// Event objects + frozen data dicts. Events lazy-materialize
|
|
595
604
|
// on first `eventAt(i)` access via the store's per-row cache.
|
|
596
|
-
this.#store = SeriesStore.fromValidatedRows(this.schema,
|
|
605
|
+
this.#store = SeriesStore.fromValidatedRows(this.schema, rows);
|
|
597
606
|
}
|
|
598
607
|
Object.freeze(this);
|
|
599
608
|
}
|
|
@@ -782,14 +791,18 @@ export class TimeSeries {
|
|
|
782
791
|
* series.events[i]` holds whenever both are accessed.
|
|
783
792
|
*/
|
|
784
793
|
at(index) {
|
|
785
|
-
//
|
|
786
|
-
//
|
|
787
|
-
// `#store.eventAt
|
|
788
|
-
//
|
|
789
|
-
//
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
794
|
+
// Non-integer / NaN inputs return undefined rather than throwing
|
|
795
|
+
// downstream from `#store.eventAt` (`this.events[NaN]` returned undefined
|
|
796
|
+
// per JS array semantics; the direct route to `#store.eventAt(NaN)` would
|
|
797
|
+
// proceed past the bounds check and materialize a key at an invalid row).
|
|
798
|
+
// Closed Codex round 4's medium finding on PR #150.
|
|
799
|
+
if (!Number.isInteger(index))
|
|
800
|
+
return undefined;
|
|
801
|
+
// Negative indices count from the end — parity with `LiveSeries.at` and
|
|
802
|
+
// `Array.prototype.at` (`at(-1)` is the last event).
|
|
803
|
+
if (index < 0)
|
|
804
|
+
index += this.#store.length;
|
|
805
|
+
if (index < 0 || index >= this.#store.length) {
|
|
793
806
|
return undefined;
|
|
794
807
|
}
|
|
795
808
|
return this.#store.eventAt(index);
|
|
@@ -847,40 +860,95 @@ export class TimeSeries {
|
|
|
847
860
|
}
|
|
848
861
|
/** Example: `series.asTime({ at: "center" })`. Converts the series key type to `"time"` using the supplied anchor within each event extent. */
|
|
849
862
|
asTime(options = {}) {
|
|
863
|
+
// Column-native rekey: reinterpret the key as `time` straight off the
|
|
864
|
+
// existing key's begin/end buffers — no `this.events`. `begin`/`end`
|
|
865
|
+
// reuse the source buffer zero-copy; `center` computes midpoints.
|
|
850
866
|
const schema = Object.freeze([
|
|
851
867
|
{ name: 'time', kind: 'time' },
|
|
852
868
|
...this.schema.slice(1),
|
|
853
869
|
]);
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
870
|
+
const keys = this.#store.store.keys;
|
|
871
|
+
const at = options.at ?? 'begin';
|
|
872
|
+
const n = keys.length;
|
|
873
|
+
let beginBuf;
|
|
874
|
+
if (at === 'center') {
|
|
875
|
+
beginBuf = new Float64Array(n);
|
|
876
|
+
for (let i = 0; i < n; i += 1) {
|
|
877
|
+
beginBuf[i] = (keys.begin[i] + keys.end[i]) / 2;
|
|
878
|
+
}
|
|
857
879
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
880
|
+
else {
|
|
881
|
+
beginBuf = at === 'end' ? keys.end : keys.begin;
|
|
882
|
+
}
|
|
883
|
+
// The source is ordered by `begin`, so `begin` stays monotonic — but for a
|
|
884
|
+
// ranged source with overlapping extents, anchoring at `end` / `center`
|
|
885
|
+
// can REORDER rows, producing a non-monotonic time axis. The pre-#200 path
|
|
886
|
+
// routed these anchors through the validating constructor, which threw on
|
|
887
|
+
// an unsorted result; `withKeyColumn` → `#fromTrustedStore` trusts the key
|
|
888
|
+
// and would silently accept it (breaking `bisect` / `timeRange` / key-range
|
|
889
|
+
// ops). Restore the throw with an O(n) scan. (`begin` can't reorder, so it
|
|
890
|
+
// is exempt — and `withKeyColumn` documents the monotonic-key precondition.)
|
|
891
|
+
if (at !== 'begin') {
|
|
892
|
+
for (let i = 1; i < n; i += 1) {
|
|
893
|
+
if (beginBuf[i] < beginBuf[i - 1]) {
|
|
894
|
+
throw new Error(`asTime({ at: '${at}' }) produced a non-monotonic time axis at ` +
|
|
895
|
+
`row ${i} (${beginBuf[i]} < ${beginBuf[i - 1]}): the source ` +
|
|
896
|
+
`extents overlap, so anchoring at '${at}' reorders rows. Anchor ` +
|
|
897
|
+
`at 'begin', or re-sort after converting.`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const store = withKeyColumn(this.#store.store, schema[0], new TimeKeyColumn(beginBuf, keys.length));
|
|
902
|
+
return TimeSeries.#fromTrustedStore(this.name, schema, store);
|
|
863
903
|
}
|
|
864
904
|
/** Example: `series.asTimeRange()`. Converts the series key type to `"timeRange"` while preserving each event extent. */
|
|
865
905
|
asTimeRange() {
|
|
906
|
+
// Column-native rekey: the timeRange covers each row's existing extent —
|
|
907
|
+
// reuse the key's begin/end buffers zero-copy, no events.
|
|
866
908
|
const schema = Object.freeze([
|
|
867
909
|
{ name: 'timeRange', kind: 'timeRange' },
|
|
868
910
|
...this.schema.slice(1),
|
|
869
911
|
]);
|
|
870
|
-
const
|
|
871
|
-
|
|
912
|
+
const keys = this.#store.store.keys;
|
|
913
|
+
const store = withKeyColumn(this.#store.store, schema[0], new TimeRangeKeyColumn(keys.begin, keys.end, keys.length));
|
|
914
|
+
return TimeSeries.#fromTrustedStore(this.name, schema, store);
|
|
872
915
|
}
|
|
873
916
|
asInterval(value) {
|
|
917
|
+
// Column-native rekey: build the interval labels straight off the key's
|
|
918
|
+
// begin/end buffers — no events. The label fn receives the interval's
|
|
919
|
+
// TimeRange (its [begin, end] extent) + index, NOT the whole event
|
|
920
|
+
// (breaking — see CHANGELOG [Unreleased]). Label kind is inferred from the
|
|
921
|
+
// first label — the two `IntervalValue` kinds, string → StringColumn /
|
|
922
|
+
// number → Float64Column — and must be consistent across rows (a mix
|
|
923
|
+
// throws, as at event intake). A type-defeated non-string/number label
|
|
924
|
+
// (e.g. a boolean via `as any`) is rejected here rather than coerced the
|
|
925
|
+
// way intake would (`true → 1`); throwing on the nonsense input is safer.
|
|
874
926
|
const schema = Object.freeze([
|
|
875
927
|
{ name: 'interval', kind: 'interval' },
|
|
876
928
|
...this.schema.slice(1),
|
|
877
929
|
]);
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
930
|
+
const keys = this.#store.store.keys;
|
|
931
|
+
const n = keys.length;
|
|
932
|
+
const labels = new Array(n);
|
|
933
|
+
if (typeof value === 'function') {
|
|
934
|
+
for (let i = 0; i < n; i += 1) {
|
|
935
|
+
labels[i] = value(new TimeRange([keys.begin[i], keys.end[i]]), i);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
labels.fill(value);
|
|
940
|
+
}
|
|
941
|
+
const labelKind = n > 0 ? typeof labels[0] : 'string';
|
|
942
|
+
for (let i = 1; i < n; i += 1) {
|
|
943
|
+
if (typeof labels[i] !== labelKind) {
|
|
944
|
+
throw new Error(`asInterval: interval label at row ${i} is ${typeof labels[i]} but earlier rows were ${labelKind} — interval labels must be one type throughout`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const labelCol = labelKind === 'number'
|
|
948
|
+
? float64ColumnFromArray(labels)
|
|
949
|
+
: stringColumnFromArray(labels);
|
|
950
|
+
const store = withKeyColumn(this.#store.store, schema[0], new IntervalKeyColumn(keys.begin, keys.end, labelCol, n));
|
|
951
|
+
return TimeSeries.#fromTrustedStore(this.name, schema, store);
|
|
884
952
|
}
|
|
885
953
|
join(other, options = {}) {
|
|
886
954
|
const [left, right] = prepareSeriesForJoin([
|
|
@@ -1237,7 +1305,8 @@ export class TimeSeries {
|
|
|
1237
1305
|
}
|
|
1238
1306
|
pivotByGroup(groupCol, valueCol, options = {}) {
|
|
1239
1307
|
if (this.schema[0].kind !== 'time') {
|
|
1240
|
-
throw new TypeError(`pivotByGroup requires a time-keyed series; got ${this.schema[0].kind}`
|
|
1308
|
+
throw new TypeError(`pivotByGroup requires a time-keyed series; got ${this.schema[0].kind}` +
|
|
1309
|
+
` — piping from aggregate/materialize? call .asTime({ at: 'begin' }) first`);
|
|
1241
1310
|
}
|
|
1242
1311
|
const valueColumnDef = this.schema.find((c) => c.name === valueCol);
|
|
1243
1312
|
if (!valueColumnDef) {
|
|
@@ -2771,6 +2840,12 @@ export class TimeSeries {
|
|
|
2771
2840
|
rollingOptions.alignment = alignment;
|
|
2772
2841
|
if (minSamples !== undefined)
|
|
2773
2842
|
rollingOptions.minSamples = minSamples;
|
|
2843
|
+
// Trust boundary: a generic mapping can't prove it satisfies the
|
|
2844
|
+
// public overload's ValidatedAggregateMap constraint (S is
|
|
2845
|
+
// unresolved here), so route through the broad-schema escape hatch
|
|
2846
|
+
// — for concrete SeriesSchema the constraint degrades to the
|
|
2847
|
+
// permissive AggregateMap by design. The computed spec keys are
|
|
2848
|
+
// collision-checked above.
|
|
2774
2849
|
const rolling = this.rolling(window, {
|
|
2775
2850
|
[avgName]: { from: col, using: 'avg' },
|
|
2776
2851
|
[sdName]: { from: col, using: 'stdev' },
|
|
@@ -2865,6 +2940,8 @@ export class TimeSeries {
|
|
|
2865
2940
|
rollingOptions.alignment = alignment;
|
|
2866
2941
|
if (minSamples !== undefined)
|
|
2867
2942
|
rollingOptions.minSamples = minSamples;
|
|
2943
|
+
// Trust boundary: see baseline() above — generic mapping routed
|
|
2944
|
+
// through the broad-schema escape hatch of ValidatedAggregateMap.
|
|
2868
2945
|
const rolling = this.rolling(window, rollingMapping, rollingOptions);
|
|
2869
2946
|
const kept = [];
|
|
2870
2947
|
for (let i = 0; i < this.events.length; i += 1) {
|
package/dist/batch/validate.js
CHANGED
|
@@ -376,11 +376,11 @@ export function validateAndNormalizeColumnar(input) {
|
|
|
376
376
|
const prevBegin = beginBuf[i - 1];
|
|
377
377
|
const curBegin = beginBuf[i];
|
|
378
378
|
if (prevBegin > curBegin) {
|
|
379
|
-
throw new ValidationError(`row ${i} is out of order`);
|
|
379
|
+
throw new ValidationError(`row ${i} is out of order — keys must be non-decreasing; pass { sort: true } to sort rows on construction, or pre-sort them`);
|
|
380
380
|
}
|
|
381
381
|
if (prevBegin === curBegin && endBuf !== beginBuf) {
|
|
382
382
|
if (endBuf[i - 1] > endBuf[i]) {
|
|
383
|
-
throw new ValidationError(`row ${i} is out of order`);
|
|
383
|
+
throw new ValidationError(`row ${i} is out of order — keys must be non-decreasing; pass { sort: true } to sort rows on construction, or pre-sort them`);
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// pond-ts ships as ES modules only. This stub is the `require` target in the
|
|
4
|
+
// package's `exports` map so that CommonJS consumers get a clear, actionable
|
|
5
|
+
// error instead of Node's cryptic `ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
6
|
+
//
|
|
7
|
+
// It is copied verbatim into `dist/` during `prepack` (see package.json) so it
|
|
8
|
+
// rides along in the published tarball; the source of truth lives at the
|
|
9
|
+
// package root and is never touched by `tsc`.
|
|
10
|
+
|
|
11
|
+
throw new Error(
|
|
12
|
+
'pond-ts is an ES module package and cannot be loaded with require(). ' +
|
|
13
|
+
"Use `import { TimeSeries } from 'pond-ts'` instead, or a dynamic " +
|
|
14
|
+
"`await import('pond-ts')` from CommonJS. See https://nodejs.org/api/esm.html.",
|
|
15
|
+
);
|
package/dist/columnar/index.d.ts
CHANGED
|
@@ -37,5 +37,5 @@ export { type IntervalLabelKind, type KeyColumn, IntervalKeyColumn, TimeKeyColum
|
|
|
37
37
|
export { type FromTrustedStoreOptions, ColumnarStore } from './store.js';
|
|
38
38
|
export { type ColumnBuilder, ArrayColumnBuilder, BooleanColumnBuilder, Float64ColumnBuilder, StringColumnBuilder, columnBuilderForKind, } from './builder.js';
|
|
39
39
|
export { type AnyColumnKind, type ArrayValue, type ColumnDef, type ColumnSchema, type KeyKind, type ScalarValue, } from './types.js';
|
|
40
|
-
export { materialize, withColumnAppended, withColumnReplaced, withColumnsRenamed, withColumnsSelected, withRowRange, withRowSelection, } from './view.js';
|
|
40
|
+
export { materialize, withColumnAppended, withColumnReplaced, withColumnsRenamed, withColumnsSelected, withKeyColumn, withRowRange, withRowSelection, } from './view.js';
|
|
41
41
|
//# sourceMappingURL=index.d.ts.map
|