@checkstack/healthcheck-postgres-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/query-collector.test.ts +3 -3
- package/src/query-collector.ts +20 -34
- package/src/strategy.test.ts +5 -5
- package/src/strategy.ts +34 -69
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @checkstack/healthcheck-postgres-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-postgres-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
|
"pg": "^8.11.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -89,8 +89,8 @@ describe("QueryCollector", () => {
|
|
|
89
89
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
90
90
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
91
91
|
|
|
92
|
-
expect(aggregated.avgExecutionTimeMs).toBe(75);
|
|
93
|
-
expect(aggregated.successRate).toBe(100);
|
|
92
|
+
expect(aggregated.avgExecutionTimeMs.avg).toBe(75);
|
|
93
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
it("should calculate success rate correctly", () => {
|
|
@@ -117,7 +117,7 @@ describe("QueryCollector", () => {
|
|
|
117
117
|
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
118
118
|
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
119
119
|
|
|
120
|
-
expect(aggregated.successRate).toBe(50);
|
|
120
|
+
expect(aggregated.successRate.rate).toBe(50);
|
|
121
121
|
});
|
|
122
122
|
});
|
|
123
123
|
|
package/src/query-collector.ts
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
type CollectorStrategy,
|
|
7
7
|
mergeAverage,
|
|
8
8
|
mergeRate,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type
|
|
9
|
+
VersionedAggregated,
|
|
10
|
+
aggregatedAverage,
|
|
11
|
+
aggregatedRate,
|
|
12
|
+
type InferAggregatedResult,
|
|
13
13
|
} from "@checkstack/backend-api";
|
|
14
14
|
import {
|
|
15
15
|
healthResultNumber,
|
|
@@ -51,29 +51,24 @@ const queryResultSchema = healthResultSchema({
|
|
|
51
51
|
|
|
52
52
|
export type QueryResult = z.infer<typeof queryResultSchema>;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// Aggregated result fields definition
|
|
55
|
+
const queryAggregatedFields = {
|
|
56
|
+
avgExecutionTimeMs: aggregatedAverage({
|
|
56
57
|
"x-chart-type": "line",
|
|
57
58
|
"x-chart-label": "Avg Execution Time",
|
|
58
59
|
"x-chart-unit": "ms",
|
|
59
60
|
}),
|
|
60
|
-
successRate:
|
|
61
|
+
successRate: aggregatedRate({
|
|
61
62
|
"x-chart-type": "gauge",
|
|
62
63
|
"x-chart-label": "Success Rate",
|
|
63
64
|
"x-chart-unit": "%",
|
|
64
65
|
}),
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const queryAggregatedInternalSchema = z.object({
|
|
68
|
-
_executionTime: averageStateSchema.optional(),
|
|
69
|
-
_success: rateStateSchema.optional(),
|
|
70
|
-
});
|
|
66
|
+
};
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
export type QueryAggregatedResult = z.infer<typeof queryAggregatedSchema>;
|
|
68
|
+
// Type inferred from field definitions
|
|
69
|
+
export type QueryAggregatedResult = InferAggregatedResult<
|
|
70
|
+
typeof queryAggregatedFields
|
|
71
|
+
>;
|
|
77
72
|
|
|
78
73
|
// ============================================================================
|
|
79
74
|
// QUERY COLLECTOR
|
|
@@ -99,9 +94,9 @@ export class QueryCollector implements CollectorStrategy<
|
|
|
99
94
|
|
|
100
95
|
config = new Versioned({ version: 1, schema: queryConfigSchema });
|
|
101
96
|
result = new Versioned({ version: 1, schema: queryResultSchema });
|
|
102
|
-
aggregatedResult = new
|
|
97
|
+
aggregatedResult = new VersionedAggregated({
|
|
103
98
|
version: 1,
|
|
104
|
-
|
|
99
|
+
fields: queryAggregatedFields,
|
|
105
100
|
});
|
|
106
101
|
|
|
107
102
|
async execute({
|
|
@@ -133,21 +128,12 @@ export class QueryCollector implements CollectorStrategy<
|
|
|
133
128
|
): QueryAggregatedResult {
|
|
134
129
|
const metadata = run.metadata;
|
|
135
130
|
|
|
136
|
-
const executionTimeState = mergeAverage(
|
|
137
|
-
existing?._executionTime as AverageState | undefined,
|
|
138
|
-
metadata?.executionTimeMs,
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const successState = mergeRate(
|
|
142
|
-
existing?._success as RateState | undefined,
|
|
143
|
-
metadata?.success,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
131
|
return {
|
|
147
|
-
avgExecutionTimeMs:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
avgExecutionTimeMs: mergeAverage(
|
|
133
|
+
existing?.avgExecutionTimeMs,
|
|
134
|
+
metadata?.executionTimeMs,
|
|
135
|
+
),
|
|
136
|
+
successRate: mergeRate(existing?.successRate, metadata?.success),
|
|
151
137
|
};
|
|
152
138
|
}
|
|
153
139
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -162,9 +162,9 @@ describe("PostgresHealthCheckStrategy", () => {
|
|
|
162
162
|
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
163
163
|
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
164
164
|
|
|
165
|
-
expect(aggregated.avgConnectionTime).toBe(75);
|
|
166
|
-
expect(aggregated.successRate).toBe(100);
|
|
167
|
-
expect(aggregated.errorCount).toBe(0);
|
|
165
|
+
expect(aggregated.avgConnectionTime.avg).toBe(75);
|
|
166
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
167
|
+
expect(aggregated.errorCount.count).toBe(0);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
it("should count errors", () => {
|
|
@@ -184,8 +184,8 @@ describe("PostgresHealthCheckStrategy", () => {
|
|
|
184
184
|
|
|
185
185
|
const aggregated = strategy.mergeResult(undefined, run);
|
|
186
186
|
|
|
187
|
-
expect(aggregated.errorCount).toBe(1);
|
|
188
|
-
expect(aggregated.successRate).toBe(0);
|
|
187
|
+
expect(aggregated.errorCount.count).toBe(1);
|
|
188
|
+
expect(aggregated.successRate.rate).toBe(0);
|
|
189
189
|
});
|
|
190
190
|
});
|
|
191
191
|
});
|
package/src/strategy.ts
CHANGED
|
@@ -3,23 +3,22 @@ import {
|
|
|
3
3
|
HealthCheckStrategy,
|
|
4
4
|
HealthCheckRunForAggregation,
|
|
5
5
|
Versioned,
|
|
6
|
+
VersionedAggregated,
|
|
7
|
+
aggregatedAverage,
|
|
8
|
+
aggregatedMinMax,
|
|
9
|
+
aggregatedRate,
|
|
10
|
+
aggregatedCounter,
|
|
11
|
+
mergeAverage,
|
|
12
|
+
mergeRate,
|
|
13
|
+
mergeCounter,
|
|
14
|
+
mergeMinMax,
|
|
6
15
|
z,
|
|
7
16
|
configString,
|
|
8
17
|
configNumber,
|
|
9
18
|
configBoolean,
|
|
10
19
|
type ConnectedClient,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
mergeCounter,
|
|
14
|
-
mergeMinMax,
|
|
15
|
-
averageStateSchema,
|
|
16
|
-
minMaxStateSchema,
|
|
17
|
-
rateStateSchema,
|
|
18
|
-
counterStateSchema,
|
|
19
|
-
type AverageState,
|
|
20
|
-
type RateState,
|
|
21
|
-
type CounterState,
|
|
22
|
-
type MinMaxState,
|
|
20
|
+
type InferAggregatedResult,
|
|
21
|
+
baseStrategyConfigSchema,
|
|
23
22
|
} from "@checkstack/backend-api";
|
|
24
23
|
import {
|
|
25
24
|
healthResultBoolean,
|
|
@@ -40,7 +39,7 @@ import type {
|
|
|
40
39
|
/**
|
|
41
40
|
* Configuration schema for PostgreSQL health checks.
|
|
42
41
|
*/
|
|
43
|
-
export const postgresConfigSchema =
|
|
42
|
+
export const postgresConfigSchema = baseStrategyConfigSchema.extend({
|
|
44
43
|
host: configString({}).describe("PostgreSQL server hostname"),
|
|
45
44
|
port: configNumber({})
|
|
46
45
|
.int()
|
|
@@ -52,10 +51,6 @@ export const postgresConfigSchema = z.object({
|
|
|
52
51
|
user: configString({}).describe("Database user"),
|
|
53
52
|
password: configString({ "x-secret": true }).describe("Database password"),
|
|
54
53
|
ssl: configBoolean({}).default(false).describe("Use SSL connection"),
|
|
55
|
-
timeout: configNumber({})
|
|
56
|
-
.min(100)
|
|
57
|
-
.default(10_000)
|
|
58
|
-
.describe("Connection timeout in milliseconds"),
|
|
59
54
|
});
|
|
60
55
|
|
|
61
56
|
export type PostgresConfig = z.infer<typeof postgresConfigSchema>;
|
|
@@ -82,45 +77,32 @@ const postgresResultSchema = healthResultSchema({
|
|
|
82
77
|
|
|
83
78
|
type PostgresResult = z.infer<typeof postgresResultSchema>;
|
|
84
79
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// UI-visible aggregated fields (for charts)
|
|
89
|
-
const postgresAggregatedDisplaySchema = healthResultSchema({
|
|
90
|
-
avgConnectionTime: healthResultNumber({
|
|
80
|
+
/** Aggregated field definitions for bucket merging */
|
|
81
|
+
const postgresAggregatedFields = {
|
|
82
|
+
avgConnectionTime: aggregatedAverage({
|
|
91
83
|
"x-chart-type": "line",
|
|
92
84
|
"x-chart-label": "Avg Connection Time",
|
|
93
85
|
"x-chart-unit": "ms",
|
|
94
86
|
}),
|
|
95
|
-
maxConnectionTime:
|
|
87
|
+
maxConnectionTime: aggregatedMinMax({
|
|
96
88
|
"x-chart-type": "line",
|
|
97
89
|
"x-chart-label": "Max Connection Time",
|
|
98
90
|
"x-chart-unit": "ms",
|
|
99
91
|
}),
|
|
100
|
-
successRate:
|
|
92
|
+
successRate: aggregatedRate({
|
|
101
93
|
"x-chart-type": "gauge",
|
|
102
94
|
"x-chart-label": "Success Rate",
|
|
103
95
|
"x-chart-unit": "%",
|
|
104
96
|
}),
|
|
105
|
-
errorCount:
|
|
97
|
+
errorCount: aggregatedCounter({
|
|
106
98
|
"x-chart-type": "counter",
|
|
107
99
|
"x-chart-label": "Errors",
|
|
108
100
|
}),
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Internal state for incremental aggregation
|
|
112
|
-
const postgresAggregatedInternalSchema = z.object({
|
|
113
|
-
_connectionTime: averageStateSchema.optional(),
|
|
114
|
-
_maxConnectionTime: minMaxStateSchema.optional(),
|
|
115
|
-
_success: rateStateSchema.optional(),
|
|
116
|
-
_errors: counterStateSchema.optional(),
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const postgresAggregatedSchema = postgresAggregatedDisplaySchema.merge(
|
|
120
|
-
postgresAggregatedInternalSchema,
|
|
121
|
-
);
|
|
101
|
+
};
|
|
122
102
|
|
|
123
|
-
type PostgresAggregatedResult =
|
|
103
|
+
type PostgresAggregatedResult = InferAggregatedResult<
|
|
104
|
+
typeof postgresAggregatedFields
|
|
105
|
+
>;
|
|
124
106
|
|
|
125
107
|
// ============================================================================
|
|
126
108
|
// DATABASE CLIENT INTERFACE (for testability)
|
|
@@ -162,7 +144,7 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
162
144
|
PostgresConfig,
|
|
163
145
|
PostgresTransportClient,
|
|
164
146
|
PostgresResult,
|
|
165
|
-
|
|
147
|
+
typeof postgresAggregatedFields
|
|
166
148
|
> {
|
|
167
149
|
id = "postgres";
|
|
168
150
|
displayName = "PostgreSQL Health Check";
|
|
@@ -200,9 +182,9 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
200
182
|
],
|
|
201
183
|
});
|
|
202
184
|
|
|
203
|
-
aggregatedResult
|
|
185
|
+
aggregatedResult = new VersionedAggregated({
|
|
204
186
|
version: 1,
|
|
205
|
-
|
|
187
|
+
fields: postgresAggregatedFields,
|
|
206
188
|
});
|
|
207
189
|
|
|
208
190
|
mergeResult(
|
|
@@ -211,40 +193,23 @@ export class PostgresHealthCheckStrategy implements HealthCheckStrategy<
|
|
|
211
193
|
): PostgresAggregatedResult {
|
|
212
194
|
const metadata = run.metadata;
|
|
213
195
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
existing?._connectionTime as AverageState | undefined,
|
|
196
|
+
const avgConnectionTime = mergeAverage(
|
|
197
|
+
existing?.avgConnectionTime,
|
|
217
198
|
metadata?.connectionTimeMs,
|
|
218
199
|
);
|
|
219
200
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
existing?._maxConnectionTime as MinMaxState | undefined,
|
|
201
|
+
const maxConnectionTime = mergeMinMax(
|
|
202
|
+
existing?.maxConnectionTime,
|
|
223
203
|
metadata?.connectionTimeMs,
|
|
224
204
|
);
|
|
225
205
|
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
existing?._success as RateState | undefined,
|
|
229
|
-
metadata?.connected,
|
|
230
|
-
);
|
|
206
|
+
const isSuccess = metadata?.connected ?? false;
|
|
207
|
+
const successRate = mergeRate(existing?.successRate, isSuccess);
|
|
231
208
|
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
existing?._errors as CounterState | undefined,
|
|
235
|
-
metadata?.error !== undefined,
|
|
236
|
-
);
|
|
209
|
+
const hasError = metadata?.error !== undefined;
|
|
210
|
+
const errorCount = mergeCounter(existing?.errorCount, hasError);
|
|
237
211
|
|
|
238
|
-
return {
|
|
239
|
-
avgConnectionTime: connectionTimeState.avg,
|
|
240
|
-
maxConnectionTime: maxConnectionTimeState.max,
|
|
241
|
-
successRate: successState.rate,
|
|
242
|
-
errorCount: errorState.count,
|
|
243
|
-
_connectionTime: connectionTimeState,
|
|
244
|
-
_maxConnectionTime: maxConnectionTimeState,
|
|
245
|
-
_success: successState,
|
|
246
|
-
_errors: errorState,
|
|
247
|
-
};
|
|
212
|
+
return { avgConnectionTime, maxConnectionTime, successRate, errorCount };
|
|
248
213
|
}
|
|
249
214
|
|
|
250
215
|
async createClient(
|