@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 +48 -0
- package/package.json +4 -4
- package/src/certificate-collector.test.ts +3 -3
- package/src/certificate-collector.ts +19 -37
- package/src/strategy.test.ts +6 -6
- package/src/strategy.ts +29 -61
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
|
|
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.
|
|
13
|
-
"@checkstack/common": "0.6.
|
|
14
|
-
"@checkstack/healthcheck-common": "0.8.
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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:
|
|
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
|
-
|
|
88
|
-
|
|
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
|
|
110
|
+
aggregatedResult = new VersionedAggregated({
|
|
120
111
|
version: 1,
|
|
121
|
-
|
|
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:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
157
|
+
avgDaysRemaining: mergeAverage(
|
|
158
|
+
existing?.avgDaysRemaining,
|
|
159
|
+
metadata?.daysRemaining,
|
|
160
|
+
),
|
|
161
|
+
validRate: mergeRate(existing?.validRate, metadata?.valid),
|
|
180
162
|
};
|
|
181
163
|
}
|
|
182
164
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
6
|
+
VersionedAggregated,
|
|
7
|
+
aggregatedAverage,
|
|
8
|
+
aggregatedMinMax,
|
|
9
|
+
aggregatedCounter,
|
|
8
10
|
mergeAverage,
|
|
9
|
-
averageStateSchema,
|
|
10
11
|
mergeCounter,
|
|
11
|
-
counterStateSchema,
|
|
12
12
|
mergeMinMax,
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
type
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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:
|
|
99
|
+
invalidCount: aggregatedCounter({
|
|
106
100
|
"x-chart-type": "counter",
|
|
107
101
|
"x-chart-label": "Invalid Certificates",
|
|
108
102
|
}),
|
|
109
|
-
errorCount:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
219
|
+
aggregatedResult = new VersionedAggregated({
|
|
239
220
|
version: 1,
|
|
240
|
-
|
|
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
|
|
250
|
-
existing?.
|
|
230
|
+
const avgDaysUntilExpiry = mergeAverage(
|
|
231
|
+
existing?.avgDaysUntilExpiry,
|
|
251
232
|
metadata?.daysUntilExpiry,
|
|
252
233
|
);
|
|
253
234
|
|
|
254
|
-
const
|
|
255
|
-
existing?.
|
|
235
|
+
const minDaysUntilExpiry = mergeMinMax(
|
|
236
|
+
existing?.minDaysUntilExpiry,
|
|
256
237
|
metadata?.daysUntilExpiry,
|
|
257
238
|
);
|
|
258
239
|
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
metadata?.isValid === false,
|
|
262
|
-
);
|
|
240
|
+
const isInvalid = metadata?.isValid === false;
|
|
241
|
+
const invalidCount = mergeCounter(existing?.invalidCount, isInvalid);
|
|
263
242
|
|
|
264
|
-
const
|
|
265
|
-
|
|
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(
|