@dnscmp/core 0.0.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/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @dnscmp/core
2
+
3
+ Library for benchmarking and comparing DNS resolution times across DNS providers.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { dnscmp } from "@dnscmp/core";
9
+
10
+ const results = await dnscmp({
11
+ providers: [
12
+ { name: "Cloudflare", ips: ["1.1.1.1", "1.0.0.1"] },
13
+ { name: "Google", ips: ["8.8.8.8", "8.8.4.4"] },
14
+ ],
15
+ });
16
+
17
+ console.log(results);
18
+ // [
19
+ // { name: "Cloudflare", resolver: "1.1.1.1", avg: 3.42 },
20
+ // { name: "Google", resolver: "8.8.8.8", avg: 7.81 },
21
+ // ]
22
+ ```
23
+
24
+ Results are sorted fastest-first. Providers with multiple IPs are all tested; only the fastest IP per provider is returned. Providers that fail to respond (timeout or 3 consecutive failures) return `avg: null` and are sorted last.
25
+
26
+ ## Build
27
+
28
+ ```bash
29
+ bun run build
30
+ ```
@@ -0,0 +1,8 @@
1
+ import type { DnsProvider, DnsResult } from "@dnscmp/types";
2
+ export type { DnsProvider, DnsResult };
3
+ export interface DnscmpOptions {
4
+ providers: DnsProvider[];
5
+ domains?: string[];
6
+ tries?: number;
7
+ }
8
+ export declare function dnscmp(options: DnscmpOptions): Promise<DnsResult[]>;
package/dist/index.js ADDED
@@ -0,0 +1,71 @@
1
+ // src/index.ts
2
+ import { Resolver } from "dns/promises";
3
+ var DEFAULT_DOMAINS = ["example.com", "example.org", "example.net"];
4
+ var DEFAULT_TRIES = 10;
5
+ var QUERY_TIMEOUT_MS = 1000;
6
+ var MAX_CONSECUTIVE_FAILURES = 3;
7
+ function withTimeout(promise, ms) {
8
+ return Promise.race([
9
+ promise,
10
+ new Promise((_, reject) => setTimeout(() => reject(new Error("ETIMEOUT")), ms))
11
+ ]);
12
+ }
13
+ async function measureAvg(resolverIp, domains, tries) {
14
+ const resolver = new Resolver;
15
+ resolver.setServers([resolverIp]);
16
+ let total = 0;
17
+ let count = 0;
18
+ let consecutiveFailures = 0;
19
+ for (let i = 0;i < tries; i++) {
20
+ for (const domain of domains) {
21
+ try {
22
+ const start = performance.now();
23
+ await withTimeout(resolver.resolve4(domain), QUERY_TIMEOUT_MS);
24
+ total += performance.now() - start;
25
+ count++;
26
+ consecutiveFailures = 0;
27
+ } catch {
28
+ consecutiveFailures++;
29
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
30
+ return null;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return count > 0 ? total / count : null;
36
+ }
37
+ async function measureProvider(provider, domains, tries) {
38
+ const resolverResults = await Promise.all(provider.resolvers.map(async (resolverIp) => ({
39
+ resolverIp,
40
+ avg: await measureAvg(resolverIp, domains, tries)
41
+ })));
42
+ resolverResults.sort((a, b) => {
43
+ if (a.avg === null && b.avg === null)
44
+ return 0;
45
+ if (a.avg === null)
46
+ return 1;
47
+ if (b.avg === null)
48
+ return -1;
49
+ return a.avg - b.avg;
50
+ });
51
+ const best = resolverResults[0];
52
+ return { name: provider.name, resolver: best.resolverIp, avg: best.avg };
53
+ }
54
+ async function dnscmp(options) {
55
+ const domains = options.domains ?? DEFAULT_DOMAINS;
56
+ const tries = options.tries ?? DEFAULT_TRIES;
57
+ const results = await Promise.all(options.providers.map((provider) => measureProvider(provider, domains, tries)));
58
+ results.sort((a, b) => {
59
+ if (a.avg === null && b.avg === null)
60
+ return 0;
61
+ if (a.avg === null)
62
+ return 1;
63
+ if (b.avg === null)
64
+ return -1;
65
+ return a.avg - b.avg;
66
+ });
67
+ return results;
68
+ }
69
+ export {
70
+ dnscmp
71
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@dnscmp/core",
3
+ "description": "Library for benchmarking and comparing DNS resolution times across DNS providers.",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "bun": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src"
16
+ ],
17
+ "homepage": "https://github.com/oBusk/dnscmp#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/oBusk/dnscmp/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/oBusk/dnscmp",
24
+ "directory": "packages/core"
25
+ },
26
+ "license": "MIT",
27
+ "author": "Oscar Busk (https://github.com/oBusk)",
28
+ "scripts": {
29
+ "build": "bun run build.ts"
30
+ },
31
+ "dependencies": {
32
+ "@dnscmp/types": "workspace:*"
33
+ },
34
+ "devDependencies": {
35
+ "@types/bun": "latest",
36
+ "bun-plugin-isolated-decl": "^0.2",
37
+ "oxc-transform": "~0.123",
38
+ "typescript": "^5"
39
+ }
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,99 @@
1
+ import type { DnsProvider, DnsResult } from "@dnscmp/types";
2
+ import { Resolver } from "dns/promises";
3
+
4
+ export type { DnsProvider, DnsResult };
5
+
6
+ const DEFAULT_DOMAINS = ["example.com", "example.org", "example.net"];
7
+ const DEFAULT_TRIES = 10;
8
+ const QUERY_TIMEOUT_MS = 1000;
9
+ const MAX_CONSECUTIVE_FAILURES = 3;
10
+
11
+ export interface DnscmpOptions {
12
+ providers: DnsProvider[];
13
+ domains?: string[];
14
+ tries?: number;
15
+ }
16
+
17
+ function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
18
+ return Promise.race([
19
+ promise,
20
+ new Promise<T>((_, reject) =>
21
+ setTimeout(() => reject(new Error("ETIMEOUT")), ms),
22
+ ),
23
+ ]);
24
+ }
25
+
26
+ async function measureAvg(
27
+ resolverIp: string,
28
+ domains: string[],
29
+ tries: number,
30
+ ): Promise<number | null> {
31
+ const resolver = new Resolver();
32
+ resolver.setServers([resolverIp]);
33
+
34
+ let total = 0;
35
+ let count = 0;
36
+ let consecutiveFailures = 0;
37
+
38
+ for (let i = 0; i < tries; i++) {
39
+ for (const domain of domains) {
40
+ try {
41
+ const start = performance.now();
42
+ await withTimeout(resolver.resolve4(domain), QUERY_TIMEOUT_MS);
43
+ total += performance.now() - start;
44
+ count++;
45
+ consecutiveFailures = 0;
46
+ } catch {
47
+ consecutiveFailures++;
48
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
49
+ return null;
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ return count > 0 ? total / count : null;
56
+ }
57
+
58
+ async function measureProvider(
59
+ provider: DnsProvider,
60
+ domains: string[],
61
+ tries: number,
62
+ ): Promise<DnsResult> {
63
+ const resolverResults = await Promise.all(
64
+ provider.resolvers.map(async (resolverIp) => ({
65
+ resolverIp,
66
+ avg: await measureAvg(resolverIp, domains, tries),
67
+ })),
68
+ );
69
+
70
+ resolverResults.sort((a, b) => {
71
+ if (a.avg === null && b.avg === null) return 0;
72
+ if (a.avg === null) return 1;
73
+ if (b.avg === null) return -1;
74
+ return a.avg - b.avg;
75
+ });
76
+
77
+ const best = resolverResults[0]!;
78
+ return { name: provider.name, resolver: best.resolverIp, avg: best.avg };
79
+ }
80
+
81
+ export async function dnscmp(options: DnscmpOptions): Promise<DnsResult[]> {
82
+ const domains = options.domains ?? DEFAULT_DOMAINS;
83
+ const tries = options.tries ?? DEFAULT_TRIES;
84
+
85
+ const results = await Promise.all(
86
+ options.providers.map((provider) =>
87
+ measureProvider(provider, domains, tries),
88
+ ),
89
+ );
90
+
91
+ results.sort((a, b) => {
92
+ if (a.avg === null && b.avg === null) return 0;
93
+ if (a.avg === null) return 1;
94
+ if (b.avg === null) return -1;
95
+ return a.avg - b.avg;
96
+ });
97
+
98
+ return results;
99
+ }