@pond-ts/react 0.11.7 → 0.12.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 +187 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,196 @@ 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.12.0...HEAD
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.12.0] — 2026-05-01
|
|
15
|
+
|
|
16
|
+
The "triggers" release. Major redesign of how live accumulators
|
|
17
|
+
control emission cadence — `Trigger` is now a first-class concept
|
|
18
|
+
shaped by two converging real-world use cases (synchronised
|
|
19
|
+
partitioned tick aggregation in the gRPC pipeline experiment,
|
|
20
|
+
sequence-sampled rolling in webapp telemetry).
|
|
21
|
+
|
|
22
|
+
Two correctness audits before publish: a Layer 2 Claude review
|
|
23
|
+
(column collision, dispose, late-spawn, peer-dep) and a Codex
|
|
24
|
+
adversarial review (quiet-partition stale samples, pre-existing
|
|
25
|
+
data replay at construction, spawn-listener cleanup). All findings
|
|
26
|
+
fixed and pinned with regression tests. 1039 / 1039 tests pass.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Trigger as a first-class concept.** A new `Trigger` factory
|
|
31
|
+
exposed at the package root lets `LiveRollingAggregation` switch
|
|
32
|
+
emission cadence without changing any other shape:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { LiveSeries, Sequence, Trigger } from 'pond-ts';
|
|
36
|
+
|
|
37
|
+
// Webapp telemetry: rolling 1m p95, emit on every 30 s of event-time
|
|
38
|
+
const rolling = timings.rolling(
|
|
39
|
+
'1m',
|
|
40
|
+
{ latency: 'p95' },
|
|
41
|
+
{ trigger: Trigger.clock(Sequence.every('30s')) },
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
rolling.on('event', (e) =>
|
|
45
|
+
fetch('/api/telemetry', { method: 'POST', body: JSON.stringify(e.data()) }),
|
|
46
|
+
);
|
|
47
|
+
rolling.value(); // current rolling-window snapshot, independent of trigger
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Two trigger variants in this release:
|
|
51
|
+
- **`Trigger.event()`** — per-event emission. Default; the historical
|
|
52
|
+
behavior of `LiveRollingAggregation` when no trigger is specified.
|
|
53
|
+
- **`Trigger.clock(sequence)`** — sequence-triggered emission. One
|
|
54
|
+
snapshot fires when a source event crosses an epoch-aligned
|
|
55
|
+
boundary of the (fixed-step) `Sequence`. Output keyed at boundary
|
|
56
|
+
instants. Calendar sequences are rejected upfront.
|
|
57
|
+
|
|
58
|
+
Future variants (`Trigger.count(n)`, custom predicates, compound
|
|
59
|
+
triggers) are reserved but not yet shipped.
|
|
60
|
+
|
|
61
|
+
- **Synchronised partitioned rolling.** `LivePartitionedSeries.rolling`
|
|
62
|
+
now accepts a clock trigger. The output is a
|
|
63
|
+
`LiveSource<RowSchema>` whose schema is `[time, <partitionColumn>,
|
|
64
|
+
...mappingColumns]`; on every boundary crossing, one event fires
|
|
65
|
+
per known partition, all sharing the same boundary timestamp.
|
|
66
|
+
Synchronised across partitions by construction (the bucket index is
|
|
67
|
+
shared, not per-partition).
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// Dashboard tick aggregation: 100 hosts, 200ms cadence
|
|
71
|
+
const ticks = live
|
|
72
|
+
.partitionBy('host')
|
|
73
|
+
.rolling(
|
|
74
|
+
'1m',
|
|
75
|
+
{ cpu: 'avg' },
|
|
76
|
+
{ trigger: Trigger.clock(Sequence.every('200ms')) },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
ticks.on('event', (e) => {
|
|
80
|
+
// e.begin() === <boundary timestamp>, same for every host this tick
|
|
81
|
+
// e.get('host') === 'api-1' | 'api-2' | …
|
|
82
|
+
// e.get('cpu') === <rolling avg for that host>
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Restricted to direct-after-`partitionBy` in this release: chained
|
|
87
|
+
sugar (`partitionBy(c).fill(...).rolling(...)`) rejects clock
|
|
88
|
+
triggers with a clear error. Lifts in a future release once a real
|
|
89
|
+
use case appears.
|
|
90
|
+
|
|
91
|
+
Closes the gRPC experiment's M3.5 dashboard friction note (the
|
|
92
|
+
hand-rolled `HostAggregator` becomes ~10 lines of pond code).
|
|
93
|
+
|
|
94
|
+
### Removed (breaking — pre-1.0)
|
|
95
|
+
|
|
96
|
+
- **`LiveSequenceRollingAggregation`** class deleted. Its capability
|
|
97
|
+
is preserved as `LiveRollingAggregation` with
|
|
98
|
+
`{ trigger: Trigger.clock(sequence) }`. Migration: replace
|
|
99
|
+
`live.rolling('1m', m).sample(seq)` with
|
|
100
|
+
`live.rolling('1m', m, { trigger: Trigger.clock(seq) })`. Single
|
|
101
|
+
rolling object now serves both backend reporting and direct
|
|
102
|
+
`.value()` reads (no separate sampler reference).
|
|
103
|
+
- **`.sample(sequence)`** method removed from `LiveRollingAggregation`.
|
|
104
|
+
Use the trigger option above.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- **`LiveRollingOptions`** gains an optional `trigger?: Trigger`
|
|
109
|
+
field. Default behavior (no `trigger` specified) is unchanged from
|
|
110
|
+
v0.11.x — per-event emission. Backward compatible for everyone
|
|
111
|
+
who didn't use `.sample()`.
|
|
112
|
+
|
|
113
|
+
### Performance
|
|
114
|
+
|
|
115
|
+
- New benchmark `scripts/perf-triggers.mjs` covers both
|
|
116
|
+
non-partitioned and synchronised partitioned cases. Headline numbers
|
|
117
|
+
on a current MacBook Pro:
|
|
118
|
+
- Non-partitioned: clock(30s) ~50% faster than per-event baseline
|
|
119
|
+
(emission is rarer); clock(1s) similar.
|
|
120
|
+
- Synchronised partitioned (100 hosts, 30k events at realistic
|
|
121
|
+
rates): ~300 ns/emission at 200ms cadence; +205% over per-
|
|
122
|
+
partition baseline at the high end. Well within budget for the
|
|
123
|
+
motivating dashboard use case.
|
|
124
|
+
|
|
125
|
+
### Notes
|
|
126
|
+
|
|
127
|
+
- **`docs/rfcs/triggers.md`** captures the full design rationale,
|
|
128
|
+
the four sign-off questions, and the migration plan. Read this if
|
|
129
|
+
you want the "why this shape" context.
|
|
130
|
+
|
|
131
|
+
### Known limitations
|
|
132
|
+
|
|
133
|
+
- **Synchronised partitioned rolling output type is loose** —
|
|
134
|
+
`LiveSource<SeriesSchema>` rather than a schema-narrowed shape.
|
|
135
|
+
Runtime schema is correct; only static types widen. Tightening is
|
|
136
|
+
queued for a follow-up release.
|
|
137
|
+
- **Synchronised partitioned rolling rejects column-name collisions**
|
|
138
|
+
between the partition column and any reducer-output column at
|
|
139
|
+
construction (e.g. `partitionBy('cpu').rolling('1m', { cpu: 'avg' }, { trigger })`).
|
|
140
|
+
Rename the reducer output (once `AggregateOutputMap` lands on live
|
|
141
|
+
rolling) or partition by a different column.
|
|
142
|
+
- **Late-spawn partitions only appear in ticks after their first event
|
|
143
|
+
arrives.** A partition unknown to the sync source contributes no
|
|
144
|
+
row to the current tick. Use `partitionBy(col, { groups: [...] })`
|
|
145
|
+
to eagerly include partitions from construction.
|
|
146
|
+
|
|
147
|
+
## [0.11.8] — 2026-04-30
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
|
|
151
|
+
- **`rolling.sample(sequence)`** on `LiveRollingAggregation` — taps a
|
|
152
|
+
rolling aggregation and emits one snapshot of the rolling state each
|
|
153
|
+
time a source event crosses an epoch-aligned boundary of `sequence`.
|
|
154
|
+
Closes the frontend-telemetry gap: collect high-frequency timing
|
|
155
|
+
events, sample p95 latency to a backend every 30 s, while the same
|
|
156
|
+
rolling drives an in-app live display (no duplicated deque).
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const rolling = timings.rolling('1m', { latency: 'p95' });
|
|
160
|
+
|
|
161
|
+
// One sampler → backend report every 30 s of event time
|
|
162
|
+
const reported = rolling.sample(Sequence.every('30s'));
|
|
163
|
+
reported.on('event', (e) =>
|
|
164
|
+
fetch('/api/telemetry', { method: 'POST', body: JSON.stringify(e.data()) }),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Same rolling drives the UI live display
|
|
168
|
+
useLiveQuery(timings, () => rolling.value());
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`sequence` must be a fixed-step `Sequence`; calendar sequences
|
|
172
|
+
(`Sequence.daily()` etc.) are rejected upfront — boundary indexing
|
|
173
|
+
needs a constant step.
|
|
174
|
+
|
|
175
|
+
Emission is **data-driven**: no `setInterval`. If the source goes
|
|
176
|
+
quiet, no events fire. A single source event spanning multiple
|
|
177
|
+
boundaries fires exactly one event at the new bucket. Snapshot is
|
|
178
|
+
taken after the boundary-crossing event is ingested by the rolling,
|
|
179
|
+
so the emitted value includes that event's contribution.
|
|
180
|
+
|
|
181
|
+
**Independent lifetimes.** `sample.dispose()` only detaches the
|
|
182
|
+
sampler from the rolling; the rolling's lifecycle stays the user's
|
|
183
|
+
responsibility. One rolling can power multiple `.sample()` cadences
|
|
184
|
+
plus direct `rolling.value()` reads without coupling.
|
|
185
|
+
|
|
186
|
+
- **`LiveSequenceRollingAggregation` exported** from package root with
|
|
187
|
+
full `LiveSource<Out>` surface and the same view-transform set as
|
|
188
|
+
`LiveRollingAggregation` (`filter`, `map`, `select`, `window`,
|
|
189
|
+
`diff`, `rate`, `pctChange`, `fill`, `cumulative`, `rolling`,
|
|
190
|
+
`aggregate`).
|
|
191
|
+
|
|
192
|
+
- **Telemetry-reporting recipe** at
|
|
193
|
+
`website/docs/recipes/telemetry-reporting.mdx` — end-to-end
|
|
194
|
+
frontend-collection → backend-summary pattern using `.sample()`,
|
|
195
|
+
plus the React in-app display via `useLiveQuery`.
|
|
196
|
+
|
|
197
|
+
[0.12.0]: https://github.com/pjm17971/pond-ts/compare/v0.11.8...v0.12.0
|
|
198
|
+
[0.11.8]: https://github.com/pjm17971/pond-ts/compare/v0.11.7...v0.11.8
|
|
199
|
+
|
|
14
200
|
## [0.11.7] — 2026-04-29
|
|
15
201
|
|
|
16
202
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pond-ts/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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.
|
|
34
|
+
"pond-ts": "^0.12.0",
|
|
35
35
|
"react": "^18.0.0 || ^19.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|