pond-ts 0.16.1 → 0.17.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.
package/CHANGELOG.md CHANGED
@@ -7,10 +7,170 @@ 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.16.1...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.17.1...HEAD
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.17.1] — 2026-05-11
15
+
16
+ Bug fix: `live.partitionBy()` now default-inherits `ordering`,
17
+ `graceWindow`, and `retention` from the source `LiveSeries`. Surfaced
18
+ by the gRPC experiment's
19
+ [M4 late-data friction note](https://github.com/pjm17971/pond-grpc-experiment/blob/main/friction-notes/M4.md),
20
+ which measured `99.5%` of late events crashing the partition router
21
+ under `source = LiveSeries({ ordering: 'reorder', graceWindow })`
22
+ followed by bare `partitionBy('host')`.
23
+
24
+ ### Fixed
25
+
26
+ - **`LiveSeries.partitionBy(by)` default-inherits source config**
27
+ ([#TBD](https://github.com/pjm17971/pond-ts/pull/TBD)). Pre-fix,
28
+ per-partition sub-series were constructed with default
29
+ `ordering: 'strict'` regardless of source mode. Under a `'reorder'`
30
+ source, late events that the source accepted via its reorder path
31
+ were routed into the partition's `#insert` and threw with a
32
+ strict-mode error; the throw propagated back through the source's
33
+ listener fan-out into `live.push()`.
34
+
35
+ Post-fix, `partitionBy(by)` defaults each per-partition sub-series'
36
+ `ordering`, `graceWindow`, and `retention` to the source's values.
37
+ Explicit options on `partitionBy(by, { ordering, ... })` override
38
+ per-field. `graceWindow` inheritance is gated on effective ordering
39
+ being `'reorder'` (LiveSeries rejects strict + graceWindow combos).
40
+
41
+ ```ts
42
+ // Pre-0.17.1: crashed the partition router
43
+ const live = new LiveSeries({
44
+ name: 'metrics',
45
+ schema,
46
+ ordering: 'reorder',
47
+ graceWindow: '30s',
48
+ });
49
+ live.partitionBy('host'); // ← partition was strict regardless
50
+
51
+ // Post-0.17.1: partitions inherit reorder + 30s grace; late events
52
+ // accept correctly via the reorder path.
53
+ ```
54
+
55
+ Existing callers with explicit `partitionBy(by, { ordering, ... })`:
56
+ unchanged. Existing callers on `'strict'` sources: unchanged.
57
+ Existing callers on `'reorder'` sources with bare `partitionBy`:
58
+ the previously-thrown late events now accept correctly — bug fix,
59
+ not a behavior change anyone could rely on.
60
+
61
+ - **`collect()` and `apply()` on `LivePartitionedSeries` default-
62
+ inherit `ordering` and `graceWindow`** from the partitioned series
63
+ (which inherits from source). Pre-fix, the unified buffer defaulted
64
+ to `'strict'`, so partition fan-in on a `'reorder'` source could
65
+ deliver events out-of-order to a strict unified buffer and throw.
66
+ Retention stays caller-explicit on these per the existing append-
67
+ only fan-in semantics.
68
+
69
+ ### Notes
70
+
71
+ - **Six regression tests pin the new defaults** in
72
+ `LivePartitionedSeries.test.ts`: inherited ordering, inherited
73
+ graceWindow within reorder, inherited retention on partitions,
74
+ explicit override of inheritance, strict-source no-change, and the
75
+ edge case where overriding ordering to strict suppresses graceWindow
76
+ inheritance. `collect()` inheritance pinned separately.
77
+ - The gRPC experiment's M4 friction note also surfaced milestone B
78
+ (capability-based late repair) as **driver-light by empirical test**
79
+ after Codex's adversarial pass caught simulator RNG leakage across
80
+ A/B legs. Drift signal collapsed to within noise on every host once
81
+ all randomness sources were seeded — milestone B's library design
82
+ stays sound, but the gRPC experiment's measurement style (last-tick
83
+ `.value()` reads) doesn't surface its payoff. Milestone B sequencing
84
+ updated in PLAN.md to reflect this finding.
85
+
86
+ ## [0.17.0] — 2026-05-08
87
+
88
+ `sample({...})` operator wave: bounded-memory stream thinning, surfaced
89
+ by the gRPC experiment's M3.5 finish-line work
90
+ ([friction note](https://github.com/pjm17971/pond-grpc-experiment/blob/main/friction-notes/rfcs/bounded-memory-sampling.md)
91
+ with measured firehose numbers). Decouples downstream baseline window
92
+ length from event rate — at firehose rates × stride 10, `sd / sqrt(N)`
93
+ standard error stays well below per-event noise while a 5-minute
94
+ baseline that wouldn't fit in a Node heap un-sampled does at
95
+ stride 10. PR [#129](https://github.com/pjm17971/pond-ts/pull/129).
96
+
97
+ ### Added
98
+
99
+ - **`series.sample({ stride | reservoir })`** on `TimeSeries` and
100
+ `PartitionedTimeSeries` — single-pass thinning that keeps the
101
+ `TimeSeries<S>` schema. Stride is deterministic 1-in-N
102
+ (`{ stride: N }`); reservoir is random K-of-N via single-pass
103
+ [Vitter's Algorithm R](https://en.wikipedia.org/wiki/Reservoir_sampling#Simple:_Algorithm_R)
104
+ (`{ reservoir: { size: K } }`), sorted by key on output to preserve
105
+ the chronological invariant. The canonical visualization shape:
106
+
107
+ ```ts
108
+ series.sample({ reservoir: { size: 500 } }).toRows();
109
+ ```
110
+
111
+ 500 uncorrelated points drawn uniformly from the source — no
112
+ `aggregate(seq, ...)` grid collapse, no regular-spacing artifact,
113
+ fixed point count regardless of source size. Per-partition state on
114
+ `PartitionedTimeSeries.sample(...)` — each partition gets its own
115
+ K-event reservoir or stride counter.
116
+
117
+ - **`live.sample({ stride })`** on `LiveSeries`, `LiveView`,
118
+ `LivePartitionedSeries`, `LivePartitionedView` — closure-captured
119
+ counter inside a `LiveView<S>`, so the chainable surface (`filter`,
120
+ `rolling`, `reduce`, `select`, `map`, `diff`, `rate`, `cumulative`,
121
+ `fill`) is immediately available downstream of the sample. The
122
+ bounded-memory firehose pattern:
123
+
124
+ ```ts
125
+ live.partitionBy('host').sample({ stride: 10 }).rolling('5m', mapping);
126
+ ```
127
+
128
+ Each host's stream is thinned 1-in-10 before flowing into a per-host
129
+ 5m rolling window. `live.stats().ingested` and `live.on('batch', cb)`
130
+ are upstream of any `.sample(...)` op — they continue counting true
131
+ throughput; only consumers downstream see the thinned stream.
132
+
133
+ - **Sampling docs page** at
134
+ [`pond-ts/transforms/sampling`](https://pjm17971.github.io/pond-ts/docs/pond-ts/transforms/sampling/)
135
+ covering when-to-use-which decision table, both strategies, the
136
+ visualization shape, multi-entity considerations, and a forward-link
137
+ to the live counterpart. New `## Sampling: bounded-memory thinning`
138
+ section in
139
+ [Live transforms](https://pjm17971.github.io/pond-ts/docs/pond-ts/live/live-transforms#sampling).
140
+
141
+ ### Deferred
142
+
143
+ - **Live-side reservoir sampling** is queued for v0.18.0+. Algorithm R's
144
+ random-slot replacement produces non-prefix evictions, but the existing
145
+ live-eviction protocol (`'evict'` event + cutoff-based mirroring in
146
+ `LiveView`) assumes prefix evictions only. Bridging needs an exact-
147
+ removal eviction channel — arriving with the streaming RFC's
148
+ `LiveChange` model (Phase 4.5 milestone A). For visualization-shaped
149
+ reservoir today, materialize via `live.toTimeSeries().sample({ reservoir })`.
150
+
151
+ ### Notes
152
+
153
+ - **Multi-entity bias trap** is documented in JSDoc on the pre-partition
154
+ sites (`LiveSeries.sample`, `LiveView.sample`) with the
155
+ `partitionBy(...).sample(...)` recommendation, matching the existing
156
+ convention for `rolling` / `aggregate` / `fill` / `diff` / `rate` /
157
+ `cumulative` / `pctChange` / `reduce`. An earlier iteration of #129
158
+ shipped a type-level `unsafeGlobal: true` token; pulled during review
159
+ for consistency with how every other stateful live operator handles
160
+ the same multi-entity consideration. Token-of-the-week novelty was
161
+ the wrong shape; the doc warning is the same answer the other
162
+ operators already give.
163
+
164
+ - **Legacy `rolling.sample(seq)` doc references removed.** Pre-v0.12
165
+ pond exposed `LiveRollingAggregation.sample(sequence)` as a separate
166
+ method (deleted in v0.12.0, replaced by `Trigger.every`). Active doc
167
+ references in `pond-ts/live/triggering.mdx`,
168
+ `pond-ts/transforms/alignment.mdx`, `pond-ts/transforms/rolling.mdx`,
169
+ and `pond-ts/live/live-transforms.mdx` removed to eliminate the
170
+ naming-collision confusion now that `series.sample({ stride | reservoir })`
171
+ is a real but completely unrelated operator. Historical record
172
+ preserved in PLAN.md, the v0.11.8 CHANGELOG entry, and the triggers RFC.
173
+
14
174
  ## [0.16.1] — 2026-05-06
15
175
 
16
176
  Patch wave addressing one ergonomic gap surfaced by the gRPC
@@ -47,7 +207,7 @@ plus the v0.16.0 docs deploy that broke since v0.15.2.
47
207
  - **Docs deploy workflow unblocked**
48
208
  ([#126](https://github.com/pjm17971/pond-ts/pull/126)). Has
49
209
  been failing since v0.15.2 with `Cannot find name
50
- 'queueMicrotask'` — TypeDoc runs the same tsconfig as the
210
+ 'queueMicrotask'` — TypeDoc runs the same tsconfig as the
51
211
  npm-publish path but from a different cwd, where `@types/node`
52
212
  doesn't resolve. Fixed via a one-line ambient declaration in
53
213
  `LiveReduce.ts`. No runtime change; `queueMicrotask` is still
@@ -101,17 +261,17 @@ narrowings.
101
261
  `eventRate()`.
102
262
  - **`stats()` accessor on every live accumulator/series.** Per-class
103
263
  shapes, all returning a plain record (cumulative integer counters
104
- + current-state fields):
105
-
106
- | Class | Shape |
107
- |---|---|
108
- | LiveSeries | `{ ingested, evicted, rejected, length, earliestTs?, latestTs? }` |
109
- | LiveRollingAggregation | `{ eventsObserved, evictions, emissions, windowSize }` |
110
- | LiveFusedRolling | `{ eventsObserved, evictions, emissions, windowSize, windowsCount }` |
111
- | LiveAggregation | `{ eventsObserved, bucketsClosed, openBuckets, openBucketStart? }` |
112
- | LiveReduce | `{ eventsObserved, evictions, emissions, bufferSize }` |
113
- | LivePartitionedSeries | `{ partitions, eventsRouted }` |
114
- | LivePartitionedSyncRolling | `{ partitions, eventsObserved, emissions, windowSize }` |
264
+ - current-state fields):
265
+
266
+ | Class | Shape |
267
+ | --------------------------- | --------------------------------------------------------------------- |
268
+ | LiveSeries | `{ ingested, evicted, rejected, length, earliestTs?, latestTs? }` |
269
+ | LiveRollingAggregation | `{ eventsObserved, evictions, emissions, windowSize }` |
270
+ | LiveFusedRolling | `{ eventsObserved, evictions, emissions, windowSize, windowsCount }` |
271
+ | LiveAggregation | `{ eventsObserved, bucketsClosed, openBuckets, openBucketStart? }` |
272
+ | LiveReduce | `{ eventsObserved, evictions, emissions, bufferSize }` |
273
+ | LivePartitionedSeries | `{ partitions, eventsRouted }` |
274
+ | LivePartitionedSyncRolling | `{ partitions, eventsObserved, emissions, windowSize }` |
115
275
  | LivePartitionedFusedRolling | `{ partitions, eventsObserved, emissions, windowSize, windowsCount }` |
116
276
 
117
277
  Per-event cost: ~1-3 integer increments in already-existing
@@ -152,7 +312,7 @@ narrowings.
152
312
 
153
313
  - **`KeyLike` type** exported from the package root (re-exported
154
314
  from `TimeSeries`). Accepts `EventKey | TimestampInput |
155
- TimeRangeInput | IntervalInput`; normalised by the new query
315
+ TimeRangeInput | IntervalInput`; normalised by the new query
156
316
  primitives.
157
317
 
158
318
  - **`DurationLiteral` and `DurationUnit` types** extracted from
@@ -186,8 +346,8 @@ narrowings.
186
346
  on). Sane transforms (data-only maps, monotonic time-shifts)
187
347
  unaffected.
188
348
  - **`LiveAggregationOptions.grace`** type tightened from
189
- `DurationInput | \`${number}${unit}\`` (redundant union) to
190
- just `DurationInput`. No behavioral change.
349
+ `DurationInput | \`${number}${unit}\``(redundant union) to
350
+ just`DurationInput`. No behavioral change.
191
351
 
192
352
  ### Notes
193
353
 
@@ -297,6 +457,8 @@ compaction); any downstream code reading `#entries` directly would
297
457
  break, but those fields are private. Public APIs and types are
298
458
  unchanged.
299
459
 
460
+ [0.17.1]: https://github.com/pjm17971/pond-ts/compare/v0.17.0...v0.17.1
461
+ [0.17.0]: https://github.com/pjm17971/pond-ts/compare/v0.16.1...v0.17.0
300
462
  [0.16.1]: https://github.com/pjm17971/pond-ts/compare/v0.16.0...v0.16.1
301
463
  [0.16.0]: https://github.com/pjm17971/pond-ts/compare/v0.15.2...v0.16.0
302
464
  [0.15.2]: https://github.com/pjm17971/pond-ts/compare/v0.15.1...v0.15.2