@checkstack/healthcheck-ssh-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-ssh-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-ssh-backend",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -104,8 +104,8 @@ describe("CommandCollector", () => {
104
104
  let aggregated = collector.mergeResult(undefined, runs[0]);
105
105
  aggregated = collector.mergeResult(aggregated, runs[1]);
106
106
 
107
- expect(aggregated.avgExecutionTimeMs).toBe(75);
108
- expect(aggregated.successRate).toBe(100);
107
+ expect(aggregated.avgExecutionTimeMs.avg).toBe(75);
108
+ expect(aggregated.successRate.rate).toBe(100);
109
109
  });
110
110
 
111
111
  it("should calculate success rate based on exit codes", () => {
@@ -142,7 +142,7 @@ describe("CommandCollector", () => {
142
142
  let aggregated = collector.mergeResult(undefined, runs[0]);
143
143
  aggregated = collector.mergeResult(aggregated, runs[1]);
144
144
 
145
- expect(aggregated.successRate).toBe(50);
145
+ expect(aggregated.successRate.rate).toBe(50);
146
146
  });
147
147
  });
148
148
 
@@ -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 commandResultSchema = healthResultSchema({
55
55
 
56
56
  export type CommandResult = z.infer<typeof commandResultSchema>;
57
57
 
58
- const commandAggregatedDisplaySchema = healthResultSchema({
59
- avgExecutionTimeMs: healthResultNumber({
58
+ // Aggregated result fields definition
59
+ const commandAggregatedFields = {
60
+ avgExecutionTimeMs: aggregatedAverage({
60
61
  "x-chart-type": "line",
61
62
  "x-chart-label": "Avg Execution Time",
62
63
  "x-chart-unit": "ms",
63
64
  }),
64
- successRate: healthResultNumber({
65
+ successRate: aggregatedRate({
65
66
  "x-chart-type": "gauge",
66
67
  "x-chart-label": "Success Rate",
67
68
  "x-chart-unit": "%",
68
69
  }),
69
- });
70
-
71
- const commandAggregatedInternalSchema = z.object({
72
- _executionTime: averageStateSchema
73
- .optional(),
74
- _success: rateStateSchema
75
- .optional(),
76
- });
70
+ };
77
71
 
78
- const commandAggregatedSchema = commandAggregatedDisplaySchema.merge(
79
- commandAggregatedInternalSchema,
80
- );
81
-
82
- export type CommandAggregatedResult = z.infer<typeof commandAggregatedSchema>;
72
+ // Type inferred from field definitions
73
+ export type CommandAggregatedResult = InferAggregatedResult<
74
+ typeof commandAggregatedFields
75
+ >;
83
76
 
84
77
  // ============================================================================
85
78
  // COMMAND COLLECTOR (PSEUDO-COLLECTOR)
@@ -112,9 +105,9 @@ export class CommandCollector implements CollectorStrategy<
112
105
 
113
106
  config = new Versioned({ version: 1, schema: commandConfigSchema });
114
107
  result = new Versioned({ version: 1, schema: commandResultSchema });
115
- aggregatedResult = new Versioned({
108
+ aggregatedResult = new VersionedAggregated({
116
109
  version: 1,
117
- schema: commandAggregatedSchema,
110
+ fields: commandAggregatedFields,
118
111
  });
119
112
 
120
113
  async execute({
@@ -145,22 +138,13 @@ export class CommandCollector implements CollectorStrategy<
145
138
  ): CommandAggregatedResult {
146
139
  const metadata = run.metadata;
147
140
 
148
- const executionTimeState = mergeAverage(
149
- existing?._executionTime as AverageState | undefined,
150
- metadata?.executionTimeMs,
151
- );
152
-
153
141
  // Success is exit code 0
154
- const successState = mergeRate(
155
- existing?._success as RateState | undefined,
156
- metadata?.exitCode === 0,
157
- );
158
-
159
142
  return {
160
- avgExecutionTimeMs: executionTimeState.avg,
161
- successRate: successState.rate,
162
- _executionTime: executionTimeState,
163
- _success: successState,
143
+ avgExecutionTimeMs: mergeAverage(
144
+ existing?.avgExecutionTimeMs,
145
+ metadata?.executionTimeMs,
146
+ ),
147
+ successRate: mergeRate(existing?.successRate, metadata?.exitCode === 0),
164
148
  };
165
149
  }
166
150
  }
@@ -142,9 +142,9 @@ describe("SshHealthCheckStrategy", () => {
142
142
  let aggregated = strategy.mergeResult(undefined, runs[0]);
143
143
  aggregated = strategy.mergeResult(aggregated, runs[1]);
144
144
 
145
- expect(aggregated.avgConnectionTime).toBe(75);
146
- expect(aggregated.successRate).toBe(100);
147
- expect(aggregated.errorCount).toBe(0);
145
+ expect(aggregated.avgConnectionTime.avg).toBe(75);
146
+ expect(aggregated.successRate.rate).toBe(100);
147
+ expect(aggregated.errorCount.count).toBe(0);
148
148
  });
149
149
 
150
150
  it("should count errors", () => {
@@ -164,8 +164,8 @@ describe("SshHealthCheckStrategy", () => {
164
164
 
165
165
  const aggregated = strategy.mergeResult(undefined, run);
166
166
 
167
- expect(aggregated.errorCount).toBe(1);
168
- expect(aggregated.successRate).toBe(0);
167
+ expect(aggregated.errorCount.count).toBe(1);
168
+ expect(aggregated.successRate.rate).toBe(0);
169
169
  });
170
170
  });
171
171
  });
package/src/strategy.ts CHANGED
@@ -3,22 +3,20 @@ import {
3
3
  HealthCheckStrategy,
4
4
  HealthCheckRunForAggregation,
5
5
  Versioned,
6
- z,
7
- configString,
8
- configNumber,
9
- type ConnectedClient,
6
+ VersionedAggregated,
7
+ aggregatedAverage,
8
+ aggregatedMinMax,
9
+ aggregatedRate,
10
+ aggregatedCounter,
10
11
  mergeAverage,
11
- averageStateSchema,
12
12
  mergeRate,
13
- rateStateSchema,
14
13
  mergeCounter,
15
- counterStateSchema,
16
14
  mergeMinMax,
17
- minMaxStateSchema,
18
- type AverageState,
19
- type RateState,
20
- type CounterState,
21
- type MinMaxState,
15
+ z,
16
+ configString,
17
+ configNumber,
18
+ type ConnectedClient,
19
+ type InferAggregatedResult,
22
20
  } from "@checkstack/backend-api";
23
21
  import {
24
22
  healthResultBoolean,
@@ -78,45 +76,30 @@ const sshResultSchema = healthResultSchema({
78
76
 
79
77
  type SshResult = z.infer<typeof sshResultSchema>;
80
78
 
81
- /**
82
- * Aggregated metadata for buckets.
83
- */
84
- const sshAggregatedDisplaySchema = healthResultSchema({
85
- avgConnectionTime: healthResultNumber({
79
+ /** Aggregated field definitions for bucket merging */
80
+ const sshAggregatedFields = {
81
+ avgConnectionTime: aggregatedAverage({
86
82
  "x-chart-type": "line",
87
83
  "x-chart-label": "Avg Connection Time",
88
84
  "x-chart-unit": "ms",
89
85
  }),
90
- maxConnectionTime: healthResultNumber({
86
+ maxConnectionTime: aggregatedMinMax({
91
87
  "x-chart-type": "line",
92
88
  "x-chart-label": "Max Connection Time",
93
89
  "x-chart-unit": "ms",
94
90
  }),
95
- successRate: healthResultNumber({
91
+ successRate: aggregatedRate({
96
92
  "x-chart-type": "gauge",
97
93
  "x-chart-label": "Success Rate",
98
94
  "x-chart-unit": "%",
99
95
  }),
100
- errorCount: healthResultNumber({
96
+ errorCount: aggregatedCounter({
101
97
  "x-chart-type": "counter",
102
98
  "x-chart-label": "Errors",
103
99
  }),
104
- });
105
-
106
- const sshAggregatedInternalSchema = z.object({
107
- _connectionTime: averageStateSchema
108
- .optional(),
109
- _maxConnectionTime: minMaxStateSchema.optional(),
110
- _success: rateStateSchema
111
- .optional(),
112
- _errors: counterStateSchema.optional(),
113
- });
114
-
115
- const sshAggregatedSchema = sshAggregatedDisplaySchema.merge(
116
- sshAggregatedInternalSchema,
117
- );
100
+ };
118
101
 
119
- type SshAggregatedResult = z.infer<typeof sshAggregatedSchema>;
102
+ type SshAggregatedResult = InferAggregatedResult<typeof sshAggregatedFields>;
120
103
 
121
104
  // ============================================================================
122
105
  // SSH CLIENT INTERFACE (for testability)
@@ -207,7 +190,7 @@ export class SshHealthCheckStrategy implements HealthCheckStrategy<
207
190
  SshConfig,
208
191
  SshTransportClient,
209
192
  SshResult,
210
- SshAggregatedResult
193
+ typeof sshAggregatedFields
211
194
  > {
212
195
  id = "ssh";
213
196
  displayName = "SSH Health Check";
@@ -229,9 +212,9 @@ export class SshHealthCheckStrategy implements HealthCheckStrategy<
229
212
  schema: sshResultSchema,
230
213
  });
231
214
 
232
- aggregatedResult: Versioned<SshAggregatedResult> = new Versioned({
215
+ aggregatedResult = new VersionedAggregated({
233
216
  version: 1,
234
- schema: sshAggregatedSchema,
217
+ fields: sshAggregatedFields,
235
218
  });
236
219
 
237
220
  mergeResult(
@@ -240,36 +223,23 @@ export class SshHealthCheckStrategy implements HealthCheckStrategy<
240
223
  ): SshAggregatedResult {
241
224
  const metadata = run.metadata;
242
225
 
243
- const connectionTimeState = mergeAverage(
244
- existing?._connectionTime as AverageState | undefined,
226
+ const avgConnectionTime = mergeAverage(
227
+ existing?.avgConnectionTime,
245
228
  metadata?.connectionTimeMs,
246
229
  );
247
230
 
248
- const maxConnectionTimeState = mergeMinMax(
249
- existing?._maxConnectionTime as MinMaxState | undefined,
231
+ const maxConnectionTime = mergeMinMax(
232
+ existing?.maxConnectionTime,
250
233
  metadata?.connectionTimeMs,
251
234
  );
252
235
 
253
- const successState = mergeRate(
254
- existing?._success as RateState | undefined,
255
- metadata?.connected,
256
- );
236
+ const isSuccess = metadata?.connected ?? false;
237
+ const successRate = mergeRate(existing?.successRate, isSuccess);
257
238
 
258
- const errorState = mergeCounter(
259
- existing?._errors as CounterState | undefined,
260
- metadata?.error !== undefined,
261
- );
239
+ const hasError = metadata?.error !== undefined;
240
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
262
241
 
263
- return {
264
- avgConnectionTime: connectionTimeState.avg,
265
- maxConnectionTime: maxConnectionTimeState.max,
266
- successRate: successState.rate,
267
- errorCount: errorState.count,
268
- _connectionTime: connectionTimeState,
269
- _maxConnectionTime: maxConnectionTimeState,
270
- _success: successState,
271
- _errors: errorState,
272
- };
242
+ return { avgConnectionTime, maxConnectionTime, successRate, errorCount };
273
243
  }
274
244
 
275
245
  /**