@checkstack/healthcheck-grpc-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/health-collector.test.ts +3 -3
- package/src/health-collector.ts +20 -36
- package/src/strategy.test.ts +7 -7
- package/src/strategy.ts +28 -64
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @checkstack/healthcheck-grpc-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-grpc-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
|
"@grpc/grpc-js": "^1.9.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -90,8 +90,8 @@ describe("HealthCollector", () => {
|
|
|
90
90
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
91
91
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
92
92
|
|
|
93
|
-
expect(aggregated.avgResponseTimeMs).toBe(75);
|
|
94
|
-
expect(aggregated.servingRate).toBe(100);
|
|
93
|
+
expect(aggregated.avgResponseTimeMs.avg).toBe(75);
|
|
94
|
+
expect(aggregated.servingRate.rate).toBe(100);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
it("should calculate serving rate correctly", () => {
|
|
@@ -122,7 +122,7 @@ describe("HealthCollector", () => {
|
|
|
122
122
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
123
123
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
124
124
|
|
|
125
|
-
expect(aggregated.servingRate).toBe(50);
|
|
125
|
+
expect(aggregated.servingRate.rate).toBe(50);
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
128
|
|
package/src/health-collector.ts
CHANGED
|
@@ -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,
|
|
@@ -55,31 +55,24 @@ const grpcHealthResultSchema = healthResultSchema({
|
|
|
55
55
|
|
|
56
56
|
export type HealthResult = z.infer<typeof grpcHealthResultSchema>;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// Aggregated result fields definition
|
|
59
|
+
const healthAggregatedFields = {
|
|
60
|
+
avgResponseTimeMs: aggregatedAverage({
|
|
60
61
|
"x-chart-type": "line",
|
|
61
62
|
"x-chart-label": "Avg Response Time",
|
|
62
63
|
"x-chart-unit": "ms",
|
|
63
64
|
}),
|
|
64
|
-
servingRate:
|
|
65
|
+
servingRate: aggregatedRate({
|
|
65
66
|
"x-chart-type": "gauge",
|
|
66
67
|
"x-chart-label": "Serving Rate",
|
|
67
68
|
"x-chart-unit": "%",
|
|
68
69
|
}),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const healthAggregatedInternalSchema = z.object({
|
|
72
|
-
_responseTime: averageStateSchema
|
|
73
|
-
.optional(),
|
|
74
|
-
_serving: rateStateSchema
|
|
75
|
-
.optional(),
|
|
76
|
-
});
|
|
70
|
+
};
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
export type HealthAggregatedResult = z.infer<typeof healthAggregatedSchema>;
|
|
72
|
+
// Type inferred from field definitions
|
|
73
|
+
export type HealthAggregatedResult = InferAggregatedResult<
|
|
74
|
+
typeof healthAggregatedFields
|
|
75
|
+
>;
|
|
83
76
|
|
|
84
77
|
// ============================================================================
|
|
85
78
|
// HEALTH COLLECTOR
|
|
@@ -105,9 +98,9 @@ export class HealthCollector implements CollectorStrategy<
|
|
|
105
98
|
|
|
106
99
|
config = new Versioned({ version: 1, schema: healthConfigSchema });
|
|
107
100
|
result = new Versioned({ version: 1, schema: grpcHealthResultSchema });
|
|
108
|
-
aggregatedResult = new
|
|
101
|
+
aggregatedResult = new VersionedAggregated({
|
|
109
102
|
version: 1,
|
|
110
|
-
|
|
103
|
+
fields: healthAggregatedFields,
|
|
111
104
|
});
|
|
112
105
|
|
|
113
106
|
async execute({
|
|
@@ -144,21 +137,12 @@ export class HealthCollector implements CollectorStrategy<
|
|
|
144
137
|
): HealthAggregatedResult {
|
|
145
138
|
const metadata = run.metadata;
|
|
146
139
|
|
|
147
|
-
const responseTimeState = mergeAverage(
|
|
148
|
-
existing?._responseTime as AverageState | undefined,
|
|
149
|
-
metadata?.responseTimeMs,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const servingState = mergeRate(
|
|
153
|
-
existing?._serving as RateState | undefined,
|
|
154
|
-
metadata?.serving,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
140
|
return {
|
|
158
|
-
avgResponseTimeMs:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
141
|
+
avgResponseTimeMs: mergeAverage(
|
|
142
|
+
existing?.avgResponseTimeMs,
|
|
143
|
+
metadata?.responseTimeMs,
|
|
144
|
+
),
|
|
145
|
+
servingRate: mergeRate(existing?.servingRate, metadata?.serving),
|
|
162
146
|
};
|
|
163
147
|
}
|
|
164
148
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -144,10 +144,10 @@ describe("GrpcHealthCheckStrategy", () => {
|
|
|
144
144
|
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
145
145
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
146
146
|
|
|
147
|
-
expect(aggregated.avgResponseTime).toBe(10);
|
|
148
|
-
expect(aggregated.successRate).toBe(100);
|
|
149
|
-
expect(aggregated.servingCount).toBe(2);
|
|
150
|
-
expect(aggregated.errorCount).toBe(0);
|
|
147
|
+
expect(aggregated.avgResponseTime.avg).toBe(10);
|
|
148
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
149
|
+
expect(aggregated.servingCount.count).toBe(2);
|
|
150
|
+
expect(aggregated.errorCount.count).toBe(0);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
it("should count errors and non-serving", () => {
|
|
@@ -183,9 +183,9 @@ describe("GrpcHealthCheckStrategy", () => {
|
|
|
183
183
|
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
184
184
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
185
185
|
|
|
186
|
-
expect(aggregated.errorCount).toBe(1);
|
|
187
|
-
expect(aggregated.servingCount).toBe(0);
|
|
188
|
-
expect(aggregated.successRate).toBe(0);
|
|
186
|
+
expect(aggregated.errorCount.count).toBe(1);
|
|
187
|
+
expect(aggregated.servingCount.count).toBe(0);
|
|
188
|
+
expect(aggregated.successRate.rate).toBe(0);
|
|
189
189
|
});
|
|
190
190
|
});
|
|
191
191
|
});
|
package/src/strategy.ts
CHANGED
|
@@ -3,17 +3,17 @@ 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
|
-
|
|
13
|
+
z,
|
|
14
|
+
type ConnectedClient,
|
|
15
|
+
type InferAggregatedResult,
|
|
16
|
+
baseStrategyConfigSchema,
|
|
17
17
|
} from "@checkstack/backend-api";
|
|
18
18
|
import {
|
|
19
19
|
healthResultBoolean,
|
|
@@ -46,7 +46,7 @@ export type GrpcHealthStatusType = z.infer<typeof GrpcHealthStatus>;
|
|
|
46
46
|
/**
|
|
47
47
|
* Configuration schema for gRPC health checks.
|
|
48
48
|
*/
|
|
49
|
-
export const grpcConfigSchema =
|
|
49
|
+
export const grpcConfigSchema = baseStrategyConfigSchema.extend({
|
|
50
50
|
host: z.string().describe("gRPC server hostname"),
|
|
51
51
|
port: z.number().int().min(1).max(65_535).describe("gRPC port"),
|
|
52
52
|
service: z
|
|
@@ -54,11 +54,6 @@ export const grpcConfigSchema = z.object({
|
|
|
54
54
|
.default("")
|
|
55
55
|
.describe("Service name to check (empty for server health)"),
|
|
56
56
|
useTls: z.boolean().default(false).describe("Use TLS connection"),
|
|
57
|
-
timeout: z
|
|
58
|
-
.number()
|
|
59
|
-
.min(100)
|
|
60
|
-
.default(5000)
|
|
61
|
-
.describe("Request timeout in milliseconds"),
|
|
62
57
|
});
|
|
63
58
|
|
|
64
59
|
export type GrpcConfig = z.infer<typeof grpcConfigSchema>;
|
|
@@ -89,44 +84,29 @@ const grpcResultSchema = healthResultSchema({
|
|
|
89
84
|
|
|
90
85
|
type GrpcResult = z.infer<typeof grpcResultSchema>;
|
|
91
86
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const grpcAggregatedDisplaySchema = healthResultSchema({
|
|
96
|
-
avgResponseTime: healthResultNumber({
|
|
87
|
+
/** Aggregated field definitions for bucket merging */
|
|
88
|
+
const grpcAggregatedFields = {
|
|
89
|
+
avgResponseTime: aggregatedAverage({
|
|
97
90
|
"x-chart-type": "line",
|
|
98
91
|
"x-chart-label": "Avg Response Time",
|
|
99
92
|
"x-chart-unit": "ms",
|
|
100
93
|
}),
|
|
101
|
-
successRate:
|
|
94
|
+
successRate: aggregatedRate({
|
|
102
95
|
"x-chart-type": "gauge",
|
|
103
96
|
"x-chart-label": "Success Rate",
|
|
104
97
|
"x-chart-unit": "%",
|
|
105
98
|
}),
|
|
106
|
-
errorCount:
|
|
99
|
+
errorCount: aggregatedCounter({
|
|
107
100
|
"x-chart-type": "counter",
|
|
108
101
|
"x-chart-label": "Errors",
|
|
109
102
|
}),
|
|
110
|
-
servingCount:
|
|
103
|
+
servingCount: aggregatedCounter({
|
|
111
104
|
"x-chart-type": "counter",
|
|
112
105
|
"x-chart-label": "Serving",
|
|
113
106
|
}),
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const grpcAggregatedInternalSchema = z.object({
|
|
117
|
-
_responseTime: averageStateSchema
|
|
118
|
-
.optional(),
|
|
119
|
-
_success: rateStateSchema
|
|
120
|
-
.optional(),
|
|
121
|
-
_errors: counterStateSchema.optional(),
|
|
122
|
-
_serving: counterStateSchema.optional(),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const grpcAggregatedSchema = grpcAggregatedDisplaySchema.merge(
|
|
126
|
-
grpcAggregatedInternalSchema,
|
|
127
|
-
);
|
|
107
|
+
};
|
|
128
108
|
|
|
129
|
-
type GrpcAggregatedResult =
|
|
109
|
+
type GrpcAggregatedResult = InferAggregatedResult<typeof grpcAggregatedFields>;
|
|
130
110
|
|
|
131
111
|
// ============================================================================
|
|
132
112
|
// GRPC CLIENT INTERFACE (for testability)
|
|
@@ -212,7 +192,7 @@ export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
212
192
|
GrpcConfig,
|
|
213
193
|
GrpcTransportClient,
|
|
214
194
|
GrpcResult,
|
|
215
|
-
|
|
195
|
+
typeof grpcAggregatedFields
|
|
216
196
|
> {
|
|
217
197
|
id = "grpc";
|
|
218
198
|
displayName = "gRPC Health Check";
|
|
@@ -251,9 +231,9 @@ export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
251
231
|
],
|
|
252
232
|
});
|
|
253
233
|
|
|
254
|
-
aggregatedResult
|
|
234
|
+
aggregatedResult = new VersionedAggregated({
|
|
255
235
|
version: 1,
|
|
256
|
-
|
|
236
|
+
fields: grpcAggregatedFields,
|
|
257
237
|
});
|
|
258
238
|
|
|
259
239
|
mergeResult(
|
|
@@ -262,36 +242,20 @@ export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
262
242
|
): GrpcAggregatedResult {
|
|
263
243
|
const metadata = run.metadata;
|
|
264
244
|
|
|
265
|
-
const
|
|
266
|
-
existing?.
|
|
245
|
+
const avgResponseTime = mergeAverage(
|
|
246
|
+
existing?.avgResponseTime,
|
|
267
247
|
metadata?.responseTimeMs,
|
|
268
248
|
);
|
|
269
249
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
metadata?.status === "SERVING",
|
|
273
|
-
);
|
|
250
|
+
const isSuccess = metadata?.status === "SERVING";
|
|
251
|
+
const successRate = mergeRate(existing?.successRate, isSuccess);
|
|
274
252
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
metadata?.error !== undefined,
|
|
278
|
-
);
|
|
253
|
+
const hasError = metadata?.error !== undefined;
|
|
254
|
+
const errorCount = mergeCounter(existing?.errorCount, hasError);
|
|
279
255
|
|
|
280
|
-
const
|
|
281
|
-
existing?._serving as CounterState | undefined,
|
|
282
|
-
metadata?.status === "SERVING",
|
|
283
|
-
);
|
|
256
|
+
const servingCount = mergeCounter(existing?.servingCount, isSuccess);
|
|
284
257
|
|
|
285
|
-
return {
|
|
286
|
-
avgResponseTime: responseTimeState.avg,
|
|
287
|
-
successRate: successState.rate,
|
|
288
|
-
errorCount: errorState.count,
|
|
289
|
-
servingCount: servingState.count,
|
|
290
|
-
_responseTime: responseTimeState,
|
|
291
|
-
_success: successState,
|
|
292
|
-
_errors: errorState,
|
|
293
|
-
_serving: servingState,
|
|
294
|
-
};
|
|
258
|
+
return { avgResponseTime, successRate, errorCount, servingCount };
|
|
295
259
|
}
|
|
296
260
|
|
|
297
261
|
async createClient(
|