Update repository guidelines and README: enhance project structure documentation, clarify build and test commands, and improve API usage examples for better user understanding.

This commit is contained in:
2025-09-24 19:19:41 -04:00
parent 3429c70bd7
commit 05445ef831
3 changed files with 165 additions and 36 deletions
+138 -10
View File
@@ -1,6 +1,13 @@
# rdapper
# 🎩 rdapper
🤵 Fetch and parse domain registration data using RDAP and falling back to WHOIS.
RDAPfirst domain registration lookups with WHOIS fallback. Produces a single, normalized record shape regardless of source.
- RDAP discovery via IANA bootstrap (`https://data.iana.org/rdap/dns.json`)
- WHOIS TCP 43 client with TLD discovery, registrar referral follow, and curated exceptions
- Normalized output: registrar, contacts, nameservers, statuses, dates, DNSSEC, source metadata
- TypeScript types included; ESMonly; no external HTTP client (uses global `fetch`)
Requirements: Node >= 18.17 (global `fetch`). WHOIS uses TCP port 43.
## Install
@@ -8,25 +15,146 @@
npm install rdapper
```
## Usage
## Quick Start
```ts
import { lookupDomain } from "rdapper";
const { ok, record, error } = await lookupDomain("example.com", {
timeoutMs: 15000,
followWhoisReferral: true,
timeoutMs: 15000, // default 15000
followWhoisReferral: true, // default true
});
if (!ok) throw new Error(error);
console.log(record);
console.log(record); // normalized DomainRecord
```
## Notes
Also available:
- Uses IANA RDAP bootstrap and RDAP JSON when available; falls back to WHOIS.
- Standardized output regardless of source.
- No external HTTP client deps; relies on global fetch. WHOIS uses TCP 43.
```ts
import { isRegistered, isAvailable } from "rdapper";
await isRegistered("example.com"); // => true
await isAvailable("likely-unregistered-thing-320485230458.com"); // => false
```
## API
- `lookupDomain(domain, options?) => Promise<LookupResult>`
- Tries RDAP first for the domains TLD; if unavailable or fails, falls back to WHOIS unless toggled off.
- Result is `{ ok: boolean, record?: DomainRecord, error?: string }`.
- `isRegistered(domain, options?) => Promise<boolean>`
- `isAvailable(domain, options?) => Promise<boolean>`
### Options
- `timeoutMs?: number` Total timeout budget per network operation (default `15000`).
- `rdapOnly?: boolean` Only attempt RDAP; do not fall back to WHOIS.
- `whoisOnly?: boolean` Skip RDAP and query WHOIS directly.
- `followWhoisReferral?: boolean` Follow registrar referral from the TLD WHOIS (default `true`).
- `customBootstrapUrl?: string` Override RDAP bootstrap URL.
- `whoisHints?: Record<string, string>` Override/add authoritative WHOIS per TLD (keys are lowercase TLDs, values may include or omit `whois://`).
- `signal?: AbortSignal` Optional cancellation signal.
### `DomainRecord` schema
The exact presence of fields depends on registry/registrar data and whether RDAP or WHOIS was used.
```ts
interface DomainRecord {
domain: string; // normalized name (unicode when available)
tld: string; // terminal TLD label (e.g., "com")
isRegistered: boolean; // availability heuristic (WHOIS) or true (RDAP)
isIDN?: boolean; // uses punycode labels (xn--)
unicodeName?: string; // RDAP unicodeName when provided
punycodeName?: string; // RDAP ldhName when provided
registry?: string; // registry operator (rarely available)
registrar?: {
name?: string;
ianaId?: string;
url?: string;
email?: string;
phone?: string;
};
reseller?: string;
statuses?: Array<{ status: string; description?: string; raw?: string }>;
creationDate?: string; // ISO 8601 (UTC)
updatedDate?: string; // ISO 8601 (UTC)
expirationDate?: string; // ISO 8601 (UTC)
deletionDate?: string; // ISO 8601 (UTC)
transferLock?: boolean; // derived from EPP statuses
dnssec?: {
enabled: boolean;
dsRecords?: Array<{ keyTag?: number; algorithm?: number; digestType?: number; digest?: string }>;
};
nameservers?: Array<{ host: string; ipv4?: string[]; ipv6?: string[] }>;
contacts?: Array<{
type: "registrant" | "admin" | "tech" | "billing" | "abuse" | "registrar" | "reseller" | "unknown";
name?: string; organization?: string; email?: string | string[]; phone?: string | string[]; fax?: string | string[];
street?: string[]; city?: string; state?: string; postalCode?: string; country?: string; countryCode?: string;
}>;
whoisServer?: string; // authoritative WHOIS queried (if any)
rdapServers?: string[]; // RDAP base URLs tried
rawRdap?: unknown; // raw RDAP JSON (RDAP source)
rawWhois?: string; // raw WHOIS text (WHOIS source)
source: "rdap" | "whois"; // which path produced data
fetchedAt: string; // ISO 8601 timestamp
warnings?: string[];
}
```
### Example output
```json
{
"domain": "example.com",
"tld": "com",
"isRegistered": true,
"registrar": { "name": "Internet Assigned Numbers Authority", "ianaId": "376" },
"statuses": [{ "status": "clientTransferProhibited" }],
"nameservers": [{ "host": "a.iana-servers.net" }, { "host": "b.iana-servers.net" }],
"dnssec": { "enabled": true },
"source": "rdap",
"fetchedAt": "2025-01-01T00:00:00Z"
}
```
## How it works
- RDAP
- Discovers base URLs for the TLD via IANAs RDAP bootstrap JSON.
- Tries each base until one responds successfully; parses standard RDAP domain JSON.
- Normalizes registrar (from `entities`), contacts (vCard), nameservers (`ipAddresses`), events (created/changed/expiration), statuses, and DNSSEC (`secureDNS`).
- WHOIS
- Discovers the authoritative TLD WHOIS via `whois.iana.org` (TCP 43), with curated exceptions for tricky zones and public SLDs.
- Queries the TLD WHOIS; if a registrar referral is present and `followWhoisReferral !== false`, follows one hop to the registrar WHOIS.
- Normalizes common key/value variants across gTLD/ccTLD formats (dates, statuses, nameservers, contacts). Availability is inferred from common phrases (besteffort heuristic).
Timeouts are enforced per request using a simple race against `timeoutMs` (default 15s). All network I/O is performed with global `fetch` (RDAP) and a raw TCP socket (WHOIS).
## Development
- Build: `npm run build`
- Test: `npm test`
- By default, tests are offline/deterministic.
- Smoke tests that hit the network are gated by `SMOKE=1`, e.g. `SMOKE=1 npm test`.
- Lint/format: `npm run lint` (Biome)
Project layout:
- `src/rdap/` RDAP bootstrap, client, and normalization
- `src/whois/` WHOIS TCP client, discovery/referral, normalization, exceptions
- `src/lib/` utilities for dates, text parsing, domain processing, async
- `src/types.ts` public types; `src/index.ts` reexports API and types
- `cli.mjs` local CLI helper for quick testing
## Caveats
- WHOIS text formats vary significantly across registries/registrars; normalization is besteffort.
- Availability detection relies on common WHOIS phrases and is not authoritative.
- Some TLDs provide no RDAP service; `rdapOnly: true` will fail for them.
- Registries may throttle or block WHOIS; respect rate limits and usage policies.
- Field presence depends on source and privacy policies (e.g., redaction/withholding).
## License