@checkstack/healthcheck-ping-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 CHANGED
@@ -1,5 +1,53 @@
1
1
  # @checkstack/healthcheck-ping-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-ping-backend",
3
- "version": "0.1.14",
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.5.2",
13
- "@checkstack/common": "0.6.1",
14
- "@checkstack/healthcheck-common": "0.8.1"
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",
@@ -117,8 +117,8 @@ describe("PingCollector", () => {
117
117
  let aggregated = collector.mergeResult(undefined, runs[0]);
118
118
  aggregated = collector.mergeResult(aggregated, runs[1]);
119
119
 
120
- expect(aggregated.avgPacketLoss).toBe(5);
121
- expect(aggregated.avgLatency).toBe(15);
120
+ expect(aggregated.avgPacketLoss.avg).toBe(5);
121
+ expect(aggregated.avgLatency.avg).toBe(15);
122
122
  });
123
123
  });
124
124
 
@@ -5,8 +5,9 @@ import {
5
5
  type CollectorResult,
6
6
  type CollectorStrategy,
7
7
  mergeAverage,
8
- averageStateSchema,
9
- type AverageState,
8
+ VersionedAggregated,
9
+ aggregatedAverage,
10
+ type InferAggregatedResult,
10
11
  } from "@checkstack/backend-api";
11
12
  import {
12
13
  healthResultNumber,
@@ -74,29 +75,24 @@ const pingResultSchema = healthResultSchema({
74
75
 
75
76
  export type PingResult = z.infer<typeof pingResultSchema>;
76
77
 
77
- const pingAggregatedDisplaySchema = healthResultSchema({
78
- avgPacketLoss: healthResultNumber({
78
+ // Aggregated result fields definition
79
+ const pingAggregatedFields = {
80
+ avgPacketLoss: aggregatedAverage({
79
81
  "x-chart-type": "gauge",
80
82
  "x-chart-label": "Avg Packet Loss",
81
83
  "x-chart-unit": "%",
82
84
  }),
83
- avgLatency: healthResultNumber({
85
+ avgLatency: aggregatedAverage({
84
86
  "x-chart-type": "line",
85
87
  "x-chart-label": "Avg Latency",
86
88
  "x-chart-unit": "ms",
87
89
  }),
88
- });
89
-
90
- const pingAggregatedInternalSchema = z.object({
91
- _packetLoss: averageStateSchema.optional(),
92
- _latency: averageStateSchema.optional(),
93
- });
90
+ };
94
91
 
95
- const pingAggregatedSchema = pingAggregatedDisplaySchema.merge(
96
- pingAggregatedInternalSchema,
97
- );
98
-
99
- export type PingAggregatedResult = z.infer<typeof pingAggregatedSchema>;
92
+ // Type inferred from field definitions
93
+ export type PingAggregatedResult = InferAggregatedResult<
94
+ typeof pingAggregatedFields
95
+ >;
100
96
 
101
97
  // ============================================================================
102
98
  // PING COLLECTOR
@@ -122,9 +118,9 @@ export class PingCollector implements CollectorStrategy<
122
118
 
123
119
  config = new Versioned({ version: 1, schema: pingConfigSchema });
124
120
  result = new Versioned({ version: 1, schema: pingResultSchema });
125
- aggregatedResult = new Versioned({
121
+ aggregatedResult = new VersionedAggregated({
126
122
  version: 1,
127
- schema: pingAggregatedSchema,
123
+ fields: pingAggregatedFields,
128
124
  });
129
125
 
130
126
  async execute({
@@ -160,21 +156,12 @@ export class PingCollector implements CollectorStrategy<
160
156
  ): PingAggregatedResult {
161
157
  const metadata = run.metadata;
162
158
 
163
- const lossState = mergeAverage(
164
- existing?._packetLoss as AverageState | undefined,
165
- metadata?.packetLoss,
166
- );
167
-
168
- const latencyState = mergeAverage(
169
- existing?._latency as AverageState | undefined,
170
- metadata?.avgLatency,
171
- );
172
-
173
159
  return {
174
- avgPacketLoss: Math.round(lossState.avg * 10) / 10,
175
- avgLatency: Math.round(latencyState.avg * 10) / 10,
176
- _packetLoss: lossState,
177
- _latency: latencyState,
160
+ avgPacketLoss: mergeAverage(
161
+ existing?.avgPacketLoss,
162
+ metadata?.packetLoss,
163
+ ),
164
+ avgLatency: mergeAverage(existing?.avgLatency, metadata?.avgLatency),
178
165
  };
179
166
  }
180
167
  }
@@ -176,10 +176,10 @@ describe("PingHealthCheckStrategy", () => {
176
176
  aggregated = strategy.mergeResult(aggregated, runs[1]);
177
177
 
178
178
  // (0 + 33) / 2 = 16.5
179
- expect(aggregated.avgPacketLoss).toBeCloseTo(16.5, 1);
180
- expect(aggregated.avgLatency).toBeCloseTo(15, 1);
181
- expect(aggregated.maxLatency).toBe(25);
182
- expect(aggregated.errorCount).toBe(0);
179
+ expect(aggregated.avgPacketLoss.avg).toBeCloseTo(16.5, 1);
180
+ expect(aggregated.avgLatency.avg).toBeCloseTo(15, 1);
181
+ expect(aggregated.maxLatency.max).toBe(25);
182
+ expect(aggregated.errorCount.count).toBe(0);
183
183
  });
184
184
 
185
185
  it("should count errors", () => {
@@ -199,7 +199,7 @@ describe("PingHealthCheckStrategy", () => {
199
199
 
200
200
  const aggregated = strategy.mergeResult(undefined, run);
201
201
 
202
- expect(aggregated.errorCount).toBe(1);
202
+ expect(aggregated.errorCount.count).toBe(1);
203
203
  });
204
204
  });
205
205
  });
package/src/strategy.ts CHANGED
@@ -2,17 +2,17 @@ import {
2
2
  HealthCheckStrategy,
3
3
  HealthCheckRunForAggregation,
4
4
  Versioned,
5
- z,
6
- type ConnectedClient,
5
+ VersionedAggregated,
6
+ aggregatedAverage,
7
+ aggregatedMinMax,
8
+ aggregatedCounter,
7
9
  mergeAverage,
8
- averageStateSchema,
9
10
  mergeCounter,
10
- counterStateSchema,
11
11
  mergeMinMax,
12
- minMaxStateSchema,
13
- type AverageState,
14
- type CounterState,
15
- type MinMaxState,
12
+ z,
13
+ type ConnectedClient,
14
+ type InferAggregatedResult,
15
+ baseStrategyConfigSchema,
16
16
  } from "@checkstack/backend-api";
17
17
  import {
18
18
  healthResultNumber,
@@ -33,13 +33,7 @@ import type {
33
33
  * Configuration schema for Ping health checks.
34
34
  * Global defaults only - action params moved to PingCollector.
35
35
  */
36
- export const pingConfigSchema = z.object({
37
- timeout: z
38
- .number()
39
- .min(100)
40
- .default(5000)
41
- .describe("Default timeout in milliseconds"),
42
- });
36
+ export const pingConfigSchema = baseStrategyConfigSchema.extend({});
43
37
 
44
38
  export type PingConfig = z.infer<typeof pingConfigSchema>;
45
39
 
@@ -90,43 +84,30 @@ const pingResultSchema = healthResultSchema({
90
84
 
91
85
  type PingResult = z.infer<typeof pingResultSchema>;
92
86
 
93
- /**
94
- * Aggregated metadata for buckets.
95
- */
96
- const pingAggregatedDisplaySchema = healthResultSchema({
97
- avgPacketLoss: healthResultNumber({
87
+ /** Aggregated field definitions for bucket merging */
88
+ const pingAggregatedFields = {
89
+ avgPacketLoss: aggregatedAverage({
98
90
  "x-chart-type": "gauge",
99
91
  "x-chart-label": "Avg Packet Loss",
100
92
  "x-chart-unit": "%",
101
93
  }),
102
- avgLatency: healthResultNumber({
94
+ avgLatency: aggregatedAverage({
103
95
  "x-chart-type": "line",
104
96
  "x-chart-label": "Avg Latency",
105
97
  "x-chart-unit": "ms",
106
98
  }),
107
- maxLatency: healthResultNumber({
99
+ maxLatency: aggregatedMinMax({
108
100
  "x-chart-type": "line",
109
101
  "x-chart-label": "Max Latency",
110
102
  "x-chart-unit": "ms",
111
103
  }),
112
- errorCount: healthResultNumber({
104
+ errorCount: aggregatedCounter({
113
105
  "x-chart-type": "counter",
114
106
  "x-chart-label": "Errors",
115
107
  }),
116
- });
117
-
118
- const pingAggregatedInternalSchema = z.object({
119
- _packetLoss: averageStateSchema.optional(),
120
- _latency: averageStateSchema.optional(),
121
- _maxLatency: minMaxStateSchema.optional(),
122
- _errors: counterStateSchema.optional(),
123
- });
108
+ };
124
109
 
125
- const pingAggregatedSchema = pingAggregatedDisplaySchema.merge(
126
- pingAggregatedInternalSchema,
127
- );
128
-
129
- type PingAggregatedResult = z.infer<typeof pingAggregatedSchema>;
110
+ type PingAggregatedResult = InferAggregatedResult<typeof pingAggregatedFields>;
130
111
 
131
112
  // ============================================================================
132
113
  // STRATEGY
@@ -136,7 +117,7 @@ export class PingHealthCheckStrategy implements HealthCheckStrategy<
136
117
  PingConfig,
137
118
  PingTransportClient,
138
119
  PingResult,
139
- PingAggregatedResult
120
+ typeof pingAggregatedFields
140
121
  > {
141
122
  id = "ping";
142
123
  displayName = "Ping Health Check";
@@ -170,9 +151,9 @@ export class PingHealthCheckStrategy implements HealthCheckStrategy<
170
151
  ],
171
152
  });
172
153
 
173
- aggregatedResult: Versioned<PingAggregatedResult> = new Versioned({
154
+ aggregatedResult = new VersionedAggregated({
174
155
  version: 1,
175
- schema: pingAggregatedSchema,
156
+ fields: pingAggregatedFields,
176
157
  });
177
158
 
178
159
  mergeResult(
@@ -181,36 +162,19 @@ export class PingHealthCheckStrategy implements HealthCheckStrategy<
181
162
  ): PingAggregatedResult {
182
163
  const metadata = run.metadata;
183
164
 
184
- const packetLossState = mergeAverage(
185
- existing?._packetLoss as AverageState | undefined,
165
+ const avgPacketLoss = mergeAverage(
166
+ existing?.avgPacketLoss,
186
167
  metadata?.packetLoss,
187
168
  );
188
169
 
189
- const latencyState = mergeAverage(
190
- existing?._latency as AverageState | undefined,
191
- metadata?.avgLatency,
192
- );
170
+ const avgLatency = mergeAverage(existing?.avgLatency, metadata?.avgLatency);
193
171
 
194
- const maxLatencyState = mergeMinMax(
195
- existing?._maxLatency as MinMaxState | undefined,
196
- metadata?.maxLatency,
197
- );
172
+ const maxLatency = mergeMinMax(existing?.maxLatency, metadata?.maxLatency);
198
173
 
199
- const errorState = mergeCounter(
200
- existing?._errors as CounterState | undefined,
201
- metadata?.error !== undefined,
202
- );
174
+ const hasError = metadata?.error !== undefined;
175
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
203
176
 
204
- return {
205
- avgPacketLoss: Math.round(packetLossState.avg * 10) / 10,
206
- avgLatency: Math.round(latencyState.avg * 10) / 10,
207
- maxLatency: maxLatencyState.max,
208
- errorCount: errorState.count,
209
- _packetLoss: packetLossState,
210
- _latency: latencyState,
211
- _maxLatency: maxLatencyState,
212
- _errors: errorState,
213
- };
177
+ return { avgPacketLoss, avgLatency, maxLatency, errorCount };
214
178
  }
215
179
 
216
180
  async createClient(