@sdxc/zone-file 2026.10.13 → 2026.11.3

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/dist/cli.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The `sdxc-zone-file` command-line program: the contract in `cli-contract.ts` with each command
4
+ * mapped onto its handler, which loads only when that command runs. Exit codes: 0 done, 1 the
5
+ * zone file has entries it could not use, 2 usage or unreadable input.
6
+ *
7
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
8
+ * @copyright Sergio Xalambrí 2026
9
+ */
10
+ /** The `sdxc-zone-file` program, run by the bin entry below and by tests through `invoke()`. */
11
+ export declare const cli: import("@sdxc/cli").CLI<import("@sdxc/cli").Group<Omit<{
12
+ readonly description: "sdxc-zone-file — read, check and write RFC 1035 DNS zone files";
13
+ readonly validate: import("@sdxc/cli").Command<{
14
+ readonly zone: import("@sdxc/cli").Argument<string>;
15
+ }, {
16
+ readonly origin: import("@sdxc/cli").ValueFlag<string, true, false, false>;
17
+ readonly ttl: import("@sdxc/cli").ValueFlag<number, false, false, false>;
18
+ readonly json: import("@sdxc/cli").Flag<boolean>;
19
+ }>;
20
+ readonly parse: import("@sdxc/cli").Command<{
21
+ readonly zone: import("@sdxc/cli").Argument<string>;
22
+ }, {
23
+ readonly origin: import("@sdxc/cli").ValueFlag<string, true, false, false>;
24
+ readonly ttl: import("@sdxc/cli").ValueFlag<number, false, false, false>;
25
+ readonly json: import("@sdxc/cli").Flag<boolean>;
26
+ }>;
27
+ readonly format: import("@sdxc/cli").Command<{
28
+ readonly input: import("@sdxc/cli").Argument<string>;
29
+ }, {
30
+ readonly origin: import("@sdxc/cli").ValueFlag<string, true, false, false>;
31
+ readonly ttl: import("@sdxc/cli").ValueFlag<number, false, false, false>;
32
+ readonly relative: import("@sdxc/cli").Flag<boolean>;
33
+ }>;
34
+ }, "description" | "hidden" | "arguments" | "flags" | "tasks">, Record<never, never>, Record<never, never>>, []>;
package/dist/cli.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The `sdxc-zone-file` command-line program: the contract in `cli-contract.ts` with each command
4
+ * mapped onto its handler, which loads only when that command runs. Exit codes: 0 done, 1 the
5
+ * zone file has entries it could not use, 2 usage or unreadable input.
6
+ *
7
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
8
+ * @copyright Sergio Xalambrí 2026
9
+ */
10
+ import { createCLI, lazy } from "@sdxc/cli";
11
+ import { ZONE_FILE_COMMANDS } from "./cli-contract.js";
12
+ /** The `sdxc-zone-file` program, run by the bin entry below and by tests through `invoke()`. */
13
+ export const cli = createCLI({ name: "sdxc-zone-file", commands: ZONE_FILE_COMMANDS });
14
+ cli.map(ZONE_FILE_COMMANDS.validate, lazy(() => import("./cli-validate.js")));
15
+ cli.map(ZONE_FILE_COMMANDS.parse, lazy(() => import("./cli-parse.js")));
16
+ cli.map(ZONE_FILE_COMMANDS.format, lazy(() => import("./cli-format.js")));
17
+ if (import.meta.main) {
18
+ process.exit(await cli.run(process.argv.slice(2)));
19
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The DNSSEC pieces DS and DNSKEY data share: the algorithm mnemonics RFC 4034 lets a zone
3
+ * file write in place of a number, and the key tag a DS record names its DNSKEY by, so a
4
+ * delegation's DS set can be matched against the child's keys.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ /** An algorithm field as a number, read from its decimal or its mnemonic; `null` for neither. */
10
+ export declare function readAlgorithm(field: string | undefined): number | null;
11
+ /**
12
+ * The key tag of DNSKEY RDATA (RFC 4034 Appendix B): the octets summed as big-endian 16-bit
13
+ * words with the carry folded back in once, and for an RSA/MD5 (algorithm 1) key the
14
+ * modulus's second- and third-to-last octets, as that appendix specifies for it.
15
+ *
16
+ * @param rdata - The DNSKEY RDATA in wire form: flags, protocol, algorithm and key.
17
+ */
18
+ export declare function keyTag(rdata: Uint8Array): number;
package/dist/dnssec.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The DNSSEC pieces DS and DNSKEY data share: the algorithm mnemonics RFC 4034 lets a zone
3
+ * file write in place of a number, and the key tag a DS record names its DNSKEY by, so a
4
+ * delegation's DS set can be matched against the child's keys.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ /** The DNSSEC algorithm numbers by IANA mnemonic; the uppercase mnemonic reads as its number. */
10
+ const ALGORITHMS = {
11
+ RSAMD5: 1,
12
+ DH: 2,
13
+ DSA: 3,
14
+ RSASHA1: 5,
15
+ "DSA-NSEC3-SHA1": 6,
16
+ "RSASHA1-NSEC3-SHA1": 7,
17
+ RSASHA256: 8,
18
+ RSASHA512: 10,
19
+ "ECC-GOST": 12,
20
+ ECDSAP256SHA256: 13,
21
+ ECDSAP384SHA384: 14,
22
+ ED25519: 15,
23
+ ED448: 16,
24
+ INDIRECT: 252,
25
+ PRIVATEDNS: 253,
26
+ PRIVATEOID: 254,
27
+ };
28
+ /** An algorithm field as a number, read from its decimal or its mnemonic; `null` for neither. */
29
+ export function readAlgorithm(field) {
30
+ if (field === undefined)
31
+ return null;
32
+ if (/^\d+$/.test(field)) {
33
+ let number = Number(field);
34
+ return number <= 0xff ? number : null;
35
+ }
36
+ return ALGORITHMS[field.toUpperCase()] ?? null;
37
+ }
38
+ /**
39
+ * The key tag of DNSKEY RDATA (RFC 4034 Appendix B): the octets summed as big-endian 16-bit
40
+ * words with the carry folded back in once, and for an RSA/MD5 (algorithm 1) key the
41
+ * modulus's second- and third-to-last octets, as that appendix specifies for it.
42
+ *
43
+ * @param rdata - The DNSKEY RDATA in wire form: flags, protocol, algorithm and key.
44
+ */
45
+ export function keyTag(rdata) {
46
+ if (rdata[3] === 1 && rdata.length >= 7)
47
+ return ((rdata[rdata.length - 3] ?? 0) << 8) | (rdata[rdata.length - 2] ?? 0);
48
+ let sum = 0;
49
+ for (let index = 0; index < rdata.length; index++)
50
+ sum += index % 2 === 0 ? (rdata[index] ?? 0) << 8 : (rdata[index] ?? 0);
51
+ sum += (sum >> 16) & 0xffff;
52
+ return sum & 0xffff;
53
+ }
@@ -8,10 +8,9 @@
8
8
  */
9
9
  import type * as ZoneFile from "./types.js";
10
10
  /**
11
- * Prints a record's data in canonical presentation format. Names print absolute with the
12
- * trailing dot (the root stays `"."`), TXT and CAA values print quoted with `"` and `\`
13
- * escaped and every octet outside printable ASCII as `\DDD`, and untyped data prints as is.
14
- * `parseRecordData(data.type, formatRecordData(data))` gives back `data`.
11
+ * Prints a record's data in canonical presentation format: names absolute, free text quoted
12
+ * with every octet outside printable ASCII as `\DDD`, hex lowercase, SvcParams in key order,
13
+ * untyped data as is. `parseRecordData(data.type, formatRecordData(data))` gives back `data`.
15
14
  *
16
15
  * @param data - Record data, as `parseRecordData` produces it.
17
16
  * @returns The RDATA in presentation format.
@@ -1,11 +1,11 @@
1
1
  import { absoluteName } from "./names.js";
2
+ import { printSvcParams } from "./svc-params.js";
2
3
  /** Encodes a character-string into the octets `\DDD` escapes stand for. */
3
4
  const ENCODER = new TextEncoder();
4
5
  /**
5
- * Prints a record's data in canonical presentation format. Names print absolute with the
6
- * trailing dot (the root stays `"."`), TXT and CAA values print quoted with `"` and `\`
7
- * escaped and every octet outside printable ASCII as `\DDD`, and untyped data prints as is.
8
- * `parseRecordData(data.type, formatRecordData(data))` gives back `data`.
6
+ * Prints a record's data in canonical presentation format: names absolute, free text quoted
7
+ * with every octet outside printable ASCII as `\DDD`, hex lowercase, SvcParams in key order,
8
+ * untyped data as is. `parseRecordData(data.type, formatRecordData(data))` gives back `data`.
9
9
  *
10
10
  * @param data - Record data, as `parseRecordData` produces it.
11
11
  * @returns The RDATA in presentation format.
@@ -62,6 +62,26 @@ export function printRecordData(data, printName) {
62
62
  soa.minimum,
63
63
  ].join(" ");
64
64
  }
65
+ case "SVCB":
66
+ case "HTTPS": {
67
+ let { priority, target, params } = data;
68
+ let printed = printSvcParams(params, quote);
69
+ return `${priority} ${printName(target)}${printed ? ` ${printed}` : ""}`;
70
+ }
71
+ case "TLSA": {
72
+ let tlsa = data;
73
+ return `${tlsa.usage} ${tlsa.selector} ${tlsa.matchingType} ${tlsa.certificateAssociation}`;
74
+ }
75
+ case "DS":
76
+ case "CDS": {
77
+ let ds = data;
78
+ return `${ds.keyTag} ${ds.algorithm} ${ds.digestType} ${ds.digest}`;
79
+ }
80
+ case "DNSKEY":
81
+ case "CDNSKEY": {
82
+ let key = data;
83
+ return `${key.flags} ${key.protocol} ${key.algorithm} ${key.publicKey}`;
84
+ }
65
85
  default:
66
86
  return String(data.data);
67
87
  }
package/dist/index.d.ts CHANGED
@@ -12,5 +12,5 @@ export { RecordDataError, ZoneFileError } from "./errors.js";
12
12
  export { formatRecordData } from "./format-record-data.js";
13
13
  export { parse } from "./parse.js";
14
14
  export { parseRecordData } from "./parse-record-data.js";
15
- export { canonicalType, typeName } from "./record-types.js";
15
+ export { canonicalType, typeCode, typeName } from "./record-types.js";
16
16
  export { stringify } from "./stringify.js";
package/dist/index.js CHANGED
@@ -10,5 +10,5 @@ export { RecordDataError, ZoneFileError } from "./errors.js";
10
10
  export { formatRecordData } from "./format-record-data.js";
11
11
  export { parse } from "./parse.js";
12
12
  export { parseRecordData } from "./parse-record-data.js";
13
- export { canonicalType, typeName } from "./record-types.js";
13
+ export { canonicalType, typeCode, typeName } from "./record-types.js";
14
14
  export { stringify } from "./stringify.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Hex and base64 codecs for the binary fields of DNSSEC, DANE and SVCB records, which the
3
+ * presentation format writes as text a zone file may split across whitespace and lines.
4
+ *
5
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
6
+ * @copyright Sergio Xalambrí 2026
7
+ */
8
+ /** The octets a hex string spells, whitespace ignored; `null` for an odd or non-hex run, or none. */
9
+ export declare function decodeHex(text: string): Uint8Array | null;
10
+ /** Octets as lowercase hex, the spelling DS digests and TLSA data compare in. */
11
+ export declare function encodeHex(octets: Uint8Array): string;
12
+ /** The octets a base64 string spells, whitespace ignored; `null` for text that is not base64, or none. */
13
+ export declare function decodeBase64(text: string): Uint8Array | null;
14
+ /** Octets as padded base64, the spelling DNSKEY keys and SVCB `ech` print in. */
15
+ export declare function encodeBase64(octets: Uint8Array): string;
package/dist/octets.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Hex and base64 codecs for the binary fields of DNSSEC, DANE and SVCB records, which the
3
+ * presentation format writes as text a zone file may split across whitespace and lines.
4
+ *
5
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
6
+ * @copyright Sergio Xalambrí 2026
7
+ */
8
+ /** Hex text in pairs, with any whitespace between digits already removed. */
9
+ const HEX_PATTERN = /^(?:[0-9a-fA-F]{2})+$/;
10
+ /** Canonical base64: complete quads, padding only at the end. */
11
+ const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
12
+ /** The octets a hex string spells, whitespace ignored; `null` for an odd or non-hex run, or none. */
13
+ export function decodeHex(text) {
14
+ let hex = text.replace(/\s+/g, "");
15
+ if (!HEX_PATTERN.test(hex))
16
+ return null;
17
+ let octets = new Uint8Array(hex.length / 2);
18
+ for (let index = 0; index < octets.length; index++)
19
+ octets[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
20
+ return octets;
21
+ }
22
+ /** Octets as lowercase hex, the spelling DS digests and TLSA data compare in. */
23
+ export function encodeHex(octets) {
24
+ return Array.from(octets, (octet) => octet.toString(16).padStart(2, "0")).join("");
25
+ }
26
+ /** The octets a base64 string spells, whitespace ignored; `null` for text that is not base64, or none. */
27
+ export function decodeBase64(text) {
28
+ let base64 = text.replace(/\s+/g, "");
29
+ if (base64.length === 0 || !BASE64_PATTERN.test(base64))
30
+ return null;
31
+ let binary = atob(base64);
32
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
33
+ }
34
+ /** Octets as padded base64, the spelling DNSKEY keys and SVCB `ech` print in. */
35
+ export function encodeBase64(octets) {
36
+ return btoa(Array.from(octets, (octet) => String.fromCharCode(octet)).join(""));
37
+ }
@@ -1,14 +1,13 @@
1
1
  import { failure, isFailure, success } from "@sdxc/result";
2
+ import { canonicalIpv6, isIpv4, printIpv6 } from "./addresses.js";
2
3
  import { decodeOctets, readCharacterStrings } from "./character-strings.js";
4
+ import { keyTag, readAlgorithm } from "./dnssec.js";
3
5
  import { RecordDataError } from "./errors.js";
4
6
  import { canonicalName, printLabels } from "./names.js";
7
+ import { decodeBase64, decodeHex, encodeBase64, encodeHex } from "./octets.js";
5
8
  import { canonicalType } from "./record-types.js";
9
+ import { readSvcParams, readWireSvcParams } from "./svc-params.js";
6
10
  import { readTtl } from "./ttl.js";
7
- /**
8
- * A dotted-quad IPv4 address without leading zeros, which `inet_aton` would read as octal
9
- * and browsers as decimal, so an address carrying them has no single meaning.
10
- */
11
- const IPV4_PATTERN = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
12
11
  /**
13
12
  * Reads a record's data into its typed fields; untyped types come back as `{ type, data }`.
14
13
  * Names come back canonical (lowercased, escapes resolved, no trailing dot, the root as
@@ -39,19 +38,41 @@ function readNumber(field, max) {
39
38
  let number = Number(field);
40
39
  return number <= max ? number : null;
41
40
  }
42
- /** An IPv6 literal in RFC 5952 canonical form, or `null` when the text is not one. */
43
- function canonicalIpv6(value) {
44
- if (!value.includes(":"))
45
- return null;
46
- let url = URL.parse(`http://[${value}]/`);
47
- return url ? url.hostname.slice(1, -1) : null;
41
+ /**
42
+ * The fields of DNSKEY data from its wire octets, the key tag computed over them, so the
43
+ * presentation and generic spellings of one key agree on every field.
44
+ */
45
+ function dnskeyFields(rdata) {
46
+ let flags = ((rdata[0] ?? 0) << 8) | (rdata[1] ?? 0);
47
+ return {
48
+ flags,
49
+ zoneKey: (flags & 0x0100) !== 0,
50
+ secureEntryPoint: (flags & 0x0001) !== 0,
51
+ protocol: rdata[2] ?? 0,
52
+ algorithm: rdata[3] ?? 0,
53
+ publicKey: encodeBase64(rdata.subarray(4)),
54
+ keyTag: keyTag(rdata),
55
+ };
56
+ }
57
+ /** Reads the leading `count` whitespace-separated fields and the rest of the text after them. */
58
+ function splitLeading(value, count) {
59
+ let fields = [];
60
+ let rest = value;
61
+ for (let index = 0; index < count; index++) {
62
+ let match = /^(\S+)\s*/.exec(rest);
63
+ if (!match)
64
+ break;
65
+ fields.push(match[1] ?? "");
66
+ rest = rest.slice(match[0].length);
67
+ }
68
+ return [fields, rest];
48
69
  }
49
70
  /** Reads presentation-format data for the typed record types. */
50
71
  function parsePresentation(type, value) {
51
72
  let fields = value.split(/\s+/).filter((field) => field.length > 0);
52
73
  switch (type) {
53
74
  case "A":
54
- return IPV4_PATTERN.test(value) ? success({ type, address: value }) : invalid(type, value);
75
+ return isIpv4(value) ? success({ type, address: value }) : invalid(type, value);
55
76
  case "AAAA": {
56
77
  let address = canonicalIpv6(value);
57
78
  return address ? success({ type, address }) : invalid(type, value);
@@ -133,6 +154,50 @@ function parsePresentation(type, value) {
133
154
  minimum,
134
155
  });
135
156
  }
157
+ case "SVCB":
158
+ case "HTTPS": {
159
+ let [[priorityField, targetField], rest] = splitLeading(value, 2);
160
+ let priority = readNumber(priorityField, 0xffff);
161
+ let target = targetField === undefined ? null : canonicalName(targetField);
162
+ let params = readSvcParams(rest);
163
+ if (priority === null || target === null || params === null)
164
+ return invalid(type, value);
165
+ return success({ type, priority, target, params });
166
+ }
167
+ case "TLSA": {
168
+ let [leading, rest] = splitLeading(value, 3);
169
+ let [usage, selector, matchingType] = leading.map((field) => readNumber(field, 0xff));
170
+ let association = decodeHex(rest);
171
+ if (usage == null || selector == null || matchingType == null || !association)
172
+ return invalid(type, value);
173
+ let certificateAssociation = encodeHex(association);
174
+ return success({ type, usage, selector, matchingType, certificateAssociation });
175
+ }
176
+ case "DS":
177
+ case "CDS": {
178
+ let [[tagField, algorithmField, digestTypeField], rest] = splitLeading(value, 3);
179
+ let keyTag = readNumber(tagField, 0xffff);
180
+ let algorithm = readAlgorithm(algorithmField);
181
+ let digestType = readNumber(digestTypeField, 0xff);
182
+ let digest = decodeHex(rest);
183
+ if (keyTag === null || algorithm === null || digestType === null || !digest)
184
+ return invalid(type, value);
185
+ return success({ type, keyTag, algorithm, digestType, digest: encodeHex(digest) });
186
+ }
187
+ case "DNSKEY":
188
+ case "CDNSKEY": {
189
+ let [[flagsField, protocolField, algorithmField], rest] = splitLeading(value, 3);
190
+ let flags = readNumber(flagsField, 0xffff);
191
+ let protocol = readNumber(protocolField, 0xff);
192
+ let algorithm = readAlgorithm(algorithmField);
193
+ let key = decodeBase64(rest);
194
+ if (flags === null || protocol === null || algorithm === null || !key)
195
+ return invalid(type, value);
196
+ let rdata = new Uint8Array(4 + key.length);
197
+ rdata.set([flags >> 8, flags & 0xff, protocol, algorithm]);
198
+ rdata.set(key, 4);
199
+ return success({ type, ...dnskeyFields(rdata) });
200
+ }
136
201
  default:
137
202
  return success({ type, data: value });
138
203
  }
@@ -187,10 +252,7 @@ class WireReader {
187
252
  }
188
253
  case "AAAA": {
189
254
  let bytes = this.#bytes(16);
190
- if (!bytes)
191
- return null;
192
- let groups = Array.from({ length: 8 }, (_, index) => (((bytes[index * 2] ?? 0) << 8) | (bytes[index * 2 + 1] ?? 0)).toString(16));
193
- return { address: canonicalIpv6(groups.join(":")) ?? groups.join(":") };
255
+ return bytes ? { address: printIpv6(bytes) } : null;
194
256
  }
195
257
  case "CNAME":
196
258
  case "PTR":
@@ -250,6 +312,39 @@ class WireReader {
250
312
  let [serial, refresh, retry, expire, minimum] = numbers;
251
313
  return { primary, mailbox, serial, refresh, retry, expire, minimum };
252
314
  }
315
+ case "SVCB":
316
+ case "HTTPS": {
317
+ let priority = this.#uint(2);
318
+ let target = this.#name();
319
+ let params = readWireSvcParams(this.#rest());
320
+ if (priority === null || target === null || params === null)
321
+ return null;
322
+ return { priority, target, params };
323
+ }
324
+ case "TLSA": {
325
+ let [usage, selector, matchingType] = [this.#uint(1), this.#uint(1), this.#uint(1)];
326
+ let association = this.#rest();
327
+ if (usage === null || selector === null || matchingType === null)
328
+ return null;
329
+ if (association.length === 0)
330
+ return null;
331
+ return { usage, selector, matchingType, certificateAssociation: encodeHex(association) };
332
+ }
333
+ case "DS":
334
+ case "CDS": {
335
+ let [keyTag, algorithm, digestType] = [this.#uint(2), this.#uint(1), this.#uint(1)];
336
+ let digest = this.#rest();
337
+ if (keyTag === null || algorithm === null || digestType === null)
338
+ return null;
339
+ if (digest.length === 0)
340
+ return null;
341
+ return { keyTag, algorithm, digestType, digest: encodeHex(digest) };
342
+ }
343
+ case "DNSKEY":
344
+ case "CDNSKEY": {
345
+ let rdata = this.#rest();
346
+ return rdata.length > 4 ? dnskeyFields(rdata) : null;
347
+ }
253
348
  default:
254
349
  return "unknown";
255
350
  }
@@ -262,6 +357,10 @@ class WireReader {
262
357
  this.#offset += length;
263
358
  return bytes;
264
359
  }
360
+ /** Every octet left, which the types ending in a variable-length field read as that field. */
361
+ #rest() {
362
+ return this.#bytes(this.#octets.length - this.#offset) ?? new Uint8Array();
363
+ }
265
364
  /** A big-endian unsigned integer of `size` octets. */
266
365
  #uint(size) {
267
366
  let bytes = this.#bytes(size);
package/dist/parse.js CHANGED
@@ -20,6 +20,8 @@ const NAME_FIELDS = {
20
20
  MX: [1],
21
21
  SRV: [3],
22
22
  SOA: [0, 1],
23
+ SVCB: [1],
24
+ HTTPS: [1],
23
25
  };
24
26
  /** The class numbers with a mnemonic, so `CLASS1` and `IN` read as one class. */
25
27
  const CLASS_NAMES = { "1": "IN", "2": "CS", "3": "CH", "4": "HS" };
@@ -8,6 +8,14 @@
8
8
  import type * as ZoneFile from "./types.js";
9
9
  /** The mnemonic for a type code, or `TYPEnnn` when it has none here. */
10
10
  export declare function typeName(code: number): string;
11
+ /**
12
+ * The IANA code of a type, given as a mnemonic or `TYPEnnn`, so a caller building a query
13
+ * message can name the type the way a zone file does; `null` for a mnemonic it does not know.
14
+ *
15
+ * @example typeCode("HTTPS") // 65
16
+ * @example typeCode("TYPE65280") // 65280
17
+ */
18
+ export declare function typeCode(type: string): number | null;
11
19
  /**
12
20
  * The spelling of a type as a record's `type`: the uppercase mnemonic, and a known
13
21
  * mnemonic for `TYPEnnn` when one exists.
@@ -43,6 +43,13 @@ const TYPED_TYPES = new Set([
43
43
  "CAA",
44
44
  "SRV",
45
45
  "SOA",
46
+ "SVCB",
47
+ "HTTPS",
48
+ "TLSA",
49
+ "DS",
50
+ "CDS",
51
+ "DNSKEY",
52
+ "CDNSKEY",
46
53
  ]);
47
54
  /** The mnemonic for each code in {@link TYPE_CODES}. */
48
55
  const TYPE_NAMES = new Map(Object.entries(TYPE_CODES).map(([name, code]) => [code, name]));
@@ -50,6 +57,22 @@ const TYPE_NAMES = new Map(Object.entries(TYPE_CODES).map(([name, code]) => [cod
50
57
  export function typeName(code) {
51
58
  return TYPE_NAMES.get(code) ?? `TYPE${code}`;
52
59
  }
60
+ /**
61
+ * The IANA code of a type, given as a mnemonic or `TYPEnnn`, so a caller building a query
62
+ * message can name the type the way a zone file does; `null` for a mnemonic it does not know.
63
+ *
64
+ * @example typeCode("HTTPS") // 65
65
+ * @example typeCode("TYPE65280") // 65280
66
+ */
67
+ export function typeCode(type) {
68
+ let upper = type.trim().toUpperCase();
69
+ let generic = /^TYPE(\d{1,5})$/.exec(upper);
70
+ if (generic) {
71
+ let code = Number(generic[1]);
72
+ return code <= 0xffff ? code : null;
73
+ }
74
+ return TYPE_CODES[upper] ?? null;
75
+ }
53
76
  /**
54
77
  * The spelling of a type as a record's `type`: the uppercase mnemonic, and a known
55
78
  * mnemonic for `TYPEnnn` when one exists.
@@ -0,0 +1,24 @@
1
+ import type * as ZoneFile from "./types.js";
2
+ /**
3
+ * Reads the params of SVCB or HTTPS presentation data, the fields after the target.
4
+ * Fails on an unknown or repeated key and on a value its key does not accept.
5
+ *
6
+ * @param text - Zero or more `key[=value]` fields.
7
+ */
8
+ export declare function readSvcParams(text: string): ZoneFile.SvcParams | null;
9
+ /**
10
+ * Reads the params of SVCB or HTTPS wire data, the octets after the target. Keys must
11
+ * strictly increase, as RFC 9460 requires of the wire form.
12
+ *
13
+ * @param octets - The SvcParams octets, `key length value` repeated.
14
+ */
15
+ export declare function readWireSvcParams(octets: Uint8Array): ZoneFile.SvcParams | null;
16
+ /**
17
+ * Prints params in canonical presentation form, keys in ascending number order and values
18
+ * quoted wherever they are free text, so two spellings of one record print alike.
19
+ *
20
+ * @param params - The params to print.
21
+ * @param quote - Prints a character-string with its escapes.
22
+ * @returns The fields joined by spaces; empty for no params.
23
+ */
24
+ export declare function printSvcParams(params: ZoneFile.SvcParams, quote: (text: string) => string): string;