@pond-ts/react 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +96 -1
  2. package/README.md +18 -14
  3. package/package.json +2 -2
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.23.0...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.25.0...HEAD
11
+ [0.25.0]: https://github.com/pjm17971/pond-ts/compare/v0.24.0...v0.25.0
12
+ [0.24.0]: https://github.com/pjm17971/pond-ts/compare/v0.23.0...v0.24.0
11
13
  [0.23.0]: https://github.com/pjm17971/pond-ts/compare/v0.22.0...v0.23.0
12
14
  [0.22.0]: https://github.com/pjm17971/pond-ts/compare/v0.21.0...v0.22.0
13
15
  [0.21.0]: https://github.com/pjm17971/pond-ts/compare/v0.20.0...v0.21.0
@@ -17,6 +19,99 @@ type-level changes; patch bumps are strictly additive.
17
19
 
18
20
  ## [Unreleased]
19
21
 
22
+ ## [0.25.0] — 2026-06-15
23
+
24
+ ### Changed
25
+
26
+ - **Reducers now treat non-finite numerics (`NaN` / `±Infinity`) as missing —
27
+ they are skipped — uniformly across every built-in reducer and all four
28
+ execution paths (`reduce`, the columnar fast path, `aggregate`/bucket, and
29
+ `rolling`/live).** Previously the paths disagreed on non-finite input: e.g.
30
+ `min`/`max` returned a position-dependent wrong extreme on the batch/columnar
31
+ paths but the true extreme on aggregate/rolling; `sum`/`avg` propagated
32
+ `NaN`. Non-finite can't enter via the row API (intake rejects it) — it only
33
+ arises inside computed columns (`cumulative` overflow, `diff`/`rate`
34
+ overflow, `collapse`, trusted construction) — so this only changes results
35
+ for those degenerate values, and makes every path agree. The three-layer
36
+ contract: **intake** stays strict (rejects non-finite), **computed writers**
37
+ stay permissive (pack honest non-finite), **reducers** are robust (skip it).
38
+ A standing parity-matrix test now pins all paths together. See
39
+ `docs/notes/reducer-nan-policy.md`. This also resolves the `aggregate('stdev')`
40
+ divergence class and the `min`/`max` NaN-laundering bug.
41
+ - Internal: `Float64Column` gained an `allFinite` fast-path flag (data-derived
42
+ at construction, conservative-by-default) so reducers skip the per-element
43
+ finite check on provably-finite columns — keeping the policy's cost off the
44
+ hot path (min/max/count stay at their pre-policy speed).
45
+
46
+ ### Fixed
47
+
48
+ - **`rolling(window, { x: 'stdev' })` is now numerically stable.** It was the
49
+ last batch stdev path still on the one-pass `Σx²/n − mean²`, which cancels
50
+ catastrophically on near-equal large values (`[1e10, 1e10+1, …]` → `0`
51
+ instead of ≈1.118, or a negative variance → `NaN`) and drifts on trending
52
+ data (cumulative distance, elevation). It now uses Welford's online variance
53
+ with an order-independent **delete** — deviation-space, so no cancellation,
54
+ and removal **by value**, which keeps it correct under the live layer's
55
+ `reorder`-mode eviction (a positional/FIFO remove would have broken it; the
56
+ documented "stdev is reorder-safe" contract is preserved). Rolling-stdev
57
+ values shift in the last ULPs (now correct); the path stays O(1) and within
58
+ run-noise of the old one-pass, and a single-element window now reports exactly
59
+ `0` at any magnitude. Like any subtractive sliding variance, evicting an
60
+ outlier far outside the residual spread loses precision — negligible until the
61
+ evicted point is ~1e7–1e8× the residual stdev, far beyond realistic data.
62
+ - A standing differential-fuzz parity suite now pins every built-in reducer's
63
+ execution paths (columnar fast path vs `bucket` vs `rolling`, and the FIFO
64
+ sliding window vs a from-scratch recompute) against silent drift across
65
+ randomized magnitudes and window sizes — the class of bug behind the stdev
66
+ and `min`/`max` divergences.
67
+
68
+ ## [0.24.0] — 2026-06-14
69
+
70
+ ### Changed
71
+
72
+ - **`TimeSeries.timeRange()` is now a columnar key-axis read instead of a
73
+ reduce over materialized events.** Behavior is unchanged, but the old
74
+ implementation materialized every `Event` on its first call — and because
75
+ `aggregate()` defaults its `range` to `series.timeRange()`, a one-shot
76
+ `aggregate()` paid full event materialization before the columnar fast
77
+ path could run, erasing the win. The new path reads the key column's
78
+ begin/end axis directly: O(1) for time-keyed series, a typed-array scan
79
+ for range/interval-keyed series, with no event materialization. Measured
80
+ on 1M rows: `timeRange()` itself ~407 ms → ~0.002 ms (time-keyed); cold
81
+ `aggregate()` with a defaulted range ~387 ms → ~6 ms (~63×). Every
82
+ `timeRange()` / `overlaps` / `contains` / `intersection` caller benefits.
83
+ (Audit v2 §3.3.)
84
+ - **`aggregate()` now takes the columnar fast path when a mapping mixes
85
+ numeric reducers with `first` / `last`.** Previously a single `first` or
86
+ `last` column (they have no numeric `reduceColumn`) bailed the entire call
87
+ to the row path. They now qualify via a boundary scan — the first/last
88
+ _defined_ cell, on any column kind. Behavior is unchanged. The big
89
+ beneficiary is **partitioned `aggregate`**, which auto-injects a `'first'`
90
+ reducer for the partition column and so was excluded from the fast path on
91
+ every call (audit v2 §3.2/§3.3). Measured on 1M rows, flat
92
+ `{ cpu: 'avg', host: 'first' }`: ~37.7 ms → ~4.8 ms (~7.8×); the
93
+ pure-numeric path is unchanged. (The remaining `partitionBy` materialization
94
+ cost is addressed separately by the columnar `partitionBy` split.)
95
+ - **`partitionBy(...)` now splits the columnar store directly instead of
96
+ materializing events.** `collect()` / the per-partition sugar methods
97
+ (`fill` / `diff` / `rolling` / …) and `toMap()` previously walked
98
+ `this.events` to bucket rows, then rebuilt each partition via `fromEvents`
99
+ (re-validating + re-packing) — silently re-paying the event-materialization
100
+ tax the columnar wave removed, and making `partitionBy(host).fill().collect()`
101
+ the #1 batch hotspot. They now group row indices off the store and gather
102
+ each partition via a zero-materialization columnar selection. Behavior is
103
+ unchanged (partition order, the `' undefined'` missing-key bucket, composite
104
+ keys, and declared `groups` all preserved). Measured on 100k rows / 64
105
+ partitions: `toMap()` ~389 → ~25 ns/row (~15×, no event materialization at
106
+ all); `diff().collect()` ~2×; `fill(hold).collect()` ~1.7× (the residual is
107
+ `TimeSeries.concat` still materializing to re-sort — a separate follow-up).
108
+ Declared-`groups` membership is validated by the same columnar scan, so that
109
+ path is materialization-free too (~331 → ~33 ns/row). **Behavior note:**
110
+ per-partition sub-series from `toMap()` / `apply()` now lazily materialize
111
+ their own `Event` objects rather than reusing the source's instances — cell
112
+ values are identical; only object identity differs (`collect()`, which
113
+ returns the source unchanged, is unaffected). (Audit v2 §3.2.)
114
+
20
115
  ## [0.23.0] — 2026-06-13
21
116
 
22
117
  ### Added
package/README.md CHANGED
@@ -152,20 +152,24 @@ const points = series.sample({ reservoir: { size: 500 } }).toRows();
152
152
 
153
153
  ## Performance
154
154
 
155
- pond-ts is **7.6x faster** than pondjs on average across all comparable
156
- operations, with no regressions. The advantage grows with data size.
157
-
158
- | Category | Speedup (N=16k) | Notes |
159
- | ----------------- | --------------- | --------------------------------------------- |
160
- | **Aggregation** | 25–32x | O(N+B) bucketing vs O(N×B) Pipeline |
161
- | **Alignment** | 32x | Forward cursor vs repeated binary search |
162
- | **Rate/diff** | 18x | Direct array walk vs Pipeline materialization |
163
- | **Fill** | 10–11x | Single-pass vs Pipeline per strategy |
164
- | **Transforms** | 316x | Pre-validated constructor skips re-validation |
165
- | **Construction** | 7x | Plain objects vs ImmutableJS wrapping |
166
- | **Statistics** | 79x | Direct computation vs ImmutableJS iteration |
167
- | **Serialization** | 4x | Simpler internal representation |
168
- | **Event access** | 23x | Array indexing vs ImmutableJS `get()` |
155
+ pond-ts is faster on **every** comparable operation, with no regressions
156
+ a **~17x** geometric-mean speedup across the measurable ops, plus a handful
157
+ of transforms (`select` / `rename`) that are **effectively instant** (O(1)
158
+ column rebinds, below the timer's resolution). The advantage grows with data
159
+ size.
160
+
161
+ | Category | Speedup (N=16k) | Notes |
162
+ | ----------------- | ---------------------------------------------------- | --------------------------------------------- |
163
+ | **Rate** | ~120x | Single columnar walk vs Pipeline |
164
+ | **Fill** | 7787x | Single columnar pass vs Pipeline per strategy |
165
+ | **Aggregation** | 57–82x | O(N+B) bucketing vs O(N×B) Pipeline |
166
+ | **Statistics** | 1880x | Typed-array reduce vs ImmutableJS iteration |
167
+ | **Alignment** | 42x | Forward cursor vs repeated binary search |
168
+ | **Construction** | 13x | Columnar intake vs ImmutableJS wrapping |
169
+ | **Chained** | 8x | Derived constructors vs per-step Pipeline |
170
+ | **Transforms** | `select`/`rename` instant; `collapse` 30x; `map` ~4x | Column reshapes vs Pipeline |
171
+ | **Event access** | 6x | Array indexing vs ImmutableJS `get()` |
172
+ | **Serialization** | 4x | Lightweight columnar representation |
169
173
 
170
174
  See the [full benchmark results](website/docs/reference/benchmarks.mdx)
171
175
  for detailed numbers. Run locally:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "test:runtime": "vitest run"
34
34
  },
35
35
  "peerDependencies": {
36
- "pond-ts": "^0.23.0",
36
+ "pond-ts": "^0.25.0",
37
37
  "react": "^18.0.0 || ^19.0.0"
38
38
  },
39
39
  "devDependencies": {