@pond-ts/react 0.11.8 → 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.
Files changed (2) hide show
  1. package/CHANGELOG.md +135 -1
  2. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,10 +7,143 @@ 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.11.8...HEAD
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
+
14
147
  ## [0.11.8] — 2026-04-30
15
148
 
16
149
  ### Added
@@ -61,6 +194,7 @@ type-level changes; patch bumps are strictly additive.
61
194
  frontend-collection → backend-summary pattern using `.sample()`,
62
195
  plus the React in-app display via `useLiveQuery`.
63
196
 
197
+ [0.12.0]: https://github.com/pjm17971/pond-ts/compare/v0.11.8...v0.12.0
64
198
  [0.11.8]: https://github.com/pjm17971/pond-ts/compare/v0.11.7...v0.11.8
65
199
 
66
200
  ## [0.11.7] — 2026-04-29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.11.8",
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.11.0",
34
+ "pond-ts": "^0.12.0",
35
35
  "react": "^18.0.0 || ^19.0.0"
36
36
  },
37
37
  "devDependencies": {