@checkstack/healthcheck-tcp-backend 0.1.14 → 0.2.1
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 +48 -0
- package/package.json +4 -4
- package/src/banner-collector.test.ts +2 -2
- package/src/banner-collector.ts +20 -36
- package/src/strategy.test.ts +5 -5
- package/src/strategy.ts +26 -58
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @checkstack/healthcheck-tcp-backend
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 869b4ab: ## Health Check Execution Improvements
|
|
8
|
+
|
|
9
|
+
### Breaking Changes (backend-api)
|
|
10
|
+
|
|
11
|
+
- `HealthCheckStrategy.createClient()` now accepts `unknown` instead of `TConfig` due to TypeScript contravariance constraints. Implementations should use `this.config.validate(config)` to narrow the type.
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- **Platform-level hard timeout**: The executor now wraps the entire health check execution (connection + all collectors) in a single timeout, ensuring checks never hang indefinitely.
|
|
16
|
+
- **Parallel collector execution**: Collectors now run in parallel using `Promise.allSettled()`, improving performance while ensuring all collectors complete regardless of individual failures.
|
|
17
|
+
- **Base strategy config schema**: All strategy configs now extend `baseStrategyConfigSchema` which provides a standardized `timeout` field with sensible defaults (30s, min 100ms).
|
|
18
|
+
|
|
19
|
+
### Fixes
|
|
20
|
+
|
|
21
|
+
- Fixed HTTP and Jenkins strategies clearing timeouts before reading the full response body.
|
|
22
|
+
- Simplified registry type signatures by using default type parameters.
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [869b4ab]
|
|
25
|
+
- @checkstack/backend-api@0.8.0
|
|
26
|
+
|
|
27
|
+
## 0.2.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- 3dd1914: Migrate health check strategies to VersionedAggregated with \_type discriminator
|
|
32
|
+
|
|
33
|
+
All 13 health check strategies now use `VersionedAggregated` for their `aggregatedResult` property, enabling automatic bucket merging with 100% mathematical fidelity.
|
|
34
|
+
|
|
35
|
+
**Key changes:**
|
|
36
|
+
|
|
37
|
+
- **`_type` discriminator**: All aggregated state objects now include a required `_type` field (`"average"`, `"rate"`, `"counter"`, `"minmax"`) for reliable type detection
|
|
38
|
+
- The `HealthCheckStrategy` interface now requires `aggregatedResult` to be a `VersionedAggregated<AggregatedResultShape>`
|
|
39
|
+
- Strategy/collector `mergeResult` methods return state objects with `_type` (e.g., `{ _type: "average", _sum, _count, avg }`)
|
|
40
|
+
- `mergeAggregatedBucketResults`, `combineBuckets`, and `reaggregateBuckets` now require `registry` and `strategyId` parameters
|
|
41
|
+
- `HealthCheckService` constructor now requires both `registry` and `collectorRegistry` parameters
|
|
42
|
+
- Frontend `extractComputedValue` now uses `_type` discriminator for robust type detection
|
|
43
|
+
|
|
44
|
+
**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.
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- Updated dependencies [3dd1914]
|
|
49
|
+
- @checkstack/backend-api@0.7.0
|
|
50
|
+
|
|
3
51
|
## 0.1.14
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-tcp-backend",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/backend-api": "0.
|
|
13
|
-
"@checkstack/common": "0.6.
|
|
14
|
-
"@checkstack/healthcheck-common": "0.8.
|
|
12
|
+
"@checkstack/backend-api": "0.7.0",
|
|
13
|
+
"@checkstack/common": "0.6.2",
|
|
14
|
+
"@checkstack/healthcheck-common": "0.8.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/bun": "^1.0.0",
|
|
@@ -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
|
|
package/src/banner-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,
|
|
@@ -56,31 +56,24 @@ const bannerResultSchema = healthResultSchema({
|
|
|
56
56
|
|
|
57
57
|
export type BannerResult = z.infer<typeof bannerResultSchema>;
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
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:
|
|
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
|
-
|
|
80
|
-
|
|
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
|
|
102
|
+
aggregatedResult = new VersionedAggregated({
|
|
110
103
|
version: 1,
|
|
111
|
-
|
|
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:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
140
|
+
avgReadTimeMs: mergeAverage(
|
|
141
|
+
existing?.avgReadTimeMs,
|
|
142
|
+
metadata?.readTimeMs,
|
|
143
|
+
),
|
|
144
|
+
bannerRate: mergeRate(existing?.bannerRate, metadata?.hasBanner),
|
|
161
145
|
};
|
|
162
146
|
}
|
|
163
147
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -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,17 @@ import {
|
|
|
2
2
|
HealthCheckStrategy,
|
|
3
3
|
HealthCheckRunForAggregation,
|
|
4
4
|
Versioned,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
VersionedAggregated,
|
|
6
|
+
aggregatedAverage,
|
|
7
|
+
aggregatedRate,
|
|
8
|
+
aggregatedCounter,
|
|
7
9
|
mergeAverage,
|
|
8
|
-
averageStateSchema,
|
|
9
10
|
mergeRate,
|
|
10
|
-
rateStateSchema,
|
|
11
11
|
mergeCounter,
|
|
12
|
-
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
|
|
12
|
+
z,
|
|
13
|
+
type ConnectedClient,
|
|
14
|
+
type InferAggregatedResult,
|
|
15
|
+
baseStrategyConfigSchema,
|
|
16
16
|
} from "@checkstack/backend-api";
|
|
17
17
|
import {
|
|
18
18
|
healthResultBoolean,
|
|
@@ -34,14 +34,9 @@ import type {
|
|
|
34
34
|
* Configuration schema for TCP health checks.
|
|
35
35
|
* Connection-only parameters - action params moved to BannerCollector.
|
|
36
36
|
*/
|
|
37
|
-
export const tcpConfigSchema =
|
|
37
|
+
export const tcpConfigSchema = baseStrategyConfigSchema.extend({
|
|
38
38
|
host: z.string().describe("Hostname or IP address"),
|
|
39
39
|
port: z.number().int().min(1).max(65_535).describe("TCP port number"),
|
|
40
|
-
timeout: z
|
|
41
|
-
.number()
|
|
42
|
-
.min(100)
|
|
43
|
-
.default(5000)
|
|
44
|
-
.describe("Connection timeout in milliseconds"),
|
|
45
40
|
});
|
|
46
41
|
|
|
47
42
|
export type TcpConfig = z.infer<typeof tcpConfigSchema>;
|
|
@@ -79,41 +74,25 @@ const tcpResultSchema = healthResultSchema({
|
|
|
79
74
|
|
|
80
75
|
type TcpResult = z.infer<typeof tcpResultSchema>;
|
|
81
76
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// UI-visible aggregated fields
|
|
86
|
-
const tcpAggregatedDisplaySchema = healthResultSchema({
|
|
87
|
-
avgConnectionTime: healthResultNumber({
|
|
77
|
+
/** Aggregated field definitions for bucket merging */
|
|
78
|
+
const tcpAggregatedFields = {
|
|
79
|
+
avgConnectionTime: aggregatedAverage({
|
|
88
80
|
"x-chart-type": "line",
|
|
89
81
|
"x-chart-label": "Avg Connection Time",
|
|
90
82
|
"x-chart-unit": "ms",
|
|
91
83
|
}),
|
|
92
|
-
successRate:
|
|
84
|
+
successRate: aggregatedRate({
|
|
93
85
|
"x-chart-type": "gauge",
|
|
94
86
|
"x-chart-label": "Success Rate",
|
|
95
87
|
"x-chart-unit": "%",
|
|
96
88
|
}),
|
|
97
|
-
errorCount:
|
|
89
|
+
errorCount: aggregatedCounter({
|
|
98
90
|
"x-chart-type": "counter",
|
|
99
91
|
"x-chart-label": "Errors",
|
|
100
92
|
}),
|
|
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
|
-
);
|
|
93
|
+
};
|
|
115
94
|
|
|
116
|
-
type TcpAggregatedResult =
|
|
95
|
+
type TcpAggregatedResult = InferAggregatedResult<typeof tcpAggregatedFields>;
|
|
117
96
|
|
|
118
97
|
// ============================================================================
|
|
119
98
|
// SOCKET INTERFACE (for testability)
|
|
@@ -186,7 +165,7 @@ export class TcpHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
186
165
|
TcpConfig,
|
|
187
166
|
TcpTransportClient,
|
|
188
167
|
TcpResult,
|
|
189
|
-
|
|
168
|
+
typeof tcpAggregatedFields
|
|
190
169
|
> {
|
|
191
170
|
id = "tcp";
|
|
192
171
|
displayName = "TCP Health Check";
|
|
@@ -228,9 +207,9 @@ export class TcpHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
228
207
|
],
|
|
229
208
|
});
|
|
230
209
|
|
|
231
|
-
aggregatedResult
|
|
210
|
+
aggregatedResult = new VersionedAggregated({
|
|
232
211
|
version: 1,
|
|
233
|
-
|
|
212
|
+
fields: tcpAggregatedFields,
|
|
234
213
|
});
|
|
235
214
|
|
|
236
215
|
mergeResult(
|
|
@@ -239,29 +218,18 @@ export class TcpHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
239
218
|
): TcpAggregatedResult {
|
|
240
219
|
const metadata = run.metadata;
|
|
241
220
|
|
|
242
|
-
const
|
|
243
|
-
existing?.
|
|
221
|
+
const avgConnectionTime = mergeAverage(
|
|
222
|
+
existing?.avgConnectionTime,
|
|
244
223
|
metadata?.connectionTimeMs,
|
|
245
224
|
);
|
|
246
225
|
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
metadata?.connected,
|
|
250
|
-
);
|
|
226
|
+
const isSuccess = metadata?.connected ?? false;
|
|
227
|
+
const successRate = mergeRate(existing?.successRate, isSuccess);
|
|
251
228
|
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
metadata?.error !== undefined,
|
|
255
|
-
);
|
|
229
|
+
const hasError = metadata?.error !== undefined;
|
|
230
|
+
const errorCount = mergeCounter(existing?.errorCount, hasError);
|
|
256
231
|
|
|
257
|
-
return {
|
|
258
|
-
avgConnectionTime: connectionTimeState.avg,
|
|
259
|
-
successRate: successState.rate,
|
|
260
|
-
errorCount: errorState.count,
|
|
261
|
-
_connectionTime: connectionTimeState,
|
|
262
|
-
_success: successState,
|
|
263
|
-
_errors: errorState,
|
|
264
|
-
};
|
|
232
|
+
return { avgConnectionTime, successRate, errorCount };
|
|
265
233
|
}
|
|
266
234
|
|
|
267
235
|
async createClient(
|