@checkstack/healthcheck-dns-backend 0.1.13 → 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 +54 -0
- package/package.json +6 -6
- package/src/lookup-collector.test.ts +13 -7
- package/src/lookup-collector.ts +34 -34
- package/src/strategy.test.ts +23 -19
- package/src/strategy.ts +44 -48
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @checkstack/healthcheck-dns-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
|
+
|
|
27
|
+
## 0.1.14
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- 48c2080: Migrate aggregation from batch to incremental (`mergeResult`)
|
|
32
|
+
|
|
33
|
+
### Breaking Changes (Internal)
|
|
34
|
+
|
|
35
|
+
- Replaced `aggregateResult(runs[])` with `mergeResult(existing, run)` interface across all HealthCheckStrategy and CollectorStrategy implementations
|
|
36
|
+
|
|
37
|
+
### New Features
|
|
38
|
+
|
|
39
|
+
- Added incremental aggregation utilities in `@checkstack/backend-api`:
|
|
40
|
+
- `mergeCounter()` - track occurrences
|
|
41
|
+
- `mergeAverage()` - track sum/count, compute avg
|
|
42
|
+
- `mergeRate()` - track success/total, compute %
|
|
43
|
+
- `mergeMinMax()` - track min/max values
|
|
44
|
+
- Exported Zod schemas for internal state: `averageStateSchema`, `rateStateSchema`, `minMaxStateSchema`, `counterStateSchema`
|
|
45
|
+
|
|
46
|
+
### Improvements
|
|
47
|
+
|
|
48
|
+
- Enables O(1) storage overhead by maintaining incremental aggregation state
|
|
49
|
+
- Prepares for real-time hourly aggregation without batch accumulation
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [f676e11]
|
|
52
|
+
- Updated dependencies [48c2080]
|
|
53
|
+
- @checkstack/common@0.6.2
|
|
54
|
+
- @checkstack/backend-api@0.6.0
|
|
55
|
+
- @checkstack/healthcheck-common@0.8.2
|
|
56
|
+
|
|
3
57
|
## 0.1.13
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-dns-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/backend-api": "0.5.
|
|
13
|
-
"@checkstack/common": "0.6.
|
|
14
|
-
"@checkstack/healthcheck-common": "0.
|
|
12
|
+
"@checkstack/backend-api": "0.5.2",
|
|
13
|
+
"@checkstack/common": "0.6.1",
|
|
14
|
+
"@checkstack/healthcheck-common": "0.8.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/bun": "^1.0.0",
|
|
18
18
|
"typescript": "^5.0.0",
|
|
19
|
-
"@checkstack/tsconfig": "0.0.
|
|
20
|
-
"@checkstack/scripts": "0.1.
|
|
19
|
+
"@checkstack/tsconfig": "0.0.3",
|
|
20
|
+
"@checkstack/scripts": "0.1.1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -4,7 +4,9 @@ import type { DnsTransportClient } from "./transport-client";
|
|
|
4
4
|
|
|
5
5
|
describe("LookupCollector", () => {
|
|
6
6
|
const createMockClient = (
|
|
7
|
-
response: { values: string[]; error?: string } = {
|
|
7
|
+
response: { values: string[]; error?: string } = {
|
|
8
|
+
values: ["192.168.1.1"],
|
|
9
|
+
},
|
|
8
10
|
): DnsTransportClient => ({
|
|
9
11
|
exec: mock(() => Promise.resolve(response)),
|
|
10
12
|
});
|
|
@@ -59,7 +61,7 @@ describe("LookupCollector", () => {
|
|
|
59
61
|
});
|
|
60
62
|
});
|
|
61
63
|
|
|
62
|
-
describe("
|
|
64
|
+
describe("mergeResult", () => {
|
|
63
65
|
it("should calculate average resolution time", () => {
|
|
64
66
|
const collector = new LookupCollector();
|
|
65
67
|
const runs = [
|
|
@@ -89,10 +91,12 @@ describe("LookupCollector", () => {
|
|
|
89
91
|
},
|
|
90
92
|
];
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
// Merge runs incrementally
|
|
95
|
+
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
96
|
+
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
93
97
|
|
|
94
|
-
expect(aggregated.avgResolutionTimeMs).toBe(75);
|
|
95
|
-
expect(aggregated.successRate).toBe(100);
|
|
98
|
+
expect(aggregated.avgResolutionTimeMs.avg).toBe(75);
|
|
99
|
+
expect(aggregated.successRate.rate).toBe(100);
|
|
96
100
|
});
|
|
97
101
|
|
|
98
102
|
it("should calculate success rate correctly", () => {
|
|
@@ -120,9 +124,11 @@ describe("LookupCollector", () => {
|
|
|
120
124
|
},
|
|
121
125
|
];
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
// Merge runs incrementally
|
|
128
|
+
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
129
|
+
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
124
130
|
|
|
125
|
-
expect(aggregated.successRate).toBe(50);
|
|
131
|
+
expect(aggregated.successRate.rate).toBe(50);
|
|
126
132
|
});
|
|
127
133
|
});
|
|
128
134
|
|
package/src/lookup-collector.ts
CHANGED
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
type HealthCheckRunForAggregation,
|
|
5
5
|
type CollectorResult,
|
|
6
6
|
type CollectorStrategy,
|
|
7
|
+
mergeAverage,
|
|
8
|
+
mergeRate,
|
|
9
|
+
VersionedAggregated,
|
|
10
|
+
aggregatedAverage,
|
|
11
|
+
aggregatedRate,
|
|
12
|
+
type InferAggregatedResult,
|
|
7
13
|
} from "@checkstack/backend-api";
|
|
8
14
|
import {
|
|
9
15
|
healthResultNumber,
|
|
@@ -50,20 +56,24 @@ const lookupResultSchema = healthResultSchema({
|
|
|
50
56
|
|
|
51
57
|
export type LookupResult = z.infer<typeof lookupResultSchema>;
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
// Aggregated result fields definition
|
|
60
|
+
const lookupAggregatedFields = {
|
|
61
|
+
avgResolutionTimeMs: aggregatedAverage({
|
|
55
62
|
"x-chart-type": "line",
|
|
56
63
|
"x-chart-label": "Avg Resolution Time",
|
|
57
64
|
"x-chart-unit": "ms",
|
|
58
65
|
}),
|
|
59
|
-
successRate:
|
|
66
|
+
successRate: aggregatedRate({
|
|
60
67
|
"x-chart-type": "gauge",
|
|
61
68
|
"x-chart-label": "Success Rate",
|
|
62
69
|
"x-chart-unit": "%",
|
|
63
70
|
}),
|
|
64
|
-
}
|
|
71
|
+
};
|
|
65
72
|
|
|
66
|
-
|
|
73
|
+
// Type inferred from field definitions
|
|
74
|
+
export type LookupAggregatedResult = InferAggregatedResult<
|
|
75
|
+
typeof lookupAggregatedFields
|
|
76
|
+
>;
|
|
67
77
|
|
|
68
78
|
// ============================================================================
|
|
69
79
|
// LOOKUP COLLECTOR
|
|
@@ -73,15 +83,12 @@ export type LookupAggregatedResult = z.infer<typeof lookupAggregatedSchema>;
|
|
|
73
83
|
* Built-in DNS lookup collector.
|
|
74
84
|
* Resolves DNS records and checks results.
|
|
75
85
|
*/
|
|
76
|
-
export class LookupCollector
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
LookupAggregatedResult
|
|
83
|
-
>
|
|
84
|
-
{
|
|
86
|
+
export class LookupCollector implements CollectorStrategy<
|
|
87
|
+
DnsTransportClient,
|
|
88
|
+
LookupConfig,
|
|
89
|
+
LookupResult,
|
|
90
|
+
LookupAggregatedResult
|
|
91
|
+
> {
|
|
85
92
|
id = "lookup";
|
|
86
93
|
displayName = "DNS Lookup";
|
|
87
94
|
description = "Resolve DNS records and check the results";
|
|
@@ -92,9 +99,9 @@ export class LookupCollector
|
|
|
92
99
|
|
|
93
100
|
config = new Versioned({ version: 1, schema: lookupConfigSchema });
|
|
94
101
|
result = new Versioned({ version: 1, schema: lookupResultSchema });
|
|
95
|
-
aggregatedResult = new
|
|
102
|
+
aggregatedResult = new VersionedAggregated({
|
|
96
103
|
version: 1,
|
|
97
|
-
|
|
104
|
+
fields: lookupAggregatedFields,
|
|
98
105
|
});
|
|
99
106
|
|
|
100
107
|
async execute({
|
|
@@ -124,28 +131,21 @@ export class LookupCollector
|
|
|
124
131
|
};
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
mergeResult(
|
|
135
|
+
existing: LookupAggregatedResult | undefined,
|
|
136
|
+
run: HealthCheckRunForAggregation<LookupResult>,
|
|
129
137
|
): LookupAggregatedResult {
|
|
130
|
-
const
|
|
131
|
-
.map((r) => r.metadata?.resolutionTimeMs)
|
|
132
|
-
.filter((v): v is number => typeof v === "number");
|
|
133
|
-
|
|
134
|
-
const recordCounts = runs
|
|
135
|
-
.map((r) => r.metadata?.recordCount)
|
|
136
|
-
.filter((v): v is number => typeof v === "number");
|
|
138
|
+
const metadata = run.metadata;
|
|
137
139
|
|
|
138
|
-
|
|
140
|
+
// Merge success rate (recordCount > 0 means success)
|
|
141
|
+
const isSuccess = (metadata?.recordCount ?? 0) > 0;
|
|
139
142
|
|
|
140
143
|
return {
|
|
141
|
-
avgResolutionTimeMs:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
successRate:
|
|
146
|
-
recordCounts.length > 0
|
|
147
|
-
? Math.round((successCount / recordCounts.length) * 100)
|
|
148
|
-
: 0,
|
|
144
|
+
avgResolutionTimeMs: mergeAverage(
|
|
145
|
+
existing?.avgResolutionTimeMs,
|
|
146
|
+
metadata?.resolutionTimeMs,
|
|
147
|
+
),
|
|
148
|
+
successRate: mergeRate(existing?.successRate, isSuccess),
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
15
15
|
resolveMx?: { priority: number; exchange: string }[] | Error;
|
|
16
16
|
resolveTxt?: string[][] | Error;
|
|
17
17
|
resolveNs?: string[] | Error;
|
|
18
|
-
} = {}
|
|
18
|
+
} = {},
|
|
19
19
|
): ResolverFactory => {
|
|
20
20
|
return () =>
|
|
21
21
|
({
|
|
@@ -23,34 +23,34 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
23
23
|
resolve4: mock(() =>
|
|
24
24
|
config.resolve4 instanceof Error
|
|
25
25
|
? Promise.reject(config.resolve4)
|
|
26
|
-
: Promise.resolve(config.resolve4 ?? [])
|
|
26
|
+
: Promise.resolve(config.resolve4 ?? []),
|
|
27
27
|
),
|
|
28
28
|
resolve6: mock(() =>
|
|
29
29
|
config.resolve6 instanceof Error
|
|
30
30
|
? Promise.reject(config.resolve6)
|
|
31
|
-
: Promise.resolve(config.resolve6 ?? [])
|
|
31
|
+
: Promise.resolve(config.resolve6 ?? []),
|
|
32
32
|
),
|
|
33
33
|
resolveCname: mock(() =>
|
|
34
34
|
config.resolveCname instanceof Error
|
|
35
35
|
? Promise.reject(config.resolveCname)
|
|
36
|
-
: Promise.resolve(config.resolveCname ?? [])
|
|
36
|
+
: Promise.resolve(config.resolveCname ?? []),
|
|
37
37
|
),
|
|
38
38
|
resolveMx: mock(() =>
|
|
39
39
|
config.resolveMx instanceof Error
|
|
40
40
|
? Promise.reject(config.resolveMx)
|
|
41
|
-
: Promise.resolve(config.resolveMx ?? [])
|
|
41
|
+
: Promise.resolve(config.resolveMx ?? []),
|
|
42
42
|
),
|
|
43
43
|
resolveTxt: mock(() =>
|
|
44
44
|
config.resolveTxt instanceof Error
|
|
45
45
|
? Promise.reject(config.resolveTxt)
|
|
46
|
-
: Promise.resolve(config.resolveTxt ?? [])
|
|
46
|
+
: Promise.resolve(config.resolveTxt ?? []),
|
|
47
47
|
),
|
|
48
48
|
resolveNs: mock(() =>
|
|
49
49
|
config.resolveNs instanceof Error
|
|
50
50
|
? Promise.reject(config.resolveNs)
|
|
51
|
-
: Promise.resolve(config.resolveNs ?? [])
|
|
51
|
+
: Promise.resolve(config.resolveNs ?? []),
|
|
52
52
|
),
|
|
53
|
-
} as DnsResolver
|
|
53
|
+
}) as DnsResolver;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
describe("createClient", () => {
|
|
@@ -102,7 +102,7 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
102
102
|
describe("client.exec", () => {
|
|
103
103
|
it("should return resolved values for successful A record resolution", async () => {
|
|
104
104
|
const strategy = new DnsHealthCheckStrategy(
|
|
105
|
-
createMockResolver({ resolve4: ["1.2.3.4", "5.6.7.8"] })
|
|
105
|
+
createMockResolver({ resolve4: ["1.2.3.4", "5.6.7.8"] }),
|
|
106
106
|
);
|
|
107
107
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
108
108
|
|
|
@@ -118,7 +118,7 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
118
118
|
|
|
119
119
|
it("should return error for DNS error", async () => {
|
|
120
120
|
const strategy = new DnsHealthCheckStrategy(
|
|
121
|
-
createMockResolver({ resolve4: new Error("NXDOMAIN") })
|
|
121
|
+
createMockResolver({ resolve4: new Error("NXDOMAIN") }),
|
|
122
122
|
);
|
|
123
123
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
124
124
|
|
|
@@ -139,7 +139,7 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
139
139
|
{ priority: 0, exchange: "mail1.example.com" },
|
|
140
140
|
{ priority: 10, exchange: "mail2.example.com" },
|
|
141
141
|
],
|
|
142
|
-
})
|
|
142
|
+
}),
|
|
143
143
|
);
|
|
144
144
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
145
145
|
|
|
@@ -154,7 +154,7 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
describe("
|
|
157
|
+
describe("mergeResult", () => {
|
|
158
158
|
it("should calculate averages correctly", () => {
|
|
159
159
|
const strategy = new DnsHealthCheckStrategy();
|
|
160
160
|
const runs = [
|
|
@@ -184,11 +184,13 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
184
184
|
},
|
|
185
185
|
];
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
// Merge runs incrementally
|
|
188
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
189
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
188
190
|
|
|
189
|
-
expect(aggregated.avgResolutionTime).toBe(15);
|
|
190
|
-
expect(aggregated.failureCount).toBe(0);
|
|
191
|
-
expect(aggregated.errorCount).toBe(0);
|
|
191
|
+
expect(aggregated.avgResolutionTime.avg).toBe(15);
|
|
192
|
+
expect(aggregated.failureCount.count).toBe(0);
|
|
193
|
+
expect(aggregated.errorCount.count).toBe(0);
|
|
192
194
|
});
|
|
193
195
|
|
|
194
196
|
it("should count failures and errors", () => {
|
|
@@ -221,10 +223,12 @@ describe("DnsHealthCheckStrategy", () => {
|
|
|
221
223
|
},
|
|
222
224
|
];
|
|
223
225
|
|
|
224
|
-
|
|
226
|
+
// Merge runs incrementally
|
|
227
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
228
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
225
229
|
|
|
226
|
-
expect(aggregated.errorCount).toBe(1);
|
|
227
|
-
expect(aggregated.failureCount).toBe(2);
|
|
230
|
+
expect(aggregated.errorCount.count).toBe(1);
|
|
231
|
+
expect(aggregated.failureCount.count).toBe(2);
|
|
228
232
|
});
|
|
229
233
|
});
|
|
230
234
|
});
|
package/src/strategy.ts
CHANGED
|
@@ -3,8 +3,14 @@ import {
|
|
|
3
3
|
HealthCheckStrategy,
|
|
4
4
|
HealthCheckRunForAggregation,
|
|
5
5
|
Versioned,
|
|
6
|
+
VersionedAggregated,
|
|
7
|
+
aggregatedCounter,
|
|
8
|
+
aggregatedAverage,
|
|
9
|
+
mergeAverage,
|
|
10
|
+
mergeCounter,
|
|
6
11
|
z,
|
|
7
12
|
type ConnectedClient,
|
|
13
|
+
type InferAggregatedResult,
|
|
8
14
|
} from "@checkstack/backend-api";
|
|
9
15
|
import {
|
|
10
16
|
healthResultNumber,
|
|
@@ -70,26 +76,24 @@ const dnsResultSchema = healthResultSchema({
|
|
|
70
76
|
|
|
71
77
|
type DnsResult = z.infer<typeof dnsResultSchema>;
|
|
72
78
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const dnsAggregatedSchema = healthResultSchema({
|
|
77
|
-
avgResolutionTime: healthResultNumber({
|
|
79
|
+
/** Aggregated field definitions for bucket merging */
|
|
80
|
+
const dnsAggregatedFields = {
|
|
81
|
+
avgResolutionTime: aggregatedAverage({
|
|
78
82
|
"x-chart-type": "line",
|
|
79
83
|
"x-chart-label": "Avg Resolution Time",
|
|
80
84
|
"x-chart-unit": "ms",
|
|
81
85
|
}),
|
|
82
|
-
failureCount:
|
|
86
|
+
failureCount: aggregatedCounter({
|
|
83
87
|
"x-chart-type": "counter",
|
|
84
88
|
"x-chart-label": "Failures",
|
|
85
89
|
}),
|
|
86
|
-
errorCount:
|
|
90
|
+
errorCount: aggregatedCounter({
|
|
87
91
|
"x-chart-type": "counter",
|
|
88
92
|
"x-chart-label": "Errors",
|
|
89
93
|
}),
|
|
90
|
-
}
|
|
94
|
+
};
|
|
91
95
|
|
|
92
|
-
type DnsAggregatedResult =
|
|
96
|
+
type DnsAggregatedResult = InferAggregatedResult<typeof dnsAggregatedFields>;
|
|
93
97
|
|
|
94
98
|
// ============================================================================
|
|
95
99
|
// RESOLVER INTERFACE (for testability)
|
|
@@ -101,7 +105,7 @@ export interface DnsResolver {
|
|
|
101
105
|
resolve6(hostname: string): Promise<string[]>;
|
|
102
106
|
resolveCname(hostname: string): Promise<string[]>;
|
|
103
107
|
resolveMx(
|
|
104
|
-
hostname: string
|
|
108
|
+
hostname: string,
|
|
105
109
|
): Promise<{ priority: number; exchange: string }[]>;
|
|
106
110
|
resolveTxt(hostname: string): Promise<string[][]>;
|
|
107
111
|
resolveNs(hostname: string): Promise<string[]>;
|
|
@@ -116,15 +120,12 @@ const defaultResolverFactory: ResolverFactory = () => new dns.Resolver();
|
|
|
116
120
|
// STRATEGY
|
|
117
121
|
// ============================================================================
|
|
118
122
|
|
|
119
|
-
export class DnsHealthCheckStrategy
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
DnsAggregatedResult
|
|
126
|
-
>
|
|
127
|
-
{
|
|
123
|
+
export class DnsHealthCheckStrategy implements HealthCheckStrategy<
|
|
124
|
+
DnsConfig,
|
|
125
|
+
DnsTransportClient,
|
|
126
|
+
DnsResult,
|
|
127
|
+
typeof dnsAggregatedFields
|
|
128
|
+
> {
|
|
128
129
|
id = "dns";
|
|
129
130
|
displayName = "DNS Health Check";
|
|
130
131
|
description = "DNS record resolution with response validation";
|
|
@@ -164,44 +165,36 @@ export class DnsHealthCheckStrategy
|
|
|
164
165
|
],
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
aggregatedResult
|
|
168
|
+
aggregatedResult = new VersionedAggregated({
|
|
168
169
|
version: 1,
|
|
169
|
-
|
|
170
|
+
fields: dnsAggregatedFields,
|
|
170
171
|
});
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
mergeResult(
|
|
174
|
+
existing: DnsAggregatedResult | undefined,
|
|
175
|
+
run: HealthCheckRunForAggregation<DnsResult>,
|
|
174
176
|
): DnsAggregatedResult {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
if (validRuns.length === 0) {
|
|
178
|
-
return { avgResolutionTime: 0, failureCount: 0, errorCount: 0 };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const resolutionTimes = validRuns
|
|
182
|
-
.map((r) => r.metadata?.resolutionTimeMs)
|
|
183
|
-
.filter((t): t is number => typeof t === "number");
|
|
177
|
+
const metadata = run.metadata;
|
|
184
178
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
: 0;
|
|
179
|
+
// Merge resolution time average
|
|
180
|
+
const avgResolutionTime = mergeAverage(
|
|
181
|
+
existing?.avgResolutionTime,
|
|
182
|
+
metadata?.resolutionTimeMs,
|
|
183
|
+
);
|
|
191
184
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
185
|
+
// Merge failure count
|
|
186
|
+
const isFailure = metadata?.recordCount === 0;
|
|
187
|
+
const failureCount = mergeCounter(existing?.failureCount, isFailure);
|
|
195
188
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
)
|
|
189
|
+
// Merge error count
|
|
190
|
+
const hasError = metadata?.error !== undefined;
|
|
191
|
+
const errorCount = mergeCounter(existing?.errorCount, hasError);
|
|
199
192
|
|
|
200
193
|
return { avgResolutionTime, failureCount, errorCount };
|
|
201
194
|
}
|
|
202
195
|
|
|
203
196
|
async createClient(
|
|
204
|
-
config: DnsConfig
|
|
197
|
+
config: DnsConfig,
|
|
205
198
|
): Promise<ConnectedClient<DnsTransportClient>> {
|
|
206
199
|
const validatedConfig = this.config.validate(config);
|
|
207
200
|
const resolver = this.resolverFactory();
|
|
@@ -214,14 +207,17 @@ export class DnsHealthCheckStrategy
|
|
|
214
207
|
exec: async (request: DnsLookupRequest): Promise<DnsLookupResult> => {
|
|
215
208
|
const timeout = validatedConfig.timeout;
|
|
216
209
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
217
|
-
setTimeout(
|
|
210
|
+
setTimeout(
|
|
211
|
+
() => reject(new Error("DNS resolution timeout")),
|
|
212
|
+
timeout,
|
|
213
|
+
),
|
|
218
214
|
);
|
|
219
215
|
|
|
220
216
|
try {
|
|
221
217
|
const resolvePromise = this.resolveRecords(
|
|
222
218
|
resolver,
|
|
223
219
|
request.hostname,
|
|
224
|
-
request.recordType
|
|
220
|
+
request.recordType,
|
|
225
221
|
);
|
|
226
222
|
|
|
227
223
|
const values = await Promise.race([resolvePromise, timeoutPromise]);
|
|
@@ -246,7 +242,7 @@ export class DnsHealthCheckStrategy
|
|
|
246
242
|
private async resolveRecords(
|
|
247
243
|
resolver: DnsResolver,
|
|
248
244
|
hostname: string,
|
|
249
|
-
recordType: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS"
|
|
245
|
+
recordType: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS",
|
|
250
246
|
): Promise<string[]> {
|
|
251
247
|
switch (recordType) {
|
|
252
248
|
case "A": {
|