@portable/dns
Functions
Dns.isNotFound
Whether a lookup failed because there are no records to return: the name doesn't exist
(ENOTFOUND), has no records of the type (ENODATA), or can't be a DNS name (EBADNAME). It
reads code, so it works for a QueryError from any copy of this package and for errors from
node:dns/promises. Any other failure is temporary, and a caller can retry it.
function isNotFound(error: unknown): boolean;import * as Dns from '@portable/dns';
const resolver = Dns.create();
const records = await resolver
.resolveTxt('_dmarc.example.com')
.catch((error) => {
if (Dns.isNotFound(error)) {
return [];
}
throw error;
});Dns.create
Creates a resolver that sends DNS queries over HTTPS (RFC 8484) with fetch, so lookups work in
browsers, Workers and other runtimes without a system resolver. Queries are GET requests with a
DNS ID of 0, which HTTP caches can store and which need no CORS preflight. Results and error
codes match node:dns/promises, and CNAME chains in the answer are followed.
function create(options: create.Options = {}): DohResolver;| Option | Type | Default | Description |
|---|---|---|---|
endpoints | readonly (string | URL)[] | ['https://cloudflare-dns.com/dns-query', 'https://dns.google/dns-query'] | DNS-over-HTTPS endpoints (RFC 8484), tried in order. The next one is tried when a request fails, times out, or gets SERVFAIL, REFUSED, NOTIMP, FORMERR or a truncated response. |
fetch | ((request: Request) => Promise<Response>) | globalThis.fetch | Sends each request. Pass one to add headers, route through a proxy, or mock in tests. |
timeout | number | 5000 | Milliseconds to wait for each endpoint, including reading the response body. |
import * as Dns from '@portable/dns';
const resolver = Dns.create({ timeout: 2000 });
const records = await resolver.resolveTxt('example.com');
const spf = records
.map((chunks) => chunks.join(''))
.find((text) => text.startsWith('v=spf1'));Errors
Dns.QueryError
Thrown when a lookup fails. code, syscall, hostname and message have the values
node:dns/promises gives them, such as queryTxt ENOTFOUND example.com. When the failure came
from fetch or the response, cause holds the original error.
class QueryError extends Errors.BaseError {
constructor(options: QueryError.Options);
readonly kind: string;
readonly code: QueryError.Code;
readonly syscall: string;
readonly hostname: string;
}| Option | Type | Default | Description |
|---|---|---|---|
cause | unknown | ||
code | Code | ||
syscall | string | The node:dns name of the operation. | |
hostname | string | The name or address the caller passed. |
import * as Dns from '@portable/dns';
const resolver = Dns.create();
try {
await resolver.resolveTxt('_dmarc.example.com');
} catch (error) {
if (error instanceof Dns.QueryError && error.code === 'ENODATA') {
console.log('no DMARC record');
}
}Types
Dns.CaaRecord
A certification authority authorization, as resolveCaa returns it. critical is the whole
flags byte (128 when the issuer-critical bit is set), and the tag is the key of the value. A tag
not listed here is still set on the object.
type CaaRecord = {
critical: number;
issue?: string | undefined;
issuewild?: string | undefined;
iodef?: string | undefined;
contactemail?: string | undefined;
contactphone?: string | undefined;
};Dns.DohResolver
The resolver that create returns: every Resolver lookup, and query for any record type.
type DohResolver = Resolver & {
query(
name: string,
type: DnsPacket.RecordType,
options?: Resolver.CallOptions,
): Promise<DnsPacket.DecodedPacket>;
};Dns.MxRecord
A mail exchange, as resolveMx returns it.
type MxRecord = {
exchange: string;
priority: number;
};Dns.NaptrRecord
A naming authority pointer, as resolveNaptr returns it.
type NaptrRecord = {
flags: string;
service: string;
regexp: string;
replacement: string;
order: number;
preference: number;
};Dns.Resolver
The lookups that SPF, DMARC and DKIM need, with the method names and result shapes of
node:dns/promises. create returns one, and node:dns/promises, a new Resolver() from it
and a plain object of functions in a test also match the type.
type Resolver = {
resolve4(
name: string,
options?: Resolver.CallOptions,
): Promise<string[]>;
resolve6(
name: string,
options?: Resolver.CallOptions,
): Promise<string[]>;
resolveCname(
name: string,
options?: Resolver.CallOptions,
): Promise<string[]>;
resolveMx(
name: string,
options?: Resolver.CallOptions,
): Promise<MxRecord[]>;
resolveNs(
name: string,
options?: Resolver.CallOptions,
): Promise<string[]>;
resolvePtr(
name: string,
options?: Resolver.CallOptions,
): Promise<string[]>;
resolveTxt(
name: string,
options?: Resolver.CallOptions,
): Promise<string[][]>;
resolveSrv(
name: string,
options?: Resolver.CallOptions,
): Promise<SrvRecord[]>;
resolveSoa(
name: string,
options?: Resolver.CallOptions,
): Promise<SoaRecord>;
resolveCaa(
name: string,
options?: Resolver.CallOptions,
): Promise<CaaRecord[]>;
resolveNaptr(
name: string,
options?: Resolver.CallOptions,
): Promise<NaptrRecord[]>;
reverse(
ip: string,
options?: Resolver.CallOptions,
): Promise<string[]>;
};Dns.SoaRecord
A start of authority, as resolveSoa returns it.
type SoaRecord = {
nsname: string;
hostmaster: string;
serial: number;
refresh: number;
retry: number;
expire: number;
minttl: number;
};Dns.SrvRecord
A service location, as resolveSrv returns it. name is the target host.
type SrvRecord = {
name: string;
port: number;
priority: number;
weight: number;
};