@pond-ts/react 0.13.2 → 0.14.1

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 (2) hide show
  1. package/CHANGELOG.md +193 -1
  2. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,10 +7,202 @@ 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.13.2...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.14.1...HEAD
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.14.1] — 2026-05-03
15
+
16
+ The "samples reducer + lifted custom-fn guard" release. Surfaced by
17
+ the gRPC experiment's step-4 (anomaly density) walkback: the use
18
+ case "compute counts of values exceeding `k·σ` from a baseline" needs
19
+ the **raw values** from the rolling window, but pond's existing
20
+ built-ins all collapse to scalars or deduplicate. Custom-function
21
+ reducers — which would cover the use case cleanly — worked on batch
22
+ but were rejected at runtime on live with a `TypeError` pointing at
23
+ `AggregateOutputMap` aliases (which don't actually solve "all values"
24
+ either). Two related changes ship together to close both gaps.
25
+
26
+ ### Added
27
+
28
+ - **`'samples'` built-in reducer.** Returns the bucket's defined
29
+ values as an array, in arrival order, with duplicates preserved.
30
+ Sits beside `'unique'` (which deduplicates) and `'top${N}'` (which
31
+ bounds and frequency-orders) — same array-output kind, same
32
+ type-system narrowing through `AggregateOutputMap`. Library-
33
+ implemented; per-event cost is O(1) `add` / O(1) `remove`
34
+ (Map-keyed by event index); `snapshot` is O(N) array copy.
35
+ Memory O(window size).
36
+
37
+ ```ts
38
+ // Anomaly density: count samples > k·σ from a separate baseline.
39
+ const stats = live.rolling(
40
+ '1m',
41
+ {
42
+ mean: { from: 'cpu', using: 'avg' },
43
+ sd: { from: 'cpu', using: 'stdev' },
44
+ },
45
+ { trigger: Trigger.every('30s') },
46
+ );
47
+
48
+ const recent = live.rolling('200ms', {
49
+ vals: { from: 'cpu', using: 'samples' },
50
+ });
51
+
52
+ // At each tick, count threshold crossings against the baseline:
53
+ stats.on('event', (e) => {
54
+ const samples = recent.value().vals as ReadonlyArray<number>;
55
+ const counts = thresholds.map(
56
+ (k) => samples.filter((v) => v - e.get('mean') > k * e.get('sd')).length,
57
+ );
58
+ // ... emit anomaly density
59
+ });
60
+ ```
61
+
62
+ Like `unique`, `samples` flattens one level on array-kind source
63
+ columns. Returns `[]` for an empty bucket.
64
+
65
+ ### Changed
66
+
67
+ - **Custom-function reducers now work on live.** Removed the runtime
68
+ `TypeError` guards on `LiveAggregation`, `LiveRollingAggregation`,
69
+ and `LivePartitionedSyncRolling` that previously rejected
70
+ function-typed reducers. New `bucketStateFor` and `rollingStateFor`
71
+ helpers in `reducers/index.ts` route built-ins to their dedicated
72
+ O(1) machinery and wrap custom functions in a generic adapter:
73
+
74
+ - **Bucket adapter** (`LiveAggregation`): buffers values, calls
75
+ the function once at `snapshot()` time. O(N) per snapshot.
76
+ - **Rolling adapter** (`LiveRollingAggregation`,
77
+ `LivePartitionedSyncRolling`): Map-keyed by event index for O(1)
78
+ `add` / O(1) `remove`; `snapshot()` calls the function with
79
+ `Array.from(map.values())` in arrival order. **O(N) per
80
+ snapshot** — the function re-runs over the current window each
81
+ time the accumulator emits.
82
+
83
+ Documented as the explicit trade-off: convenience of writing
84
+ `(values) => ...` inline against the perf cliff at high event
85
+ rates. For high-throughput streams prefer built-ins or `'samples'`
86
+ (collapse the window once on the producer side, run custom logic
87
+ on the consumer). For low-rate dashboards / debug pipelines /
88
+ prototypes, the convenience usually wins.
89
+
90
+ Pre-v0.14.1, calling `live.rolling(...)` with a custom-function
91
+ reducer threw `TypeError: live rolling reducer for output 'X' must
92
+ be a built-in name; ...`. Post-v0.14.1, the same call constructs
93
+ successfully and runs.
94
+
95
+ ### Tests
96
+
97
+ - 15 new tests in `test/samples-reducer.test.ts` covering: batch
98
+ reduce / aggregate / rolling (including the array-source
99
+ flattening); live aggregate (per-bucket arrays); live rolling
100
+ (window eviction, snapshot correctness through multiple cycles);
101
+ synced partitioned rolling with samples per partition; an
102
+ end-to-end anomaly-density-against-baseline scenario.
103
+ - 2 obsolete tests in `LiveAggregateOutputMap.test.ts` rewritten —
104
+ previously asserted the rejection error, now assert that custom
105
+ functions construct successfully and produce the right value.
106
+ - Total core tests: 1087 (was 1072).
107
+
108
+ ### Docs
109
+
110
+ - `pond-ts/transforms/reducer-reference.mdx`: new `'samples'` entry
111
+ in the Array-producing reducers section; "Choosing a reducer"
112
+ matrix updated; empty-bucket and rolling-complexity tables
113
+ updated; Custom reducers section gained the live perf-cliff
114
+ callout.
115
+ - `pond-ts/transforms/rolling.mdx`: replaced the "Custom-function
116
+ reducers are batch-only" note with the new "O(N) per snapshot on
117
+ live" perf-cliff note pointing at the reducer reference.
118
+
119
+ [0.14.1]: https://github.com/pjm17971/pond-ts/compare/v0.14.0...v0.14.1
120
+
121
+ ## [0.14.0] — 2026-05-01
122
+
123
+ Two perf wins driven by the gRPC experiment's V3 profiling pass
124
+ (PR #14 on `pond-grpc-experiment`): `estimateEventBytes` at 6.2%
125
+ self time and the partition router's `Event → row → Event`
126
+ round-trip (combined ~7% in `#validateRow` + `Event` constructor
127
+ re-allocations). Both root-caused, both fixed.
128
+
129
+ Benchmark deltas on `scripts/perf-live-partitioned.mjs`
130
+ (100k events, median ms):
131
+
132
+ | Scenario | Before | After | Δ |
133
+ | ------------------------------------- | -----: | -----: | ------: |
134
+ | bare `LiveSeries.push` | 41.11 | 30.08 | **−27%** |
135
+ | `partitionBy('host')` routing (10) | 83.14 | 39.10 | **−53%** |
136
+ | `partitionBy + collect()` | 124.82 | 49.96 | **−60%** |
137
+ | `partitionBy + apply(fill)` | 120.53 | 49.64 | **−59%** |
138
+ | `partitionBy('host')` routing (1000) | 105.92 | 43.23 | **−59%** |
139
+
140
+ The bare-push delta is from the byte-estimate removal; the
141
+ partition-routing deltas are from the trusted-pipeline path that
142
+ skips `Event → row → Event` reconstruction at every routing hop.
143
+
144
+ ### Removed (breaking, pre-1.0)
145
+
146
+ - **`retention.maxBytes`** option on `LiveSeriesOptions`. Speculative
147
+ feature from pre-v0.10 that no real user has reached for. Use
148
+ `retention.maxEvents` for count-based caps; `maxBytes` was
149
+ approximate (rough per-event byte estimate) and the imprecision
150
+ meant it was rarely used as designed.
151
+
152
+ Migration: replace `{ retention: { maxBytes: N } }` with
153
+ `{ retention: { maxEvents: M } }` where M is your desired
154
+ upper bound on event count.
155
+
156
+ ### Changed
157
+
158
+ - **`estimateEventBytes` and the `#byteEstimate` accumulator
159
+ removed** from `LiveSeries`. Closes the 6.2% per-push self-time
160
+ line the gRPC experiment surfaced. Bare push is now ~27% faster
161
+ for the typical case where `maxBytes` was never set.
162
+
163
+ - **Partition router uses a trusted-pipeline fast path.**
164
+ `LivePartitionedSeries.#routeEvent`, `collect()`, and `apply()`
165
+ previously round-tripped `Event → row → Event` at every routing
166
+ hop — re-validating and re-allocating Events that the source
167
+ pipeline had already constructed. New `_pushTrustedEvents` method
168
+ on `LiveSeries` accepts pre-validated Event references (under a
169
+ schema-identity contract; only used internally where the source
170
+ and target schemas are guaranteed identical). Closes the ~7%
171
+ combined self-time line in `#validateRow` (×2) and `Event`
172
+ constructor (×2) that the gRPC profile flagged.
173
+
174
+ Trusted-pipeline applies to: the source-to-partition route, the
175
+ per-partition replay-on-construct prefix, the unified-buffer
176
+ `collect()` subscriber, and `apply()`'s factory-output forwarding.
177
+ All four sites had identical schemas at both ends — the trust
178
+ contract holds without runtime re-checking.
179
+
180
+ `_pushTrustedEvents` is `@internal` and not exported from the
181
+ public type surface. Reach for `pushMany` from any external
182
+ context; the trusted variant skips schema validation and is
183
+ only safe for pond's own internal pipelines.
184
+
185
+ ### Tests
186
+
187
+ - 4 new tests in `test/LiveSeries.test.ts` for the trusted-pipeline
188
+ path: insertion without re-validation, listener fan-out and
189
+ retention behaviour, ordering enforcement (strict still rejects
190
+ out-of-order on the trusted path — the trust contract is only
191
+ about validation/allocation, not insertion ordering), empty-array
192
+ no-op.
193
+ - Removed the `retention: maxBytes` describe block in
194
+ `test/LiveSeries.test.ts` and the `forwards retention.maxBytes`
195
+ assertion in `test/LiveSeries.snapshot-append.test.ts`.
196
+ - Total core tests: 1072 (was 1070; +4 new for the trusted path,
197
+ −2 for the removed maxBytes assertions).
198
+
199
+ ### Docs
200
+
201
+ - `live-series.mdx`: retention table and example trimmed to
202
+ `maxEvents` + `maxAge` only. Removed the byte-estimate prose.
203
+
204
+ [0.14.0]: https://github.com/pjm17971/pond-ts/compare/v0.13.2...v0.14.0
205
+
14
206
  ## [0.13.2] — 2026-05-01
15
207
 
16
208
  Strictly additive over v0.13.1. Adds `Trigger.count(n)` per the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.13.2",
3
+ "version": "0.14.1",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  "test:runtime": "vitest run"
32
32
  },
33
33
  "peerDependencies": {
34
- "pond-ts": "^0.13.0",
34
+ "pond-ts": "^0.14.0",
35
35
  "react": "^18.0.0 || ^19.0.0"
36
36
  },
37
37
  "devDependencies": {