@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 +24 -0
- package/package.json +1 -1
- package/src/command-collector.test.ts +3 -3
- package/src/command-collector.ts +20 -36
- package/src/strategy.test.ts +5 -5
- package/src/strategy.ts +30 -60
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
|
@@ -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
|
|
package/src/command-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 commandResultSchema = healthResultSchema({
|
|
|
55
55
|
|
|
56
56
|
export type CommandResult = z.infer<typeof commandResultSchema>;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
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:
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|
108
|
+
aggregatedResult = new VersionedAggregated({
|
|
116
109
|
version: 1,
|
|
117
|
-
|
|
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:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
143
|
+
avgExecutionTimeMs: mergeAverage(
|
|
144
|
+
existing?.avgExecutionTimeMs,
|
|
145
|
+
metadata?.executionTimeMs,
|
|
146
|
+
),
|
|
147
|
+
successRate: mergeRate(existing?.successRate, metadata?.exitCode === 0),
|
|
164
148
|
};
|
|
165
149
|
}
|
|
166
150
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type
|
|
21
|
-
type
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
215
|
+
aggregatedResult = new VersionedAggregated({
|
|
233
216
|
version: 1,
|
|
234
|
-
|
|
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
|
|
244
|
-
existing?.
|
|
226
|
+
const avgConnectionTime = mergeAverage(
|
|
227
|
+
existing?.avgConnectionTime,
|
|
245
228
|
metadata?.connectionTimeMs,
|
|
246
229
|
);
|
|
247
230
|
|
|
248
|
-
const
|
|
249
|
-
existing?.
|
|
231
|
+
const maxConnectionTime = mergeMinMax(
|
|
232
|
+
existing?.maxConnectionTime,
|
|
250
233
|
metadata?.connectionTimeMs,
|
|
251
234
|
);
|
|
252
235
|
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
metadata?.connected,
|
|
256
|
-
);
|
|
236
|
+
const isSuccess = metadata?.connected ?? false;
|
|
237
|
+
const successRate = mergeRate(existing?.successRate, isSuccess);
|
|
257
238
|
|
|
258
|
-
const
|
|
259
|
-
|
|
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
|
/**
|