pond-ts 0.65.0 → 0.67.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.
@@ -1,3 +1,4 @@
1
+ import { rollingIndexedEntries, rollingSortedExtreme } from './rolling.js';
1
2
  import { parsePercentile, percentileReducer } from './percentile.js';
2
3
  import { parseTopN, topReducer } from './top.js';
3
4
  import { count } from './count.js';
@@ -102,31 +103,24 @@ export function bucketStateFor(reducer) {
102
103
  },
103
104
  };
104
105
  }
105
- /**
106
- * Build a `RollingReducerState` for a reducer that may be either a
107
- * built-in name (string) or a custom function. Built-ins use their
108
- * dedicated incremental machinery (O(1) `add`/`remove`/`snapshot`).
109
- *
110
- * Custom functions use a generic adapter:
111
- * - `add(idx, v)` stores into a Map keyed by event index.
112
- * - `remove(idx, v)` deletes by index.
113
- * - `snapshot()` calls the function with the current window's values
114
- * in arrival order — O(N) per snapshot.
115
- *
116
- * **Performance characteristic.** Per-event cost is O(1) for state
117
- * maintenance but `snapshot` is O(window size) — the function re-
118
- * runs over every value in the current window each time the
119
- * accumulator emits. Compare to built-in reducers which maintain a
120
- * running result incrementally and snapshot in O(1).
121
- *
122
- * For high-throughput live use, prefer built-ins or `'samples'`
123
- * (which lets you compute the custom logic once at the consumer,
124
- * after the rolling has already collapsed events to the value list).
125
- * Custom-function reducers shine on low-rate streams where
126
- * convenience matters more than per-snapshot cost.
127
- */
128
- export function rollingStateFor(reducer) {
106
+ export function rollingStateFor(reducer, evictionOrder = 'arrival') {
129
107
  if (typeof reducer === 'string') {
108
+ if (evictionOrder === 'sorted') {
109
+ // [PND-LIVFIX] The four forward-sliding-window states assume
110
+ // oldest-arrived-first eviction; swap in the any-order structures
111
+ // for a source that evicts its sorted prefix. Every other built-in
112
+ // already removes by index or by value.
113
+ switch (reducer) {
114
+ case 'min':
115
+ return skipNonFiniteRolling(rollingSortedExtreme('min'));
116
+ case 'max':
117
+ return skipNonFiniteRolling(rollingSortedExtreme('max'));
118
+ case 'first':
119
+ return skipNonFiniteRolling(rollingIndexedEntries('first'));
120
+ case 'last':
121
+ return skipNonFiniteRolling(rollingIndexedEntries('last'));
122
+ }
123
+ }
130
124
  return skipNonFiniteRolling(resolveReducer(reducer).rollingState());
131
125
  }
132
126
  // Custom-function adapter: Map keyed by event index for O(1) remove.
@@ -6,6 +6,28 @@ type RollingWindowEntry<T> = {
6
6
  };
7
7
  export declare function rollingMonotoneDeque(compare: (a: number, b: number) => boolean): RollingReducerState;
8
8
  export declare function rollingOrderedEntries(pick: (entries: RollingWindowEntry<ColumnValue>[], head: number) => ColumnValue | undefined): RollingReducerState;
9
+ /**
10
+ * [PND-LIVFIX] Ordered entries that tolerate removal of ANY index, not only
11
+ * the head. `rollingOrderedEntries` above is a forward-sliding window: it
12
+ * assumes evictions arrive oldest-added-first, which holds for append-only
13
+ * sources but not for a `reorder` source with retention, whose sorted-prefix
14
+ * eviction can drop a middle or the newest arrival — and then `first` /
15
+ * `last` kept reporting an evicted value. Entries live in a `Map` keyed by
16
+ * index (insertion order = arrival order) so any index removes in O(1);
17
+ * `first` is the map's first value and `last` is a tracked tail index,
18
+ * re-derived by an O(n) scan when the tail itself is evicted — on a
19
+ * reorder source that is the headline case (the newest arrival being the
20
+ * sorted prefix), so `last` is O(n) per such eviction and O(1) otherwise.
21
+ */
22
+ export declare function rollingIndexedEntries(pick: 'first' | 'last'): RollingReducerState;
23
+ /**
24
+ * [PND-LIVFIX] `min` / `max` over a sorted array — removal by value, so the
25
+ * extreme is exact whatever order evictions arrive in. O(n) per insert /
26
+ * remove (a `splice`), against the monotone deque's amortised O(1); it is
27
+ * selected only for sources whose eviction order is not arrival order
28
+ * (`reorder` + retention), where the deque is wrong rather than slow.
29
+ */
30
+ export declare function rollingSortedExtreme(pick: 'min' | 'max'): RollingReducerState;
9
31
  export declare function rollingSortedArray(): {
10
32
  sorted: number[];
11
33
  add(index: number, value: ColumnValue | undefined): void;
@@ -51,6 +51,71 @@ export function rollingOrderedEntries(pick) {
51
51
  },
52
52
  };
53
53
  }
54
+ /**
55
+ * [PND-LIVFIX] Ordered entries that tolerate removal of ANY index, not only
56
+ * the head. `rollingOrderedEntries` above is a forward-sliding window: it
57
+ * assumes evictions arrive oldest-added-first, which holds for append-only
58
+ * sources but not for a `reorder` source with retention, whose sorted-prefix
59
+ * eviction can drop a middle or the newest arrival — and then `first` /
60
+ * `last` kept reporting an evicted value. Entries live in a `Map` keyed by
61
+ * index (insertion order = arrival order) so any index removes in O(1);
62
+ * `first` is the map's first value and `last` is a tracked tail index,
63
+ * re-derived by an O(n) scan when the tail itself is evicted — on a
64
+ * reorder source that is the headline case (the newest arrival being the
65
+ * sorted prefix), so `last` is O(n) per such eviction and O(1) otherwise.
66
+ */
67
+ export function rollingIndexedEntries(pick) {
68
+ const items = new Map();
69
+ let tail;
70
+ return {
71
+ add(index, value) {
72
+ if (value === undefined)
73
+ return;
74
+ items.set(index, value);
75
+ if (tail === undefined || index > tail)
76
+ tail = index;
77
+ },
78
+ remove(index, value) {
79
+ if (value === undefined)
80
+ return;
81
+ if (!items.delete(index))
82
+ return;
83
+ if (index === tail) {
84
+ tail = undefined;
85
+ for (const k of items.keys())
86
+ if (tail === undefined || k > tail)
87
+ tail = k;
88
+ }
89
+ },
90
+ snapshot() {
91
+ if (items.size === 0)
92
+ return undefined;
93
+ if (pick === 'first')
94
+ return items.values().next().value;
95
+ return tail === undefined ? undefined : items.get(tail);
96
+ },
97
+ };
98
+ }
99
+ /**
100
+ * [PND-LIVFIX] `min` / `max` over a sorted array — removal by value, so the
101
+ * extreme is exact whatever order evictions arrive in. O(n) per insert /
102
+ * remove (a `splice`), against the monotone deque's amortised O(1); it is
103
+ * selected only for sources whose eviction order is not arrival order
104
+ * (`reorder` + retention), where the deque is wrong rather than slow.
105
+ */
106
+ export function rollingSortedExtreme(pick) {
107
+ const state = rollingSortedArray();
108
+ return {
109
+ add: state.add,
110
+ remove: state.remove,
111
+ snapshot() {
112
+ const s = state.sorted;
113
+ if (s.length === 0)
114
+ return undefined;
115
+ return pick === 'min' ? s[0] : s[s.length - 1];
116
+ },
117
+ };
118
+ }
54
119
  export function rollingSortedArray() {
55
120
  const sorted = [];
56
121
  function bisect(v) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pond-ts",
3
- "version": "0.65.0",
3
+ "version": "0.67.0",
4
4
  "description": "TypeScript-first time series primitives",
5
5
  "license": "MIT",
6
6
  "repository": {