@checkstack/healthcheck-tls-backend 0.1.14 → 0.2.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-tls-backend
2
2
 
3
+ ## 0.2.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.2.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.1.14
4
52
 
5
53
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-tls-backend",
3
- "version": "0.1.14",
3
+ "version": "0.2.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",
@@ -115,8 +115,8 @@ describe("CertificateCollector", () => {
115
115
  let aggregated = collector.mergeResult(undefined, runs[0]);
116
116
  aggregated = collector.mergeResult(aggregated, runs[1]);
117
117
 
118
- expect(aggregated.avgDaysRemaining).toBe(45);
119
- expect(aggregated.validRate).toBe(100);
118
+ expect(aggregated.avgDaysRemaining.avg).toBe(45);
119
+ expect(aggregated.validRate.rate).toBe(100);
120
120
  });
121
121
 
122
122
  it("should calculate valid rate correctly", () => {
@@ -157,7 +157,7 @@ describe("CertificateCollector", () => {
157
157
  let aggregated = collector.mergeResult(undefined, runs[0]);
158
158
  aggregated = collector.mergeResult(aggregated, runs[1]);
159
159
 
160
- expect(aggregated.validRate).toBe(50);
160
+ expect(aggregated.validRate.rate).toBe(50);
161
161
  });
162
162
  });
163
163
 
@@ -5,11 +5,11 @@ import {
5
5
  type CollectorResult,
6
6
  type CollectorStrategy,
7
7
  mergeAverage,
8
- averageStateSchema,
9
8
  mergeRate,
10
- rateStateSchema,
11
- type AverageState,
12
- type RateState,
9
+ VersionedAggregated,
10
+ aggregatedAverage,
11
+ aggregatedRate,
12
+ type InferAggregatedResult,
13
13
  } from "@checkstack/backend-api";
14
14
  import {
15
15
  healthResultNumber,
@@ -64,32 +64,23 @@ const certificateResultSchema = healthResultSchema({
64
64
 
65
65
  export type CertificateResult = z.infer<typeof certificateResultSchema>;
66
66
 
67
- const certificateAggregatedDisplaySchema = healthResultSchema({
68
- avgDaysRemaining: healthResultNumber({
67
+ // Aggregated result fields definition
68
+ const certificateAggregatedFields = {
69
+ avgDaysRemaining: aggregatedAverage({
69
70
  "x-chart-type": "gauge",
70
71
  "x-chart-label": "Avg Days Remaining",
71
72
  "x-chart-unit": "days",
72
73
  }),
73
- validRate: healthResultNumber({
74
+ validRate: aggregatedRate({
74
75
  "x-chart-type": "gauge",
75
76
  "x-chart-label": "Valid Rate",
76
77
  "x-chart-unit": "%",
77
78
  }),
78
- });
79
-
80
- const certificateAggregatedInternalSchema = z.object({
81
- _daysRemaining: averageStateSchema
82
- .optional(),
83
- _valid: rateStateSchema
84
- .optional(),
85
- });
79
+ };
86
80
 
87
- const certificateAggregatedSchema = certificateAggregatedDisplaySchema.merge(
88
- certificateAggregatedInternalSchema,
89
- );
90
-
91
- export type CertificateAggregatedResult = z.infer<
92
- typeof certificateAggregatedSchema
81
+ // Type inferred from field definitions
82
+ export type CertificateAggregatedResult = InferAggregatedResult<
83
+ typeof certificateAggregatedFields
93
84
  >;
94
85
 
95
86
  // ============================================================================
@@ -116,9 +107,9 @@ export class CertificateCollector implements CollectorStrategy<
116
107
 
117
108
  config = new Versioned({ version: 1, schema: certificateConfigSchema });
118
109
  result = new Versioned({ version: 1, schema: certificateResultSchema });
119
- aggregatedResult = new Versioned({
110
+ aggregatedResult = new VersionedAggregated({
120
111
  version: 1,
121
- schema: certificateAggregatedSchema,
112
+ fields: certificateAggregatedFields,
122
113
  });
123
114
 
124
115
  async execute({
@@ -162,21 +153,12 @@ export class CertificateCollector implements CollectorStrategy<
162
153
  ): CertificateAggregatedResult {
163
154
  const metadata = run.metadata;
164
155
 
165
- const daysState = mergeAverage(
166
- existing?._daysRemaining as AverageState | undefined,
167
- metadata?.daysRemaining,
168
- );
169
-
170
- const validState = mergeRate(
171
- existing?._valid as RateState | undefined,
172
- metadata?.valid,
173
- );
174
-
175
156
  return {
176
- avgDaysRemaining: daysState.avg,
177
- validRate: validState.rate,
178
- _daysRemaining: daysState,
179
- _valid: validState,
157
+ avgDaysRemaining: mergeAverage(
158
+ existing?.avgDaysRemaining,
159
+ metadata?.daysRemaining,
160
+ ),
161
+ validRate: mergeRate(existing?.validRate, metadata?.valid),
180
162
  };
181
163
  }
182
164
  }
@@ -207,10 +207,10 @@ describe("TlsHealthCheckStrategy", () => {
207
207
  let aggregated = strategy.mergeResult(undefined, runs[0]);
208
208
  aggregated = strategy.mergeResult(aggregated, runs[1]);
209
209
 
210
- expect(aggregated.avgDaysUntilExpiry).toBe(25);
211
- expect(aggregated.minDaysUntilExpiry).toBe(20);
212
- expect(aggregated.invalidCount).toBe(0);
213
- expect(aggregated.errorCount).toBe(0);
210
+ expect(aggregated.avgDaysUntilExpiry.avg).toBe(25);
211
+ expect(aggregated.minDaysUntilExpiry.min).toBe(20);
212
+ expect(aggregated.invalidCount.count).toBe(0);
213
+ expect(aggregated.errorCount.count).toBe(0);
214
214
  });
215
215
 
216
216
  it("should count invalid and errors", () => {
@@ -256,8 +256,8 @@ describe("TlsHealthCheckStrategy", () => {
256
256
  let aggregated = strategy.mergeResult(undefined, runs[0]);
257
257
  aggregated = strategy.mergeResult(aggregated, runs[1]);
258
258
 
259
- expect(aggregated.invalidCount).toBe(2);
260
- expect(aggregated.errorCount).toBe(1);
259
+ expect(aggregated.invalidCount.count).toBe(2);
260
+ expect(aggregated.errorCount.count).toBe(1);
261
261
  });
262
262
  });
263
263
  });
package/src/strategy.ts CHANGED
@@ -3,16 +3,17 @@ import {
3
3
  HealthCheckStrategy,
4
4
  HealthCheckRunForAggregation,
5
5
  Versioned,
6
- z,
7
- type ConnectedClient,
6
+ VersionedAggregated,
7
+ aggregatedAverage,
8
+ aggregatedMinMax,
9
+ aggregatedCounter,
8
10
  mergeAverage,
9
- averageStateSchema,
10
11
  mergeCounter,
11
- counterStateSchema,
12
12
  mergeMinMax,
13
- type AverageState,
14
- type CounterState,
15
- type MinMaxState,
13
+ z,
14
+ type ConnectedClient,
15
+ type InferAggregatedResult,
16
+ baseStrategyConfigSchema,
16
17
  } from "@checkstack/backend-api";
17
18
  import {
18
19
  healthResultBoolean,
@@ -33,18 +34,13 @@ import type {
33
34
  /**
34
35
  * Configuration schema for TLS health checks.
35
36
  */
36
- export const tlsConfigSchema = z.object({
37
+ export const tlsConfigSchema = baseStrategyConfigSchema.extend({
37
38
  host: z.string().describe("Hostname to connect to"),
38
39
  port: z.number().int().min(1).max(65_535).default(443).describe("TLS port"),
39
40
  servername: z
40
41
  .string()
41
42
  .optional()
42
43
  .describe("Server name for SNI (defaults to host)"),
43
- timeout: z
44
- .number()
45
- .min(100)
46
- .default(10_000)
47
- .describe("Connection timeout in milliseconds"),
48
44
  minDaysUntilExpiry: z
49
45
  .number()
50
46
  .int()
@@ -88,44 +84,29 @@ const tlsResultSchema = healthResultSchema({
88
84
 
89
85
  type TlsResult = z.infer<typeof tlsResultSchema>;
90
86
 
91
- /**
92
- * Aggregated metadata for buckets.
93
- */
94
- const tlsAggregatedDisplaySchema = healthResultSchema({
95
- avgDaysUntilExpiry: healthResultNumber({
87
+ /** Aggregated field definitions for bucket merging */
88
+ const tlsAggregatedFields = {
89
+ avgDaysUntilExpiry: aggregatedAverage({
96
90
  "x-chart-type": "line",
97
91
  "x-chart-label": "Avg Days Until Expiry",
98
92
  "x-chart-unit": "days",
99
93
  }),
100
- minDaysUntilExpiry: healthResultNumber({
94
+ minDaysUntilExpiry: aggregatedMinMax({
101
95
  "x-chart-type": "line",
102
96
  "x-chart-label": "Min Days Until Expiry",
103
97
  "x-chart-unit": "days",
104
98
  }),
105
- invalidCount: healthResultNumber({
99
+ invalidCount: aggregatedCounter({
106
100
  "x-chart-type": "counter",
107
101
  "x-chart-label": "Invalid Certificates",
108
102
  }),
109
- errorCount: healthResultNumber({
103
+ errorCount: aggregatedCounter({
110
104
  "x-chart-type": "counter",
111
105
  "x-chart-label": "Errors",
112
106
  }),
113
- });
114
-
115
- const tlsAggregatedInternalSchema = z.object({
116
- _daysUntilExpiry: averageStateSchema.optional(),
117
- _minDaysUntilExpiry: z
118
- .object({ min: z.number(), max: z.number() })
119
- .optional(),
120
- _invalid: counterStateSchema.optional(),
121
- _errors: counterStateSchema.optional(),
122
- });
123
-
124
- const tlsAggregatedSchema = tlsAggregatedDisplaySchema.merge(
125
- tlsAggregatedInternalSchema,
126
- );
107
+ };
127
108
 
128
- type TlsAggregatedResult = z.infer<typeof tlsAggregatedSchema>;
109
+ type TlsAggregatedResult = InferAggregatedResult<typeof tlsAggregatedFields>;
129
110
 
130
111
  // ============================================================================
131
112
  // TLS CLIENT INTERFACE (for testability)
@@ -197,7 +178,7 @@ export class TlsHealthCheckStrategy implements HealthCheckStrategy<
197
178
  TlsConfig,
198
179
  TlsTransportClient,
199
180
  TlsResult,
200
- TlsAggregatedResult
181
+ typeof tlsAggregatedFields
201
182
  > {
202
183
  id = "tls";
203
184
  displayName = "TLS/SSL Health Check";
@@ -235,9 +216,9 @@ export class TlsHealthCheckStrategy implements HealthCheckStrategy<
235
216
  ],
236
217
  });
237
218
 
238
- aggregatedResult: Versioned<TlsAggregatedResult> = new Versioned({
219
+ aggregatedResult = new VersionedAggregated({
239
220
  version: 1,
240
- schema: tlsAggregatedSchema,
221
+ fields: tlsAggregatedFields,
241
222
  });
242
223
 
243
224
  mergeResult(
@@ -246,36 +227,23 @@ export class TlsHealthCheckStrategy implements HealthCheckStrategy<
246
227
  ): TlsAggregatedResult {
247
228
  const metadata = run.metadata;
248
229
 
249
- const daysState = mergeAverage(
250
- existing?._daysUntilExpiry as AverageState | undefined,
230
+ const avgDaysUntilExpiry = mergeAverage(
231
+ existing?.avgDaysUntilExpiry,
251
232
  metadata?.daysUntilExpiry,
252
233
  );
253
234
 
254
- const minDaysState = mergeMinMax(
255
- existing?._minDaysUntilExpiry as MinMaxState | undefined,
235
+ const minDaysUntilExpiry = mergeMinMax(
236
+ existing?.minDaysUntilExpiry,
256
237
  metadata?.daysUntilExpiry,
257
238
  );
258
239
 
259
- const invalidState = mergeCounter(
260
- existing?._invalid as CounterState | undefined,
261
- metadata?.isValid === false,
262
- );
240
+ const isInvalid = metadata?.isValid === false;
241
+ const invalidCount = mergeCounter(existing?.invalidCount, isInvalid);
263
242
 
264
- const errorState = mergeCounter(
265
- existing?._errors as CounterState | undefined,
266
- metadata?.error !== undefined,
267
- );
243
+ const hasError = metadata?.error !== undefined;
244
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
268
245
 
269
- return {
270
- avgDaysUntilExpiry: daysState.avg,
271
- minDaysUntilExpiry: minDaysState.min,
272
- invalidCount: invalidState.count,
273
- errorCount: errorState.count,
274
- _daysUntilExpiry: daysState,
275
- _minDaysUntilExpiry: minDaysState,
276
- _invalid: invalidState,
277
- _errors: errorState,
278
- };
246
+ return { avgDaysUntilExpiry, minDaysUntilExpiry, invalidCount, errorCount };
279
247
  }
280
248
 
281
249
  async createClient(