@checkstack/healthcheck-http-backend 0.2.5 → 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 +48 -0
- package/package.json +4 -4
- package/src/request-collector.test.ts +3 -3
- package/src/request-collector.ts +20 -31
- package/src/strategy.test.ts +2 -2
- package/src/strategy.ts +20 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @checkstack/healthcheck-http-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.5
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-http-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/backend-api": "0.
|
|
13
|
-
"@checkstack/healthcheck-common": "0.8.
|
|
12
|
+
"@checkstack/backend-api": "0.7.0",
|
|
13
|
+
"@checkstack/healthcheck-common": "0.8.2",
|
|
14
14
|
"jsonpath-plus": "^10.3.0",
|
|
15
|
-
"@checkstack/common": "0.6.
|
|
15
|
+
"@checkstack/common": "0.6.2"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/bun": "^1.0.0",
|
|
@@ -156,8 +156,8 @@ describe("RequestCollector", () => {
|
|
|
156
156
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
157
157
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
158
158
|
|
|
159
|
-
expect(aggregated.avgResponseTimeMs).toBe(75);
|
|
160
|
-
expect(aggregated.successRate).toBe(100);
|
|
159
|
+
expect(aggregated.avgResponseTimeMs.avg).toBe(75);
|
|
160
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
it("should calculate success rate correctly", () => {
|
|
@@ -199,7 +199,7 @@ describe("RequestCollector", () => {
|
|
|
199
199
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
200
200
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
201
201
|
|
|
202
|
-
expect(aggregated.successRate).toBe(50);
|
|
202
|
+
expect(aggregated.successRate.rate).toBe(50);
|
|
203
203
|
});
|
|
204
204
|
});
|
|
205
205
|
|
package/src/request-collector.ts
CHANGED
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
type CollectorStrategy,
|
|
8
8
|
mergeAverage,
|
|
9
9
|
mergeRate,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
VersionedAggregated,
|
|
11
|
+
aggregatedAverage,
|
|
12
|
+
aggregatedRate,
|
|
13
|
+
type InferAggregatedResult,
|
|
12
14
|
} from "@checkstack/backend-api";
|
|
13
15
|
import {
|
|
14
16
|
healthResultNumber,
|
|
@@ -80,32 +82,24 @@ const requestResultSchema = healthResultSchema({
|
|
|
80
82
|
|
|
81
83
|
export type RequestResult = z.infer<typeof requestResultSchema>;
|
|
82
84
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
avgResponseTimeMs:
|
|
85
|
+
// Aggregated result fields definition
|
|
86
|
+
const requestAggregatedFields = {
|
|
87
|
+
avgResponseTimeMs: aggregatedAverage({
|
|
86
88
|
"x-chart-type": "line",
|
|
87
89
|
"x-chart-label": "Avg Response Time",
|
|
88
90
|
"x-chart-unit": "ms",
|
|
89
91
|
}),
|
|
90
|
-
successRate:
|
|
92
|
+
successRate: aggregatedRate({
|
|
91
93
|
"x-chart-type": "gauge",
|
|
92
94
|
"x-chart-label": "Success Rate",
|
|
93
95
|
"x-chart-unit": "%",
|
|
94
96
|
}),
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Internal state for incremental aggregation (not shown in charts)
|
|
98
|
-
const requestAggregatedInternalSchema = z.object({
|
|
99
|
-
_responseTime: averageStateSchema.optional(),
|
|
100
|
-
_success: rateStateSchema.optional(),
|
|
101
|
-
});
|
|
97
|
+
};
|
|
102
98
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
export type RequestAggregatedResult = z.infer<typeof requestAggregatedSchema>;
|
|
99
|
+
// Type inferred automatically from field definitions
|
|
100
|
+
export type RequestAggregatedResult = InferAggregatedResult<
|
|
101
|
+
typeof requestAggregatedFields
|
|
102
|
+
>;
|
|
109
103
|
|
|
110
104
|
// ============================================================================
|
|
111
105
|
// REQUEST COLLECTOR
|
|
@@ -131,9 +125,9 @@ export class RequestCollector implements CollectorStrategy<
|
|
|
131
125
|
|
|
132
126
|
config = new Versioned({ version: 1, schema: requestConfigSchema });
|
|
133
127
|
result = new Versioned({ version: 1, schema: requestResultSchema });
|
|
134
|
-
aggregatedResult = new
|
|
128
|
+
aggregatedResult = new VersionedAggregated({
|
|
135
129
|
version: 1,
|
|
136
|
-
|
|
130
|
+
fields: requestAggregatedFields,
|
|
137
131
|
});
|
|
138
132
|
|
|
139
133
|
async execute({
|
|
@@ -182,17 +176,12 @@ export class RequestCollector implements CollectorStrategy<
|
|
|
182
176
|
existing: RequestAggregatedResult | undefined,
|
|
183
177
|
newRun: HealthCheckRunForAggregation<RequestResult>,
|
|
184
178
|
): RequestAggregatedResult {
|
|
185
|
-
const responseTime = mergeAverage(
|
|
186
|
-
existing?._responseTime,
|
|
187
|
-
newRun.metadata?.responseTimeMs,
|
|
188
|
-
);
|
|
189
|
-
const success = mergeRate(existing?._success, newRun.metadata?.success);
|
|
190
|
-
|
|
191
179
|
return {
|
|
192
|
-
avgResponseTimeMs:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
180
|
+
avgResponseTimeMs: mergeAverage(
|
|
181
|
+
existing?.avgResponseTimeMs,
|
|
182
|
+
newRun.metadata?.responseTimeMs,
|
|
183
|
+
),
|
|
184
|
+
successRate: mergeRate(existing?.successRate, newRun.metadata?.success),
|
|
196
185
|
};
|
|
197
186
|
}
|
|
198
187
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -218,7 +218,7 @@ describe("HttpHealthCheckStrategy", () => {
|
|
|
218
218
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
219
219
|
aggregated = strategy.mergeResult(aggregated, runs[2]);
|
|
220
220
|
|
|
221
|
-
expect(aggregated.errorCount).toBe(2);
|
|
221
|
+
expect(aggregated.errorCount.count).toBe(2);
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
it("should return zero errors when all runs succeed", () => {
|
|
@@ -245,7 +245,7 @@ describe("HttpHealthCheckStrategy", () => {
|
|
|
245
245
|
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
246
246
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
247
247
|
|
|
248
|
-
expect(aggregated.errorCount).toBe(0);
|
|
248
|
+
expect(aggregated.errorCount.count).toBe(0);
|
|
249
249
|
});
|
|
250
250
|
});
|
|
251
251
|
});
|
package/src/strategy.ts
CHANGED
|
@@ -2,12 +2,15 @@ import {
|
|
|
2
2
|
HealthCheckStrategy,
|
|
3
3
|
HealthCheckRunForAggregation,
|
|
4
4
|
Versioned,
|
|
5
|
+
VersionedAggregated,
|
|
6
|
+
aggregatedCounter,
|
|
7
|
+
mergeCounter,
|
|
5
8
|
z,
|
|
9
|
+
type InferAggregatedResult,
|
|
6
10
|
type ConnectedClient,
|
|
7
|
-
|
|
11
|
+
baseStrategyConfigSchema,
|
|
8
12
|
} from "@checkstack/backend-api";
|
|
9
13
|
import {
|
|
10
|
-
healthResultNumber,
|
|
11
14
|
healthResultString,
|
|
12
15
|
healthResultSchema,
|
|
13
16
|
} from "@checkstack/healthcheck-common";
|
|
@@ -25,14 +28,7 @@ import type {
|
|
|
25
28
|
* HTTP health check configuration schema.
|
|
26
29
|
* Global defaults only - action params moved to RequestCollector.
|
|
27
30
|
*/
|
|
28
|
-
export const httpHealthCheckConfigSchema =
|
|
29
|
-
timeout: z
|
|
30
|
-
.number()
|
|
31
|
-
.int()
|
|
32
|
-
.min(100)
|
|
33
|
-
.default(30_000)
|
|
34
|
-
.describe("Default request timeout in milliseconds"),
|
|
35
|
-
});
|
|
31
|
+
export const httpHealthCheckConfigSchema = baseStrategyConfigSchema.extend({});
|
|
36
32
|
|
|
37
33
|
export type HttpHealthCheckConfig = z.infer<typeof httpHealthCheckConfigSchema>;
|
|
38
34
|
|
|
@@ -58,15 +54,15 @@ const httpResultMetadataSchema = healthResultSchema({
|
|
|
58
54
|
|
|
59
55
|
type HttpResultMetadata = z.infer<typeof httpResultMetadataSchema>;
|
|
60
56
|
|
|
61
|
-
/** Aggregated
|
|
62
|
-
const
|
|
63
|
-
errorCount:
|
|
57
|
+
/** Aggregated field definitions for bucket merging */
|
|
58
|
+
const httpAggregatedFields = {
|
|
59
|
+
errorCount: aggregatedCounter({
|
|
64
60
|
"x-chart-type": "counter",
|
|
65
61
|
"x-chart-label": "Errors",
|
|
66
62
|
}),
|
|
67
|
-
}
|
|
63
|
+
};
|
|
68
64
|
|
|
69
|
-
type
|
|
65
|
+
type HttpAggregatedResult = InferAggregatedResult<typeof httpAggregatedFields>;
|
|
70
66
|
|
|
71
67
|
// ============================================================================
|
|
72
68
|
// STRATEGY
|
|
@@ -76,7 +72,7 @@ export class HttpHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
76
72
|
HttpHealthCheckConfig,
|
|
77
73
|
HttpTransportClient,
|
|
78
74
|
HttpResultMetadata,
|
|
79
|
-
|
|
75
|
+
typeof httpAggregatedFields
|
|
80
76
|
> {
|
|
81
77
|
id = "http";
|
|
82
78
|
displayName = "HTTP/HTTPS Health Check";
|
|
@@ -112,21 +108,18 @@ export class HttpHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
112
108
|
schema: httpResultMetadataSchema,
|
|
113
109
|
});
|
|
114
110
|
|
|
115
|
-
aggregatedResult
|
|
111
|
+
aggregatedResult = new VersionedAggregated({
|
|
116
112
|
version: 1,
|
|
117
|
-
|
|
113
|
+
fields: httpAggregatedFields,
|
|
118
114
|
});
|
|
119
115
|
|
|
120
116
|
mergeResult(
|
|
121
|
-
existing:
|
|
117
|
+
existing: HttpAggregatedResult | undefined,
|
|
122
118
|
newRun: HealthCheckRunForAggregation<HttpResultMetadata>,
|
|
123
|
-
):
|
|
119
|
+
): HttpAggregatedResult {
|
|
124
120
|
const hasError = !!newRun.metadata?.error;
|
|
125
121
|
return {
|
|
126
|
-
errorCount: mergeCounter(
|
|
127
|
-
existing ? { count: existing.errorCount } : undefined,
|
|
128
|
-
hasError,
|
|
129
|
-
).count,
|
|
122
|
+
errorCount: mergeCounter(existing?.errorCount, hasError),
|
|
130
123
|
};
|
|
131
124
|
}
|
|
132
125
|
|
|
@@ -155,9 +148,10 @@ export class HttpHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
155
148
|
signal: controller.signal,
|
|
156
149
|
});
|
|
157
150
|
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
// Read body BEFORE clearing timeout - body streaming can also hang
|
|
160
152
|
const body = await response.text();
|
|
153
|
+
|
|
154
|
+
clearTimeout(timeoutId);
|
|
161
155
|
const headers: Record<string, string> = {};
|
|
162
156
|
|
|
163
157
|
// eslint-disable-next-line unicorn/no-array-for-each
|