@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-grpc-backend",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -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
 
@@ -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,
@@ -55,31 +55,24 @@ const grpcHealthResultSchema = healthResultSchema({
55
55
 
56
56
  export type HealthResult = z.infer<typeof grpcHealthResultSchema>;
57
57
 
58
- const healthAggregatedDisplaySchema = healthResultSchema({
59
- avgResponseTimeMs: healthResultNumber({
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: healthResultNumber({
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
- const healthAggregatedSchema = healthAggregatedDisplaySchema.merge(
79
- healthAggregatedInternalSchema,
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 Versioned({
101
+ aggregatedResult = new VersionedAggregated({
109
102
  version: 1,
110
- schema: healthAggregatedSchema,
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: responseTimeState.avg,
159
- servingRate: servingState.rate,
160
- _responseTime: responseTimeState,
161
- _serving: servingState,
141
+ avgResponseTimeMs: mergeAverage(
142
+ existing?.avgResponseTimeMs,
143
+ metadata?.responseTimeMs,
144
+ ),
145
+ servingRate: mergeRate(existing?.servingRate, metadata?.serving),
162
146
  };
163
147
  }
164
148
  }
@@ -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
- z,
7
- type ConnectedClient,
6
+ VersionedAggregated,
7
+ aggregatedAverage,
8
+ aggregatedRate,
9
+ aggregatedCounter,
8
10
  mergeAverage,
9
- averageStateSchema,
10
11
  mergeRate,
11
- rateStateSchema,
12
12
  mergeCounter,
13
- counterStateSchema,
14
- type AverageState,
15
- type RateState,
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
- * Aggregated metadata for buckets.
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: healthResultNumber({
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: healthResultNumber({
103
+ errorCount: aggregatedCounter({
107
104
  "x-chart-type": "counter",
108
105
  "x-chart-label": "Errors",
109
106
  }),
110
- servingCount: healthResultNumber({
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 = z.infer<typeof grpcAggregatedSchema>;
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
- GrpcAggregatedResult
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: Versioned<GrpcAggregatedResult> = new Versioned({
238
+ aggregatedResult = new VersionedAggregated({
255
239
  version: 1,
256
- schema: grpcAggregatedSchema,
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 responseTimeState = mergeAverage(
266
- existing?._responseTime as AverageState | undefined,
249
+ const avgResponseTime = mergeAverage(
250
+ existing?.avgResponseTime,
267
251
  metadata?.responseTimeMs,
268
252
  );
269
253
 
270
- const successState = mergeRate(
271
- existing?._success as RateState | undefined,
272
- metadata?.status === "SERVING",
273
- );
254
+ const isSuccess = metadata?.status === "SERVING";
255
+ const successRate = mergeRate(existing?.successRate, isSuccess);
274
256
 
275
- const errorState = mergeCounter(
276
- existing?._errors as CounterState | undefined,
277
- metadata?.error !== undefined,
278
- );
257
+ const hasError = metadata?.error !== undefined;
258
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
279
259
 
280
- const servingState = mergeCounter(
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(