Use when performing WHOIS lookups in Stacks — domain queries, batch lookups, SOCKS proxy support, TLD server discovery, response parsing, the WhoIsParser class, or the built-in SocksClient. Covers @stacksjs/whois.
Domain WHOIS lookup with SOCKS proxy support, batch processing, TLD server discovery, and response parsing. Includes a native SOCKS4/SOCKS5 client implementation.
storage/framework/core/whois/src/whois/src/
├── index.ts # whois(), lookup(), batchWhois(), tcpWhois(), WhoIsParser, exports
├── types.ts # WhoIsResponse, WhoIsOptions, ProxyData, ProxyType
├── constants.ts # IANA_CHK_URL, PARAMETERS, SERVERS
├── utils.ts # shallowCopy helper
└── socks.ts # Native SocksClient implementation (SOCKS4/SOCKS5)
async function whois(
domain: string,
parse?: boolean, // default: false (but still parses with default fields)
options?: WhoIsOptions | null,
): Promise<WhoIsResponse>
When parse is false (default), it still calls WhoIsParser.parseData(rawData, null) which parses with the default field set. When parse is true and options.parseData is provided, it uses a shallow copy of that object as the parse template.
Flow:
options.tld)SERVERS map first, then query IANA via findWhoIsServer()getParameters() for the servertcpWhois()WhoIsParser.parseData(){ _raw: string, parsedData: any | null }function lookup(domain: string, options?: WhoIsOptions | null): Promise<WhoIsResponse>
Calls whois(domain, true, options) -- always parses the response.
async function batchWhois(
domains: string[],
parallel?: boolean, // default: false
threads?: number, // default: 1 (batch size for parallel mode)
parse?: boolean, // default: false
options?: WhoIsOptions | null,
): Promise<WhoIsResponse[]>
parallel=false): processes domains one at a timeparallel=true): processes in batches of threads size using Promise.allthreads > domains.length, clamps to domains.lengthresponse is reassigned, not concatenated)async function tcpWhois(
domain: string,
queryOptions: string, // server-specific prefix (e.g., '-T dn,ace')
server: string,
port: number, // default 43
encoding: string, // default 'utf-8'
proxy: ProxyData | null,
): Promise<string>
Net.Socket, connects directly to WHOIS server on specified portSocksClient.createConnection() to tunnel through SOCKS proxy"<queryOptions> <domain>\r\n" or "<domain>\r\n" if no queryOptionsTextDecoder with specified encoding for responseasync function findWhoIsServer(tld: string): Promise<string>
Fetches https://www.iana.org/whois?q=<tld> and extracts server from whois:\s+(\S+) regex match. Returns empty string on failure.
function getWhoIsServer(tld: keyof typeof SERVERS): string | undefined
Looks up TLD in the built-in SERVERS map. Special case: 'com' always returns 'whois.verisign-grs.com'.
function getTLD(domain: string): keyof typeof SERVERS
Splits domain by . and checks from longest suffix first against the SERVERS map. For example, sub.example.co.uk checks co.uk first (which exists in SERVERS), not just uk. Falls back to the last part if no match found.
function getParameters(server: string): string | undefined
Returns server-specific query format from the PARAMETERS map.
class WhoIsParser {
// Parse raw WHOIS text with custom or default field template
static parseData(rawData: string, outputData: any | null): any
// Internal iterative parser (private)
private static iterParse(rawData: string, outputData: any): any
}
When outputData is null, parseData() uses this default template:
{
'Domain Name': '',
'Creation Date': '',
'Updated Date': '',
'Registry Expiry Date': '',
'Domain Status': [], // array collects multiple values
'Registrar': '',
}
The parser iterates character by character through raw WHOIS text:
outputData": " separatorDomain Status) accumulate multiple values"Record maintained by" or ">>>" markersCustom parse templates: pass an object with the field names you want extracted. Use empty string '' for single values, use [] for fields with multiple values.
const result = await whois('example.com', true, {
parseData: {
'Domain Name': '',
'Domain Status': [], // collects ALL status values
'Registrar': '',
'Name Server': [], // collects ALL name servers
}
})
interface WhoIsResponse {
_raw: string // raw text from WHOIS server
parsedData: any | null // parsed key-value pairs (null on error)
}
interface WhoIsOptions {
tld?: string | null // override TLD detection
encoding?: string | null // character encoding (default: 'utf-8')
proxy?: ProxyData | null // SOCKS proxy configuration
server?: string | null // override WHOIS server
serverPort?: number | null // override port (default: 43)
parseData?: object | null // custom parse template (keys to extract)
}
interface ProxyData {
ip: string
port: number
username?: string | null
password?: string | null
type: ProxyType
}
enum ProxyType {
SOCKS4 = 0,
SOCKS5 = 1,
}
Note: ProxyType.SOCKS4 = 0 and ProxyType.SOCKS5 = 1 -- these are mapped to SOCKS versions 4 and 5 internally when creating SocksClient options.
const result = await whois('example.com', false, {
proxy: {
ip: '127.0.0.1',
port: 1080,
type: ProxyType.SOCKS5,
username: 'user', // optional
password: 'pass', // optional
}
})
Native SOCKS client implementation using node:net. Replaces the socks npm package.
class SocksClient {
static createConnection(
options: SocksClientOptions,
callback: (err: Error | null, info?: SocksClientEstablishedEvent) => void,
): void
}
interface SocksClientOptions {
proxy: SocksProxy
command: 'connect' | 'bind' | 'associate'
destination: SocksDestination
}
interface SocksProxy {
host: string
port: number
type: 4 | 5 // SOCKS version
userId?: string
password?: string
}
interface SocksDestination {
host: string
port: number
}
interface SocksClientEstablishedEvent {
socket: Net.Socket
remoteHost?: string
remotePort?: number
}
0.0.0.1)0x5A (90)0x00) and username/password auth (0x02)0x01), IPv6 (0x04), and domain name (0x03) address typesconst IANA_CHK_URL = 'https://www.iana.org/whois?q='
const PARAMETERS: Record<string, string> = {
'whois.denic.de': '-T dn,ace',
'whois.nic.fr': '-V Md5.2',
}
Comprehensive mapping of hundreds of TLDs to their WHOIS servers. Examples:
'com' -> 'whois.verisign-grs.com''net' -> 'whois.verisign-grs.com''org' -> 'whois.pir.org''io' -> 'whois.nic.io''co.uk' -> compound TLD support via getTLD() longest-match logicbr.com, co.ca)function shallowCopy<T>(obj: T): T
Recursively copies objects and arrays. Used to clone parseData templates before filling them with parsed values, so the original template is not mutated.
Functions: whois, lookup, batchWhois, tcpWhois, findWhoIsServer, getWhoIsServer, getTLD, getParameters
Classes: WhoIsParser, SocksClient
Constants: IANA_CHK_URL, PARAMETERS, SERVERS
Types: WhoIsResponse, WhoIsOptions, ProxyData, ProxyType, SocksClientOptions
node:net -- TCP socket connections (direct WHOIS queries and SOCKS client)@stacksjs/logging -- log for debug logging during lookupswhois() with parse=false still parses with default fields -- the parse flag controls whether custom parseData is usedbatchWhois() in parallel mode has a bug: it reassigns response each batch instead of concatenating, so only the last batch's results are returnedProxyType enum values are 0 and 1, not 4 and 5 -- they are mapped to SOCKS versions internallygetWhoIsServer() returns undefined for unknown TLDs, not an empty stringfindWhoIsServer() returns empty string '' on failure (not undefined)getTLD() checks compound TLDs (like co.uk) before simple ones via longest-suffix matching"Record maintained by" or ">>>" markers in the raw responseshallowCopy() is a recursive deep copy despite its name -- it clones nested objects and arraysnode:net Socket, not fetch -- WHOIS is a raw TCP protocol on port 43"-T dn,ace example.de\r\n")