@checkstack/healthcheck-grpc-backend 0.1.14 → 0.2.0
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 +24 -0
- package/package.json +1 -1
- 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 +26 -58
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @checkstack/healthcheck-grpc-backend
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3dd1914: Migrate health check strategies to VersionedAggregated with \_type discriminator
|
|
8
|
+
|
|
9
|
+
All 13 health check strategies now use `VersionedAggregated` for their `aggregatedResult` property, enabling automatic bucket merging with 100% mathematical fidelity.
|
|
10
|
+
|
|
11
|
+
**Key changes:**
|
|
12
|
+
|
|
13
|
+
- **`_type` discriminator**: All aggregated state objects now include a required `_type` field (`"average"`, `"rate"`, `"counter"`, `"minmax"`) for reliable type detection
|
|
14
|
+
- The `HealthCheckStrategy` interface now requires `aggregatedResult` to be a `VersionedAggregated<AggregatedResultShape>`
|
|
15
|
+
- Strategy/collector `mergeResult` methods return state objects with `_type` (e.g., `{ _type: "average", _sum, _count, avg }`)
|
|
16
|
+
- `mergeAggregatedBucketResults`, `combineBuckets`, and `reaggregateBuckets` now require `registry` and `strategyId` parameters
|
|
17
|
+
- `HealthCheckService` constructor now requires both `registry` and `collectorRegistry` parameters
|
|
18
|
+
- Frontend `extractComputedValue` now uses `_type` discriminator for robust type detection
|
|
19
|
+
|
|
20
|
+
**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.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [3dd1914]
|
|
25
|
+
- @checkstack/backend-api@0.7.0
|
|
26
|
+
|
|
3
27
|
## 0.1.14
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -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,16 @@ 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
|
-
type CounterState,
|
|
13
|
+
z,
|
|
14
|
+
type ConnectedClient,
|
|
15
|
+
type InferAggregatedResult,
|
|
17
16
|
} from "@checkstack/backend-api";
|
|
18
17
|
import {
|
|
19
18
|
healthResultBoolean,
|
|
@@ -89,44 +88,29 @@ const grpcResultSchema = healthResultSchema({
|
|
|
89
88
|
|
|
90
89
|
type GrpcResult = z.infer<typeof grpcResultSchema>;
|
|
91
90
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const grpcAggregatedDisplaySchema = healthResultSchema({
|
|
96
|
-
avgResponseTime: healthResultNumber({
|
|
91
|
+
/** Aggregated field definitions for bucket merging */
|
|
92
|
+
const grpcAggregatedFields = {
|
|
93
|
+
avgResponseTime: aggregatedAverage({
|
|
97
94
|
"x-chart-type": "line",
|
|
98
95
|
"x-chart-label": "Avg Response Time",
|
|
99
96
|
"x-chart-unit": "ms",
|
|
100
97
|
}),
|
|
101
|
-
successRate:
|
|
98
|
+
successRate: aggregatedRate({
|
|
102
99
|
"x-chart-type": "gauge",
|
|
103
100
|
"x-chart-label": "Success Rate",
|
|
104
101
|
"x-chart-unit": "%",
|
|
105
102
|
}),
|
|
106
|
-
errorCount:
|
|
103
|
+
errorCount: aggregatedCounter({
|
|
107
104
|
"x-chart-type": "counter",
|
|
108
105
|
"x-chart-label": "Errors",
|
|
109
106
|
}),
|
|
110
|
-
servingCount:
|
|
107
|
+
servingCount: aggregatedCounter({
|
|
111
108
|
"x-chart-type": "counter",
|
|
112
109
|
"x-chart-label": "Serving",
|
|
113
110
|
}),
|
|
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
|
-
);
|
|
111
|
+
};
|
|
128
112
|
|
|
129
|
-
type GrpcAggregatedResult =
|
|
113
|
+
type GrpcAggregatedResult = InferAggregatedResult<typeof grpcAggregatedFields>;
|
|
130
114
|
|
|
131
115
|
// ============================================================================
|
|
132
116
|
// GRPC CLIENT INTERFACE (for testability)
|
|
@@ -212,7 +196,7 @@ export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
212
196
|
GrpcConfig,
|
|
213
197
|
GrpcTransportClient,
|
|
214
198
|
GrpcResult,
|
|
215
|
-
|
|
199
|
+
typeof grpcAggregatedFields
|
|
216
200
|
> {
|
|
217
201
|
id = "grpc";
|
|
218
202
|
displayName = "gRPC Health Check";
|
|
@@ -251,9 +235,9 @@ export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
251
235
|
],
|
|
252
236
|
});
|
|
253
237
|
|
|
254
|
-
aggregatedResult
|
|
238
|
+
aggregatedResult = new VersionedAggregated({
|
|
255
239
|
version: 1,
|
|
256
|
-
|
|
240
|
+
fields: grpcAggregatedFields,
|
|
257
241
|
});
|
|
258
242
|
|
|
259
243
|
mergeResult(
|
|
@@ -262,36 +246,20 @@ export class GrpcHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
262
246
|
): GrpcAggregatedResult {
|
|
263
247
|
const metadata = run.metadata;
|
|
264
248
|
|
|
265
|
-
const
|
|
266
|
-
existing?.
|
|
249
|
+
const avgResponseTime = mergeAverage(
|
|
250
|
+
existing?.avgResponseTime,
|
|
267
251
|
metadata?.responseTimeMs,
|
|
268
252
|
);
|
|
269
253
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
metadata?.status === "SERVING",
|
|
273
|
-
);
|
|
254
|
+
const isSuccess = metadata?.status === "SERVING";
|
|
255
|
+
const successRate = mergeRate(existing?.successRate, isSuccess);
|
|
274
256
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
metadata?.error !== undefined,
|
|
278
|
-
);
|
|
257
|
+
const hasError = metadata?.error !== undefined;
|
|
258
|
+
const errorCount = mergeCounter(existing?.errorCount, hasError);
|
|
279
259
|
|
|
280
|
-
const
|
|
281
|
-
existing?._serving as CounterState | undefined,
|
|
282
|
-
metadata?.status === "SERVING",
|
|
283
|
-
);
|
|
260
|
+
const servingCount = mergeCounter(existing?.servingCount, isSuccess);
|
|
284
261
|
|
|
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
|
-
};
|
|
262
|
+
return { avgResponseTime, successRate, errorCount, servingCount };
|
|
295
263
|
}
|
|
296
264
|
|
|
297
265
|
async createClient(
|