From db96e8d9332a38fc519cc7573495f467e161cb63 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Sun, 12 Oct 2025 13:52:50 -0400 Subject: [PATCH] Lazy load `node:net` for improved edge runtime compatibility --- README.md | 14 ++++++++++++++ package-lock.json | 8 ++++---- package.json | 2 +- src/whois/client.test.ts | 15 +++++++++++++++ src/whois/client.ts | 19 ++++++++++++++++--- tsconfig.json | 3 ++- vitest.config.ts | 2 +- 7 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 src/whois/client.test.ts diff --git a/README.md b/README.md index 329ed9a..156bdcd 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,20 @@ toRegistrableDomain("192.168.0.1"); // => null - `isRegistered(domain, options?) => Promise` - `isAvailable(domain, options?) => Promise` +### Edge runtimes (e.g., Vercel Edge) + +WHOIS requires a raw TCP connection over port 43 via `node:net`, which is not available on edge runtimes. This package lazily loads `node:net` only when the WHOIS code path runs. To use rdapper safely on edge: + +- Prefer RDAP only: + +```ts +import { lookupDomain } from "rdapper"; + +const res = await lookupDomain("example.com", { rdapOnly: true }); +``` + +- If `rdapOnly` is omitted and the code path reaches WHOIS on edge, rdapper throws a clear runtime error indicating WHOIS is unsupported on edge and to run in Node or set `rdapOnly: true`. + ### Options - `timeoutMs?: number` – Total timeout budget per network operation (default `15000`). diff --git a/package-lock.json b/package-lock.json index fadb105..9fa56e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@biomejs/biome": "2.2.5", - "@types/node": "24.7.1", + "@types/node": "24.7.2", "tsdown": "0.15.6", "typescript": "5.9.3", "vitest": "^3.2.4" @@ -1392,9 +1392,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz", - "integrity": "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q==", + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a912245..e84cb94 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@biomejs/biome": "2.2.5", - "@types/node": "24.7.1", + "@types/node": "24.7.2", "tsdown": "0.15.6", "typescript": "5.9.3", "vitest": "^3.2.4" diff --git a/src/whois/client.test.ts b/src/whois/client.test.ts new file mode 100644 index 0000000..2c5b829 --- /dev/null +++ b/src/whois/client.test.ts @@ -0,0 +1,15 @@ +// src/whois/edge-mock.test.ts +import { describe, expect, it, vi } from "vitest"; + +vi.mock("net", () => { + throw new Error("node:net unavailable (edge)"); +}); + +describe("edge runtime behavior", () => { + it("throws a clear error when WHOIS hits node:net on edge", async () => { + const { whoisQuery } = await import("../whois/client"); + await expect(whoisQuery("whois.iana.org", "com")).rejects.toThrow( + /WHOIS client is only available in Node.js runtimes/, + ); + }); +}); diff --git a/src/whois/client.ts b/src/whois/client.ts index 4abe2fc..20ebb02 100644 --- a/src/whois/client.ts +++ b/src/whois/client.ts @@ -1,4 +1,3 @@ -import { createConnection } from "node:net"; import { withTimeout } from "../lib/async"; import { DEFAULT_TIMEOUT_MS } from "../lib/constants"; import type { LookupOptions } from "../types"; @@ -29,14 +28,28 @@ export async function whoisQuery( } // Low-level WHOIS TCP client. Some registries require CRLF after the domain query. -function queryTcp( +async function queryTcp( host: string, port: number, query: string, options?: LookupOptions, ): Promise { + let net: typeof import("net") | null; + try { + // biome-ignore lint/style/useNodejsImportProtocol: compatibility + net = await import("net"); + } catch { + net = null; + } + + if (!net?.createConnection) { + throw new Error( + "WHOIS client is only available in Node.js runtimes; try setting `rdapOnly: true`.", + ); + } + return new Promise((resolve, reject) => { - const socket = createConnection({ host, port }); + const socket = net.createConnection({ host, port }); let data = ""; let done = false; const cleanup = () => { diff --git a/tsconfig.json b/tsconfig.json index 90a35a8..5e1110b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "esModuleInterop": true, "isolatedModules": true, "verbatimModuleSyntax": true, - "skipLibCheck": true + "skipLibCheck": true, + "types": ["vitest/globals"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/vitest.config.ts b/vitest.config.ts index 3887f28..efc8d75 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ include: ["src/**/*.test.ts"], exclude: ["dist/**", "node_modules/**"], sequence: { hooks: "list" }, - globals: false, + globals: true, testTimeout: process.env.SMOKE === "1" ? 30000 : 5000, }, });