@checkstack/healthcheck-jenkins-backend 0.2.13 → 0.3.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-jenkins-backend
2
2
 
3
+ ## 0.3.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.2.13
4
28
 
5
29
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-jenkins-backend",
3
- "version": "0.2.13",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -101,7 +101,7 @@ describe("BuildHistoryCollector", () => {
101
101
  let aggregated = collector.mergeResult(undefined, runs[0]);
102
102
  aggregated = collector.mergeResult(aggregated, runs[1]);
103
103
 
104
- expect(aggregated.avgSuccessRate).toBe(70);
105
- expect(aggregated.avgBuildDuration).toBe(70000);
104
+ expect(aggregated.avgSuccessRate.avg).toBe(70);
105
+ expect(aggregated.avgBuildDuration.avg).toBe(70000);
106
106
  });
107
107
  });
@@ -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 { healthResultNumber } from "@checkstack/healthcheck-common";
12
13
  import { pluginMetadata } from "../plugin-metadata";
@@ -89,32 +90,23 @@ const buildHistoryResultSchema = z.object({
89
90
 
90
91
  export type BuildHistoryResult = z.infer<typeof buildHistoryResultSchema>;
91
92
 
92
- const buildHistoryAggregatedDisplaySchema = z.object({
93
- avgSuccessRate: healthResultNumber({
93
+ // Aggregated result fields definition
94
+ const buildHistoryAggregatedFields = {
95
+ avgSuccessRate: aggregatedAverage({
94
96
  "x-chart-type": "gauge",
95
97
  "x-chart-label": "Avg Success Rate",
96
98
  "x-chart-unit": "%",
97
99
  }),
98
- avgBuildDuration: healthResultNumber({
100
+ avgBuildDuration: aggregatedAverage({
99
101
  "x-chart-type": "line",
100
102
  "x-chart-label": "Avg Build Duration",
101
103
  "x-chart-unit": "ms",
102
104
  }),
103
- });
104
-
105
- const buildHistoryAggregatedInternalSchema = z.object({
106
- _successRate: averageStateSchema
107
- .optional(),
108
- _duration: averageStateSchema
109
- .optional(),
110
- });
105
+ };
111
106
 
112
- const buildHistoryAggregatedSchema = buildHistoryAggregatedDisplaySchema.merge(
113
- buildHistoryAggregatedInternalSchema,
114
- );
115
-
116
- export type BuildHistoryAggregatedResult = z.infer<
117
- typeof buildHistoryAggregatedSchema
107
+ // Type inferred from field definitions
108
+ export type BuildHistoryAggregatedResult = InferAggregatedResult<
109
+ typeof buildHistoryAggregatedFields
118
110
  >;
119
111
 
120
112
  // ============================================================================
@@ -140,9 +132,9 @@ export class BuildHistoryCollector implements CollectorStrategy<
140
132
 
141
133
  config = new Versioned({ version: 1, schema: buildHistoryConfigSchema });
142
134
  result = new Versioned({ version: 1, schema: buildHistoryResultSchema });
143
- aggregatedResult = new Versioned({
135
+ aggregatedResult = new VersionedAggregated({
144
136
  version: 1,
145
- schema: buildHistoryAggregatedSchema,
137
+ fields: buildHistoryAggregatedFields,
146
138
  });
147
139
 
148
140
  async execute({
@@ -270,21 +262,15 @@ export class BuildHistoryCollector implements CollectorStrategy<
270
262
  ): BuildHistoryAggregatedResult {
271
263
  const metadata = run.metadata;
272
264
 
273
- const successRateState = mergeAverage(
274
- existing?._successRate as AverageState | undefined,
275
- metadata?.successRate,
276
- );
277
-
278
- const durationState = mergeAverage(
279
- existing?._duration as AverageState | undefined,
280
- metadata?.avgDurationMs,
281
- );
282
-
283
265
  return {
284
- avgSuccessRate: successRateState.avg,
285
- avgBuildDuration: durationState.avg,
286
- _successRate: successRateState,
287
- _duration: durationState,
266
+ avgSuccessRate: mergeAverage(
267
+ existing?.avgSuccessRate,
268
+ metadata?.successRate,
269
+ ),
270
+ avgBuildDuration: mergeAverage(
271
+ existing?.avgBuildDuration,
272
+ metadata?.avgDurationMs,
273
+ ),
288
274
  };
289
275
  }
290
276
  }
@@ -141,8 +141,8 @@ describe("JobStatusCollector", () => {
141
141
  aggregated = collector.mergeResult(aggregated, runs[1]);
142
142
  aggregated = collector.mergeResult(aggregated, runs[2]);
143
143
 
144
- expect(aggregated.successRate).toBe(67); // 2/3
145
- expect(aggregated.avgBuildDurationMs).toBe(60000); // (60000+80000+40000)/3
146
- expect(aggregated.buildableRate).toBe(100);
144
+ expect(aggregated.successRate.rate).toBe(67); // 2/3
145
+ expect(aggregated.avgBuildDurationMs.avg).toBe(60000); // (60000+80000+40000)/3
146
+ expect(aggregated.buildableRate.rate).toBe(100);
147
147
  });
148
148
  });
@@ -6,10 +6,10 @@ import {
6
6
  type CollectorStrategy,
7
7
  mergeAverage,
8
8
  mergeRate,
9
- averageStateSchema,
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,
@@ -83,36 +83,28 @@ const jobStatusResultSchema = z.object({
83
83
 
84
84
  export type JobStatusResult = z.infer<typeof jobStatusResultSchema>;
85
85
 
86
- const jobStatusAggregatedDisplaySchema = z.object({
87
- avgBuildDurationMs: healthResultNumber({
86
+ // Aggregated result fields definition
87
+ const jobStatusAggregatedFields = {
88
+ avgBuildDurationMs: aggregatedAverage({
88
89
  "x-chart-type": "line",
89
90
  "x-chart-label": "Avg Build Duration",
90
91
  "x-chart-unit": "ms",
91
92
  }),
92
- successRate: healthResultNumber({
93
+ successRate: aggregatedRate({
93
94
  "x-chart-type": "gauge",
94
95
  "x-chart-label": "Success Rate",
95
96
  "x-chart-unit": "%",
96
97
  }),
97
- buildableRate: healthResultNumber({
98
+ buildableRate: aggregatedRate({
98
99
  "x-chart-type": "gauge",
99
100
  "x-chart-label": "Enabled Rate",
100
101
  "x-chart-unit": "%",
101
102
  }),
102
- });
103
-
104
- const jobStatusAggregatedInternalSchema = z.object({
105
- _duration: averageStateSchema.optional(),
106
- _success: rateStateSchema.optional(),
107
- _buildable: rateStateSchema.optional(),
108
- });
109
-
110
- const jobStatusAggregatedSchema = jobStatusAggregatedDisplaySchema.merge(
111
- jobStatusAggregatedInternalSchema,
112
- );
103
+ };
113
104
 
114
- export type JobStatusAggregatedResult = z.infer<
115
- typeof jobStatusAggregatedSchema
105
+ // Type inferred from field definitions
106
+ export type JobStatusAggregatedResult = InferAggregatedResult<
107
+ typeof jobStatusAggregatedFields
116
108
  >;
117
109
 
118
110
  // ============================================================================
@@ -138,9 +130,9 @@ export class JobStatusCollector implements CollectorStrategy<
138
130
 
139
131
  config = new Versioned({ version: 1, schema: jobStatusConfigSchema });
140
132
  result = new Versioned({ version: 1, schema: jobStatusResultSchema });
141
- aggregatedResult = new Versioned({
133
+ aggregatedResult = new VersionedAggregated({
142
134
  version: 1,
143
- schema: jobStatusAggregatedSchema,
135
+ fields: jobStatusAggregatedFields,
144
136
  });
145
137
 
146
138
  async execute({
@@ -224,29 +216,16 @@ export class JobStatusCollector implements CollectorStrategy<
224
216
  ): JobStatusAggregatedResult {
225
217
  const metadata = run.metadata;
226
218
 
227
- const durationState = mergeAverage(
228
- existing?._duration as AverageState | undefined,
229
- metadata?.lastBuildDurationMs,
230
- );
231
-
232
- // Success is when lastBuildResult === "SUCCESS"
233
- const successState = mergeRate(
234
- existing?._success as RateState | undefined,
235
- metadata?.lastBuildResult === "SUCCESS",
236
- );
237
-
238
- const buildableState = mergeRate(
239
- existing?._buildable as RateState | undefined,
240
- metadata?.buildable,
241
- );
242
-
243
219
  return {
244
- avgBuildDurationMs: durationState.avg,
245
- successRate: successState.rate,
246
- buildableRate: buildableState.rate,
247
- _duration: durationState,
248
- _success: successState,
249
- _buildable: buildableState,
220
+ avgBuildDurationMs: mergeAverage(
221
+ existing?.avgBuildDurationMs,
222
+ metadata?.lastBuildDurationMs,
223
+ ),
224
+ successRate: mergeRate(
225
+ existing?.successRate,
226
+ metadata?.lastBuildResult === "SUCCESS",
227
+ ),
228
+ buildableRate: mergeRate(existing?.buildableRate, metadata?.buildable),
250
229
  };
251
230
  }
252
231
  }
@@ -143,8 +143,8 @@ describe("NodeHealthCollector", () => {
143
143
  let aggregated = collector.mergeResult(undefined, runs[0]);
144
144
  aggregated = collector.mergeResult(aggregated, runs[1]);
145
145
 
146
- expect(aggregated.avgOnlineNodes).toBe(4);
147
- expect(aggregated.avgUtilization).toBe(60);
148
- expect(aggregated.minOnlineNodes).toBe(3);
146
+ expect(aggregated.avgOnlineNodes.avg).toBe(4);
147
+ expect(aggregated.avgUtilization.avg).toBe(60);
148
+ expect(aggregated.minOnlineNodes.min).toBe(3);
149
149
  });
150
150
  });
@@ -6,10 +6,10 @@ import {
6
6
  type CollectorStrategy,
7
7
  mergeAverage,
8
8
  mergeMinMax,
9
- averageStateSchema,
10
- minMaxStateSchema,
11
- type AverageState,
12
- type MinMaxState,
9
+ VersionedAggregated,
10
+ aggregatedAverage,
11
+ aggregatedMinMax,
12
+ type InferAggregatedResult,
13
13
  } from "@checkstack/backend-api";
14
14
  import {
15
15
  healthResultBoolean,
@@ -83,34 +83,26 @@ const nodeHealthResultSchema = z.object({
83
83
 
84
84
  export type NodeHealthResult = z.infer<typeof nodeHealthResultSchema>;
85
85
 
86
- const nodeHealthAggregatedDisplaySchema = z.object({
87
- avgOnlineNodes: healthResultNumber({
86
+ // Aggregated result fields definition
87
+ const nodeHealthAggregatedFields = {
88
+ avgOnlineNodes: aggregatedAverage({
88
89
  "x-chart-type": "line",
89
90
  "x-chart-label": "Avg Online Nodes",
90
91
  }),
91
- avgUtilization: healthResultNumber({
92
+ avgUtilization: aggregatedAverage({
92
93
  "x-chart-type": "gauge",
93
94
  "x-chart-label": "Avg Utilization",
94
95
  "x-chart-unit": "%",
95
96
  }),
96
- minOnlineNodes: healthResultNumber({
97
+ minOnlineNodes: aggregatedMinMax({
97
98
  "x-chart-type": "line",
98
99
  "x-chart-label": "Min Online Nodes",
99
100
  }),
100
- });
101
-
102
- const nodeHealthAggregatedInternalSchema = z.object({
103
- _onlineNodes: averageStateSchema.optional(),
104
- _utilization: averageStateSchema.optional(),
105
- _minOnlineNodes: minMaxStateSchema.optional(),
106
- });
107
-
108
- const nodeHealthAggregatedSchema = nodeHealthAggregatedDisplaySchema.merge(
109
- nodeHealthAggregatedInternalSchema,
110
- );
101
+ };
111
102
 
112
- export type NodeHealthAggregatedResult = z.infer<
113
- typeof nodeHealthAggregatedSchema
103
+ // Type inferred from field definitions
104
+ export type NodeHealthAggregatedResult = InferAggregatedResult<
105
+ typeof nodeHealthAggregatedFields
114
106
  >;
115
107
 
116
108
  // ============================================================================
@@ -136,9 +128,9 @@ export class NodeHealthCollector implements CollectorStrategy<
136
128
 
137
129
  config = new Versioned({ version: 1, schema: nodeHealthConfigSchema });
138
130
  result = new Versioned({ version: 1, schema: nodeHealthResultSchema });
139
- aggregatedResult = new Versioned({
131
+ aggregatedResult = new VersionedAggregated({
140
132
  version: 1,
141
- schema: nodeHealthAggregatedSchema,
133
+ fields: nodeHealthAggregatedFields,
142
134
  });
143
135
 
144
136
  async execute({
@@ -294,28 +286,19 @@ export class NodeHealthCollector implements CollectorStrategy<
294
286
  ): NodeHealthAggregatedResult {
295
287
  const metadata = run.metadata;
296
288
 
297
- const onlineNodesState = mergeAverage(
298
- existing?._onlineNodes as AverageState | undefined,
299
- metadata?.onlineNodes,
300
- );
301
-
302
- const utilizationState = mergeAverage(
303
- existing?._utilization as AverageState | undefined,
304
- metadata?.executorUtilization,
305
- );
306
-
307
- const minOnlineNodesState = mergeMinMax(
308
- existing?._minOnlineNodes as MinMaxState | undefined,
309
- metadata?.onlineNodes,
310
- );
311
-
312
289
  return {
313
- avgOnlineNodes: onlineNodesState.avg,
314
- avgUtilization: utilizationState.avg,
315
- minOnlineNodes: minOnlineNodesState.min,
316
- _onlineNodes: onlineNodesState,
317
- _utilization: utilizationState,
318
- _minOnlineNodes: minOnlineNodesState,
290
+ avgOnlineNodes: mergeAverage(
291
+ existing?.avgOnlineNodes,
292
+ metadata?.onlineNodes,
293
+ ),
294
+ avgUtilization: mergeAverage(
295
+ existing?.avgUtilization,
296
+ metadata?.executorUtilization,
297
+ ),
298
+ minOnlineNodes: mergeMinMax(
299
+ existing?.minOnlineNodes,
300
+ metadata?.onlineNodes,
301
+ ),
319
302
  };
320
303
  }
321
304
  }
@@ -107,8 +107,8 @@ describe("QueueInfoCollector", () => {
107
107
  let aggregated = collector.mergeResult(undefined, runs[0]);
108
108
  aggregated = collector.mergeResult(aggregated, runs[1]);
109
109
 
110
- expect(aggregated.avgQueueLength).toBe(4);
111
- expect(aggregated.maxQueueLength).toBe(5);
112
- expect(aggregated.avgWaitTime).toBe(15000);
110
+ expect(aggregated.avgQueueLength.avg).toBe(4);
111
+ expect(aggregated.maxQueueLength.max).toBe(5);
112
+ expect(aggregated.avgWaitTime.avg).toBe(15000);
113
113
  });
114
114
  });
@@ -6,10 +6,10 @@ import {
6
6
  type CollectorStrategy,
7
7
  mergeAverage,
8
8
  mergeMinMax,
9
- averageStateSchema,
10
- minMaxStateSchema,
11
- type AverageState,
12
- type MinMaxState,
9
+ VersionedAggregated,
10
+ aggregatedAverage,
11
+ aggregatedMinMax,
12
+ type InferAggregatedResult,
13
13
  } from "@checkstack/backend-api";
14
14
  import { healthResultNumber } from "@checkstack/healthcheck-common";
15
15
  import { pluginMetadata } from "../plugin-metadata";
@@ -58,34 +58,26 @@ const queueInfoResultSchema = z.object({
58
58
 
59
59
  export type QueueInfoResult = z.infer<typeof queueInfoResultSchema>;
60
60
 
61
- const queueInfoAggregatedDisplaySchema = z.object({
62
- avgQueueLength: healthResultNumber({
61
+ // Aggregated result fields definition
62
+ const queueInfoAggregatedFields = {
63
+ avgQueueLength: aggregatedAverage({
63
64
  "x-chart-type": "line",
64
65
  "x-chart-label": "Avg Queue Length",
65
66
  }),
66
- maxQueueLength: healthResultNumber({
67
+ maxQueueLength: aggregatedMinMax({
67
68
  "x-chart-type": "line",
68
69
  "x-chart-label": "Max Queue Length",
69
70
  }),
70
- avgWaitTime: healthResultNumber({
71
+ avgWaitTime: aggregatedAverage({
71
72
  "x-chart-type": "line",
72
73
  "x-chart-label": "Avg Wait Time",
73
74
  "x-chart-unit": "ms",
74
75
  }),
75
- });
76
-
77
- const queueInfoAggregatedInternalSchema = z.object({
78
- _queueLength: averageStateSchema.optional(),
79
- _maxQueueLength: minMaxStateSchema.optional(),
80
- _waitTime: averageStateSchema.optional(),
81
- });
82
-
83
- const queueInfoAggregatedSchema = queueInfoAggregatedDisplaySchema.merge(
84
- queueInfoAggregatedInternalSchema,
85
- );
76
+ };
86
77
 
87
- export type QueueInfoAggregatedResult = z.infer<
88
- typeof queueInfoAggregatedSchema
78
+ // Type inferred from field definitions
79
+ export type QueueInfoAggregatedResult = InferAggregatedResult<
80
+ typeof queueInfoAggregatedFields
89
81
  >;
90
82
 
91
83
  // ============================================================================
@@ -110,9 +102,9 @@ export class QueueInfoCollector implements CollectorStrategy<
110
102
 
111
103
  config = new Versioned({ version: 1, schema: queueInfoConfigSchema });
112
104
  result = new Versioned({ version: 1, schema: queueInfoResultSchema });
113
- aggregatedResult = new Versioned({
105
+ aggregatedResult = new VersionedAggregated({
114
106
  version: 1,
115
- schema: queueInfoAggregatedSchema,
107
+ fields: queueInfoAggregatedFields,
116
108
  });
117
109
 
118
110
  async execute({
@@ -206,28 +198,16 @@ export class QueueInfoCollector implements CollectorStrategy<
206
198
  ): QueueInfoAggregatedResult {
207
199
  const metadata = run.metadata;
208
200
 
209
- const queueLengthState = mergeAverage(
210
- existing?._queueLength as AverageState | undefined,
211
- metadata?.queueLength,
212
- );
213
-
214
- const maxQueueLengthState = mergeMinMax(
215
- existing?._maxQueueLength as MinMaxState | undefined,
216
- metadata?.queueLength,
217
- );
218
-
219
- const waitTimeState = mergeAverage(
220
- existing?._waitTime as AverageState | undefined,
221
- metadata?.avgWaitingMs,
222
- );
223
-
224
201
  return {
225
- avgQueueLength: queueLengthState.avg,
226
- maxQueueLength: maxQueueLengthState.max,
227
- avgWaitTime: waitTimeState.avg,
228
- _queueLength: queueLengthState,
229
- _maxQueueLength: maxQueueLengthState,
230
- _waitTime: waitTimeState,
202
+ avgQueueLength: mergeAverage(
203
+ existing?.avgQueueLength,
204
+ metadata?.queueLength,
205
+ ),
206
+ maxQueueLength: mergeMinMax(
207
+ existing?.maxQueueLength,
208
+ metadata?.queueLength,
209
+ ),
210
+ avgWaitTime: mergeAverage(existing?.avgWaitTime, metadata?.avgWaitingMs),
231
211
  };
232
212
  }
233
213
  }
@@ -9,7 +9,7 @@ describe("ServerInfoCollector", () => {
9
9
  const collector = new ServerInfoCollector();
10
10
 
11
11
  const createMockClient = (
12
- response: JenkinsResponse
12
+ response: JenkinsResponse,
13
13
  ): JenkinsTransportClient => ({
14
14
  exec: async () => response,
15
15
  });
@@ -83,9 +83,9 @@ describe("ServerInfoCollector", () => {
83
83
  ];
84
84
 
85
85
  let aggregated = collector.mergeResult(undefined, runs[0]);
86
- aggregated = collector.mergeResult(aggregated, runs[1]);
86
+ aggregated = collector.mergeResult(aggregated, runs[1]);
87
87
 
88
- expect(aggregated.avgExecutors).toBe(5);
89
- expect(aggregated.avgTotalJobs).toBe(11);
88
+ expect(aggregated.avgExecutors.avg).toBe(5);
89
+ expect(aggregated.avgTotalJobs.avg).toBe(11);
90
90
  });
91
91
  });
@@ -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,
@@ -57,28 +58,21 @@ const serverInfoResultSchema = z.object({
57
58
 
58
59
  export type ServerInfoResult = z.infer<typeof serverInfoResultSchema>;
59
60
 
60
- const serverInfoAggregatedDisplaySchema = z.object({
61
- avgExecutors: healthResultNumber({
61
+ // Aggregated result fields definition
62
+ const serverInfoAggregatedFields = {
63
+ avgExecutors: aggregatedAverage({
62
64
  "x-chart-type": "line",
63
65
  "x-chart-label": "Avg Executors",
64
66
  }),
65
- avgTotalJobs: healthResultNumber({
67
+ avgTotalJobs: aggregatedAverage({
66
68
  "x-chart-type": "line",
67
69
  "x-chart-label": "Avg Jobs",
68
70
  }),
69
- });
70
-
71
- const serverInfoAggregatedInternalSchema = z.object({
72
- _executors: averageStateSchema.optional(),
73
- _jobs: averageStateSchema.optional(),
74
- });
71
+ };
75
72
 
76
- const serverInfoAggregatedSchema = serverInfoAggregatedDisplaySchema.merge(
77
- serverInfoAggregatedInternalSchema,
78
- );
79
-
80
- export type ServerInfoAggregatedResult = z.infer<
81
- typeof serverInfoAggregatedSchema
73
+ // Type inferred from field definitions
74
+ export type ServerInfoAggregatedResult = InferAggregatedResult<
75
+ typeof serverInfoAggregatedFields
82
76
  >;
83
77
 
84
78
  // ============================================================================
@@ -103,9 +97,9 @@ export class ServerInfoCollector implements CollectorStrategy<
103
97
 
104
98
  config = new Versioned({ version: 1, schema: serverInfoConfigSchema });
105
99
  result = new Versioned({ version: 1, schema: serverInfoResultSchema });
106
- aggregatedResult = new Versioned({
100
+ aggregatedResult = new VersionedAggregated({
107
101
  version: 1,
108
- schema: serverInfoAggregatedSchema,
102
+ fields: serverInfoAggregatedFields,
109
103
  });
110
104
 
111
105
  async execute({
@@ -159,21 +153,12 @@ export class ServerInfoCollector implements CollectorStrategy<
159
153
  ): ServerInfoAggregatedResult {
160
154
  const metadata = run.metadata;
161
155
 
162
- const executorsState = mergeAverage(
163
- existing?._executors as AverageState | undefined,
164
- metadata?.numExecutors,
165
- );
166
-
167
- const jobsState = mergeAverage(
168
- existing?._jobs as AverageState | undefined,
169
- metadata?.totalJobs,
170
- );
171
-
172
156
  return {
173
- avgExecutors: executorsState.avg,
174
- avgTotalJobs: jobsState.avg,
175
- _executors: executorsState,
176
- _jobs: jobsState,
157
+ avgExecutors: mergeAverage(
158
+ existing?.avgExecutors,
159
+ metadata?.numExecutors,
160
+ ),
161
+ avgTotalJobs: mergeAverage(existing?.avgTotalJobs, metadata?.totalJobs),
177
162
  };
178
163
  }
179
164
  }
@@ -184,9 +184,9 @@ describe("JenkinsHealthCheckStrategy", () => {
184
184
  aggregated = strategy.mergeResult(aggregated, runs[1]);
185
185
  aggregated = strategy.mergeResult(aggregated, runs[2]);
186
186
 
187
- expect(aggregated.successRate).toBe(67); // 2/3
188
- expect(aggregated.avgResponseTimeMs).toBe(175); // (150+200)/2
189
- expect(aggregated.errorCount).toBe(1);
187
+ expect(aggregated.successRate.rate).toBe(67); // 2/3
188
+ expect(aggregated.avgResponseTimeMs.avg).toBe(175); // (150+200)/2
189
+ expect(aggregated.errorCount.count).toBe(1);
190
190
  });
191
191
 
192
192
  it("should handle single run", () => {
@@ -197,9 +197,9 @@ describe("JenkinsHealthCheckStrategy", () => {
197
197
  };
198
198
  const aggregated = strategy.mergeResult(undefined, run);
199
199
 
200
- expect(aggregated.successRate).toBe(100);
201
- expect(aggregated.avgResponseTimeMs).toBe(150);
202
- expect(aggregated.errorCount).toBe(0);
200
+ expect(aggregated.successRate.rate).toBe(100);
201
+ expect(aggregated.avgResponseTimeMs.avg).toBe(150);
202
+ expect(aggregated.errorCount.count).toBe(0);
203
203
  });
204
204
  });
205
205
  });
package/src/strategy.ts CHANGED
@@ -2,19 +2,18 @@ import {
2
2
  HealthCheckStrategy,
3
3
  HealthCheckRunForAggregation,
4
4
  Versioned,
5
+ VersionedAggregated,
6
+ aggregatedAverage,
7
+ aggregatedRate,
8
+ aggregatedCounter,
9
+ mergeAverage,
10
+ mergeRate,
11
+ mergeCounter,
5
12
  z,
6
13
  configString,
7
14
  configNumber,
8
15
  type ConnectedClient,
9
- mergeAverage,
10
- averageStateSchema,
11
- mergeRate,
12
- rateStateSchema,
13
- mergeCounter,
14
- counterStateSchema,
15
- type AverageState,
16
- type RateState,
17
- type CounterState,
16
+ type InferAggregatedResult,
18
17
  } from "@checkstack/backend-api";
19
18
  import {
20
19
  healthResultNumber,
@@ -75,37 +74,27 @@ const jenkinsResultSchema = healthResultSchema({
75
74
 
76
75
  type JenkinsResult = z.infer<typeof jenkinsResultSchema>;
77
76
 
78
- /** Aggregated metadata for buckets */
79
- const jenkinsAggregatedDisplaySchema = healthResultSchema({
80
- successRate: healthResultNumber({
77
+ /** Aggregated field definitions for bucket merging */
78
+ const jenkinsAggregatedFields = {
79
+ successRate: aggregatedRate({
81
80
  "x-chart-type": "gauge",
82
81
  "x-chart-label": "Success Rate",
83
82
  "x-chart-unit": "%",
84
83
  }),
85
- avgResponseTimeMs: healthResultNumber({
84
+ avgResponseTimeMs: aggregatedAverage({
86
85
  "x-chart-type": "line",
87
86
  "x-chart-label": "Avg Response Time",
88
87
  "x-chart-unit": "ms",
89
88
  }),
90
- errorCount: healthResultNumber({
89
+ errorCount: aggregatedCounter({
91
90
  "x-chart-type": "counter",
92
91
  "x-chart-label": "Errors",
93
92
  }),
94
- });
93
+ };
95
94
 
96
- const jenkinsAggregatedInternalSchema = z.object({
97
- _responseTime: averageStateSchema
98
- .optional(),
99
- _success: rateStateSchema
100
- .optional(),
101
- _errors: counterStateSchema.optional(),
102
- });
103
-
104
- const jenkinsAggregatedSchema = jenkinsAggregatedDisplaySchema.merge(
105
- jenkinsAggregatedInternalSchema,
106
- );
107
-
108
- type JenkinsAggregatedResult = z.infer<typeof jenkinsAggregatedSchema>;
95
+ type JenkinsAggregatedResult = InferAggregatedResult<
96
+ typeof jenkinsAggregatedFields
97
+ >;
109
98
 
110
99
  // ============================================================================
111
100
  // STRATEGY
@@ -115,7 +104,7 @@ export class JenkinsHealthCheckStrategy implements HealthCheckStrategy<
115
104
  JenkinsConfig,
116
105
  JenkinsTransportClient,
117
106
  JenkinsResult,
118
- JenkinsAggregatedResult
107
+ typeof jenkinsAggregatedFields
119
108
  > {
120
109
  id = "jenkins";
121
110
  displayName = "Jenkins Health Check";
@@ -131,9 +120,9 @@ export class JenkinsHealthCheckStrategy implements HealthCheckStrategy<
131
120
  schema: jenkinsResultSchema,
132
121
  });
133
122
 
134
- aggregatedResult: Versioned<JenkinsAggregatedResult> = new Versioned({
123
+ aggregatedResult = new VersionedAggregated({
135
124
  version: 1,
136
- schema: jenkinsAggregatedSchema,
125
+ fields: jenkinsAggregatedFields,
137
126
  });
138
127
 
139
128
  /**
@@ -224,28 +213,17 @@ export class JenkinsHealthCheckStrategy implements HealthCheckStrategy<
224
213
  ): JenkinsAggregatedResult {
225
214
  const metadata = run.metadata;
226
215
 
227
- const responseTimeState = mergeAverage(
228
- existing?._responseTime as AverageState | undefined,
216
+ const avgResponseTimeMs = mergeAverage(
217
+ existing?.avgResponseTimeMs,
229
218
  metadata?.responseTimeMs,
230
219
  );
231
220
 
232
- const successState = mergeRate(
233
- existing?._success as RateState | undefined,
234
- metadata?.connected,
235
- );
221
+ const isSuccess = metadata?.connected ?? false;
222
+ const successRate = mergeRate(existing?.successRate, isSuccess);
236
223
 
237
- const errorState = mergeCounter(
238
- existing?._errors as CounterState | undefined,
239
- metadata?.error !== undefined,
240
- );
224
+ const hasError = metadata?.error !== undefined;
225
+ const errorCount = mergeCounter(existing?.errorCount, hasError);
241
226
 
242
- return {
243
- successRate: successState.rate,
244
- avgResponseTimeMs: responseTimeState.avg,
245
- errorCount: errorState.count,
246
- _responseTime: responseTimeState,
247
- _success: successState,
248
- _errors: errorState,
249
- };
227
+ return { successRate, avgResponseTimeMs, errorCount };
250
228
  }
251
229
  }