@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.
- package/CHANGELOG.md +126 -14
- 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.
|
|
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:
|
|
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
|
-
|
|
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
|
|
133
|
-
|
|
|
134
|
-
| bare `LiveSeries.push`
|
|
135
|
-
| `partitionBy('host')` routing (10)
|
|
136
|
-
| `partitionBy + collect()`
|
|
137
|
-
| `partitionBy + apply(fill)`
|
|
138
|
-
| `partitionBy('host')` routing (1000)
|
|
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
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|