@checkstack/healthcheck-tcp-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-tcp-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-tcp-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("BannerCollector", () => {
90
90
  let aggregated = collector.mergeResult(undefined, runs[0]);
91
91
  aggregated = collector.mergeResult(aggregated, runs[1]);
92
92
 
93
- expect(aggregated.avgReadTimeMs).toBe(75);
94
- expect(aggregated.bannerRate).toBe(50);
93
+ expect(aggregated.avgReadTimeMs.avg).toBe(75);
94
+ expect(aggregated.bannerRate.rate).toBe(50);
95
95
  });
96
96
  });
97
97
 
@@ -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,
@@ -56,31 +56,24 @@ const bannerResultSchema = healthResultSchema({
56
56
 
57
57
  export type BannerResult = z.infer<typeof bannerResultSchema>;
58
58
 
59
- const bannerAggregatedDisplaySchema = healthResultSchema({
60
- avgReadTimeMs: healthResultNumber({
59
+ // Aggregated result fields definition
60
+ const bannerAggregatedFields = {
61
+ avgReadTimeMs: aggregatedAverage({
61
62
  "x-chart-type": "line",
62
63
  "x-chart-label": "Avg Read Time",
63
64
  "x-chart-unit": "ms",
64
65
  }),
65
- bannerRate: healthResultNumber({
66
+ bannerRate: aggregatedRate({
66
67
  "x-chart-type": "gauge",
67
68
  "x-chart-label": "Banner Rate",
68
69
  "x-chart-unit": "%",
69
70
  }),
70
- });
71
-
72
- const bannerAggregatedInternalSchema = z.object({
73
- _readTime: averageStateSchema
74
- .optional(),
75
- _banner: rateStateSchema
76
- .optional(),
77
- });
71
+ };
78
72
 
79
- const bannerAggregatedSchema = bannerAggregatedDisplaySchema.merge(
80
- bannerAggregatedInternalSchema,
81
- );
82
-
83
- export type BannerAggregatedResult = z.infer<typeof bannerAggregatedSchema>;
73
+ // Type inferred from field definitions
74
+ export type BannerAggregatedResult = InferAggregatedResult<
75
+ typeof bannerAggregatedFields
76
+ >;
84
77
 
85
78
  // ============================================================================
86
79
  // BANNER COLLECTOR
@@ -106,9 +99,9 @@ export class BannerCollector implements CollectorStrategy<
106
99
 
107
100
  config = new Versioned({ version: 1, schema: bannerConfigSchema });
108
101
  result = new Versioned({ version: 1, schema: bannerResultSchema });
109
- aggregatedResult = new Versioned({
102
+ aggregatedResult = new VersionedAggregated({
110
103
  version: 1,
111
- schema: bannerAggregatedSchema,
104
+ fields: bannerAggregatedFields,
112
105
  });
113
106
 
114
107
  async execute({
@@ -143,21 +136,12 @@ export class BannerCollector implements CollectorStrategy<
143
136
  ): BannerAggregatedResult {
144
137
  const metadata = run.metadata;
145
138
 
146
- const readTimeState = mergeAverage(
147
- existing?._readTime as AverageState | undefined,
148
- metadata?.readTimeMs,
149
- );
150
-
151
- const bannerState = mergeRate(
152
- existing?._banner as RateState | undefined,
153
- metadata?.hasBanner,
154
- );
155
-
156
139
  return {
157
- avgReadTimeMs: readTimeState.avg,
158
- bannerRate: bannerState.rate,
159
- _readTime: readTimeState,
160
- _banner: bannerState,
140
+ avgReadTimeMs: mergeAverage(
141
+ existing?.avgReadTimeMs,
142
+ metadata?.readTimeMs,
143
+ ),
144
+ bannerRate: mergeRate(existing?.bannerRate, metadata?.hasBanner),
161
145
  };
162
146
  }
163
147
  }
@@ -121,9 +121,9 @@ describe("TcpHealthCheckStrategy", () => {
121
121
  let aggregated = strategy.mergeResult(undefined, runs[0]);
122
122
  aggregated = strategy.mergeResult(aggregated, runs[1]);
123
123
 
124
- expect(aggregated.avgConnectionTime).toBe(15);
125
- expect(aggregated.successRate).toBe(100);
126
- expect(aggregated.errorCount).toBe(0);
124
+ expect(aggregated.avgConnectionTime.avg).toBe(15);
125
+ expect(aggregated.successRate.rate).toBe(100);
126
+ expect(aggregated.errorCount.count).toBe(0);
127
127
  });
128
128
 
129
129
  it("should count errors and calculate success rate", () => {
@@ -157,8 +157,8 @@ describe("TcpHealthCheckStrategy", () => {
157
157
  let aggregated = strategy.mergeResult(undefined, runs[0]);
158
158
  aggregated = strategy.mergeResult(aggregated, runs[1]);
159
159
 
160
- expect(aggregated.successRate).toBe(50);
161
- expect(aggregated.errorCount).toBe(1);
160
+ expect(aggregated.successRate.rate).toBe(50);
161
+ expect(aggregated.errorCount.count).toBe(1);
162
162
  });
163
163
  });
164
164
  });
package/src/strategy.ts CHANGED
@@ -2,17 +2,16 @@ import {
2
2
  HealthCheckStrategy,
3
3
  HealthCheckRunForAggregation,
4
4
  Versioned,
5
- z,
6
- type ConnectedClient,
5
+ VersionedAggregated,
6
+ aggregatedAverage,
7
+ aggregatedRate,
8
+ aggregatedCounter,
7
9
  mergeAverage,
8
- averageStateSchema,
9
10
  mergeRate,
10
- rateStateSchema,
11
11
  mergeCounter,
12
- counterStateSchema,
13
- type AverageState,
14
- type RateState,
15
- type CounterState,
12
+ z,
13
+ type ConnectedClient,
14
+ type InferAggregatedResult,
16
15
  } from "@checkstack/backend-api";
17
16
  import {
18
17
  healthResultBoolean,
@@ -79,41 +78,25 @@ const tcpResultSchema = healthResultSchema({
79
78
 
80
79
  type TcpResult = z.infer<typeof tcpResultSchema>;
81
80
 
82
- /**
83
- * Aggregated metadata for buckets.
84
- */
85
- // UI-visible aggregated fields
86
- const tcpAggregatedDisplaySchema = healthResultSchema({
87
- avgConnectionTime: healthResultNumber({
81
+ /** Aggregated field definitions for bucket merging */
82
+ const tcpAggregatedFields = {
83
+ avgConnectionTime: aggregatedAverage({
88
84
  "x-chart-type": "line",
89
85
  "x-chart-label": "Avg Connection Time",
90
86
  "x-chart-unit": "ms",
91
87
  }),
92
- successRate: healthResultNumber({
88
+ successRate: aggregatedRate({
93
89
  "x-chart-type": "gauge",
94
90
  "x-chart-label": "Success Rate",
95
91
  "x-chart-unit": "%",
96
92
  }),
97
- errorCount: healthResultNumber({
93
+ errorCount: aggregatedCounter({
98
94
  "x-chart-type": "counter",
99
95
  "x-chart-label": "Errors",
100
96
  }),
101
- });
102
-
103
- // Internal state for incremental aggregation
104
- const tcpAggregatedInternalSchema = z.object({
105
- _connectionTime: averageStateSchema
106
- .optional(),
107
- _success: rateStateSchema
108
- .optional(),
109
- _errors: counterStateSchema.optional(),
110
- });
111
-
112
- const tcpAggregatedSchema = tcpAggregatedDisplaySchema.merge(
113
- tcpAggregatedInternalSchema,
114
- );
97
+ };
115
98
 
116
- type TcpAggregatedResult = z.infer<typeof tcpAggregatedSchema>;
99
+ type TcpAggregatedResult = InferAggregatedResult<typeof tcpAggregatedFields>;
117
100
 
118
101
  // ============================================================================
119
102
  // SOCKET INTERFACE (for testability)
@@ -186,7 +169,7 @@ export class TcpHealthCheckStrategy implements HealthCheckStrategy<
186
169
  TcpConfig,
187
170
  TcpTransportClient,
188
171
  TcpResult,
189
- TcpAggregatedResult
172
+ typeof tcpAggregatedFields
190
173
  > {
191
174
  id = "tcp";
192
175
  displayName = "TCP Health Check";
@@ -228,9 +211,9 @@ export class TcpHealthCheckStrategy implements HealthCheckStrategy<
228
211
  ],
229
212
  });
230
213
 
231
- aggregatedResult: Versioned<TcpAggregatedResult> = new Versioned({
214
+ aggregatedResult = new VersionedAggregated({
232
215
  version: 1,
233
- schema: tcpAggregatedSchema,
216
+ fields: tcpAggregatedFields,
234
217
  });
235
218
 
236
219
  mergeResult(
@@ -239,29 +222,18 @@ export class TcpHealthCheckStrategy implements HealthCheckStrategy<
239
222
  ): TcpAggregatedResult {
240
223
  const metadata = run.metadata;
241
224
 
242
- const connectionTimeState = mergeAverage(
243
- existing?._connectionTime as AverageState | undefined,
225
+ const avgConnectionTime = mergeAverage(
226
+ existing?.avgConnectionTime,
244
227
  metadata?.connectionTimeMs,
245
228
  );
246
229
 
247
- const successState = mergeRate(
248
- existing?._success as RateState | undefined,
249
- metadata?.connected,
250
- );
230
+ const isSuccess = metadata?.connected ?? false;
231
+ const successRate = mergeRate(existing?.successRate, isSuccess);
251
232
 
252
- const errorState = mergeCounter(
253
- existing?._errors as CounterState | undefined,
254
- metadata?.error !== undefined,
255
- );
233
+ const hasError = metadata?.error !== undefined;
234
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
256
235
 
257
- return {
258
- avgConnectionTime: connectionTimeState.avg,
259
- successRate: successState.rate,
260
- errorCount: errorState.count,
261
- _connectionTime: connectionTimeState,
262
- _success: successState,
263
- _errors: errorState,
264
- };
236
+ return { avgConnectionTime, successRate, errorCount };
265
237
  }
266
238
 
267
239
  async createClient(