@checkstack/healthcheck-script-backend 0.2.0 → 0.3.1

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
@@ -1,5 +1,53 @@
1
1
  # @checkstack/healthcheck-script-backend
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 869b4ab: ## Health Check Execution Improvements
8
+
9
+ ### Breaking Changes (backend-api)
10
+
11
+ - `HealthCheckStrategy.createClient()` now accepts `unknown` instead of `TConfig` due to TypeScript contravariance constraints. Implementations should use `this.config.validate(config)` to narrow the type.
12
+
13
+ ### Features
14
+
15
+ - **Platform-level hard timeout**: The executor now wraps the entire health check execution (connection + all collectors) in a single timeout, ensuring checks never hang indefinitely.
16
+ - **Parallel collector execution**: Collectors now run in parallel using `Promise.allSettled()`, improving performance while ensuring all collectors complete regardless of individual failures.
17
+ - **Base strategy config schema**: All strategy configs now extend `baseStrategyConfigSchema` which provides a standardized `timeout` field with sensible defaults (30s, min 100ms).
18
+
19
+ ### Fixes
20
+
21
+ - Fixed HTTP and Jenkins strategies clearing timeouts before reading the full response body.
22
+ - Simplified registry type signatures by using default type parameters.
23
+
24
+ - Updated dependencies [869b4ab]
25
+ - @checkstack/backend-api@0.8.0
26
+
27
+ ## 0.3.0
28
+
29
+ ### Minor Changes
30
+
31
+ - 3dd1914: Migrate health check strategies to VersionedAggregated with \_type discriminator
32
+
33
+ All 13 health check strategies now use `VersionedAggregated` for their `aggregatedResult` property, enabling automatic bucket merging with 100% mathematical fidelity.
34
+
35
+ **Key changes:**
36
+
37
+ - **`_type` discriminator**: All aggregated state objects now include a required `_type` field (`"average"`, `"rate"`, `"counter"`, `"minmax"`) for reliable type detection
38
+ - The `HealthCheckStrategy` interface now requires `aggregatedResult` to be a `VersionedAggregated<AggregatedResultShape>`
39
+ - Strategy/collector `mergeResult` methods return state objects with `_type` (e.g., `{ _type: "average", _sum, _count, avg }`)
40
+ - `mergeAggregatedBucketResults`, `combineBuckets`, and `reaggregateBuckets` now require `registry` and `strategyId` parameters
41
+ - `HealthCheckService` constructor now requires both `registry` and `collectorRegistry` parameters
42
+ - Frontend `extractComputedValue` now uses `_type` discriminator for robust type detection
43
+
44
+ **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.
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies [3dd1914]
49
+ - @checkstack/backend-api@0.7.0
50
+
3
51
  ## 0.2.0
4
52
 
5
53
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-script-backend",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -9,9 +9,9 @@
9
9
  "lint:code": "eslint . --max-warnings 0"
10
10
  },
11
11
  "dependencies": {
12
- "@checkstack/backend-api": "0.5.2",
13
- "@checkstack/common": "0.6.1",
14
- "@checkstack/healthcheck-common": "0.8.1"
12
+ "@checkstack/backend-api": "0.7.0",
13
+ "@checkstack/common": "0.6.2",
14
+ "@checkstack/healthcheck-common": "0.8.2"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/bun": "^1.0.0",
@@ -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
 
@@ -6,11 +6,11 @@ import {
6
6
  type CollectorResult,
7
7
  type CollectorStrategy,
8
8
  mergeAverage,
9
- averageStateSchema,
10
9
  mergeRate,
11
- rateStateSchema,
12
- type AverageState,
13
- type RateState,
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
- const executeAggregatedDisplaySchema = healthResultSchema({
82
- avgExecutionTimeMs: healthResultNumber({
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: healthResultNumber({
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
- const executeAggregatedSchema = executeAggregatedDisplaySchema.merge(
100
- executeAggregatedInternalSchema,
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 Versioned({
124
+ aggregatedResult = new VersionedAggregated({
130
125
  version: 1,
131
- schema: executeAggregatedSchema,
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: executionTimeState.avg,
188
- successRate: successState.rate,
189
- _executionTime: executionTimeState,
190
- _success: successState,
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
- rateStateSchema,
13
- type AverageState,
14
- type RateState,
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
- const inlineScriptAggregatedDisplaySchema = healthResultSchema({
183
- avgExecutionTimeMs: healthResultNumber({
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: healthResultNumber({
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
- const inlineScriptAggregatedSchema = inlineScriptAggregatedDisplaySchema.merge(
201
- inlineScriptAggregatedInternalSchema,
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 Versioned({
249
+ aggregatedResult = new VersionedAggregated({
257
250
  version: 1,
258
- schema: inlineScriptAggregatedSchema,
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: executionTimeState.avg,
359
- successRate: successState.rate,
360
- _executionTime: executionTimeState,
361
- _success: successState,
341
+ avgExecutionTimeMs: mergeAverage(
342
+ existing?.avgExecutionTimeMs,
343
+ metadata?.executionTimeMs,
344
+ ),
345
+ successRate: mergeRate(existing?.successRate, metadata?.success),
362
346
  };
363
347
  }
364
348
  }
@@ -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,17 @@ import {
3
3
  HealthCheckStrategy,
4
4
  HealthCheckRunForAggregation,
5
5
  Versioned,
6
- z,
7
- type ConnectedClient,
6
+ VersionedAggregated,
7
+ aggregatedAverage,
8
+ aggregatedRate,
9
+ aggregatedCounter,
8
10
  mergeAverage,
9
- averageStateSchema,
10
11
  mergeRate,
11
- rateStateSchema,
12
12
  mergeCounter,
13
- counterStateSchema,
14
- type AverageState,
15
- type RateState,
16
- type CounterState,
13
+ z,
14
+ type ConnectedClient,
15
+ type InferAggregatedResult,
16
+ baseStrategyConfigSchema,
17
17
  } from "@checkstack/backend-api";
18
18
  import {
19
19
  healthResultBoolean,
@@ -35,13 +35,7 @@ import type {
35
35
  * Configuration schema for Script health checks.
36
36
  * Global defaults only - action params moved to ExecuteCollector.
37
37
  */
38
- export const scriptConfigSchema = z.object({
39
- timeout: z
40
- .number()
41
- .min(100)
42
- .default(30_000)
43
- .describe("Default execution timeout in milliseconds"),
44
- });
38
+ export const scriptConfigSchema = baseStrategyConfigSchema.extend({});
45
39
 
46
40
  export type ScriptConfig = z.infer<typeof scriptConfigSchema>;
47
41
  export type ScriptConfigInput = z.input<typeof scriptConfigSchema>;
@@ -88,44 +82,31 @@ const scriptResultSchema = healthResultSchema({
88
82
 
89
83
  type ScriptResult = z.infer<typeof scriptResultSchema>;
90
84
 
91
- /**
92
- * Aggregated metadata for buckets.
93
- */
94
- const scriptAggregatedDisplaySchema = healthResultSchema({
95
- avgExecutionTime: healthResultNumber({
85
+ /** Aggregated field definitions for bucket merging */
86
+ const scriptAggregatedFields = {
87
+ avgExecutionTime: aggregatedAverage({
96
88
  "x-chart-type": "line",
97
89
  "x-chart-label": "Avg Execution Time",
98
90
  "x-chart-unit": "ms",
99
91
  }),
100
- successRate: healthResultNumber({
92
+ successRate: aggregatedRate({
101
93
  "x-chart-type": "gauge",
102
94
  "x-chart-label": "Success Rate",
103
95
  "x-chart-unit": "%",
104
96
  }),
105
- errorCount: healthResultNumber({
97
+ errorCount: aggregatedCounter({
106
98
  "x-chart-type": "counter",
107
99
  "x-chart-label": "Errors",
108
100
  }),
109
- timeoutCount: healthResultNumber({
101
+ timeoutCount: aggregatedCounter({
110
102
  "x-chart-type": "counter",
111
103
  "x-chart-label": "Timeouts",
112
104
  }),
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
- );
105
+ };
127
106
 
128
- type ScriptAggregatedResult = z.infer<typeof scriptAggregatedSchema>;
107
+ type ScriptAggregatedResult = InferAggregatedResult<
108
+ typeof scriptAggregatedFields
109
+ >;
129
110
 
130
111
  // ============================================================================
131
112
  // SCRIPT EXECUTOR INTERFACE (for testability)
@@ -208,7 +189,7 @@ export class ScriptHealthCheckStrategy implements HealthCheckStrategy<
208
189
  ScriptConfig,
209
190
  ScriptTransportClient,
210
191
  ScriptResult,
211
- ScriptAggregatedResult
192
+ typeof scriptAggregatedFields
212
193
  > {
213
194
  id = "script";
214
195
  displayName = "Script Health Check";
@@ -248,9 +229,9 @@ export class ScriptHealthCheckStrategy implements HealthCheckStrategy<
248
229
  ],
249
230
  });
250
231
 
251
- aggregatedResult: Versioned<ScriptAggregatedResult> = new Versioned({
232
+ aggregatedResult = new VersionedAggregated({
252
233
  version: 1,
253
- schema: scriptAggregatedSchema,
234
+ fields: scriptAggregatedFields,
254
235
  });
255
236
 
256
237
  mergeResult(
@@ -259,36 +240,21 @@ export class ScriptHealthCheckStrategy implements HealthCheckStrategy<
259
240
  ): ScriptAggregatedResult {
260
241
  const metadata = run.metadata;
261
242
 
262
- const executionTimeState = mergeAverage(
263
- existing?._executionTime as AverageState | undefined,
243
+ const avgExecutionTime = mergeAverage(
244
+ existing?.avgExecutionTime,
264
245
  metadata?.executionTimeMs,
265
246
  );
266
247
 
267
- const successState = mergeRate(
268
- existing?._success as RateState | undefined,
269
- metadata?.success,
270
- );
248
+ const isSuccess = metadata?.success ?? false;
249
+ const successRate = mergeRate(existing?.successRate, isSuccess);
271
250
 
272
- const errorState = mergeCounter(
273
- existing?._errors as CounterState | undefined,
274
- metadata?.error !== undefined,
275
- );
251
+ const hasError = metadata?.error !== undefined;
252
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
276
253
 
277
- const timeoutState = mergeCounter(
278
- existing?._timeouts as CounterState | undefined,
279
- metadata?.timedOut === true,
280
- );
254
+ const hasTimeout = metadata?.timedOut === true;
255
+ const timeoutCount = mergeCounter(existing?.timeoutCount, hasTimeout);
281
256
 
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
- };
257
+ return { avgExecutionTime, successRate, errorCount, timeoutCount };
292
258
  }
293
259
 
294
260
  async createClient(