@pond-ts/react 0.14.1 → 0.14.3

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 +126 -14
  2. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,10 +7,123 @@ 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.14.1...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.14.3...HEAD
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.14.3] — 2026-05-04
15
+
16
+ A targeted allocation fix in the `'samples'` reducer's rolling-state
17
+ implementation. Motivated by gRPC experiment V7 numbers — at the
18
+ ceiling regime (1k partitions × 1k events/s, 1M target) the all-
19
+ pond pipeline using `samples()` regressed throughput ~19% vs V6's
20
+ hybrid pond-rolling + manual-deque pattern, with +17% heap at
21
+ moderate loads. Per-event cost analysis pointed at a 1-element
22
+ `ScalarValue[]` allocation per scalar `add()` — one wasted
23
+ allocation per event compounding under sustained kHz × N-partition
24
+ load.
25
+
26
+ ### Changed
27
+
28
+ - **`samples.rollingState()` skips array wrap for scalar source
29
+ columns.** Scalar values (the common case at saturation) now
30
+ store directly into the keyed map; only array-kind sources
31
+ build a sub-array (because `remove(index)` needs to drop a
32
+ single event's contributions together). Snapshot branches on
33
+ `Array.isArray` to flatten the mixed map.
34
+
35
+ ```
36
+ Focused micro-bench (5M scalar add+remove cycles):
37
+ median (ms) min (ms) max (ms)
38
+ baseline (v0.14.2) 239.85 236.62 244.58
39
+ v0.14.3 209.09 207.42 215.26
40
+ delta −12.8% −12.3% −12.0%
41
+
42
+ Integration bench (100k events × N hosts, full pipeline):
43
+ Tight wall-clock parity within run-to-run noise across all
44
+ scenarios (samples 1m/5s, scalar/array). Allocation pressure
45
+ isn't the dominant cost at this scale; the optimization
46
+ compounds only at saturation regimes where GC pressure stacks.
47
+ ```
48
+
49
+ Behavior is preserved bit-for-bit — every existing
50
+ `samples-reducer.test.ts` assertion passes without modification.
51
+
52
+ ### Added
53
+
54
+ - `packages/core/scripts/perf-samples-reducer.mjs` — benchmark
55
+ covering the focused micro-bench + four integration scenarios
56
+ (scalar moderate / scalar high-cardinality / scalar high-churn
57
+ / array source) with a comparison anchor against `'avg'` on
58
+ the same shape. Run with `node --expose-gc` for heap numbers.
59
+
60
+ ### Note on saturation regimes
61
+
62
+ V7's regression isn't fully closed by this fix. The remaining gap
63
+ is architectural — V7 routes events through two full
64
+ `LiveRollingAggregation` pipelines (Map ops + reducer state +
65
+ trigger dispatch + subscriber fan-out per pipeline), where V6's
66
+ hybrid had one pond rolling for stats plus a passive
67
+ `array.push` listener for raw values. At the kHz × 1k-partition
68
+ saturation regime, the manual-deque pattern is genuinely the
69
+ right shape; pond's `samples` is for typical loads where per-
70
+ event overhead is invisible. A shared-buffer primitive (parked
71
+ as `tap()` in PLAN.md) would close the saturation gap; out of
72
+ scope for v0.14.3.
73
+
74
+ [0.14.3]: https://github.com/pjm17971/pond-ts/compare/v0.14.2...v0.14.3
75
+
76
+ ## [0.14.2] — 2026-05-03
77
+
78
+ Hotfix over v0.14.1 — closes a type-narrowing gap on the new
79
+ `'samples'` reducer that the v0.14.1 Layer 2 review caught
80
+ post-merge. The runtime worked, but TypeScript didn't know about
81
+ `'samples'`: passing it through `series.aggregate({ col: 'samples' })`
82
+ or `live.rolling(window, { col: 'samples' })` produced
83
+ `Type '"samples"' is not assignable to type 'AggregateReducer'`,
84
+ and `series.reduce({ col: 'samples' }).col` fell through to
85
+ `ColumnValue | undefined` instead of the narrowed array type.
86
+
87
+ ### Fixed
88
+
89
+ - **`'samples'` is now in the type system everywhere.** Added to
90
+ `AggregateFunction` union, both branches of
91
+ `AggregateFunctionsForKind` (numeric and array/string/boolean),
92
+ `AggregateKindForColumn` (so output columns get
93
+ `kind: 'array'`), `ArrayAggregateKind`, and the array branch of
94
+ `ReduceResult` in `types-reduce.ts`.
95
+
96
+ ```ts
97
+ // Pre-v0.14.2: TS error, but ran correctly.
98
+ // Post-v0.14.2: typechecks and narrows the same way `unique` and
99
+ // `top${N}` do — `ReadonlyArray<T>` for source kind T.
100
+ series.reduce({ vals: 'samples' }).vals; // ReadonlyArray<number> | undefined
101
+
102
+ series.aggregate(Sequence.every('5s'), { vals: 'samples' });
103
+ // Output column: { name: 'vals', kind: 'array' }
104
+ ```
105
+
106
+ - **`reducer-reference.mdx`** updated: "14 built-in reducers" → 15.
107
+
108
+ ### Added
109
+
110
+ - `test-d/types.test-d.ts` block pinning `'samples'` narrowing
111
+ parity with `'unique'` / `'top${N}'`. Closes the regression hole
112
+ the v0.14.1 review surfaced.
113
+
114
+ ### Known follow-up
115
+
116
+ The v0.14.1 review also flagged that `npm run verify`'s
117
+ `test:type` step uses `tsconfig.types.json` (covers `src` +
118
+ `test-d/`), not `tsconfig.vitest.json` (covers `test/`) — that's
119
+ why the missing `'samples'` narrowing didn't fail CI even though
120
+ `packages/core/test/samples-reducer.test.ts` had ~30 type errors.
121
+ Captured in DOCPLAN.md / PLAN.md as a future safety-net widening;
122
+ not in scope for v0.14.2 because pre-existing test files have
123
+ their own type drift that would need cleanup first.
124
+
125
+ [0.14.2]: https://github.com/pjm17971/pond-ts/compare/v0.14.1...v0.14.2
126
+
14
127
  ## [0.14.1] — 2026-05-03
15
128
 
16
129
  The "samples reducer + lifted custom-fn guard" release. Surfaced by
@@ -40,7 +153,7 @@ either). Two related changes ship together to close both gaps.
40
153
  '1m',
41
154
  {
42
155
  mean: { from: 'cpu', using: 'avg' },
43
- sd: { from: 'cpu', using: 'stdev' },
156
+ sd: { from: 'cpu', using: 'stdev' },
44
157
  },
45
158
  { trigger: Trigger.every('30s') },
46
159
  );
@@ -70,7 +183,6 @@ either). Two related changes ship together to close both gaps.
70
183
  function-typed reducers. New `bucketStateFor` and `rollingStateFor`
71
184
  helpers in `reducers/index.ts` route built-ins to their dedicated
72
185
  O(1) machinery and wrap custom functions in a generic adapter:
73
-
74
186
  - **Bucket adapter** (`LiveAggregation`): buffers values, calls
75
187
  the function once at `snapshot()` time. O(N) per snapshot.
76
188
  - **Rolling adapter** (`LiveRollingAggregation`,
@@ -89,7 +201,7 @@ either). Two related changes ship together to close both gaps.
89
201
 
90
202
  Pre-v0.14.1, calling `live.rolling(...)` with a custom-function
91
203
  reducer threw `TypeError: live rolling reducer for output 'X' must
92
- be a built-in name; ...`. Post-v0.14.1, the same call constructs
204
+ be a built-in name; ...`. Post-v0.14.1, the same call constructs
93
205
  successfully and runs.
94
206
 
95
207
  ### Tests
@@ -129,13 +241,13 @@ re-allocations). Both root-caused, both fixed.
129
241
  Benchmark deltas on `scripts/perf-live-partitioned.mjs`
130
242
  (100k events, median ms):
131
243
 
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%** |
244
+ | Scenario | Before | After | Δ |
245
+ | ------------------------------------ | -----: | ----: | -------: |
246
+ | bare `LiveSeries.push` | 41.11 | 30.08 | **−27%** |
247
+ | `partitionBy('host')` routing (10) | 83.14 | 39.10 | **−53%** |
248
+ | `partitionBy + collect()` | 124.82 | 49.96 | **−60%** |
249
+ | `partitionBy + apply(fill)` | 120.53 | 49.64 | **−59%** |
250
+ | `partitionBy('host')` routing (1000) | 105.92 | 43.23 | **−59%** |
139
251
 
140
252
  The bare-push delta is from the byte-estimate removal; the
141
253
  partition-routing deltas are from the trusted-pipeline path that
@@ -378,7 +490,7 @@ the live surface.
378
490
  - **Better error message when a custom-function reducer is passed to
379
491
  live aggregation.** `LiveAggregation` already failed at construction
380
492
  via `resolveReducer(reducer)` (with a generic `unsupported aggregate
381
- reducer` message); now the eager built-in-name check runs first and
493
+ reducer` message); now the eager built-in-name check runs first and
382
494
  emits a targeted error pointing at the `AggregateOutputMap` alias
383
495
  workaround. Same eager behavior on `LivePartitionedSyncRolling`,
384
496
  which previously failed lazily when the first partition spawned —
@@ -412,8 +524,8 @@ the live surface.
412
524
  required TS to see the `trigger` field's discriminator at the call
413
525
  site — so a caller writing
414
526
  `const opts: LiveRollingOptions = { trigger: Trigger.event() };
415
- partitioned.rolling(window, mapping, opts);` got `TS2769 No
416
- overload matches this call`. Pre-existing hole on the partitioned
527
+ partitioned.rolling(window, mapping, opts);` got `TS2769 No
528
+ overload matches this call`. Pre-existing hole on the partitioned
417
529
  surface; surfaced by the v0.13.0 Codex adversarial pass. Closed by
418
530
  adding catch-all overloads that accept the broader
419
531
  `LiveRollingOptions` and return the union of both trigger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {