@checkstack/healthcheck-script-backend 0.2.0 → 0.3.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 +24 -0
- package/package.json +1 -1
- package/src/execute-collector.test.ts +3 -3
- package/src/execute-collector.ts +20 -34
- package/src/inline-script-collector.test.ts +2 -2
- package/src/inline-script-collector.ts +19 -35
- package/src/strategy.test.ts +7 -7
- package/src/strategy.ts +29 -58
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @checkstack/healthcheck-script-backend
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3dd1914: Migrate health check strategies to VersionedAggregated with \_type discriminator
|
|
8
|
+
|
|
9
|
+
All 13 health check strategies now use `VersionedAggregated` for their `aggregatedResult` property, enabling automatic bucket merging with 100% mathematical fidelity.
|
|
10
|
+
|
|
11
|
+
**Key changes:**
|
|
12
|
+
|
|
13
|
+
- **`_type` discriminator**: All aggregated state objects now include a required `_type` field (`"average"`, `"rate"`, `"counter"`, `"minmax"`) for reliable type detection
|
|
14
|
+
- The `HealthCheckStrategy` interface now requires `aggregatedResult` to be a `VersionedAggregated<AggregatedResultShape>`
|
|
15
|
+
- Strategy/collector `mergeResult` methods return state objects with `_type` (e.g., `{ _type: "average", _sum, _count, avg }`)
|
|
16
|
+
- `mergeAggregatedBucketResults`, `combineBuckets`, and `reaggregateBuckets` now require `registry` and `strategyId` parameters
|
|
17
|
+
- `HealthCheckService` constructor now requires both `registry` and `collectorRegistry` parameters
|
|
18
|
+
- Frontend `extractComputedValue` now uses `_type` discriminator for robust type detection
|
|
19
|
+
|
|
20
|
+
**Breaking Change**: State objects now require `_type`. Merge functions automatically add `_type` to output. The bucket merging functions and `HealthCheckService` now require additional required parameters.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [3dd1914]
|
|
25
|
+
- @checkstack/backend-api@0.7.0
|
|
26
|
+
|
|
3
27
|
## 0.2.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -139,8 +139,8 @@ describe("ExecuteCollector", () => {
|
|
|
139
139
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
140
140
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
141
141
|
|
|
142
|
-
expect(aggregated.avgExecutionTimeMs).toBe(75);
|
|
143
|
-
expect(aggregated.successRate).toBe(100);
|
|
142
|
+
expect(aggregated.avgExecutionTimeMs.avg).toBe(75);
|
|
143
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
it("should calculate success rate correctly", () => {
|
|
@@ -181,7 +181,7 @@ describe("ExecuteCollector", () => {
|
|
|
181
181
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
182
182
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
183
183
|
|
|
184
|
-
expect(aggregated.successRate).toBe(50);
|
|
184
|
+
expect(aggregated.successRate.rate).toBe(50);
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
187
|
|
package/src/execute-collector.ts
CHANGED
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
type CollectorResult,
|
|
7
7
|
type CollectorStrategy,
|
|
8
8
|
mergeAverage,
|
|
9
|
-
averageStateSchema,
|
|
10
9
|
mergeRate,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
VersionedAggregated,
|
|
11
|
+
aggregatedAverage,
|
|
12
|
+
aggregatedRate,
|
|
13
|
+
type InferAggregatedResult,
|
|
14
14
|
} from "@checkstack/backend-api";
|
|
15
15
|
import {
|
|
16
16
|
healthResultNumber,
|
|
@@ -78,29 +78,24 @@ const executeResultSchema = healthResultSchema({
|
|
|
78
78
|
|
|
79
79
|
export type ExecuteResult = z.infer<typeof executeResultSchema>;
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Aggregated result fields definition
|
|
82
|
+
const executeAggregatedFields = {
|
|
83
|
+
avgExecutionTimeMs: aggregatedAverage({
|
|
83
84
|
"x-chart-type": "line",
|
|
84
85
|
"x-chart-label": "Avg Execution Time",
|
|
85
86
|
"x-chart-unit": "ms",
|
|
86
87
|
}),
|
|
87
|
-
successRate:
|
|
88
|
+
successRate: aggregatedRate({
|
|
88
89
|
"x-chart-type": "gauge",
|
|
89
90
|
"x-chart-label": "Success Rate",
|
|
90
91
|
"x-chart-unit": "%",
|
|
91
92
|
}),
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const executeAggregatedInternalSchema = z.object({
|
|
95
|
-
_executionTime: averageStateSchema.optional(),
|
|
96
|
-
_success: rateStateSchema.optional(),
|
|
97
|
-
});
|
|
93
|
+
};
|
|
98
94
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
export type ExecuteAggregatedResult = z.infer<typeof executeAggregatedSchema>;
|
|
95
|
+
// Type inferred from field definitions
|
|
96
|
+
export type ExecuteAggregatedResult = InferAggregatedResult<
|
|
97
|
+
typeof executeAggregatedFields
|
|
98
|
+
>;
|
|
104
99
|
|
|
105
100
|
// ============================================================================
|
|
106
101
|
// EXECUTE COLLECTOR
|
|
@@ -126,9 +121,9 @@ export class ExecuteCollector implements CollectorStrategy<
|
|
|
126
121
|
|
|
127
122
|
config = new Versioned({ version: 1, schema: executeConfigSchema });
|
|
128
123
|
result = new Versioned({ version: 1, schema: executeResultSchema });
|
|
129
|
-
aggregatedResult = new
|
|
124
|
+
aggregatedResult = new VersionedAggregated({
|
|
130
125
|
version: 1,
|
|
131
|
-
|
|
126
|
+
fields: executeAggregatedFields,
|
|
132
127
|
});
|
|
133
128
|
|
|
134
129
|
async execute({
|
|
@@ -173,21 +168,12 @@ export class ExecuteCollector implements CollectorStrategy<
|
|
|
173
168
|
): ExecuteAggregatedResult {
|
|
174
169
|
const metadata = run.metadata;
|
|
175
170
|
|
|
176
|
-
const executionTimeState = mergeAverage(
|
|
177
|
-
existing?._executionTime as AverageState | undefined,
|
|
178
|
-
metadata?.executionTimeMs,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const successState = mergeRate(
|
|
182
|
-
existing?._success as RateState | undefined,
|
|
183
|
-
metadata?.success,
|
|
184
|
-
);
|
|
185
|
-
|
|
186
171
|
return {
|
|
187
|
-
avgExecutionTimeMs:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
172
|
+
avgExecutionTimeMs: mergeAverage(
|
|
173
|
+
existing?.avgExecutionTimeMs,
|
|
174
|
+
metadata?.executionTimeMs,
|
|
175
|
+
),
|
|
176
|
+
successRate: mergeRate(existing?.successRate, metadata?.success),
|
|
191
177
|
};
|
|
192
178
|
}
|
|
193
179
|
}
|
|
@@ -249,8 +249,8 @@ describe("InlineScriptCollector", () => {
|
|
|
249
249
|
aggregated = collector.mergeResult(aggregated, run2 as never);
|
|
250
250
|
aggregated = collector.mergeResult(aggregated, run3 as never);
|
|
251
251
|
|
|
252
|
-
expect(aggregated.avgExecutionTimeMs).toBe(150); // (100+200+150)/3
|
|
253
|
-
expect(aggregated.successRate).toBeCloseTo(67, 0); // 2/3 * 100 = ~67
|
|
252
|
+
expect(aggregated.avgExecutionTimeMs.avg).toBe(150); // (100+200+150)/3
|
|
253
|
+
expect(aggregated.successRate.rate).toBeCloseTo(67, 0); // 2/3 * 100 = ~67
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
});
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
type CollectorResult,
|
|
8
8
|
type CollectorStrategy,
|
|
9
9
|
mergeAverage,
|
|
10
|
-
averageStateSchema,
|
|
11
10
|
mergeRate,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
VersionedAggregated,
|
|
12
|
+
aggregatedAverage,
|
|
13
|
+
aggregatedRate,
|
|
14
|
+
type InferAggregatedResult,
|
|
15
15
|
} from "@checkstack/backend-api";
|
|
16
16
|
import {
|
|
17
17
|
healthResultNumber,
|
|
@@ -179,30 +179,23 @@ const inlineScriptResultSchema = healthResultSchema({
|
|
|
179
179
|
|
|
180
180
|
export type InlineScriptResult = z.infer<typeof inlineScriptResultSchema>;
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
// Aggregated result fields definition
|
|
183
|
+
const inlineScriptAggregatedFields = {
|
|
184
|
+
avgExecutionTimeMs: aggregatedAverage({
|
|
184
185
|
"x-chart-type": "line",
|
|
185
186
|
"x-chart-label": "Avg Execution Time",
|
|
186
187
|
"x-chart-unit": "ms",
|
|
187
188
|
}),
|
|
188
|
-
successRate:
|
|
189
|
+
successRate: aggregatedRate({
|
|
189
190
|
"x-chart-type": "gauge",
|
|
190
191
|
"x-chart-label": "Success Rate",
|
|
191
192
|
"x-chart-unit": "%",
|
|
192
193
|
}),
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const inlineScriptAggregatedInternalSchema = z.object({
|
|
196
|
-
_executionTime: averageStateSchema.optional(),
|
|
197
|
-
_success: rateStateSchema.optional(),
|
|
198
|
-
});
|
|
194
|
+
};
|
|
199
195
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
export type InlineScriptAggregatedResult = z.infer<
|
|
205
|
-
typeof inlineScriptAggregatedSchema
|
|
196
|
+
// Type inferred from field definitions
|
|
197
|
+
export type InlineScriptAggregatedResult = InferAggregatedResult<
|
|
198
|
+
typeof inlineScriptAggregatedFields
|
|
206
199
|
>;
|
|
207
200
|
|
|
208
201
|
// ============================================================================
|
|
@@ -253,9 +246,9 @@ export class InlineScriptCollector implements CollectorStrategy<
|
|
|
253
246
|
|
|
254
247
|
config = new Versioned({ version: 1, schema: inlineScriptConfigSchema });
|
|
255
248
|
result = new Versioned({ version: 1, schema: inlineScriptResultSchema });
|
|
256
|
-
aggregatedResult = new
|
|
249
|
+
aggregatedResult = new VersionedAggregated({
|
|
257
250
|
version: 1,
|
|
258
|
-
|
|
251
|
+
fields: inlineScriptAggregatedFields,
|
|
259
252
|
});
|
|
260
253
|
|
|
261
254
|
async execute({
|
|
@@ -344,21 +337,12 @@ export class InlineScriptCollector implements CollectorStrategy<
|
|
|
344
337
|
): InlineScriptAggregatedResult {
|
|
345
338
|
const metadata = run.metadata;
|
|
346
339
|
|
|
347
|
-
const executionTimeState = mergeAverage(
|
|
348
|
-
existing?._executionTime as AverageState | undefined,
|
|
349
|
-
metadata?.executionTimeMs,
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
const successState = mergeRate(
|
|
353
|
-
existing?._success as RateState | undefined,
|
|
354
|
-
metadata?.success,
|
|
355
|
-
);
|
|
356
|
-
|
|
357
340
|
return {
|
|
358
|
-
avgExecutionTimeMs:
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
341
|
+
avgExecutionTimeMs: mergeAverage(
|
|
342
|
+
existing?.avgExecutionTimeMs,
|
|
343
|
+
metadata?.executionTimeMs,
|
|
344
|
+
),
|
|
345
|
+
successRate: mergeRate(existing?.successRate, metadata?.success),
|
|
362
346
|
};
|
|
363
347
|
}
|
|
364
348
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -175,10 +175,10 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
175
175
|
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
176
176
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
177
177
|
|
|
178
|
-
expect(aggregated.avgExecutionTime).toBe(75);
|
|
179
|
-
expect(aggregated.successRate).toBe(100);
|
|
180
|
-
expect(aggregated.errorCount).toBe(0);
|
|
181
|
-
expect(aggregated.timeoutCount).toBe(0);
|
|
178
|
+
expect(aggregated.avgExecutionTime.avg).toBe(75);
|
|
179
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
180
|
+
expect(aggregated.errorCount.count).toBe(0);
|
|
181
|
+
expect(aggregated.timeoutCount.count).toBe(0);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it("should count errors and timeouts", () => {
|
|
@@ -217,9 +217,9 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
217
217
|
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
218
218
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
219
219
|
|
|
220
|
-
expect(aggregated.errorCount).toBe(1);
|
|
221
|
-
expect(aggregated.timeoutCount).toBe(1);
|
|
222
|
-
expect(aggregated.successRate).toBe(0);
|
|
220
|
+
expect(aggregated.errorCount.count).toBe(1);
|
|
221
|
+
expect(aggregated.timeoutCount.count).toBe(1);
|
|
222
|
+
expect(aggregated.successRate.rate).toBe(0);
|
|
223
223
|
});
|
|
224
224
|
});
|
|
225
225
|
});
|
package/src/strategy.ts
CHANGED
|
@@ -3,17 +3,16 @@ import {
|
|
|
3
3
|
HealthCheckStrategy,
|
|
4
4
|
HealthCheckRunForAggregation,
|
|
5
5
|
Versioned,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
VersionedAggregated,
|
|
7
|
+
aggregatedAverage,
|
|
8
|
+
aggregatedRate,
|
|
9
|
+
aggregatedCounter,
|
|
8
10
|
mergeAverage,
|
|
9
|
-
averageStateSchema,
|
|
10
11
|
mergeRate,
|
|
11
|
-
rateStateSchema,
|
|
12
12
|
mergeCounter,
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
type CounterState,
|
|
13
|
+
z,
|
|
14
|
+
type ConnectedClient,
|
|
15
|
+
type InferAggregatedResult,
|
|
17
16
|
} from "@checkstack/backend-api";
|
|
18
17
|
import {
|
|
19
18
|
healthResultBoolean,
|
|
@@ -88,44 +87,31 @@ const scriptResultSchema = healthResultSchema({
|
|
|
88
87
|
|
|
89
88
|
type ScriptResult = z.infer<typeof scriptResultSchema>;
|
|
90
89
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const scriptAggregatedDisplaySchema = healthResultSchema({
|
|
95
|
-
avgExecutionTime: healthResultNumber({
|
|
90
|
+
/** Aggregated field definitions for bucket merging */
|
|
91
|
+
const scriptAggregatedFields = {
|
|
92
|
+
avgExecutionTime: aggregatedAverage({
|
|
96
93
|
"x-chart-type": "line",
|
|
97
94
|
"x-chart-label": "Avg Execution Time",
|
|
98
95
|
"x-chart-unit": "ms",
|
|
99
96
|
}),
|
|
100
|
-
successRate:
|
|
97
|
+
successRate: aggregatedRate({
|
|
101
98
|
"x-chart-type": "gauge",
|
|
102
99
|
"x-chart-label": "Success Rate",
|
|
103
100
|
"x-chart-unit": "%",
|
|
104
101
|
}),
|
|
105
|
-
errorCount:
|
|
102
|
+
errorCount: aggregatedCounter({
|
|
106
103
|
"x-chart-type": "counter",
|
|
107
104
|
"x-chart-label": "Errors",
|
|
108
105
|
}),
|
|
109
|
-
timeoutCount:
|
|
106
|
+
timeoutCount: aggregatedCounter({
|
|
110
107
|
"x-chart-type": "counter",
|
|
111
108
|
"x-chart-label": "Timeouts",
|
|
112
109
|
}),
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const scriptAggregatedInternalSchema = z.object({
|
|
116
|
-
_executionTime: averageStateSchema
|
|
117
|
-
.optional(),
|
|
118
|
-
_success: rateStateSchema
|
|
119
|
-
.optional(),
|
|
120
|
-
_errors: counterStateSchema.optional(),
|
|
121
|
-
_timeouts: counterStateSchema.optional(),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const scriptAggregatedSchema = scriptAggregatedDisplaySchema.merge(
|
|
125
|
-
scriptAggregatedInternalSchema,
|
|
126
|
-
);
|
|
110
|
+
};
|
|
127
111
|
|
|
128
|
-
type ScriptAggregatedResult =
|
|
112
|
+
type ScriptAggregatedResult = InferAggregatedResult<
|
|
113
|
+
typeof scriptAggregatedFields
|
|
114
|
+
>;
|
|
129
115
|
|
|
130
116
|
// ============================================================================
|
|
131
117
|
// SCRIPT EXECUTOR INTERFACE (for testability)
|
|
@@ -208,7 +194,7 @@ export class ScriptHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
208
194
|
ScriptConfig,
|
|
209
195
|
ScriptTransportClient,
|
|
210
196
|
ScriptResult,
|
|
211
|
-
|
|
197
|
+
typeof scriptAggregatedFields
|
|
212
198
|
> {
|
|
213
199
|
id = "script";
|
|
214
200
|
displayName = "Script Health Check";
|
|
@@ -248,9 +234,9 @@ export class ScriptHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
248
234
|
],
|
|
249
235
|
});
|
|
250
236
|
|
|
251
|
-
aggregatedResult
|
|
237
|
+
aggregatedResult = new VersionedAggregated({
|
|
252
238
|
version: 1,
|
|
253
|
-
|
|
239
|
+
fields: scriptAggregatedFields,
|
|
254
240
|
});
|
|
255
241
|
|
|
256
242
|
mergeResult(
|
|
@@ -259,36 +245,21 @@ export class ScriptHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
259
245
|
): ScriptAggregatedResult {
|
|
260
246
|
const metadata = run.metadata;
|
|
261
247
|
|
|
262
|
-
const
|
|
263
|
-
existing?.
|
|
248
|
+
const avgExecutionTime = mergeAverage(
|
|
249
|
+
existing?.avgExecutionTime,
|
|
264
250
|
metadata?.executionTimeMs,
|
|
265
251
|
);
|
|
266
252
|
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
metadata?.success,
|
|
270
|
-
);
|
|
253
|
+
const isSuccess = metadata?.success ?? false;
|
|
254
|
+
const successRate = mergeRate(existing?.successRate, isSuccess);
|
|
271
255
|
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
metadata?.error !== undefined,
|
|
275
|
-
);
|
|
256
|
+
const hasError = metadata?.error !== undefined;
|
|
257
|
+
const errorCount = mergeCounter(existing?.errorCount, hasError);
|
|
276
258
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
metadata?.timedOut === true,
|
|
280
|
-
);
|
|
259
|
+
const hasTimeout = metadata?.timedOut === true;
|
|
260
|
+
const timeoutCount = mergeCounter(existing?.timeoutCount, hasTimeout);
|
|
281
261
|
|
|
282
|
-
return {
|
|
283
|
-
avgExecutionTime: executionTimeState.avg,
|
|
284
|
-
successRate: successState.rate,
|
|
285
|
-
errorCount: errorState.count,
|
|
286
|
-
timeoutCount: timeoutState.count,
|
|
287
|
-
_executionTime: executionTimeState,
|
|
288
|
-
_success: successState,
|
|
289
|
-
_errors: errorState,
|
|
290
|
-
_timeouts: timeoutState,
|
|
291
|
-
};
|
|
262
|
+
return { avgExecutionTime, successRate, errorCount, timeoutCount };
|
|
292
263
|
}
|
|
293
264
|
|
|
294
265
|
async createClient(
|