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 +168 -1
- package/dist/LiveFusedRolling.d.ts +55 -0
- package/dist/LiveFusedRolling.d.ts.map +1 -0
- package/dist/LiveFusedRolling.js +393 -0
- package/dist/LiveFusedRolling.js.map +1 -0
- package/dist/LivePartitionedFusedRolling.d.ts +65 -0
- package/dist/LivePartitionedFusedRolling.d.ts.map +1 -0
- package/dist/LivePartitionedFusedRolling.js +380 -0
- package/dist/LivePartitionedFusedRolling.js.map +1 -0
- package/dist/LivePartitionedSeries.d.ts +54 -3
- package/dist/LivePartitionedSeries.d.ts.map +1 -1
- package/dist/LivePartitionedSeries.js +63 -2
- package/dist/LivePartitionedSeries.js.map +1 -1
- package/dist/LiveSeries.d.ts +21 -0
- package/dist/LiveSeries.d.ts.map +1 -1
- package/dist/LiveSeries.js +10 -2
- package/dist/LiveSeries.js.map +1 -1
- package/dist/LiveView.d.ts +9 -0
- package/dist/LiveView.d.ts.map +1 -1
- package/dist/LiveView.js +6 -2
- package/dist/LiveView.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types-fused-rolling.d.ts +143 -0
- package/dist/types-fused-rolling.d.ts.map +1 -0
- package/dist/types-fused-rolling.js +2 -0
- package/dist/types-fused-rolling.js.map +1 -0
- package/package.json +1 -1
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.
|
|
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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"}
|