pond-ts 0.14.3 → 0.15.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 CHANGED
@@ -7,10 +7,177 @@ 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.3...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.15.0...HEAD
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.15.0] — 2026-05-05
15
+
16
+ The "fused multi-window rolling" release. Shipping the primitive
17
+ that closes the gRPC experiment's V6→V7 architectural cliff: a
18
+ keyed-form overload on `live.rolling()` that maintains N windows
19
+ in one ingest pass over a single shared deque, emits one merged
20
+ event per trigger boundary, and (on the partitioned variant) eats
21
+ the doubled `#routeEvent` / `#evictPartition` / `_pushTrustedEvents`
22
+ hops V7 surfaced.
23
+
24
+ Two independent signals motivated this: the gRPC profile-diff
25
+ (PR #19 in `pond-grpc-experiment`) and the buffer-as-window
26
+ persona's metric-agent call site
27
+ (`series.rolling(RETENTION, mapping, ...)` as workaround). Both
28
+ point at one primitive; both shipped together. RFC #20 in
29
+ `pond-grpc-experiment` is the design record.
30
+
31
+ ### Added
32
+
33
+ - **Keyed-form fused rolling on `LiveSeries.rolling`,
34
+ `LiveView.rolling`, and `LivePartitionedSeries.rolling`.** Pass
35
+ a record of `{ duration: mapping }` instead of `(window, mapping)`
36
+ to declare multiple windows; the rolling maintains them all in
37
+ one ingest pass:
38
+
39
+ ```ts
40
+ const fused = byHost.rolling(
41
+ {
42
+ '1m': {
43
+ cpu_avg: { from: 'cpu', using: 'avg' },
44
+ cpu_sd: { from: 'cpu', using: 'stdev' },
45
+ },
46
+ '200ms': { cpu_samples: { from: 'cpu', using: 'samples' } },
47
+ },
48
+ { trigger: Trigger.every('200ms') },
49
+ );
50
+ // fused emits one merged event per boundary with all four
51
+ // columns; one ingest pass per source event.
52
+ ```
53
+
54
+ - **Output: one merged stream.** All declared windows' columns
55
+ concatenated into one record per trigger fire — not N
56
+ accumulators or N streams. User code collapses to one event
57
+ handler (the V7 → V8 migration in the gRPC experiment drops
58
+ ~30 lines of `pendingByTs` / `partsFor` / `tryEmit` join
59
+ machinery).
60
+ - **Constraints.** Time-based windows only (object keys are
61
+ duration strings); single trigger across all windows by
62
+ design (per-window cadence falls back to two `rolling()`
63
+ calls, paying the V7 cost). On partitioned series, clock
64
+ trigger is required.
65
+ - **Per-window options.** Use the elaborated value form
66
+ (`{ mapping, minSamples }`) when one window needs different
67
+ options from the rest; bare-mapping value stays clean for
68
+ the common case.
69
+ - **Duplicate output column names** across windows are rejected
70
+ at construction with a clear error. Partition column auto-
71
+ injection is unified across all windows.
72
+ - **Single-window equivalence pin.**
73
+ `live.rolling('1m', mapping, opts)` and
74
+ `live.rolling({ '1m': mapping }, opts)` produce identical
75
+ output (locked down by tests).
76
+
77
+ - **`LiveFusedRolling<S, Out>`** — non-partitioned class, exposed
78
+ on the public surface via `live.rolling({...}, opts)`.
79
+ - **`LivePartitionedFusedRolling<S, K, Out>`** — synchronised-cross-
80
+ partition class, exposed via `byHost.rolling({...}, { trigger })`.
81
+ - **Type-level surface:** `FusedMapping<S>`, `FusedMappingValue<S>`,
82
+ `FusedMappingElaborated<S>`, `FusedRollingSchema<S, FM>`,
83
+ `FusedPartitionedRollingSchema<S, ByCol, FM>`, and
84
+ `DurationString` — all exported from `pond-ts`. Output column
85
+ kinds narrow correctly through `event.get('cpu_avg')` to
86
+ `number | undefined`.
87
+
88
+ ### Performance
89
+
90
+ `packages/core/scripts/perf-fused-rolling.mjs` — bench against
91
+ gRPC RFC #20 acceptance criteria. Headline numbers (median of 3
92
+ runs, `node --expose-gc`):
93
+
94
+ ```
95
+ Partitioned, 100k events × 100 hosts (the gRPC use case):
96
+ wall (ms) heap (MB)
97
+ single rolling baseline 95.20 74.33
98
+ two separate rollings (V7 shape) 141.12 101.71
99
+ fused two-window (V8 shape) 112.36 68.46
100
+
101
+ Fused vs V7 shape: -20.4% wall, -32.7% heap
102
+ Fused vs baseline: +18.0% wall, -7.9% heap
103
+
104
+ Partitioned, 100k events × 1000 hosts (saturation):
105
+ wall (ms) heap (MB)
106
+ two separate rollings (V7 shape) 700.35 556.56
107
+ fused two-window (V8 shape) 446.21 309.25
108
+
109
+ Fused vs V7 shape: -36.3% wall, -44.4% heap
110
+ ```
111
+
112
+ **Scaling beyond two windows — the architectural argument
113
+ verified.** Every per-event pond hop runs ONCE in fused vs N times
114
+ in N separate rollings. The bench scales N from 2 to 5 windows
115
+ over the same 100k-events × 100-hosts source:
116
+
117
+ ```
118
+ Separate (ms) Fused (ms) Wall delta
119
+ N = 2 152.91 102.91 -32.7%
120
+ N = 3 186.63 79.89 -57.2%
121
+ N = 4 245.42 107.51 -56.2%
122
+ N = 5 279.79 118.90 -57.5%
123
+
124
+ Separate (MB) Fused (MB) Heap delta
125
+ N = 2 108.13 72.20 -33.2%
126
+ N = 3 93.30 43.08 -53.8%
127
+ N = 4 113.69 47.19 -58.5%
128
+ N = 5 137.17 47.12 -65.6%
129
+ ```
130
+
131
+ Fused stays roughly constant (~100ms) across N=2..5; separate
132
+ scales linearly. At N=5: **2.4× faster wall, 34% of the heap.**
133
+
134
+ The architectural cliff is closed and the win compounds with N.
135
+ Fused rolling's per-event cost is O(1) in the number of windows
136
+ for pipeline overhead — only O(N) for the unavoidable per-window
137
+ reducer-state updates (which separate also pays). Heap is
138
+ dominated by the saved per-rolling deque + per-partition state.
139
+
140
+ ### Notes on what this does NOT include
141
+
142
+ - **`live.reduce(mapping)` sugar.** Designed in PLAN as
143
+ `live.rolling({ buffer: mapping }, { history: false })`; the
144
+ `'buffer'` sentinel is reserved at the type level but throws at
145
+ runtime for now. Lands with the buffer-as-window Tier 1 PR.
146
+ - **`TimeSeries.rolling` snapshot-side parity.** The keyed-form
147
+ overload is live-side only in v0.15.0; batch-side comes in a
148
+ follow-up.
149
+ - **Path A (share `LiveSeries` buffer).** Currently Path B (own
150
+ deque) — fused rolling subscribes via `'event'` and maintains
151
+ its own per-partition deque. Path A is a transparent perf
152
+ follow-up; same API.
153
+ - **Compile-time uniqueness check on output columns.** Runtime
154
+ check is in place; the type-level `CheckUniqueOutputs` helper
155
+ is parked as a follow-up. Same with tightening `DurationString`
156
+ to reject `'1min'`-style typos at the type level (today's
157
+ template-literal type is permissive; runtime `parseDuration`
158
+ catches malformed durations).
159
+
160
+ ### Migration
161
+
162
+ Existing `live.rolling(window, mapping, opts)` calls are
163
+ unchanged. The keyed form is opt-in and additive. Two-rolling
164
+ patterns can migrate by collapsing to one fused call:
165
+
166
+ ```ts
167
+ // Before:
168
+ const baseline = byHost.rolling('1m', m1, { trigger });
169
+ const slice = byHost.rolling('200ms', m2, { trigger });
170
+ // Then a per-(ts, host) join over both event streams …
171
+
172
+ // After:
173
+ const fused = byHost.rolling({ '1m': m1, '200ms': m2 }, { trigger });
174
+ fused.on('event', (e) => {
175
+ // All columns from both windows on one event.
176
+ });
177
+ ```
178
+
179
+ [0.15.0]: https://github.com/pjm17971/pond-ts/compare/v0.14.3...v0.15.0
180
+
14
181
  ## [0.14.3] — 2026-05-04
15
182
 
16
183
  A targeted allocation fix in the `'samples'` reducer's rolling-state
@@ -0,0 +1,55 @@
1
+ import type { ColumnValue, EventForSchema, LiveSource, SeriesSchema } from './types.js';
2
+ import type { FusedMapping } from './types-fused-rolling.js';
3
+ import type { LiveRollingOptions } from './LiveRollingAggregation.js';
4
+ type EventListener = (event: any) => void;
5
+ /**
6
+ * Multi-window rolling that maintains N windows in one ingest pass
7
+ * over a single shared deque. Replaces the workaround of multiple
8
+ * separate `LiveRollingAggregation`s sharing the same source — the
9
+ * gRPC experiment's V6→V7 profile-diff (PR #19) showed every per-
10
+ * event pond hop roughly doubled when running two rollings.
11
+ *
12
+ * Each declared window has:
13
+ * - Its own resolved duration (clipped to retention; see PLAN.md)
14
+ * - Its own column-spec list and reducer states
15
+ * - Its own head cursor into the shared deque
16
+ *
17
+ * Output is ONE merged stream: one event per trigger boundary, with
18
+ * all windows' columns concatenated into one record. Duplicate
19
+ * output column names across windows are rejected at construction
20
+ * with a clear error (compile-time detection is queued as a
21
+ * follow-up).
22
+ *
23
+ * **Single trigger.** All windows share the configured trigger.
24
+ * Per-window cadence is explicitly NOT supported — that's what
25
+ * fusion saves. Users who need per-window cadence fall back to two
26
+ * separate `rolling()` calls and pay the V7 cost.
27
+ *
28
+ * **Time-based windows only.** Object keys are duration strings.
29
+ * Count-based windows stay on the existing single-window
30
+ * `LiveRollingAggregation`. This constraint keeps the
31
+ * window-clip-to-retention rule and boundary-detection logic clean.
32
+ *
33
+ * Public API: constructed via the `live.rolling(fusedMapping, opts)`
34
+ * keyed-form overload on `LiveSeries` / `LiveView`. User code
35
+ * doesn't import this class directly.
36
+ */
37
+ export declare class LiveFusedRolling<S extends SeriesSchema, Out extends SeriesSchema = SeriesSchema> implements LiveSource<Out> {
38
+ #private;
39
+ readonly name: string;
40
+ readonly schema: Out;
41
+ constructor(source: LiveSource<S>, fusedMapping: FusedMapping<S>, options?: LiveRollingOptions);
42
+ get length(): number;
43
+ at(index: number): EventForSchema<Out> | undefined;
44
+ /**
45
+ * Read the current merged snapshot — every window's reducer
46
+ * outputs concatenated into one record. Useful for live-display
47
+ * patterns where the consumer wants the latest values without
48
+ * waiting for the next trigger fire.
49
+ */
50
+ value(): Record<string, ColumnValue | undefined>;
51
+ on(type: 'event', fn: EventListener): () => void;
52
+ dispose(): void;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=LiveFusedRolling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveFusedRolling.d.ts","sourceRoot":"","sources":["../src/LiveFusedRolling.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAGV,WAAW,EACX,cAAc,EACd,UAAU,EACV,YAAY,EACb,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,0BAA0B,CAAC;AAChF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAwCtE,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,gBAAgB,CAC3B,CAAC,SAAS,YAAY,EACtB,GAAG,SAAS,YAAY,GAAG,YAAY,CACvC,YAAW,UAAU,CAAC,GAAG,CAAC;;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;gBAoCnB,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EACrB,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,EAC7B,OAAO,GAAE,kBAAuB;IAyGlC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,SAAS;IAKlD;;;;;OAKG;IACH,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAahD,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa,GAAG,MAAM,IAAI;IAYhD,OAAO,IAAI,IAAI;CA0IhB"}
@@ -0,0 +1,393 @@
1
+ import { normalizeAggregateColumns, } from './aggregate-columns.js';
2
+ import { Event } from './Event.js';
3
+ import { Time } from './Time.js';
4
+ import { rollingStateFor } from './reducers/index.js';
5
+ import { bucketIndexFor, boundaryTimestampFor, } from './triggers.js';
6
+ import { parseDuration } from './utils/duration.js';
7
+ /**
8
+ * Multi-window rolling that maintains N windows in one ingest pass
9
+ * over a single shared deque. Replaces the workaround of multiple
10
+ * separate `LiveRollingAggregation`s sharing the same source — the
11
+ * gRPC experiment's V6→V7 profile-diff (PR #19) showed every per-
12
+ * event pond hop roughly doubled when running two rollings.
13
+ *
14
+ * Each declared window has:
15
+ * - Its own resolved duration (clipped to retention; see PLAN.md)
16
+ * - Its own column-spec list and reducer states
17
+ * - Its own head cursor into the shared deque
18
+ *
19
+ * Output is ONE merged stream: one event per trigger boundary, with
20
+ * all windows' columns concatenated into one record. Duplicate
21
+ * output column names across windows are rejected at construction
22
+ * with a clear error (compile-time detection is queued as a
23
+ * follow-up).
24
+ *
25
+ * **Single trigger.** All windows share the configured trigger.
26
+ * Per-window cadence is explicitly NOT supported — that's what
27
+ * fusion saves. Users who need per-window cadence fall back to two
28
+ * separate `rolling()` calls and pay the V7 cost.
29
+ *
30
+ * **Time-based windows only.** Object keys are duration strings.
31
+ * Count-based windows stay on the existing single-window
32
+ * `LiveRollingAggregation`. This constraint keeps the
33
+ * window-clip-to-retention rule and boundary-detection logic clean.
34
+ *
35
+ * Public API: constructed via the `live.rolling(fusedMapping, opts)`
36
+ * keyed-form overload on `LiveSeries` / `LiveView`. User code
37
+ * doesn't import this class directly.
38
+ */
39
+ export class LiveFusedRolling {
40
+ name;
41
+ schema;
42
+ /** Shared deque sized by the longest declared window. */
43
+ #entries;
44
+ /** Per-window state, in declared order. */
45
+ #windows;
46
+ /**
47
+ * Longest declared window in ms — bounds the shared deque. Equal
48
+ * to `max(windows[i].windowMs)`.
49
+ */
50
+ #longestWindowMs;
51
+ /**
52
+ * Output column specs in declared order — flat union across all
53
+ * windows. Used to assemble emit records and the output schema.
54
+ */
55
+ #emitColumns;
56
+ #trigger;
57
+ /**
58
+ * For clock triggers: the bucket index of the most recently
59
+ * crossed boundary. Undefined until the first event is ingested.
60
+ */
61
+ #lastClockBucketIdx;
62
+ /**
63
+ * For count triggers: the number of events ingested since the
64
+ * most recent emission.
65
+ */
66
+ #countSinceLastEmit;
67
+ #nextIndex;
68
+ #outputEvents;
69
+ #onEvent;
70
+ #unsubscribe;
71
+ constructor(source, fusedMapping, options = {}) {
72
+ this.name = source.name;
73
+ const topMinSamples = options.minSamples ?? 0;
74
+ if (!Number.isInteger(topMinSamples) || topMinSamples < 0) {
75
+ throw new TypeError('rolling minSamples must be a non-negative integer (default 0)');
76
+ }
77
+ this.#trigger = options.trigger ?? { kind: 'event' };
78
+ this.#lastClockBucketIdx = undefined;
79
+ this.#countSinceLastEmit = 0;
80
+ this.#nextIndex = 0;
81
+ this.#entries = [];
82
+ this.#outputEvents = [];
83
+ this.#onEvent = new Set();
84
+ // Resolve each window: parse the duration key, normalize the
85
+ // mapping (handles bare AggregateMap, AggregateOutputMap, and the
86
+ // elaborated `{ mapping, minSamples }` wrapper), build per-column
87
+ // reducer state.
88
+ const windowKeys = Object.keys(fusedMapping);
89
+ if (windowKeys.length === 0) {
90
+ throw new TypeError('fused rolling: at least one window must be declared');
91
+ }
92
+ const windows = [];
93
+ let longestMs = 0;
94
+ for (const key of windowKeys) {
95
+ const value = fusedMapping[key];
96
+ const { innerMapping, perWindowMinSamples } = unwrapFusedMappingValue(value);
97
+ const windowMs = resolveWindowKey(key);
98
+ if (windowMs > longestMs)
99
+ longestMs = windowMs;
100
+ // Reuse the same column-normalisation helper used by the
101
+ // single-window rolling — keeps reducer-state behavior
102
+ // identical to today's-shape `LiveRollingAggregation`.
103
+ const columns = normalizeAggregateColumns(source.schema, innerMapping);
104
+ const states = columns.map((c) => rollingStateFor(c.reducer));
105
+ windows.push({
106
+ id: key,
107
+ windowMs,
108
+ columns,
109
+ states,
110
+ minSamples: perWindowMinSamples ?? topMinSamples,
111
+ head: 0,
112
+ });
113
+ }
114
+ this.#windows = windows;
115
+ this.#longestWindowMs = longestMs;
116
+ // Build the merged output schema. Reject duplicate output column
117
+ // names across windows. (Compile-time detection is a follow-up;
118
+ // runtime check keeps shipping unblocked.)
119
+ const seenOutputs = new Set();
120
+ const emitColumns = [];
121
+ for (const win of this.#windows) {
122
+ for (const col of win.columns) {
123
+ if (seenOutputs.has(col.output)) {
124
+ throw new TypeError(`fused rolling: duplicate output column '${col.output}' across windows. ` +
125
+ `Each output column name must be unique across the merged schema. ` +
126
+ `Either rename the alias (use AggregateOutputMap with a distinct \`from\` ` +
127
+ `→ alias mapping) or drop one of the duplicating windows.`);
128
+ }
129
+ seenOutputs.add(col.output);
130
+ emitColumns.push(col);
131
+ }
132
+ }
133
+ this.#emitColumns = emitColumns;
134
+ this.schema = Object.freeze([
135
+ source.schema[0],
136
+ ...emitColumns.map((c) => ({
137
+ name: c.output,
138
+ kind: c.kind,
139
+ required: false,
140
+ })),
141
+ ]);
142
+ // Replay the source's existing events through the same ingest
143
+ // path, so a fused rolling created on a non-empty source matches
144
+ // the streaming-from-construction shape.
145
+ for (let i = 0; i < source.length; i++) {
146
+ this.#ingest(source.at(i));
147
+ }
148
+ this.#unsubscribe = source.on('event', (event) => {
149
+ this.#ingest(event);
150
+ });
151
+ }
152
+ // ── LiveSource<Out> contract ────────────────────────────────
153
+ get length() {
154
+ return this.#outputEvents.length;
155
+ }
156
+ at(index) {
157
+ if (index < 0)
158
+ index = this.#outputEvents.length + index;
159
+ return this.#outputEvents[index];
160
+ }
161
+ /**
162
+ * Read the current merged snapshot — every window's reducer
163
+ * outputs concatenated into one record. Useful for live-display
164
+ * patterns where the consumer wants the latest values without
165
+ * waiting for the next trigger fire.
166
+ */
167
+ value() {
168
+ const result = {};
169
+ for (const win of this.#windows) {
170
+ const warmup = this.#windowSize(win) < win.minSamples;
171
+ for (let i = 0; i < win.columns.length; i++) {
172
+ result[win.columns[i].output] = warmup
173
+ ? undefined
174
+ : win.states[i].snapshot();
175
+ }
176
+ }
177
+ return result;
178
+ }
179
+ on(type, fn) {
180
+ if (type !== 'event') {
181
+ throw new TypeError(`LiveFusedRolling.on: unsupported event type '${String(type)}'`);
182
+ }
183
+ this.#onEvent.add(fn);
184
+ return () => {
185
+ this.#onEvent.delete(fn);
186
+ };
187
+ }
188
+ dispose() {
189
+ this.#unsubscribe();
190
+ }
191
+ // ── Private ─────────────────────────────────────────────────
192
+ /**
193
+ * Number of events currently in window `w`'s reducer state.
194
+ * Equals `entries.length - (w.head - frontAbsIdx)` where
195
+ * `frontAbsIdx = entries[0].absIdx`. Used for minSamples gating.
196
+ */
197
+ #windowSize(w) {
198
+ if (this.#entries.length === 0)
199
+ return 0;
200
+ const frontAbsIdx = this.#entries[0].absIdx;
201
+ return this.#entries.length - (w.head - frontAbsIdx);
202
+ }
203
+ #ingest(event) {
204
+ const data = event.data();
205
+ const absIdx = this.#nextIndex++;
206
+ const ts = event.begin();
207
+ const entry = { absIdx, timestamp: ts, data };
208
+ this.#entries.push(entry);
209
+ // Per-window: advance head while leading entries are out-of-window,
210
+ // then add the new event to every column's reducer state.
211
+ for (const win of this.#windows) {
212
+ const cutoff = ts - win.windowMs;
213
+ while (win.head < absIdx &&
214
+ this.#getEntry(win.head).timestamp < cutoff) {
215
+ const old = this.#getEntry(win.head);
216
+ for (let i = 0; i < win.columns.length; i++) {
217
+ win.states[i].remove(old.absIdx, old.data[win.columns[i].source]);
218
+ }
219
+ win.head++;
220
+ }
221
+ for (let i = 0; i < win.columns.length; i++) {
222
+ win.states[i].add(absIdx, data[win.columns[i].source]);
223
+ }
224
+ }
225
+ // Compact the shared deque: drop entries before the leftmost
226
+ // window head. The longest window's head IS this leftmost head
227
+ // in steady state.
228
+ this.#compactFront();
229
+ // Emission gated by the configured trigger.
230
+ switch (this.#trigger.kind) {
231
+ case 'event':
232
+ this.#emitEvent(event.key());
233
+ return;
234
+ case 'clock':
235
+ this.#emitClock(ts, this.#trigger);
236
+ return;
237
+ case 'count':
238
+ this.#emitCount(event.key(), this.#trigger.n);
239
+ return;
240
+ }
241
+ }
242
+ /**
243
+ * Translate an absolute event index to its current position in
244
+ * the shared deque. Returns `undefined` if the entry has been
245
+ * compacted out of the front.
246
+ */
247
+ #getEntry(absIdx) {
248
+ if (this.#entries.length === 0)
249
+ return undefined;
250
+ const front = this.#entries[0].absIdx;
251
+ const offset = absIdx - front;
252
+ if (offset < 0 || offset >= this.#entries.length)
253
+ return undefined;
254
+ return this.#entries[offset];
255
+ }
256
+ /**
257
+ * Drop entries from the front of the shared deque whose absIdx
258
+ * is less than every window's head. The longest window's head
259
+ * defines the leftmost-still-live cursor.
260
+ *
261
+ * Uses `Array.shift()` for now (matches today's
262
+ * `LiveRollingAggregation`); the head-index-pointer ring-buffer
263
+ * optimization is queued as a separate tactical fix in PLAN.md.
264
+ */
265
+ #compactFront() {
266
+ if (this.#entries.length === 0)
267
+ return;
268
+ let minHead = this.#windows[0].head;
269
+ for (let i = 1; i < this.#windows.length; i++) {
270
+ const h = this.#windows[i].head;
271
+ if (h < minHead)
272
+ minHead = h;
273
+ }
274
+ while (this.#entries.length > 0 && this.#entries[0].absIdx < minHead) {
275
+ this.#entries.shift();
276
+ }
277
+ }
278
+ #emitCount(key, n) {
279
+ this.#countSinceLastEmit++;
280
+ if (this.#countSinceLastEmit < n)
281
+ return;
282
+ this.#countSinceLastEmit = 0;
283
+ this.#emitEvent(key);
284
+ }
285
+ /**
286
+ * Build one merged event with every window's reducer snapshot
287
+ * concatenated into one record, then push to outputs and notify
288
+ * listeners.
289
+ */
290
+ #emitEvent(key) {
291
+ const record = {};
292
+ for (const win of this.#windows) {
293
+ const warmup = this.#windowSize(win) < win.minSamples;
294
+ for (let i = 0; i < win.columns.length; i++) {
295
+ record[win.columns[i].output] = warmup
296
+ ? undefined
297
+ : win.states[i].snapshot();
298
+ }
299
+ }
300
+ const outputEvent = new Event(key, record);
301
+ this.#outputEvents.push(outputEvent);
302
+ for (const fn of this.#onEvent)
303
+ fn(outputEvent);
304
+ }
305
+ #emitClock(eventTs, trigger) {
306
+ const bucketIdx = bucketIndexFor(trigger, eventTs);
307
+ if (this.#lastClockBucketIdx === undefined) {
308
+ this.#lastClockBucketIdx = bucketIdx;
309
+ return;
310
+ }
311
+ if (bucketIdx > this.#lastClockBucketIdx) {
312
+ const boundaryMs = boundaryTimestampFor(trigger, bucketIdx);
313
+ this.#emitEvent(new Time(boundaryMs));
314
+ this.#lastClockBucketIdx = bucketIdx;
315
+ }
316
+ }
317
+ }
318
+ // ── Helpers (module-private) ──────────────────────────────────
319
+ /**
320
+ * Resolve a fused-mapping window key (duration string or `'buffer'`
321
+ * sentinel) to a duration in ms. The `'buffer'` sentinel is reserved
322
+ * for future use by `live.reduce()` and not yet implemented; throws
323
+ * a clear error for now.
324
+ */
325
+ function resolveWindowKey(key) {
326
+ if (key === 'buffer') {
327
+ throw new TypeError(`fused rolling: 'buffer' sentinel key is reserved for live.reduce(); ` +
328
+ `not yet implemented. Use an explicit duration string ` +
329
+ `(e.g. '1m', '200ms') for now.`);
330
+ }
331
+ // parseDuration's input type is `number | DurationString` — at runtime it
332
+ // throws on bad input. Cast to satisfy the type and let parseDuration
333
+ // surface a clear error if the key isn't a valid duration string. Wrap
334
+ // the throw to point at the fused-rolling context.
335
+ try {
336
+ return parseDuration(key);
337
+ }
338
+ catch {
339
+ throw new TypeError(`fused rolling: invalid window key '${key}'. Keys must be duration ` +
340
+ `strings (e.g. '1m', '200ms', '5s'). Count-based windows stay on ` +
341
+ `the existing single-window overload.`);
342
+ }
343
+ }
344
+ /**
345
+ * Peel off the elaborated wrapper if present, returning the inner
346
+ * mapping and the per-window minSamples (if specified).
347
+ */
348
+ /**
349
+ * True when `v` looks like an `AggregateOutputSpec` — i.e., a bare
350
+ * `{ from: string, using: ... }` shape. Used to disambiguate the
351
+ * elaborated-wrapper detection: a user with an `AggregateOutputMap`
352
+ * entry literally named `'mapping'` (e.g. `{ '1m': { mapping: { from:
353
+ * 'cpu', using: 'avg' } } }`) must NOT be unwrapped as the wrapper.
354
+ *
355
+ * The wrapper's `mapping` field carries a whole record of column
356
+ * specs; the colliding-name AggregateOutputMap entry's `mapping` key
357
+ * carries one spec. This check distinguishes the two by looking for
358
+ * the `from` + `using` discriminators that only exist on a spec.
359
+ */
360
+ function isAggregateOutputSpec(v) {
361
+ return (v !== null &&
362
+ typeof v === 'object' &&
363
+ typeof v.from === 'string' &&
364
+ 'using' in v);
365
+ }
366
+ function unwrapFusedMappingValue(value) {
367
+ if (value !== null &&
368
+ typeof value === 'object' &&
369
+ 'mapping' in value &&
370
+ typeof value.mapping === 'object' &&
371
+ // Disambiguation: if `.mapping` is itself an AggregateOutputSpec,
372
+ // the user named an alias `mapping` in an AggregateOutputMap —
373
+ // this is NOT the elaborated wrapper. Fall through to the bare
374
+ // mapping branch.
375
+ !isAggregateOutputSpec(value.mapping)) {
376
+ const elaborated = value;
377
+ if (elaborated.minSamples !== undefined) {
378
+ if (!Number.isInteger(elaborated.minSamples) ||
379
+ elaborated.minSamples < 0) {
380
+ throw new TypeError('fused rolling: per-window minSamples must be a non-negative integer');
381
+ }
382
+ }
383
+ return {
384
+ innerMapping: elaborated.mapping,
385
+ perWindowMinSamples: elaborated.minSamples,
386
+ };
387
+ }
388
+ return {
389
+ innerMapping: value,
390
+ perWindowMinSamples: undefined,
391
+ };
392
+ }
393
+ //# sourceMappingURL=LiveFusedRolling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveFusedRolling.js","sourceRoot":"","sources":["../src/LiveFusedRolling.ts"],"names":[],"mappings":"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"}