@checkstack/backend-api 0.6.0 → 0.8.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 +48 -0
- package/package.json +5 -4
- package/src/aggregated-result.test.ts +277 -0
- package/src/aggregated-result.ts +473 -0
- package/src/base-strategy-config.ts +26 -0
- package/src/collector-strategy.ts +6 -2
- package/src/health-check.ts +23 -44
- package/src/incremental-aggregation.test.ts +118 -21
- package/src/incremental-aggregation.ts +152 -22
- package/src/index.ts +2 -0
|
@@ -4,58 +4,67 @@ import {
|
|
|
4
4
|
mergeAverage,
|
|
5
5
|
mergeRate,
|
|
6
6
|
mergeMinMax,
|
|
7
|
+
mergeCounterStates,
|
|
8
|
+
mergeAverageStates,
|
|
9
|
+
mergeRateStates,
|
|
10
|
+
mergeMinMaxStates,
|
|
7
11
|
} from "./incremental-aggregation";
|
|
8
12
|
|
|
9
13
|
describe("mergeCounter", () => {
|
|
10
14
|
it("creates new counter from undefined", () => {
|
|
11
15
|
const result = mergeCounter(undefined, true);
|
|
12
|
-
expect(result).toEqual({ count: 1 });
|
|
16
|
+
expect(result).toEqual({ _type: "counter", count: 1 });
|
|
13
17
|
});
|
|
14
18
|
|
|
15
19
|
it("increments existing counter with true", () => {
|
|
16
20
|
const result = mergeCounter({ count: 5 }, true);
|
|
17
|
-
expect(result).toEqual({ count: 6 });
|
|
21
|
+
expect(result).toEqual({ _type: "counter", count: 6 });
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
it("does not increment with false", () => {
|
|
21
25
|
const result = mergeCounter({ count: 5 }, false);
|
|
22
|
-
expect(result).toEqual({ count: 5 });
|
|
26
|
+
expect(result).toEqual({ _type: "counter", count: 5 });
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
it("accepts numeric increment", () => {
|
|
26
30
|
const result = mergeCounter({ count: 10 }, 3);
|
|
27
|
-
expect(result).toEqual({ count: 13 });
|
|
31
|
+
expect(result).toEqual({ _type: "counter", count: 13 });
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
it("handles zero increment", () => {
|
|
31
35
|
const result = mergeCounter({ count: 10 }, 0);
|
|
32
|
-
expect(result).toEqual({ count: 10 });
|
|
36
|
+
expect(result).toEqual({ _type: "counter", count: 10 });
|
|
33
37
|
});
|
|
34
38
|
});
|
|
35
39
|
|
|
36
40
|
describe("mergeAverage", () => {
|
|
37
41
|
it("creates new average from undefined with value", () => {
|
|
38
42
|
const result = mergeAverage(undefined, 100);
|
|
39
|
-
expect(result).toEqual({
|
|
43
|
+
expect(result).toEqual({
|
|
44
|
+
_type: "average",
|
|
45
|
+
_sum: 100,
|
|
46
|
+
_count: 1,
|
|
47
|
+
avg: 100,
|
|
48
|
+
});
|
|
40
49
|
});
|
|
41
50
|
|
|
42
51
|
it("returns initial state when undefined value passed to undefined", () => {
|
|
43
52
|
const result = mergeAverage(undefined, undefined);
|
|
44
|
-
expect(result).toEqual({ _sum: 0, _count: 0, avg: 0 });
|
|
53
|
+
expect(result).toEqual({ _type: "average", _sum: 0, _count: 0, avg: 0 });
|
|
45
54
|
});
|
|
46
55
|
|
|
47
56
|
it("correctly computes average across multiple values", () => {
|
|
48
57
|
let state = mergeAverage(undefined, 100);
|
|
49
58
|
state = mergeAverage(state, 200);
|
|
50
59
|
state = mergeAverage(state, 300);
|
|
51
|
-
expect(state).toEqual({ _sum: 600, _count: 3, avg: 200 });
|
|
60
|
+
expect(state).toEqual({ _type: "average", _sum: 600, _count: 3, avg: 200 });
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
it("skips undefined values without affecting count", () => {
|
|
55
64
|
let state = mergeAverage(undefined, 100);
|
|
56
65
|
state = mergeAverage(state, undefined);
|
|
57
66
|
state = mergeAverage(state, 200);
|
|
58
|
-
expect(state).toEqual({ _sum: 300, _count: 2, avg: 150 });
|
|
67
|
+
expect(state).toEqual({ _type: "average", _sum: 300, _count: 2, avg: 150 });
|
|
59
68
|
});
|
|
60
69
|
|
|
61
70
|
it("rounds average to 1 decimal place", () => {
|
|
@@ -69,17 +78,22 @@ describe("mergeAverage", () => {
|
|
|
69
78
|
describe("mergeRate", () => {
|
|
70
79
|
it("creates new rate from undefined with success", () => {
|
|
71
80
|
const result = mergeRate(undefined, true);
|
|
72
|
-
expect(result).toEqual({
|
|
81
|
+
expect(result).toEqual({
|
|
82
|
+
_type: "rate",
|
|
83
|
+
_success: 1,
|
|
84
|
+
_total: 1,
|
|
85
|
+
rate: 100,
|
|
86
|
+
});
|
|
73
87
|
});
|
|
74
88
|
|
|
75
89
|
it("creates new rate from undefined with failure", () => {
|
|
76
90
|
const result = mergeRate(undefined, false);
|
|
77
|
-
expect(result).toEqual({ _success: 0, _total: 1, rate: 0 });
|
|
91
|
+
expect(result).toEqual({ _type: "rate", _success: 0, _total: 1, rate: 0 });
|
|
78
92
|
});
|
|
79
93
|
|
|
80
94
|
it("returns initial state when undefined value passed to undefined", () => {
|
|
81
95
|
const result = mergeRate(undefined, undefined);
|
|
82
|
-
expect(result).toEqual({ _success: 0, _total: 0, rate: 0 });
|
|
96
|
+
expect(result).toEqual({ _type: "rate", _success: 0, _total: 0, rate: 0 });
|
|
83
97
|
});
|
|
84
98
|
|
|
85
99
|
it("correctly computes rate across multiple values", () => {
|
|
@@ -88,38 +102,38 @@ describe("mergeRate", () => {
|
|
|
88
102
|
state = mergeRate(state, false);
|
|
89
103
|
state = mergeRate(state, true);
|
|
90
104
|
// 3/4 = 75%
|
|
91
|
-
expect(state).toEqual({ _success: 3, _total: 4, rate: 75 });
|
|
105
|
+
expect(state).toEqual({ _type: "rate", _success: 3, _total: 4, rate: 75 });
|
|
92
106
|
});
|
|
93
107
|
|
|
94
108
|
it("skips undefined values without affecting totals", () => {
|
|
95
109
|
let state = mergeRate(undefined, true);
|
|
96
110
|
state = mergeRate(state, undefined);
|
|
97
111
|
state = mergeRate(state, false);
|
|
98
|
-
expect(state).toEqual({ _success: 1, _total: 2, rate: 50 });
|
|
112
|
+
expect(state).toEqual({ _type: "rate", _success: 1, _total: 2, rate: 50 });
|
|
99
113
|
});
|
|
100
114
|
});
|
|
101
115
|
|
|
102
116
|
describe("mergeMinMax", () => {
|
|
103
117
|
it("creates new min/max from undefined", () => {
|
|
104
118
|
const result = mergeMinMax(undefined, 50);
|
|
105
|
-
expect(result).toEqual({ min: 50, max: 50 });
|
|
119
|
+
expect(result).toEqual({ _type: "minmax", min: 50, max: 50 });
|
|
106
120
|
});
|
|
107
121
|
|
|
108
122
|
it("returns initial state when undefined value passed to undefined", () => {
|
|
109
123
|
const result = mergeMinMax(undefined, undefined);
|
|
110
|
-
expect(result).toEqual({ min: 0, max: 0 });
|
|
124
|
+
expect(result).toEqual({ _type: "minmax", min: 0, max: 0 });
|
|
111
125
|
});
|
|
112
126
|
|
|
113
127
|
it("updates min when new value is lower", () => {
|
|
114
128
|
let state = mergeMinMax(undefined, 50);
|
|
115
129
|
state = mergeMinMax(state, 30);
|
|
116
|
-
expect(state).toEqual({ min: 30, max: 50 });
|
|
130
|
+
expect(state).toEqual({ _type: "minmax", min: 30, max: 50 });
|
|
117
131
|
});
|
|
118
132
|
|
|
119
133
|
it("updates max when new value is higher", () => {
|
|
120
134
|
let state = mergeMinMax(undefined, 50);
|
|
121
135
|
state = mergeMinMax(state, 80);
|
|
122
|
-
expect(state).toEqual({ min: 50, max: 80 });
|
|
136
|
+
expect(state).toEqual({ _type: "minmax", min: 50, max: 80 });
|
|
123
137
|
});
|
|
124
138
|
|
|
125
139
|
it("updates both when appropriate", () => {
|
|
@@ -127,20 +141,103 @@ describe("mergeMinMax", () => {
|
|
|
127
141
|
state = mergeMinMax(state, 20);
|
|
128
142
|
state = mergeMinMax(state, 100);
|
|
129
143
|
state = mergeMinMax(state, 60);
|
|
130
|
-
expect(state).toEqual({ min: 20, max: 100 });
|
|
144
|
+
expect(state).toEqual({ _type: "minmax", min: 20, max: 100 });
|
|
131
145
|
});
|
|
132
146
|
|
|
133
147
|
it("skips undefined values", () => {
|
|
134
148
|
let state = mergeMinMax(undefined, 50);
|
|
135
149
|
state = mergeMinMax(state, undefined);
|
|
136
150
|
state = mergeMinMax(state, 30);
|
|
137
|
-
expect(state).toEqual({ min: 30, max: 50 });
|
|
151
|
+
expect(state).toEqual({ _type: "minmax", min: 30, max: 50 });
|
|
138
152
|
});
|
|
139
153
|
|
|
140
154
|
it("handles negative values", () => {
|
|
141
155
|
let state = mergeMinMax(undefined, -10);
|
|
142
156
|
state = mergeMinMax(state, -50);
|
|
143
157
|
state = mergeMinMax(state, -5);
|
|
144
|
-
expect(state).toEqual({ min: -50, max: -5 });
|
|
158
|
+
expect(state).toEqual({ _type: "minmax", min: -50, max: -5 });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// State-Merge Utilities (for combining pre-aggregated states)
|
|
164
|
+
// =============================================================================
|
|
165
|
+
|
|
166
|
+
describe("mergeCounterStates", () => {
|
|
167
|
+
it("combines two counter states", () => {
|
|
168
|
+
const result = mergeCounterStates({ count: 5 }, { count: 3 });
|
|
169
|
+
expect(result).toEqual({ _type: "counter", count: 8 });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("handles zero counts", () => {
|
|
173
|
+
const result = mergeCounterStates({ count: 0 }, { count: 10 });
|
|
174
|
+
expect(result).toEqual({ _type: "counter", count: 10 });
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("mergeAverageStates", () => {
|
|
179
|
+
it("correctly merges two average states", () => {
|
|
180
|
+
const a = { _sum: 100, _count: 2, avg: 50 };
|
|
181
|
+
const b = { _sum: 200, _count: 2, avg: 100 };
|
|
182
|
+
const result = mergeAverageStates(a, b);
|
|
183
|
+
expect(result).toEqual({ _type: "average", _sum: 300, _count: 4, avg: 75 });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("handles unequal counts", () => {
|
|
187
|
+
const a = { _sum: 100, _count: 1, avg: 100 };
|
|
188
|
+
const b = { _sum: 200, _count: 4, avg: 50 };
|
|
189
|
+
const result = mergeAverageStates(a, b);
|
|
190
|
+
// (100 + 200) / 5 = 60
|
|
191
|
+
expect(result).toEqual({ _type: "average", _sum: 300, _count: 5, avg: 60 });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("handles zero count", () => {
|
|
195
|
+
const a = { _sum: 0, _count: 0, avg: 0 };
|
|
196
|
+
const b = { _sum: 100, _count: 2, avg: 50 };
|
|
197
|
+
const result = mergeAverageStates(a, b);
|
|
198
|
+
expect(result).toEqual({ _type: "average", _sum: 100, _count: 2, avg: 50 });
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("mergeRateStates", () => {
|
|
203
|
+
it("correctly merges two rate states", () => {
|
|
204
|
+
const a = { _success: 3, _total: 4, rate: 75 };
|
|
205
|
+
const b = { _success: 7, _total: 10, rate: 70 };
|
|
206
|
+
const result = mergeRateStates(a, b);
|
|
207
|
+
// 10/14 ≈ 71.4% -> rounds to 71
|
|
208
|
+
expect(result).toEqual({
|
|
209
|
+
_type: "rate",
|
|
210
|
+
_success: 10,
|
|
211
|
+
_total: 14,
|
|
212
|
+
rate: 71,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("handles zero total", () => {
|
|
217
|
+
const a = { _success: 0, _total: 0, rate: 0 };
|
|
218
|
+
const b = { _success: 5, _total: 10, rate: 50 };
|
|
219
|
+
const result = mergeRateStates(a, b);
|
|
220
|
+
expect(result).toEqual({
|
|
221
|
+
_type: "rate",
|
|
222
|
+
_success: 5,
|
|
223
|
+
_total: 10,
|
|
224
|
+
rate: 50,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("mergeMinMaxStates", () => {
|
|
230
|
+
it("takes min of mins and max of maxes", () => {
|
|
231
|
+
const a = { min: 10, max: 50 };
|
|
232
|
+
const b = { min: 5, max: 100 };
|
|
233
|
+
const result = mergeMinMaxStates(a, b);
|
|
234
|
+
expect(result).toEqual({ _type: "minmax", min: 5, max: 100 });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("handles overlapping ranges", () => {
|
|
238
|
+
const a = { min: 20, max: 60 };
|
|
239
|
+
const b = { min: 40, max: 80 };
|
|
240
|
+
const result = mergeMinMaxStates(a, b);
|
|
241
|
+
expect(result).toEqual({ _type: "minmax", min: 20, max: 80 });
|
|
145
242
|
});
|
|
146
243
|
});
|
|
@@ -5,25 +5,37 @@ import { z } from "zod";
|
|
|
5
5
|
* These utilities enable O(1) memory aggregation without storing raw data.
|
|
6
6
|
*
|
|
7
7
|
* Each pattern provides:
|
|
8
|
-
* - A Zod schema for validation/serialization
|
|
9
|
-
* -
|
|
10
|
-
* - A merge function
|
|
8
|
+
* - A Zod schema for validation/serialization (with required _type)
|
|
9
|
+
* - TypeScript types: State (with _type) and StateInput (without _type)
|
|
10
|
+
* - A merge function that always outputs the _type discriminator
|
|
11
|
+
*
|
|
12
|
+
* Strategy/collector implementations provide data WITHOUT _type.
|
|
13
|
+
* The merge functions add _type automatically.
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
|
-
//
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// COUNTER PATTERN
|
|
18
|
+
// =============================================================================
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Zod schema for accumulated counter state.
|
|
22
|
+
* _type is required for reliable type detection.
|
|
17
23
|
*/
|
|
18
24
|
export const counterStateSchema = z.object({
|
|
25
|
+
_type: z.literal("counter"),
|
|
19
26
|
count: z.number(),
|
|
20
27
|
});
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
|
-
*
|
|
30
|
+
* Counter state with required _type discriminator (derived from schema).
|
|
24
31
|
*/
|
|
25
32
|
export type CounterState = z.infer<typeof counterStateSchema>;
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Counter state input type - without _type (for strategy/collector implementations).
|
|
36
|
+
*/
|
|
37
|
+
export type CounterStateInput = Omit<CounterState, "_type">;
|
|
38
|
+
|
|
27
39
|
/**
|
|
28
40
|
* Incrementally merge a counter.
|
|
29
41
|
* Use for tracking occurrences (errorCount, requestCount, etc.)
|
|
@@ -32,23 +44,28 @@ export type CounterState = z.infer<typeof counterStateSchema>;
|
|
|
32
44
|
* @param increment - Value to add (boolean true = 1, false = 0, or direct number)
|
|
33
45
|
*/
|
|
34
46
|
export function mergeCounter(
|
|
35
|
-
existing:
|
|
47
|
+
existing: CounterStateInput | undefined,
|
|
36
48
|
increment: boolean | number,
|
|
37
49
|
): CounterState {
|
|
38
50
|
const value =
|
|
39
51
|
typeof increment === "boolean" ? (increment ? 1 : 0) : increment;
|
|
40
52
|
return {
|
|
53
|
+
_type: "counter",
|
|
41
54
|
count: (existing?.count ?? 0) + value,
|
|
42
55
|
};
|
|
43
56
|
}
|
|
44
57
|
|
|
45
|
-
//
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// AVERAGE PATTERN
|
|
60
|
+
// =============================================================================
|
|
46
61
|
|
|
47
62
|
/**
|
|
48
63
|
* Zod schema for accumulated average state.
|
|
49
64
|
* Internal `_sum` and `_count` fields enable accurate averaging.
|
|
65
|
+
* _type is required for reliable type detection.
|
|
50
66
|
*/
|
|
51
67
|
export const averageStateSchema = z.object({
|
|
68
|
+
_type: z.literal("average"),
|
|
52
69
|
/** Internal: sum of all values */
|
|
53
70
|
_sum: z.number(),
|
|
54
71
|
/** Internal: count of values */
|
|
@@ -58,10 +75,15 @@ export const averageStateSchema = z.object({
|
|
|
58
75
|
});
|
|
59
76
|
|
|
60
77
|
/**
|
|
61
|
-
*
|
|
78
|
+
* Average state with required _type discriminator (derived from schema).
|
|
62
79
|
*/
|
|
63
80
|
export type AverageState = z.infer<typeof averageStateSchema>;
|
|
64
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Average state input type - without _type (for strategy/collector implementations).
|
|
84
|
+
*/
|
|
85
|
+
export type AverageStateInput = Omit<AverageState, "_type">;
|
|
86
|
+
|
|
65
87
|
/**
|
|
66
88
|
* Incrementally merge an average.
|
|
67
89
|
* Use for tracking averages (avgResponseTimeMs, avgExecutionTimeMs, etc.)
|
|
@@ -70,18 +92,24 @@ export type AverageState = z.infer<typeof averageStateSchema>;
|
|
|
70
92
|
* @param value - New value to incorporate (undefined skipped)
|
|
71
93
|
*/
|
|
72
94
|
export function mergeAverage(
|
|
73
|
-
existing:
|
|
95
|
+
existing: AverageStateInput | undefined,
|
|
74
96
|
value: number | undefined,
|
|
75
97
|
): AverageState {
|
|
76
98
|
if (value === undefined) {
|
|
77
|
-
// No new value, return existing or initial state
|
|
78
|
-
return
|
|
99
|
+
// No new value, return existing with _type or initial state
|
|
100
|
+
return {
|
|
101
|
+
_type: "average",
|
|
102
|
+
_sum: existing?._sum ?? 0,
|
|
103
|
+
_count: existing?._count ?? 0,
|
|
104
|
+
avg: existing?.avg ?? 0,
|
|
105
|
+
};
|
|
79
106
|
}
|
|
80
107
|
|
|
81
108
|
const sum = (existing?._sum ?? 0) + value;
|
|
82
109
|
const count = (existing?._count ?? 0) + 1;
|
|
83
110
|
|
|
84
111
|
return {
|
|
112
|
+
_type: "average",
|
|
85
113
|
_sum: sum,
|
|
86
114
|
_count: count,
|
|
87
115
|
// Round to 1 decimal place to preserve precision for float metrics (e.g., load averages)
|
|
@@ -89,13 +117,17 @@ export function mergeAverage(
|
|
|
89
117
|
};
|
|
90
118
|
}
|
|
91
119
|
|
|
92
|
-
//
|
|
120
|
+
// =============================================================================
|
|
121
|
+
// RATE PATTERN
|
|
122
|
+
// =============================================================================
|
|
93
123
|
|
|
94
124
|
/**
|
|
95
125
|
* Zod schema for accumulated rate state (percentage).
|
|
96
126
|
* Internal `_success` and `_total` fields enable accurate rate calculation.
|
|
127
|
+
* _type is required for reliable type detection.
|
|
97
128
|
*/
|
|
98
129
|
export const rateStateSchema = z.object({
|
|
130
|
+
_type: z.literal("rate"),
|
|
99
131
|
/** Internal: count of successes */
|
|
100
132
|
_success: z.number(),
|
|
101
133
|
/** Internal: total count */
|
|
@@ -105,10 +137,15 @@ export const rateStateSchema = z.object({
|
|
|
105
137
|
});
|
|
106
138
|
|
|
107
139
|
/**
|
|
108
|
-
*
|
|
140
|
+
* Rate state with required _type discriminator (derived from schema).
|
|
109
141
|
*/
|
|
110
142
|
export type RateState = z.infer<typeof rateStateSchema>;
|
|
111
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Rate state input type - without _type (for strategy/collector implementations).
|
|
146
|
+
*/
|
|
147
|
+
export type RateStateInput = Omit<RateState, "_type">;
|
|
148
|
+
|
|
112
149
|
/**
|
|
113
150
|
* Incrementally merge a rate (percentage).
|
|
114
151
|
* Use for tracking success rates, availability percentages, etc.
|
|
@@ -117,39 +154,54 @@ export type RateState = z.infer<typeof rateStateSchema>;
|
|
|
117
154
|
* @param success - Whether this run was successful (undefined skipped)
|
|
118
155
|
*/
|
|
119
156
|
export function mergeRate(
|
|
120
|
-
existing:
|
|
157
|
+
existing: RateStateInput | undefined,
|
|
121
158
|
success: boolean | undefined,
|
|
122
159
|
): RateState {
|
|
123
160
|
if (success === undefined) {
|
|
124
|
-
// No new value, return existing or initial state
|
|
125
|
-
return
|
|
161
|
+
// No new value, return existing with _type or initial state
|
|
162
|
+
return {
|
|
163
|
+
_type: "rate",
|
|
164
|
+
_success: existing?._success ?? 0,
|
|
165
|
+
_total: existing?._total ?? 0,
|
|
166
|
+
rate: existing?.rate ?? 0,
|
|
167
|
+
};
|
|
126
168
|
}
|
|
127
169
|
|
|
128
170
|
const successCount = (existing?._success ?? 0) + (success ? 1 : 0);
|
|
129
171
|
const total = (existing?._total ?? 0) + 1;
|
|
130
172
|
|
|
131
173
|
return {
|
|
174
|
+
_type: "rate",
|
|
132
175
|
_success: successCount,
|
|
133
176
|
_total: total,
|
|
134
177
|
rate: Math.round((successCount / total) * 100),
|
|
135
178
|
};
|
|
136
179
|
}
|
|
137
180
|
|
|
138
|
-
//
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// MINMAX PATTERN
|
|
183
|
+
// =============================================================================
|
|
139
184
|
|
|
140
185
|
/**
|
|
141
186
|
* Zod schema for accumulated min/max state.
|
|
187
|
+
* _type is required for reliable type detection.
|
|
142
188
|
*/
|
|
143
189
|
export const minMaxStateSchema = z.object({
|
|
190
|
+
_type: z.literal("minmax"),
|
|
144
191
|
min: z.number(),
|
|
145
192
|
max: z.number(),
|
|
146
193
|
});
|
|
147
194
|
|
|
148
195
|
/**
|
|
149
|
-
*
|
|
196
|
+
* MinMax state with required _type discriminator (derived from schema).
|
|
150
197
|
*/
|
|
151
198
|
export type MinMaxState = z.infer<typeof minMaxStateSchema>;
|
|
152
199
|
|
|
200
|
+
/**
|
|
201
|
+
* MinMax state input type - without _type (for strategy/collector implementations).
|
|
202
|
+
*/
|
|
203
|
+
export type MinMaxStateInput = Omit<MinMaxState, "_type">;
|
|
204
|
+
|
|
153
205
|
/**
|
|
154
206
|
* Incrementally merge min/max values.
|
|
155
207
|
* Use for tracking min/max latency, memory, etc.
|
|
@@ -158,21 +210,99 @@ export type MinMaxState = z.infer<typeof minMaxStateSchema>;
|
|
|
158
210
|
* @param value - New value to incorporate (undefined skipped)
|
|
159
211
|
*/
|
|
160
212
|
export function mergeMinMax(
|
|
161
|
-
existing:
|
|
213
|
+
existing: MinMaxStateInput | undefined,
|
|
162
214
|
value: number | undefined,
|
|
163
215
|
): MinMaxState {
|
|
164
216
|
if (value === undefined) {
|
|
165
|
-
// No new value, return existing or initial state
|
|
166
|
-
return
|
|
217
|
+
// No new value, return existing with _type or initial state
|
|
218
|
+
return {
|
|
219
|
+
_type: "minmax",
|
|
220
|
+
min: existing?.min ?? 0,
|
|
221
|
+
max: existing?.max ?? 0,
|
|
222
|
+
};
|
|
167
223
|
}
|
|
168
224
|
|
|
169
225
|
if (existing === undefined) {
|
|
170
226
|
// First value
|
|
171
|
-
return { min: value, max: value };
|
|
227
|
+
return { _type: "minmax", min: value, max: value };
|
|
172
228
|
}
|
|
173
229
|
|
|
174
230
|
return {
|
|
231
|
+
_type: "minmax",
|
|
175
232
|
min: Math.min(existing.min, value),
|
|
176
233
|
max: Math.max(existing.max, value),
|
|
177
234
|
};
|
|
178
235
|
}
|
|
236
|
+
|
|
237
|
+
// =============================================================================
|
|
238
|
+
// STATE-MERGE UTILITIES - For merging two pre-aggregated states
|
|
239
|
+
// =============================================================================
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Merge two CounterStates.
|
|
243
|
+
* Used when combining pre-aggregated buckets (e.g., in combineBuckets).
|
|
244
|
+
* Always includes _type discriminator for reliable type detection.
|
|
245
|
+
*/
|
|
246
|
+
export function mergeCounterStates(
|
|
247
|
+
a: CounterStateInput,
|
|
248
|
+
b: CounterStateInput,
|
|
249
|
+
): CounterState {
|
|
250
|
+
return {
|
|
251
|
+
_type: "counter",
|
|
252
|
+
count: a.count + b.count,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Merge two AverageStates.
|
|
258
|
+
* Used when combining pre-aggregated buckets.
|
|
259
|
+
* Always includes _type discriminator for reliable type detection.
|
|
260
|
+
*/
|
|
261
|
+
export function mergeAverageStates(
|
|
262
|
+
a: AverageStateInput,
|
|
263
|
+
b: AverageStateInput,
|
|
264
|
+
): AverageState {
|
|
265
|
+
const sum = a._sum + b._sum;
|
|
266
|
+
const count = a._count + b._count;
|
|
267
|
+
return {
|
|
268
|
+
_type: "average",
|
|
269
|
+
_sum: sum,
|
|
270
|
+
_count: count,
|
|
271
|
+
avg: count > 0 ? Math.round((sum / count) * 10) / 10 : 0,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Merge two RateStates.
|
|
277
|
+
* Used when combining pre-aggregated buckets.
|
|
278
|
+
* Always includes _type discriminator for reliable type detection.
|
|
279
|
+
*/
|
|
280
|
+
export function mergeRateStates(
|
|
281
|
+
a: RateStateInput,
|
|
282
|
+
b: RateStateInput,
|
|
283
|
+
): RateState {
|
|
284
|
+
const success = a._success + b._success;
|
|
285
|
+
const total = a._total + b._total;
|
|
286
|
+
return {
|
|
287
|
+
_type: "rate",
|
|
288
|
+
_success: success,
|
|
289
|
+
_total: total,
|
|
290
|
+
rate: total > 0 ? Math.round((success / total) * 100) : 0,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Merge two MinMaxStates.
|
|
296
|
+
* Used when combining pre-aggregated buckets.
|
|
297
|
+
* Always includes _type discriminator for reliable type detection.
|
|
298
|
+
*/
|
|
299
|
+
export function mergeMinMaxStates(
|
|
300
|
+
a: MinMaxStateInput,
|
|
301
|
+
b: MinMaxStateInput,
|
|
302
|
+
): MinMaxState {
|
|
303
|
+
return {
|
|
304
|
+
_type: "minmax",
|
|
305
|
+
min: Math.min(a.min, b.min),
|
|
306
|
+
max: Math.max(a.max, b.max),
|
|
307
|
+
};
|
|
308
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./extension-point";
|
|
|
3
3
|
export * from "./core-services";
|
|
4
4
|
export * from "./plugin-system";
|
|
5
5
|
export * from "./health-check";
|
|
6
|
+
export * from "./base-strategy-config";
|
|
6
7
|
export * from "./auth-strategy";
|
|
7
8
|
export * from "./zod-config";
|
|
8
9
|
export * from "./encryption";
|
|
@@ -25,3 +26,4 @@ export * from "./transport-client";
|
|
|
25
26
|
export * from "./collector-strategy";
|
|
26
27
|
export * from "./collector-registry";
|
|
27
28
|
export * from "./incremental-aggregation";
|
|
29
|
+
export * from "./aggregated-result";
|