@checkstack/healthcheck-dns-backend 0.0.2

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 ADDED
@@ -0,0 +1,37 @@
1
+ # @checkstack/healthcheck-dns-backend
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
8
+ - Updated dependencies [d20d274]
9
+ - @checkstack/backend-api@0.0.2
10
+ - @checkstack/common@0.0.2
11
+ - @checkstack/healthcheck-common@0.0.2
12
+
13
+ ## 0.0.3
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [b4eb432]
18
+ - Updated dependencies [a65e002]
19
+ - @checkstack/backend-api@1.1.0
20
+ - @checkstack/common@0.2.0
21
+ - @checkstack/healthcheck-common@0.1.1
22
+
23
+ ## 0.0.2
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [ffc28f6]
28
+ - Updated dependencies [4dd644d]
29
+ - Updated dependencies [71275dd]
30
+ - Updated dependencies [ae19ff6]
31
+ - Updated dependencies [0babb9c]
32
+ - Updated dependencies [b55fae6]
33
+ - Updated dependencies [b354ab3]
34
+ - Updated dependencies [81f3f85]
35
+ - @checkstack/common@0.1.0
36
+ - @checkstack/backend-api@1.0.0
37
+ - @checkstack/healthcheck-common@0.1.0
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@checkstack/healthcheck-dns-backend",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "scripts": {
7
+ "typecheck": "tsc --noEmit",
8
+ "lint": "bun run lint:code",
9
+ "lint:code": "eslint . --max-warnings 0"
10
+ },
11
+ "dependencies": {
12
+ "@checkstack/backend-api": "workspace:*",
13
+ "@checkstack/common": "workspace:*",
14
+ "@checkstack/healthcheck-common": "workspace:*"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "^1.0.0",
18
+ "typescript": "^5.0.0",
19
+ "@checkstack/tsconfig": "workspace:*",
20
+ "@checkstack/scripts": "workspace:*"
21
+ }
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ import {
2
+ createBackendPlugin,
3
+ coreServices,
4
+ } from "@checkstack/backend-api";
5
+ import { DnsHealthCheckStrategy } from "./strategy";
6
+ import { pluginMetadata } from "./plugin-metadata";
7
+
8
+ export default createBackendPlugin({
9
+ metadata: pluginMetadata,
10
+ register(env) {
11
+ env.registerInit({
12
+ deps: {
13
+ healthCheckRegistry: coreServices.healthCheckRegistry,
14
+ logger: coreServices.logger,
15
+ },
16
+ init: async ({ healthCheckRegistry, logger }) => {
17
+ logger.debug("🔌 Registering DNS Health Check Strategy...");
18
+ const strategy = new DnsHealthCheckStrategy();
19
+ healthCheckRegistry.register(strategy);
20
+ },
21
+ });
22
+ },
23
+ });
@@ -0,0 +1,8 @@
1
+ import { definePluginMetadata } from "@checkstack/common";
2
+
3
+ /**
4
+ * Plugin metadata for the DNS Health Check backend.
5
+ */
6
+ export const pluginMetadata = definePluginMetadata({
7
+ pluginId: "healthcheck-dns",
8
+ });
@@ -0,0 +1,270 @@
1
+ import { describe, expect, it, mock } from "bun:test";
2
+ import {
3
+ DnsHealthCheckStrategy,
4
+ DnsResolver,
5
+ ResolverFactory,
6
+ } from "./strategy";
7
+
8
+ describe("DnsHealthCheckStrategy", () => {
9
+ // Helper to create mock resolver factory
10
+ const createMockResolver = (
11
+ config: {
12
+ resolve4?: string[] | Error;
13
+ resolve6?: string[] | Error;
14
+ resolveCname?: string[] | Error;
15
+ resolveMx?: { priority: number; exchange: string }[] | Error;
16
+ resolveTxt?: string[][] | Error;
17
+ resolveNs?: string[] | Error;
18
+ } = {}
19
+ ): ResolverFactory => {
20
+ return () =>
21
+ ({
22
+ setServers: mock(() => {}),
23
+ resolve4: mock(() =>
24
+ config.resolve4 instanceof Error
25
+ ? Promise.reject(config.resolve4)
26
+ : Promise.resolve(config.resolve4 ?? [])
27
+ ),
28
+ resolve6: mock(() =>
29
+ config.resolve6 instanceof Error
30
+ ? Promise.reject(config.resolve6)
31
+ : Promise.resolve(config.resolve6 ?? [])
32
+ ),
33
+ resolveCname: mock(() =>
34
+ config.resolveCname instanceof Error
35
+ ? Promise.reject(config.resolveCname)
36
+ : Promise.resolve(config.resolveCname ?? [])
37
+ ),
38
+ resolveMx: mock(() =>
39
+ config.resolveMx instanceof Error
40
+ ? Promise.reject(config.resolveMx)
41
+ : Promise.resolve(config.resolveMx ?? [])
42
+ ),
43
+ resolveTxt: mock(() =>
44
+ config.resolveTxt instanceof Error
45
+ ? Promise.reject(config.resolveTxt)
46
+ : Promise.resolve(config.resolveTxt ?? [])
47
+ ),
48
+ resolveNs: mock(() =>
49
+ config.resolveNs instanceof Error
50
+ ? Promise.reject(config.resolveNs)
51
+ : Promise.resolve(config.resolveNs ?? [])
52
+ ),
53
+ } as DnsResolver);
54
+ };
55
+
56
+ describe("execute", () => {
57
+ it("should return healthy for successful A record resolution", async () => {
58
+ const strategy = new DnsHealthCheckStrategy(
59
+ createMockResolver({ resolve4: ["1.2.3.4", "5.6.7.8"] })
60
+ );
61
+
62
+ const result = await strategy.execute({
63
+ hostname: "example.com",
64
+ recordType: "A",
65
+ timeout: 5000,
66
+ });
67
+
68
+ expect(result.status).toBe("healthy");
69
+ expect(result.metadata?.resolvedValues).toEqual(["1.2.3.4", "5.6.7.8"]);
70
+ expect(result.metadata?.recordCount).toBe(2);
71
+ });
72
+
73
+ it("should return unhealthy for DNS error", async () => {
74
+ const strategy = new DnsHealthCheckStrategy(
75
+ createMockResolver({ resolve4: new Error("NXDOMAIN") })
76
+ );
77
+
78
+ const result = await strategy.execute({
79
+ hostname: "nonexistent.example.com",
80
+ recordType: "A",
81
+ timeout: 5000,
82
+ });
83
+
84
+ expect(result.status).toBe("unhealthy");
85
+ expect(result.message).toContain("NXDOMAIN");
86
+ expect(result.metadata?.error).toBeDefined();
87
+ });
88
+
89
+ it("should pass recordExists assertion when records found", async () => {
90
+ const strategy = new DnsHealthCheckStrategy(
91
+ createMockResolver({ resolve4: ["1.2.3.4"] })
92
+ );
93
+
94
+ const result = await strategy.execute({
95
+ hostname: "example.com",
96
+ recordType: "A",
97
+ timeout: 5000,
98
+ assertions: [{ field: "recordExists", operator: "isTrue" }],
99
+ });
100
+
101
+ expect(result.status).toBe("healthy");
102
+ });
103
+
104
+ it("should fail recordExists assertion when no records", async () => {
105
+ const strategy = new DnsHealthCheckStrategy(
106
+ createMockResolver({ resolve4: [] })
107
+ );
108
+
109
+ const result = await strategy.execute({
110
+ hostname: "example.com",
111
+ recordType: "A",
112
+ timeout: 5000,
113
+ assertions: [{ field: "recordExists", operator: "isTrue" }],
114
+ });
115
+
116
+ expect(result.status).toBe("unhealthy");
117
+ expect(result.message).toContain("Assertion failed");
118
+ });
119
+
120
+ it("should pass recordValue assertion with matching value", async () => {
121
+ const strategy = new DnsHealthCheckStrategy(
122
+ createMockResolver({ resolveCname: ["cdn.example.com"] })
123
+ );
124
+
125
+ const result = await strategy.execute({
126
+ hostname: "example.com",
127
+ recordType: "CNAME",
128
+ timeout: 5000,
129
+ assertions: [
130
+ { field: "recordValue", operator: "contains", value: "cdn" },
131
+ ],
132
+ });
133
+
134
+ expect(result.status).toBe("healthy");
135
+ });
136
+
137
+ it("should pass recordCount assertion", async () => {
138
+ const strategy = new DnsHealthCheckStrategy(
139
+ createMockResolver({ resolve4: ["1.2.3.4", "5.6.7.8", "9.10.11.12"] })
140
+ );
141
+
142
+ const result = await strategy.execute({
143
+ hostname: "example.com",
144
+ recordType: "A",
145
+ timeout: 5000,
146
+ assertions: [
147
+ { field: "recordCount", operator: "greaterThanOrEqual", value: 2 },
148
+ ],
149
+ });
150
+
151
+ expect(result.status).toBe("healthy");
152
+ });
153
+
154
+ it("should resolve MX records correctly", async () => {
155
+ const strategy = new DnsHealthCheckStrategy(
156
+ createMockResolver({
157
+ resolveMx: [
158
+ { priority: 0, exchange: "mail1.example.com" },
159
+ { priority: 10, exchange: "mail2.example.com" },
160
+ ],
161
+ })
162
+ );
163
+
164
+ const result = await strategy.execute({
165
+ hostname: "example.com",
166
+ recordType: "MX",
167
+ timeout: 5000,
168
+ });
169
+
170
+ expect(result.status).toBe("healthy");
171
+ expect(result.metadata?.resolvedValues).toContain("0 mail1.example.com");
172
+ });
173
+
174
+ it("should use custom nameserver when provided", async () => {
175
+ const setServersMock = mock(() => {});
176
+ const strategy = new DnsHealthCheckStrategy(() => ({
177
+ setServers: setServersMock,
178
+ resolve4: mock(() => Promise.resolve(["1.2.3.4"])),
179
+ resolve6: mock(() => Promise.resolve([])),
180
+ resolveCname: mock(() => Promise.resolve([])),
181
+ resolveMx: mock(() => Promise.resolve([])),
182
+ resolveTxt: mock(() => Promise.resolve([])),
183
+ resolveNs: mock(() => Promise.resolve([])),
184
+ }));
185
+
186
+ await strategy.execute({
187
+ hostname: "example.com",
188
+ recordType: "A",
189
+ nameserver: "8.8.8.8",
190
+ timeout: 5000,
191
+ });
192
+
193
+ expect(setServersMock).toHaveBeenCalledWith(["8.8.8.8"]);
194
+ });
195
+ });
196
+
197
+ describe("aggregateResult", () => {
198
+ it("should calculate averages correctly", () => {
199
+ const strategy = new DnsHealthCheckStrategy();
200
+ const runs = [
201
+ {
202
+ id: "1",
203
+ status: "healthy" as const,
204
+ latencyMs: 10,
205
+ checkId: "c1",
206
+ timestamp: new Date(),
207
+ metadata: {
208
+ resolvedValues: ["1.2.3.4"],
209
+ recordCount: 1,
210
+ resolutionTimeMs: 10,
211
+ },
212
+ },
213
+ {
214
+ id: "2",
215
+ status: "healthy" as const,
216
+ latencyMs: 20,
217
+ checkId: "c1",
218
+ timestamp: new Date(),
219
+ metadata: {
220
+ resolvedValues: ["5.6.7.8"],
221
+ recordCount: 1,
222
+ resolutionTimeMs: 20,
223
+ },
224
+ },
225
+ ];
226
+
227
+ const aggregated = strategy.aggregateResult(runs);
228
+
229
+ expect(aggregated.avgResolutionTime).toBe(15);
230
+ expect(aggregated.failureCount).toBe(0);
231
+ expect(aggregated.errorCount).toBe(0);
232
+ });
233
+
234
+ it("should count failures and errors", () => {
235
+ const strategy = new DnsHealthCheckStrategy();
236
+ const runs = [
237
+ {
238
+ id: "1",
239
+ status: "unhealthy" as const,
240
+ latencyMs: 10,
241
+ checkId: "c1",
242
+ timestamp: new Date(),
243
+ metadata: {
244
+ resolvedValues: [],
245
+ recordCount: 0,
246
+ resolutionTimeMs: 10,
247
+ error: "NXDOMAIN",
248
+ },
249
+ },
250
+ {
251
+ id: "2",
252
+ status: "unhealthy" as const,
253
+ latencyMs: 20,
254
+ checkId: "c1",
255
+ timestamp: new Date(),
256
+ metadata: {
257
+ resolvedValues: [],
258
+ recordCount: 0,
259
+ resolutionTimeMs: 20,
260
+ },
261
+ },
262
+ ];
263
+
264
+ const aggregated = strategy.aggregateResult(runs);
265
+
266
+ expect(aggregated.errorCount).toBe(1);
267
+ expect(aggregated.failureCount).toBe(1);
268
+ });
269
+ });
270
+ });
@@ -0,0 +1,314 @@
1
+ import * as dns from "node:dns/promises";
2
+ import {
3
+ HealthCheckStrategy,
4
+ HealthCheckResult,
5
+ HealthCheckRunForAggregation,
6
+ Versioned,
7
+ z,
8
+ booleanField,
9
+ stringField,
10
+ numericField,
11
+ timeThresholdField,
12
+ evaluateAssertions,
13
+ } from "@checkstack/backend-api";
14
+ import {
15
+ healthResultNumber,
16
+ healthResultString,
17
+ } from "@checkstack/healthcheck-common";
18
+
19
+ // ============================================================================
20
+ // SCHEMAS
21
+ // ============================================================================
22
+
23
+ /**
24
+ * Assertion schema for DNS health checks using shared factories.
25
+ */
26
+ const dnsAssertionSchema = z.discriminatedUnion("field", [
27
+ booleanField("recordExists"),
28
+ stringField("recordValue"),
29
+ numericField("recordCount", { min: 0 }),
30
+ timeThresholdField("resolutionTime"),
31
+ ]);
32
+
33
+ export type DnsAssertion = z.infer<typeof dnsAssertionSchema>;
34
+
35
+ /**
36
+ * Configuration schema for DNS health checks.
37
+ */
38
+ export const dnsConfigSchema = z.object({
39
+ hostname: z.string().describe("Hostname to resolve"),
40
+ recordType: z
41
+ .enum(["A", "AAAA", "CNAME", "MX", "TXT", "NS"])
42
+ .default("A")
43
+ .describe("DNS record type to query"),
44
+ nameserver: z
45
+ .string()
46
+ .optional()
47
+ .describe("Custom nameserver (optional, uses system default)"),
48
+ timeout: z
49
+ .number()
50
+ .min(100)
51
+ .default(5000)
52
+ .describe("Timeout in milliseconds"),
53
+ assertions: z
54
+ .array(dnsAssertionSchema)
55
+ .optional()
56
+ .describe("Conditions for validation"),
57
+ });
58
+
59
+ export type DnsConfig = z.infer<typeof dnsConfigSchema>;
60
+
61
+ /**
62
+ * Per-run result metadata.
63
+ */
64
+ const dnsResultSchema = z.object({
65
+ resolvedValues: z.array(z.string()).meta({
66
+ "x-chart-type": "text",
67
+ "x-chart-label": "Resolved Values",
68
+ }),
69
+ recordCount: healthResultNumber({
70
+ "x-chart-type": "counter",
71
+ "x-chart-label": "Record Count",
72
+ }),
73
+ nameserver: healthResultString({
74
+ "x-chart-type": "text",
75
+ "x-chart-label": "Nameserver",
76
+ }).optional(),
77
+ resolutionTimeMs: healthResultNumber({
78
+ "x-chart-type": "line",
79
+ "x-chart-label": "Resolution Time",
80
+ "x-chart-unit": "ms",
81
+ }),
82
+ failedAssertion: dnsAssertionSchema.optional(),
83
+ error: healthResultString({
84
+ "x-chart-type": "status",
85
+ "x-chart-label": "Error",
86
+ }).optional(),
87
+ });
88
+
89
+ export type DnsResult = z.infer<typeof dnsResultSchema>;
90
+
91
+ /**
92
+ * Aggregated metadata for buckets.
93
+ */
94
+ const dnsAggregatedSchema = z.object({
95
+ avgResolutionTime: healthResultNumber({
96
+ "x-chart-type": "line",
97
+ "x-chart-label": "Avg Resolution Time",
98
+ "x-chart-unit": "ms",
99
+ }),
100
+ failureCount: healthResultNumber({
101
+ "x-chart-type": "counter",
102
+ "x-chart-label": "Failures",
103
+ }),
104
+ errorCount: healthResultNumber({
105
+ "x-chart-type": "counter",
106
+ "x-chart-label": "Errors",
107
+ }),
108
+ });
109
+
110
+ export type DnsAggregatedResult = z.infer<typeof dnsAggregatedSchema>;
111
+
112
+ // ============================================================================
113
+ // RESOLVER INTERFACE (for testability)
114
+ // ============================================================================
115
+
116
+ export interface DnsResolver {
117
+ setServers(servers: string[]): void;
118
+ resolve4(hostname: string): Promise<string[]>;
119
+ resolve6(hostname: string): Promise<string[]>;
120
+ resolveCname(hostname: string): Promise<string[]>;
121
+ resolveMx(
122
+ hostname: string
123
+ ): Promise<{ priority: number; exchange: string }[]>;
124
+ resolveTxt(hostname: string): Promise<string[][]>;
125
+ resolveNs(hostname: string): Promise<string[]>;
126
+ }
127
+
128
+ export type ResolverFactory = () => DnsResolver;
129
+
130
+ // Default factory using Node.js dns module
131
+ const defaultResolverFactory: ResolverFactory = () =>
132
+ new dns.Resolver() as DnsResolver;
133
+
134
+ // ============================================================================
135
+ // STRATEGY
136
+ // ============================================================================
137
+
138
+ export class DnsHealthCheckStrategy
139
+ implements HealthCheckStrategy<DnsConfig, DnsResult, DnsAggregatedResult>
140
+ {
141
+ id = "dns";
142
+ displayName = "DNS Health Check";
143
+ description = "DNS record resolution with response validation";
144
+
145
+ // Injected resolver factory for testing
146
+ private resolverFactory: ResolverFactory;
147
+
148
+ constructor(resolverFactory: ResolverFactory = defaultResolverFactory) {
149
+ this.resolverFactory = resolverFactory;
150
+ }
151
+
152
+ config: Versioned<DnsConfig> = new Versioned({
153
+ version: 1,
154
+ schema: dnsConfigSchema,
155
+ });
156
+
157
+ result: Versioned<DnsResult> = new Versioned({
158
+ version: 1,
159
+ schema: dnsResultSchema,
160
+ });
161
+
162
+ aggregatedResult: Versioned<DnsAggregatedResult> = new Versioned({
163
+ version: 1,
164
+ schema: dnsAggregatedSchema,
165
+ });
166
+
167
+ aggregateResult(
168
+ runs: HealthCheckRunForAggregation<DnsResult>[]
169
+ ): DnsAggregatedResult {
170
+ let totalResolutionTime = 0;
171
+ let failureCount = 0;
172
+ let errorCount = 0;
173
+ let validRuns = 0;
174
+
175
+ for (const run of runs) {
176
+ if (run.metadata?.error) {
177
+ errorCount++;
178
+ continue;
179
+ }
180
+ if (run.status === "unhealthy") {
181
+ failureCount++;
182
+ }
183
+ if (run.metadata) {
184
+ totalResolutionTime += run.metadata.resolutionTimeMs;
185
+ validRuns++;
186
+ }
187
+ }
188
+
189
+ return {
190
+ avgResolutionTime: validRuns > 0 ? totalResolutionTime / validRuns : 0,
191
+ failureCount,
192
+ errorCount,
193
+ };
194
+ }
195
+
196
+ async execute(config: DnsConfig): Promise<HealthCheckResult<DnsResult>> {
197
+ const validatedConfig = this.config.validate(config);
198
+ const start = performance.now();
199
+
200
+ try {
201
+ // Configure resolver with custom nameserver if provided
202
+ const resolver = this.resolverFactory();
203
+ if (validatedConfig.nameserver) {
204
+ resolver.setServers([validatedConfig.nameserver]);
205
+ }
206
+
207
+ // Perform DNS lookup based on record type
208
+ const resolvedValues = await this.resolveRecords(
209
+ resolver,
210
+ validatedConfig.hostname,
211
+ validatedConfig.recordType,
212
+ validatedConfig.timeout
213
+ );
214
+
215
+ const end = performance.now();
216
+ const resolutionTimeMs = Math.round(end - start);
217
+
218
+ const result: Omit<DnsResult, "failedAssertion" | "error"> = {
219
+ resolvedValues,
220
+ recordCount: resolvedValues.length,
221
+ nameserver: validatedConfig.nameserver,
222
+ resolutionTimeMs,
223
+ };
224
+
225
+ // Evaluate assertions using shared utility
226
+ const failedAssertion = evaluateAssertions(validatedConfig.assertions, {
227
+ recordExists: resolvedValues.length > 0,
228
+ recordValue: resolvedValues[0] ?? "",
229
+ recordCount: resolvedValues.length,
230
+ resolutionTime: resolutionTimeMs,
231
+ });
232
+
233
+ if (failedAssertion) {
234
+ return {
235
+ status: "unhealthy",
236
+ latencyMs: resolutionTimeMs,
237
+ message: `Assertion failed: ${failedAssertion.field} ${
238
+ failedAssertion.operator
239
+ }${"value" in failedAssertion ? ` ${failedAssertion.value}` : ""}`,
240
+ metadata: { ...result, failedAssertion },
241
+ };
242
+ }
243
+
244
+ return {
245
+ status: "healthy",
246
+ latencyMs: resolutionTimeMs,
247
+ message: `Resolved ${validatedConfig.hostname} (${
248
+ validatedConfig.recordType
249
+ }): ${resolvedValues.slice(0, 3).join(", ")}${
250
+ resolvedValues.length > 3 ? "..." : ""
251
+ }`,
252
+ metadata: result,
253
+ };
254
+ } catch (error: unknown) {
255
+ const end = performance.now();
256
+ const isError = error instanceof Error;
257
+ return {
258
+ status: "unhealthy",
259
+ latencyMs: Math.round(end - start),
260
+ message: isError ? error.message : "DNS resolution failed",
261
+ metadata: {
262
+ resolvedValues: [],
263
+ recordCount: 0,
264
+ nameserver: validatedConfig.nameserver,
265
+ resolutionTimeMs: Math.round(end - start),
266
+ error: isError ? error.name : "UnknownError",
267
+ },
268
+ };
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Resolve DNS records based on type.
274
+ */
275
+ private async resolveRecords(
276
+ resolver: DnsResolver,
277
+ hostname: string,
278
+ recordType: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS",
279
+ timeout: number
280
+ ): Promise<string[]> {
281
+ // Create timeout promise
282
+ const timeoutPromise = new Promise<never>((_, reject) => {
283
+ setTimeout(() => reject(new Error("DNS resolution timeout")), timeout);
284
+ });
285
+
286
+ // Resolve based on record type
287
+ const resolvePromise = (async () => {
288
+ switch (recordType) {
289
+ case "A": {
290
+ return await resolver.resolve4(hostname);
291
+ }
292
+ case "AAAA": {
293
+ return await resolver.resolve6(hostname);
294
+ }
295
+ case "CNAME": {
296
+ return await resolver.resolveCname(hostname);
297
+ }
298
+ case "MX": {
299
+ const records = await resolver.resolveMx(hostname);
300
+ return records.map((r) => `${r.priority} ${r.exchange}`);
301
+ }
302
+ case "TXT": {
303
+ const records = await resolver.resolveTxt(hostname);
304
+ return records.map((r) => r.join(""));
305
+ }
306
+ case "NS": {
307
+ return await resolver.resolveNs(hostname);
308
+ }
309
+ }
310
+ })();
311
+
312
+ return Promise.race([resolvePromise, timeoutPromise]);
313
+ }
314
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/backend.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }