@nictool/dns-resource-record 1.2.3 → 1.2.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ Notable changes to this project are documented in this file.
4
4
 
5
5
  #### Unreleased
6
6
 
7
+ ### [1.2.4] - 2025-10-06
8
+
9
+ - rr: rename ucfirst -> ucFirst
10
+ - rr: setTTL was checking this.owner (copy/paste error)
11
+ - rr: improved throwHelp message formatting
12
+ - aaaa: compress & expand returns lower case
13
+ - caa, dnskey, ds, ipseckey, key, naptr, rrsig, smimea, sshfp, tlsa: refactored opts as JS maps
14
+ - deps: bump to latest versions
15
+
7
16
  ### [1.2.3] - 2025-04-14
8
17
 
9
18
  - test(\*): fully qualify node modules
@@ -310,6 +319,7 @@ Notable changes to this project are documented in this file.
310
319
  [1.1.8]: https://github.com/NicTool/dns-resource-record/releases/tag/1.1.8
311
320
  [1.1.7]: https://github.com/NicTool/dns-resource-record/releases/tag/1.1.7
312
321
  [1.2.0]: https://github.com/NicTool/dns-resource-record/releases/tag/1.2.0
313
- [1.2.1]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.2.1
322
+ [1.2.1]: https://github.com/NicTool/dns-resource-record/releases/tag/1.2.1
314
323
  [1.2.2]: https://github.com/NicTool/dns-resource-record/releases/tag/1.2.2
315
- [1.2.3]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.2.3
324
+ [1.2.3]: https://github.com/NicTool/dns-resource-record/releases/tag/1.2.3
325
+ [1.2.4]: https://github.com/NicTool/dns-resource-record/releases/tag/v1.2.4
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # dns-resource-record
5
5
 
6
- DNS resource record parser, validator, importer, and exporter.
6
+ DNS resource record parser, validator, importer, and exporter for node.js & browsers.
7
7
 
8
8
  ## SYNOPSIS
9
9
 
@@ -181,7 +181,7 @@ Consider this a "running with scissors" mode.
181
181
 
182
182
  ## Supported Records
183
183
 
184
- This module intends to include support for all current (ie, not officially deprecated) DNS RRs **and** all RRs that are in active use on the internet.
184
+ This module intends to support all current (ie, not officially deprecated) DNS RRs **and** all RRs that are in active use on the internet.
185
185
 
186
186
  PRs are welcome, especially PRs with tests.
187
187
 
@@ -244,17 +244,6 @@ PRs are welcome, especially PRs with tests.
244
244
 
245
245
  ## TODO
246
246
 
247
- - [x] Change all IPs to use [RFC example/doc](https://en.wikipedia.org/wiki/Reserved_IP_addresses) address space
248
- - [x] change all domains to use reserved doc names
249
- - [x] import tests from nictool/server/t/12_records.t
250
- - [x] add defaults for empty values like TTL
251
- - [x] DNSSEC RRs: DS, NSEC, NSEC3, NSEC3PARAM, RRSIG
252
- - [x] CERT RRs: CERT, KEY, SIG, OPENPGPKEY
253
- - [x] RFC 4034: if the type of RR is NS, MD, MF, CNAME, SOA, MB,
254
- MG, MR, PTR, HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT,
255
- NAPTR, KX, SRV, DNAME, A6, RRSIG, or NSEC, all uppercase
256
- letters in the DNS names contained within the RDATA are replaced by the lowercase letters;
257
- - [x] LOC record ingest/out isn't consistent with API
258
247
  - [ ] export a web page for each RR type
259
248
 
260
249
  ## DEVELOP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nictool/dns-resource-record",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "DNS Resource Records",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -19,7 +19,8 @@
19
19
  "prettier:fix": "npx prettier --ignore-path .gitignore --write .",
20
20
  "test": "npx mocha",
21
21
  "versions": "npx dependency-version-checker check",
22
- "watch": "npm run test -- --watch"
22
+ "watch": "npm run test -- --watch",
23
+ "versions:fix": "npx dependency-version-checker update"
23
24
  },
24
25
  "repository": {
25
26
  "type": "git",
@@ -46,13 +47,13 @@
46
47
  "homepage": "https://github.com/NicTool/dns-resource-record#readme",
47
48
  "devDependencies": {
48
49
  "@eslint/js": "^10.0.0",
49
- "eslint": "^9.15.0",
50
- "globals": "^16.0.0",
51
- "mocha": "^11.2.2"
50
+ "eslint": "^9.37.0",
51
+ "globals": "^16.4.0",
52
+ "mocha": "^11.7.4"
52
53
  },
53
54
  "prettier": {
54
55
  "semi": false,
55
56
  "singleQuote": true,
56
57
  "trailingComma": "all"
57
58
  }
58
- }
59
+ }
package/rr/aaaa.js CHANGED
@@ -15,7 +15,7 @@ export default class AAAA extends RR {
15
15
  }
16
16
 
17
17
  getCompressed(val) {
18
- this.compress(val || this.get('address'))
18
+ return this.compress(val || this.get('address'))
19
19
  }
20
20
 
21
21
  getDescription() {
@@ -112,12 +112,12 @@ export default class AAAA extends RR {
112
112
 
113
113
  for (const re of mostConsecutiveZeros) {
114
114
  if (re.test(r)) {
115
- r = r.replace(re, '::')
115
+ r = r.replace(re, '::') // 4.2
116
116
  break
117
117
  }
118
118
  }
119
119
 
120
- return r
120
+ return r.toLowerCase() // 4.3
121
121
  }
122
122
 
123
123
  expand(val, delimiter) {
@@ -134,11 +134,12 @@ export default class AAAA extends RR {
134
134
  .split(':')
135
135
  .map((s) => s.padStart(4, 0))
136
136
  .join(delimiter)
137
+ .toLowerCase()
137
138
  }
138
139
 
139
140
  /****** EXPORTERS *******/
140
141
  toBind(zone_opts) {
141
- return `${this.getPrefix(zone_opts)}\t${this.compress(this.get('address'))}\n`
142
+ return `${this.getPrefix(zone_opts)}\t${this.getCompressed()}\n`
142
143
  }
143
144
 
144
145
  toTinydns() {
package/rr/caa.js CHANGED
@@ -10,25 +10,36 @@ export default class CAA extends RR {
10
10
  setFlags(val) {
11
11
  this.is8bitInt('CAA', 'flags', val)
12
12
 
13
- if (![0, 128].includes(val)) {
13
+ if (!this.getFlagsOptions().has(val)) {
14
14
  this.throwHelp(`CAA flags ${val} not recognized`)
15
15
  }
16
16
 
17
17
  this.set('flags', val)
18
18
  }
19
19
 
20
+ getFlagsOptions() {
21
+ return new Map([
22
+ [0, 'Non Critical'],
23
+ [128, 'Critical'],
24
+ ])
25
+ }
26
+
20
27
  setTag(val) {
21
28
  if (typeof val !== 'string' || val.length < 1 || /[^a-z0-9]/.test(val))
22
29
  this.throwHelp(
23
30
  `CAA tag must be a sequence of ASCII letters and numbers in lowercase`,
24
31
  )
25
32
 
26
- if (!['issue', 'issuewild', 'iodef'].includes(val)) {
33
+ if (!this.getTagOptions().has(val)) {
27
34
  this.throwHelp(`CAA tag ${val} not recognized`)
28
35
  }
29
36
  this.set('tag', val)
30
37
  }
31
38
 
39
+ getTagOptions() {
40
+ return new Map([['issue'], ['issuewild'], ['iodef']])
41
+ }
42
+
32
43
  setValue(val) {
33
44
  // either (2) a quoted string or
34
45
  // (1) a contiguous set of characters without interior spaces
package/rr/dnskey.js CHANGED
@@ -12,34 +12,67 @@ export default class DNSKEY extends RR {
12
12
  // a 2 octet Flags Field
13
13
  this.is16bitInt('DNSKEY', 'flags', val)
14
14
 
15
- // the possible values are: 0, 256, and 257; RFC 4034
16
- if (![0, 256, 257].includes(val)) this.throwHelp(`DNSKEY: flags invalid`)
15
+ if (!this.getFlagsOptions().has(val)) {
16
+ this.throwHelp(
17
+ `DNSKEY: flags must be in the set: ${this.getFlagsOptions()}`,
18
+ )
19
+ }
17
20
 
18
21
  this.set('flags', val)
19
22
  }
20
23
 
24
+ // possible values are: 0, 256, and 257; RFC 4034
25
+ getFlagsOptions() {
26
+ return new Map([[0], [256], [257]])
27
+ }
28
+
21
29
  setProtocol(val) {
22
30
  // 1 octet
23
31
  this.is8bitInt('DNSKEY', 'protocol', val)
24
32
 
25
33
  // The Protocol Field MUST be represented as an unsigned decimal integer with a value of 3.
26
- if (![3].includes(val)) this.throwHelp(`DNSKEY: protocol invalid`)
34
+ if (!this.getProtocolOptions().has(val))
35
+ this.throwHelp(`DNSKEY: protocol invalid`)
27
36
 
28
37
  this.set('protocol', val)
29
38
  }
30
39
 
40
+ getProtocolOptions() {
41
+ return new Map([[3]])
42
+ }
43
+
31
44
  setAlgorithm(val) {
32
45
  // 1 octet
33
46
  this.is8bitInt('DNSKEY', 'algorithm', val)
34
47
 
35
48
  // https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
36
- // 1=RSA/MD5, 2=DH, 3=DSA/SHA-1, 4=EC, 5=RSA/SHA-1
37
- if (![...Array(16).keys(), 253, 254].includes(val))
49
+ if (!this.getAlgorithmOptions().has(val))
38
50
  console.error(`DNSKEY: algorithm (${val}) not recognized`)
39
51
 
40
52
  this.set('algorithm', val)
41
53
  }
42
54
 
55
+ getAlgorithmOptions() {
56
+ return new Map([
57
+ [1, 'RSA/MD5 (DEPRECATED)'],
58
+ [2, 'DH'],
59
+ [3, 'DSA/SHA-1'],
60
+ [4, 'EC'],
61
+ [5, 'RSA/SHA-1'],
62
+ [6, 'DSA-NSEC3-SHA1'],
63
+ [7, 'RSASHA1-NSEC3-SHA1'],
64
+ [8, 'RSA/SHA-256'],
65
+ [9, ''],
66
+ [10, 'RSA/SHA-512'],
67
+ [13, 'ECDSA Curve P-256 with SHA-256'],
68
+ [14, 'ECDSA Curve P-384 with SHA-384'],
69
+ [15, 'Ed25519'],
70
+ [16, 'Ed448'],
71
+ [253],
72
+ [254],
73
+ ])
74
+ }
75
+
43
76
  setPublickey(val) {
44
77
  if (!val) this.throwHelp(`DNSKEY: publickey is required`)
45
78
 
package/rr/ds.js CHANGED
@@ -17,13 +17,24 @@ export default class DS extends RR {
17
17
  }
18
18
 
19
19
  setAlgorithm(val) {
20
- // 1=RSA/MD5, 2=DH, 3=DSA/SHA-1, 4=EC, 5=RSA/SHA-1
21
- if (![1, 2, 3, 4, 5, 253, 254].includes(val))
20
+ if (!this.getAlgorithmOptions().has(val))
22
21
  this.throwHelp(`DS: algorithm invalid`)
23
22
 
24
23
  this.set('algorithm', val)
25
24
  }
26
25
 
26
+ getAlgorithmOptions() {
27
+ return new Map([
28
+ [1, 'RSA/MD5'],
29
+ [2, 'DH'],
30
+ [3, 'DSA/SHA-1'],
31
+ [4, 'EC'],
32
+ [5, 'RSA/SHA-1'],
33
+ [253, ''],
34
+ [254, ''],
35
+ ])
36
+ }
37
+
27
38
  setDigestType(val) {
28
39
  if (![1, 2].includes(val)) this.throwHelp(`DS: digest type invalid`)
29
40
 
package/rr/ipseckey.js CHANGED
@@ -16,23 +16,41 @@ export default class IPSECKEY extends RR {
16
16
  }
17
17
 
18
18
  setGatewayType(val) {
19
- // 0 (none), 1 (4-byte IPv4), 2 (16-byte IPv6), 3 (wire encoded domain name)
20
- if (![0, 1, 2, 3].includes(val))
19
+ if (!this.getGatewayTypeOptions().has(val))
21
20
  this.throwHelp(`IPSECKEY: Gateway Type is invalid`)
22
21
 
23
22
  this.set('gateway type', val)
24
23
  }
25
24
 
25
+ getGatewayTypeOptions() {
26
+ return new Map([
27
+ [0, 'none'],
28
+ [1, '4-byte IPv4'],
29
+ [2, '16-byte IPv6'],
30
+ [3, 'wire encoded domain name'],
31
+ ])
32
+ }
33
+
26
34
  setAlgorithm(val) {
27
- // unsigned int, 1 octet, values: 1=DSA, 2=RSA
28
- if (![1, 2].includes(val)) this.throwHelp(`IPSECKEY: Algorithm invalid`)
35
+ if (!this.getAlgorithmOptions().has(val))
36
+ this.throwHelp(`IPSECKEY: Algorithm invalid`)
29
37
 
30
38
  this.set('algorithm', val)
31
39
  }
32
40
 
41
+ getAlgorithmOptions() {
42
+ return new Map([
43
+ [1, 'DSA'],
44
+ [2, 'RSA'],
45
+ ])
46
+ }
47
+
33
48
  setGateway(val) {
34
- const gwErr = new Error(`IPSECKEY: gateway invalid (${val})`)
35
- switch (this.get('gateway type')) {
49
+ const type = this.get('gateway type')
50
+ const gwErr = new Error(
51
+ `IPSECKEY: gateway invalid (${val}) for type ${type}`,
52
+ )
53
+ switch (type) {
36
54
  case 0:
37
55
  if (val !== '.') throw gwErr
38
56
  break
package/rr/key.js CHANGED
@@ -22,13 +22,25 @@ export default class KEY extends RR {
22
22
 
23
23
  setAlgorithm(val) {
24
24
  // 1 octet
25
- // 1=RSA/MD5, 2=DH, 3=DSA/SHA-1, 4=EC, 5=RSA/SHA-1
26
- if (![1, 2, 3, 4, 5, 253, 254].includes(val))
25
+
26
+ if (!this.getAlgorithmOptions().has(val))
27
27
  this.throwHelp(`KEY: algorithm invalid`)
28
28
 
29
29
  this.set('algorithm', val)
30
30
  }
31
31
 
32
+ getAlgorithmOptions() {
33
+ return new Map([
34
+ [1, 'RSA/MD5'],
35
+ [2, 'DH'],
36
+ [3, 'DSA/SHA-1'],
37
+ [4, 'EC'],
38
+ [5, 'RSA/SHA-1'],
39
+ [253, ''],
40
+ [254, ''],
41
+ ])
42
+ }
43
+
32
44
  setPublickey(val) {
33
45
  if (!val) this.throwHelp(`KEY: publickey is required`)
34
46
 
package/rr/naptr.js CHANGED
@@ -40,12 +40,16 @@ export default class NAPTR extends RR {
40
40
  }
41
41
 
42
42
  setFlags(val) {
43
- if (!['', 'S', 'A', 'U', 'P'].includes(val.toUpperCase()))
43
+ if (!this.getFlagsOptions().has(val.toUpperCase()))
44
44
  this.throwHelp(`NAPTR flags are invalid`)
45
45
 
46
46
  this.set('flags', val.toUpperCase())
47
47
  }
48
48
 
49
+ getFlagsOptions() {
50
+ return new Map([[''], ['S'], ['A'], ['U'], ['P']])
51
+ }
52
+
49
53
  setService(val) {
50
54
  this.set('service', val)
51
55
  }
package/rr/rrsig.js CHANGED
@@ -16,13 +16,24 @@ export default class RRSIG extends RR {
16
16
 
17
17
  setAlgorithm(val) {
18
18
  // a 1 octet Algorithm field
19
- // 1=RSA/MD5, 2=DH, 3=RRSIGA/SHA-1, 4=EC, 5=RSA/SHA-1
20
- if (![1, 2, 3, 4, 5, 253, 254].includes(val))
19
+ if (!this.getAlgorithmOptions().has(val))
21
20
  this.throwHelp(`RRSIG: algorithm invalid`)
22
21
 
23
22
  this.set('algorithm', val)
24
23
  }
25
24
 
25
+ getAlgorithmOptions() {
26
+ return new Map([
27
+ [1, 'RSA/MD5'],
28
+ [2, 'DH'],
29
+ [3, 'RRSIGA/SHA-1'],
30
+ [4, 'EC'],
31
+ [5, 'RSA/SHA-1'],
32
+ [253],
33
+ [254],
34
+ ])
35
+ }
36
+
26
37
  setLabels(val) {
27
38
  // a 1 octet Labels field
28
39
  this.is8bitInt('RRSIG', 'labels', val)
package/rr/smimea.js CHANGED
@@ -7,24 +7,50 @@ export default class SMIMEA extends RR {
7
7
 
8
8
  /****** Resource record specific setters *******/
9
9
  setCertificateUsage(val) {
10
- if (![0, 1, 2, 3].includes(val))
10
+ if (!this.getCertificateUsageOptions().has(val))
11
11
  this.throwHelp(`SMIMEA: certificate usage invalid`)
12
12
 
13
13
  this.set('certificate usage', val)
14
14
  }
15
15
 
16
+ getCertificateUsageOptions() {
17
+ return new Map([
18
+ [0, 'CA certificate'],
19
+ [1, 'an end entity certificate'],
20
+ [2, 'the trust anchor'],
21
+ [3, 'domain-issued certificate'],
22
+ ])
23
+ }
24
+
16
25
  setSelector(val) {
17
- if (![0, 1].includes(val)) this.throwHelp(`SMIMEA: selector invalid`)
26
+ if (!this.getSelectorOptions().has(val))
27
+ this.throwHelp(`SMIMEA: selector invalid`)
18
28
 
19
29
  this.set('selector', val)
20
30
  }
21
31
 
32
+ getSelectorOptions() {
33
+ return new Map([
34
+ [0, 'Full certificate'],
35
+ [1, 'SubjectPublicKeyInfo'],
36
+ ])
37
+ }
38
+
22
39
  setMatchingType(val) {
23
- if (![0, 1, 2].includes(val)) this.throwHelp(`SMIMEA: matching type`)
40
+ if (!this.getMatchingTypeOptions().has(val))
41
+ this.throwHelp(`SMIMEA: matching type`)
24
42
 
25
43
  this.set('matching type', val)
26
44
  }
27
45
 
46
+ getMatchingTypeOptions() {
47
+ return new Map([
48
+ [0, 'Exact match'],
49
+ [1, 'SHA-256 hash'],
50
+ [2, 'SHA-512 hash'],
51
+ ])
52
+ }
53
+
28
54
  setCertificateAssociationData(val) {
29
55
  this.set('certificate association data', val)
30
56
  }
package/rr/sshfp.js CHANGED
@@ -8,19 +8,36 @@ export default class SSHFP extends RR {
8
8
 
9
9
  /****** Resource record specific setters *******/
10
10
  setAlgorithm(val) {
11
- // 0: reserved 1: RSA 2: DSA 3: ECDSA 4: Ed25519 6: Ed448
12
11
  this.is8bitInt('SSHFP', 'algorithm', val)
13
12
 
14
13
  this.set('algorithm', val)
15
14
  }
16
15
 
16
+ getAlgorithmOptions() {
17
+ return new Map([
18
+ [0, 'reserved'],
19
+ [1, 'RSA'],
20
+ [2, 'DSA'],
21
+ [3, 'ECDSA'],
22
+ [4, 'Ed25519'],
23
+ [6, 'Ed448'],
24
+ ])
25
+ }
26
+
17
27
  setFptype(val) {
18
- // 0: reserved, 1: SHA-1, 2: SHA-256
19
- this.is8bitInt('SSHFP', 'type', val)
28
+ this.is8bitInt('SSHFP', 'fptype', val)
20
29
 
21
30
  this.set('fptype', val)
22
31
  }
23
32
 
33
+ getFptypeOptions() {
34
+ return new Map([
35
+ [0, 'reserved'],
36
+ [1, 'SHA-1'],
37
+ [2, 'SHA-256'],
38
+ ])
39
+ }
40
+
24
41
  setFingerprint(val) {
25
42
  this.set('fingerprint', val)
26
43
  }
package/rr/tlsa.js CHANGED
@@ -9,24 +9,50 @@ export default class TLSA extends RR {
9
9
 
10
10
  /****** Resource record specific setters *******/
11
11
  setCertificateUsage(val) {
12
- if (![0, 1, 2, 3].includes(val))
12
+ if (!this.getCertificateUsageOptions().has(val))
13
13
  this.throwHelp(`TLSA: certificate usage invalid`)
14
14
 
15
15
  this.set('certificate usage', val)
16
16
  }
17
17
 
18
+ getCertificateUsageOptions() {
19
+ return new Map([
20
+ [0, 'CA certificate'],
21
+ [1, 'an end entity certificate'],
22
+ [2, 'the trust anchor'],
23
+ [3, 'domain-issued certificate'],
24
+ ])
25
+ }
26
+
18
27
  setSelector(val) {
19
- if (![0, 1].includes(val)) this.throwHelp(`TLSA: selector invalid`)
28
+ if (!this.getSelectorOptions().has(val))
29
+ this.throwHelp(`TLSA: selector invalid`)
20
30
 
21
31
  this.set('selector', val)
22
32
  }
23
33
 
34
+ getSelectorOptions() {
35
+ return new Map([
36
+ [0, 'Full certificate'],
37
+ [1, 'SubjectPublicKeyInfo'],
38
+ ])
39
+ }
40
+
24
41
  setMatchingType(val) {
25
- if (![0, 1, 2].includes(val)) this.throwHelp(`TLSA: matching type`)
42
+ if (!this.getMatchingTypeOptions().has(val))
43
+ this.throwHelp(`TLSA: matching type`)
26
44
 
27
45
  this.set('matching type', val)
28
46
  }
29
47
 
48
+ getMatchingTypeOptions() {
49
+ return new Map([
50
+ [0, 'Exact match'],
51
+ [1, 'SHA-256 hash'],
52
+ [2, 'SHA-512 hash'],
53
+ ])
54
+ }
55
+
30
56
  setCertificateAssociationData(val) {
31
57
  this.set('certificate association data', val)
32
58
  }
package/rr.js CHANGED
@@ -19,7 +19,7 @@ export default class RR extends Map {
19
19
  this.setClass(opts?.class)
20
20
 
21
21
  for (const f of this.getFields('rdata')) {
22
- const fnName = `set${this.ucfirst(f)}`
22
+ const fnName = `set${this.ucFirst(f)}`
23
23
  if (this[fnName] === undefined)
24
24
  this.throwHelp(`Missing ${fnName} in class ${this.get('type')}`)
25
25
  this[fnName](opts[f])
@@ -28,7 +28,7 @@ export default class RR extends Map {
28
28
  if (opts.comment) this.set('comment', opts.comment)
29
29
  }
30
30
 
31
- ucfirst(str) {
31
+ ucFirst(str) {
32
32
  return str
33
33
  .split(/\s/)
34
34
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
@@ -104,7 +104,7 @@ export default class RR extends Map {
104
104
  this.throwHelp(`TTL must be numeric (${typeof t})`)
105
105
 
106
106
  // RFC 1035, 2181
107
- this.is32bitInt(this.owner, 'TTL', t)
107
+ this.is32bitInt(this.get('type'), 'TTL', t)
108
108
 
109
109
  this.set('ttl', t)
110
110
  }
@@ -126,7 +126,7 @@ export default class RR extends Map {
126
126
  if (this.constructor.name === 'RR') throw new Error(e)
127
127
 
128
128
  const example = this.getCanonical
129
- ? `Example ${this.constructor.name}:\n${this.getCanonical()}\n\n`
129
+ ? `Example ${this.constructor.name}:\n${JSON.stringify(this.getCanonical(), null, '\t')}\n\n`
130
130
  : `${this.constructor.name} records have the fields: ${this.getFields().join(', ')}\n\n`
131
131
 
132
132
  throw new Error(`${e}\n\n${example}${this.citeRFC()}\n`)