uptimeify-dnsbl 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -26,9 +26,9 @@ We intentionally **do not** perform the DNS lookups internally. This allows you
26
26
  We maintain definitions for the most reliable and widely used lists:
27
27
 
28
28
  - **Spamhaus ZEN** (SBL, CSS, XBL, PBL) - _The gold standard_
29
+ - **Spamhaus DQS (ZEN)** - Same result parsing, but query via Spamhaus DQS (requires a DQS key)
29
30
  - **Barracuda** (BRBL)
30
31
  - **SpamCop**
31
- - **Abusix Mail Intelligence**
32
32
  - **SORBS** (Aggregate)
33
33
  - **UCEPROTECT** (Level 1)
34
34
  - **Hostkarma**
@@ -38,7 +38,6 @@ We maintain definitions for the most reliable and widely used lists:
38
38
  - **DroneBL**
39
39
  - **Spam Eating Monkey** (SEM-BLACK)
40
40
  - **URIBL Black**
41
- - **Madavi DNSBL**
42
41
  - **RV-SOFT Technology**
43
42
  - **ZapBL**
44
43
  - **Suomispam Reputation**
@@ -52,7 +51,6 @@ We maintain definitions for the most reliable and widely used lists:
52
51
  - **Abuse.ch Combined**
53
52
  - **UCEPROTECT Level 2**
54
53
  - **Abuse.ch Drone**
55
- - **OrveDB AuBads**
56
54
  - **0spam RBL**
57
55
  - **Singular TTK PTE**
58
56
  - **SpamRats Spam**
@@ -61,7 +59,6 @@ We maintain definitions for the most reliable and widely used lists:
61
59
  - **Woody's SMTP Blacklist**
62
60
  - **WPBL**
63
61
  - **UCEPROTECT Level 3**
64
- - **Duinv AuPads**
65
62
  - **Gweep Proxy**
66
63
  - **Gweep Relays**
67
64
  - **Abuse.ch Spam**
@@ -77,7 +74,6 @@ We maintain definitions for the most reliable and widely used lists:
77
74
  - **Mailspike Z**
78
75
  - **Anonmails.de**
79
76
  - **Pedantic.org**
80
- - **Swinog**
81
77
  - **GBUdb Truncate**
82
78
  - **LashBack UBL**
83
79
 
@@ -97,8 +93,15 @@ import { resolve4 } from "node:dns/promises";
97
93
 
98
94
  async function check(ip) {
99
95
  // 1. Get the list of domains to query
100
- const checks = getLookupDomains(ip);
96
+ const checks = getLookupDomains(ip, {
97
+ spamhaus: {
98
+ // Default: "zen" (public). Use "dqs" to query Spamhaus via DQS.
99
+ mode: process.env.SPAMHAUS_DQS_KEY ? "dqs" : "zen",
100
+ dqsKey: process.env.SPAMHAUS_DQS_KEY,
101
+ },
102
+ });
101
103
  // Returns array: [{ address: "4.3.2.1.zen.spamhaus.org", listKey: "zen.spamhaus.org" }, ...]
104
+ // Or with DQS: [{ address: "4.3.2.1.<DQS_KEY>.zen.dq.spamhaus.net", listKey: "zen.dq.spamhaus.net" }, ...]
102
105
 
103
106
  // 2. Run your DNS lookups
104
107
  // We use typical Promise handling here, but you can use any async pattern
@@ -133,10 +136,28 @@ check("127.0.0.2");
133
136
 
134
137
  ## API
135
138
 
136
- ### `getLookupDomains(ip)`
139
+ ### `getLookupDomains(ip, options?)`
137
140
 
138
141
  Returns an array of objects containing the fully qualified domain to query (`address`) and the identifier key (`listKey`).
139
142
 
143
+ #### Spamhaus DQS
144
+
145
+ You can switch Spamhaus from the public DNSBL hostnames to Spamhaus DQS by passing an options object.
146
+
147
+ ```js
148
+ const checks = getLookupDomains("1.2.3.4", {
149
+ spamhaus: {
150
+ mode: "dqs",
151
+ dqsKey: process.env.SPAMHAUS_DQS_KEY,
152
+ },
153
+ });
154
+ ```
155
+
156
+ Notes:
157
+
158
+ - When `mode: "dqs"` is set, this library will generate DQS lookup domains for Spamhaus (and will not query the public Spamhaus DNSBL hostnames).
159
+ - Keep the DQS key secret (don’t commit it to git or log it).
160
+
140
161
  ### `parseLookupResult(listKey, resultCode)`
141
162
 
142
163
  Takes the list identifier and the IP address returned by the DNS query (e.g., `127.0.0.2`) and returns a rich object with the listing name, reason, and delist URL.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Uptimeify DNSBL - Spamhaus DQS Example
3
+ *
4
+ * This script demonstrates how to query Spamhaus via DQS using a DQS key.
5
+ *
6
+ * IMPORTANT:
7
+ * - Treat the key as a secret in real projects (use env vars / secret manager).
8
+ * - The key below is a placeholder example provided by the user.
9
+ */
10
+
11
+ import { getLookupDomains, parseLookupResult } from "../src/index.js";
12
+ import { resolve4 } from "node:dns/promises";
13
+
14
+ const DQS_KEY = process.env.SPAMHAUS_DQS_KEY;
15
+
16
+ async function checkIP(ip) {
17
+ console.log(`\n🔎 Checking IP (Spamhaus DQS): ${ip}`);
18
+
19
+ const domains = getLookupDomains(ip, {
20
+ spamhaus: {
21
+ mode: "dqs",
22
+ dqsKey: DQS_KEY,
23
+ },
24
+ });
25
+
26
+ // Reduce output noise: only show Spamhaus checks here.
27
+ const spamhausOnly = domains.filter(
28
+ (d) => d.listKey === "zen.dq.spamhaus.net" || d.listKey === "sbl.dq.spamhaus.net",
29
+ );
30
+
31
+ console.log(` Using DQS key: ${DQS_KEY.slice(0, 4)}… (masked)`);
32
+ console.log(` Querying ${spamhausOnly.length} Spamhaus list(s) via DQS...`);
33
+
34
+ const results = await Promise.all(
35
+ spamhausOnly.map(async ({ address, listKey }) => {
36
+ try {
37
+ const [code] = await resolve4(address);
38
+ return parseLookupResult(listKey, code);
39
+ } catch (error) {
40
+ if (error.code === "ENOTFOUND") {
41
+ return {
42
+ name: listKey,
43
+ listed: false,
44
+ reason: "Not Listed",
45
+ code: null,
46
+ delistUrl: null,
47
+ };
48
+ }
49
+
50
+ return {
51
+ name: listKey,
52
+ listed: false,
53
+ reason: `DNS Error: ${error.code || error.message}`,
54
+ code: null,
55
+ delistUrl: null,
56
+ };
57
+ }
58
+ }),
59
+ );
60
+
61
+ const listed = results.filter((r) => r.listed);
62
+ const notListed = results.filter((r) => !r.listed);
63
+
64
+ if (listed.length > 0) {
65
+ console.log(`\n❌ [LISTED] Found in ${listed.length} Spamhaus list(s):`);
66
+ for (const res of listed) {
67
+ console.log(` - ${res.name}`);
68
+ console.log(` Reason: ${res.reason}`);
69
+ console.log(` Code: ${res.code}`);
70
+ if (res.delistUrl) console.log(` Delist: ${res.delistUrl}`);
71
+ }
72
+ } else {
73
+ console.log("\n✅ [CLEAN] Not listed in Spamhaus (via DQS).");
74
+ }
75
+
76
+ // Show non-listed results too, because DQS can return useful block/policy messages.
77
+ if (notListed.length > 0) {
78
+ console.log("\nℹ️ Other results:");
79
+ for (const res of notListed) {
80
+ console.log(` - ${res.name}: ${res.reason}${res.code ? ` (${res.code})` : ""}`);
81
+ }
82
+ }
83
+ }
84
+
85
+ console.log("--- Spamhaus DQS Verification Utility ---");
86
+
87
+ // Example IPs (replace as needed)
88
+ await checkIP("213.209.159.159");
89
+ await checkIP("2a06:4880:8000::99");
package/examples/test.js CHANGED
@@ -21,10 +21,22 @@ import { performance } from "node:perf_hooks";
21
21
  async function checkIP(ip) {
22
22
  console.log(`\n🔎 Checking IP: ${ip}`);
23
23
 
24
+ const lookupOptions = process.env.SPAMHAUS_DQS_KEY
25
+ ? {
26
+ spamhaus: {
27
+ mode: "dqs",
28
+ dqsKey: process.env.SPAMHAUS_DQS_KEY,
29
+ },
30
+ }
31
+ : undefined;
32
+ console.log(
33
+ ` Spamhaus mode: ${lookupOptions ? "dqs" : "zen"}${lookupOptions ? " (via SPAMHAUS_DQS_KEY)" : ""}`,
34
+ );
35
+
24
36
  try {
25
37
  // 1. Get the list of all DNSBL domains to query for this IP
26
38
  const startGetDomains = performance.now();
27
- const domains = getLookupDomains(ip);
39
+ const domains = getLookupDomains(ip, lookupOptions);
28
40
  const endGetDomains = performance.now();
29
41
  console.log(
30
42
  ` Preparing to query ${domains.length} blacklists... (took ${(endGetDomains - startGetDomains).toFixed(5)}ms)`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uptimeify-dnsbl",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Helper module to prepare DNSBL queries and parse results.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -14,6 +14,29 @@ export const blacklists = {
14
14
  name: "Spamhaus ZEN",
15
15
  delistUrl: "https://check.spamhaus.org/",
16
16
  mappings: {
17
+ "127.255.255.254":
18
+ "Query blocked by Spamhaus (rate limit / missing authorization / policy)",
19
+ "127.0.0.2": "Listed in SBL (Spamhaus Block List - Direct spam sources)",
20
+ "127.0.0.3":
21
+ "Listed in CSS (Combating Spam Syndicate - Compromised hosts)",
22
+ "127.0.0.4":
23
+ "Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
24
+ "127.0.0.5":
25
+ "Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
26
+ "127.0.0.6":
27
+ "Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
28
+ "127.0.0.7":
29
+ "Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
30
+ "127.0.0.10": "PBL - ISP Policy",
31
+ "127.0.0.11": "PBL - ISP Policy",
32
+ },
33
+ },
34
+ "zen.dq.spamhaus.net": {
35
+ name: "Spamhaus DQS (ZEN)",
36
+ delistUrl: "https://check.spamhaus.org/",
37
+ mappings: {
38
+ "127.255.255.254":
39
+ "Query blocked by Spamhaus (rate limit / missing authorization / policy)",
17
40
  "127.0.0.2": "Listed in SBL (Spamhaus Block List - Direct spam sources)",
18
41
  "127.0.0.3":
19
42
  "Listed in CSS (Combating Spam Syndicate - Compromised hosts)",
@@ -43,15 +66,16 @@ export const blacklists = {
43
66
  "127.0.0.2": "Listed in SpamCop",
44
67
  },
45
68
  },
46
- "combined.mail.abusix.zone": {
47
- name: "Abusix Mail Intelligence",
48
- delistUrl: "https://abusix.com/lookup/",
49
- mappings: {
50
- "127.0.0.2": "Blacklisted (Generic)",
51
- "127.0.0.3": "Blacklisted (External)",
52
- "127.0.0.4": "Blacklisted (Dynamic/Policy)",
53
- },
54
- },
69
+ // Disabled as Abusix RBLs are now paid services
70
+ // "combined.mail.abusix.zone": {
71
+ // name: "Abusix Mail Intelligence",
72
+ // delistUrl: "https://abusix.com/lookup/",
73
+ // mappings: {
74
+ // "127.0.0.2": "Blacklisted (Generic)",
75
+ // "127.0.0.3": "Blacklisted (External)",
76
+ // "127.0.0.4": "Blacklisted (Dynamic/Policy)",
77
+ // },
78
+ //},
55
79
  "dnsbl.sorbs.net": {
56
80
  name: "SORBS Aggregate",
57
81
  delistUrl: "http://www.sorbs.net/lookup.shtml",
@@ -139,16 +163,19 @@ export const blacklists = {
139
163
  name: "URIBL Black",
140
164
  delistUrl: "https://lookup.uribl.com/",
141
165
  mappings: {
166
+ "127.0.0.1": "Query blocked, possibly due to high volume",
142
167
  "127.0.0.2": "Listed in URIBL Black",
168
+ "127.0.0.255": "Query blocked",
143
169
  },
144
170
  },
145
- "dnsbl.madavi.de": {
146
- name: "Madavi DNSBL",
147
- delistUrl: "https://www.madavi.de/en/dnsbl.html",
148
- mappings: {
149
- "127.0.0.2": "Listed in Madavi DNSBL",
150
- },
151
- },
171
+ // Dead on arrival, not reliable
172
+ // "dnsbl.madavi.de": {
173
+ // name: "Madavi DNSBL",
174
+ // delistUrl: "https://www.madavi.de/en/dnsbl.html",
175
+ // mappings: {
176
+ // "127.0.0.2": "Listed in Madavi DNSBL",
177
+ // },
178
+ // },
152
179
  "dnsbl.rv-soft.info": {
153
180
  name: "RV-SOFT Technology DNSBL",
154
181
  delistUrl: "http://dnsbl.rv-soft.info",
@@ -229,6 +256,17 @@ export const blacklists = {
229
256
  name: "Spamhaus SBL",
230
257
  delistUrl: "https://check.spamhaus.org/",
231
258
  mappings: {
259
+ "127.255.255.254":
260
+ "Query blocked by Spamhaus (rate limit / missing authorization / policy)",
261
+ "127.0.0.2": "Listed in SBL (Spamhaus Block List)",
262
+ },
263
+ },
264
+ "sbl.dq.spamhaus.net": {
265
+ name: "Spamhaus DQS (SBL)",
266
+ delistUrl: "https://check.spamhaus.org/",
267
+ mappings: {
268
+ "127.255.255.254":
269
+ "Query blocked by Spamhaus (rate limit / missing authorization / policy)",
232
270
  "127.0.0.2": "Listed in SBL (Spamhaus Block List)",
233
271
  },
234
272
  },
@@ -246,13 +284,14 @@ export const blacklists = {
246
284
  "127.0.0.2": "Listed in Pedantic.org",
247
285
  },
248
286
  },
249
- "swinog.spam.dnsbl.ch": {
250
- name: "Swinog DNSBL",
251
- delistUrl: "https://www.swinog.ch/spam/",
252
- mappings: {
253
- "127.0.0.2": "Listed in Swinog",
254
- },
255
- },
287
+ // Disabled as Swinog DNSBL is not stable/reliable
288
+ //"swinog.spam.dnsbl.ch": {
289
+ // name: "Swinog DNSBL",
290
+ // delistUrl: "https://www.swinog.ch/spam/",
291
+ // mappings: {
292
+ // "127.0.0.2": "Listed in Swinog",
293
+ // },
294
+ //},
256
295
  "truncate.gbudb.net": {
257
296
  name: "GBUdb Truncate",
258
297
  delistUrl: "http://www.gbudb.com/truncate/",
@@ -295,13 +334,14 @@ export const blacklists = {
295
334
  "127.0.0.2": "Listed in Abuse.ch Drone",
296
335
  },
297
336
  },
298
- "orvedb.aupads.org": {
299
- name: "OrveDB AuBads",
300
- delistUrl: "https://aupads.org",
301
- mappings: {
302
- "127.0.0.2": "Listed in OrveDB",
303
- },
304
- },
337
+ // Dead project
338
+ //"orvedb.aupads.org": {
339
+ // name: "OrveDB AuBads",
340
+ // delistUrl: "https://aupads.org",
341
+ // mappings: {
342
+ // "127.0.0.2": "Listed in OrveDB",
343
+ // },
344
+ //},
305
345
  "rbl.0spam.org": {
306
346
  name: "0spam RBL",
307
347
  delistUrl: "https://0spam.org",
@@ -344,13 +384,14 @@ export const blacklists = {
344
384
  "127.0.0.2": "Listed in Woody's",
345
385
  },
346
386
  },
347
- "db.wpbl.info": {
348
- name: "WPBL",
349
- delistUrl: "http://wpbl.info",
350
- mappings: {
351
- "127.0.0.2": "Listed in WPBL",
352
- },
353
- },
387
+ // Often overloaded/unreliable
388
+ // "db.wpbl.info": {
389
+ // name: "WPBL",
390
+ // delistUrl: "http://wpbl.info",
391
+ // mappings: {
392
+ // "127.0.0.2": "Listed in WPBL",
393
+ // },
394
+ // },
354
395
  "dnsbl-3.uceprotect.net": {
355
396
  name: "UCEPROTECT Level 3",
356
397
  delistUrl: "http://www.uceprotect.net/en/rblcheck.php",
@@ -358,13 +399,14 @@ export const blacklists = {
358
399
  "127.0.0.2": "Listed in UCEPROTECT Level 3",
359
400
  },
360
401
  },
361
- "duinv.aupads.org": {
362
- name: "Duinv AuPads",
363
- delistUrl: "https://aupads.org",
364
- mappings: {
365
- "127.0.0.2": "Listed in Duinv",
366
- },
367
- },
402
+ // Dead project
403
+ // "duinv.aupads.org": {
404
+ // name: "Duinv AuPads",
405
+ // delistUrl: "https://aupads.org",
406
+ // mappings: {
407
+ // "127.0.0.2": "Listed in Duinv",
408
+ // },
409
+ // },
368
410
  "proxy.bl.gweep.ca": {
369
411
  name: "Gweep Proxy",
370
412
  delistUrl: "http://gweep.ca",
package/src/index.d.ts CHANGED
@@ -27,6 +27,15 @@ export interface BlacklistDefinition {
27
27
  mappings: Record<string, string>;
28
28
  }
29
29
 
30
+ export interface GetLookupDomainsOptions {
31
+ spamhaus?: {
32
+ /** Use Spamhaus public DNSBLs (zen) or Spamhaus DQS (dqs). Default: zen */
33
+ mode?: "zen" | "dqs";
34
+ /** Required when mode is 'dqs' */
35
+ dqsKey?: string;
36
+ };
37
+ }
38
+
30
39
  /**
31
40
  * Definition of known blacklists and their return codes.
32
41
  */
@@ -40,6 +49,14 @@ export const blacklists: Record<string, BlacklistDefinition>;
40
49
  */
41
50
  export function getLookupDomains(ip: string): LookupDomain[];
42
51
 
52
+ /**
53
+ * Generates the list of domains to query, with optional Spamhaus DQS support.
54
+ */
55
+ export function getLookupDomains(
56
+ ip: string,
57
+ options?: GetLookupDomainsOptions,
58
+ ): LookupDomain[];
59
+
43
60
  /**
44
61
  * Parses the result from a DNS A-record lookup.
45
62
  *
package/src/index.js CHANGED
@@ -67,9 +67,13 @@ function reverseIP(ip) {
67
67
  * Generates the list of domains to query.
68
68
  *
69
69
  * @param {string} ip - The IPv4 or IPv6 address to check.
70
+ * @param {Object} [options]
71
+ * @param {Object} [options.spamhaus]
72
+ * @param {"zen"|"dqs"} [options.spamhaus.mode] - Use Spamhaus public DNSBLs (zen) or Spamhaus DQS (dqs).
73
+ * @param {string} [options.spamhaus.dqsKey] - Required when mode is 'dqs'.
70
74
  * @returns {Array<LookupDomain>} List of domains to query.
71
75
  */
72
- export function getLookupDomains(ip) {
76
+ export function getLookupDomains(ip, options = undefined) {
73
77
  if (!ip || typeof ip !== "string") {
74
78
  throw new Error("IP address must be a non-empty string");
75
79
  }
@@ -79,10 +83,50 @@ export function getLookupDomains(ip) {
79
83
  throw new Error(`Invalid IP address: ${ip}`);
80
84
  }
81
85
 
82
- return Object.keys(blacklists).map((listKey) => {
86
+ const spamhausMode = options?.spamhaus?.mode || "zen";
87
+ const dqsKeyRaw = options?.spamhaus?.dqsKey;
88
+ const dqsKey = typeof dqsKeyRaw === "string" ? dqsKeyRaw.trim() : "";
89
+
90
+ if (spamhausMode !== "zen" && spamhausMode !== "dqs") {
91
+ throw new Error("options.spamhaus.mode must be either 'zen' or 'dqs'");
92
+ }
93
+
94
+ if (spamhausMode === "dqs") {
95
+ if (!dqsKey) {
96
+ throw new Error(
97
+ "options.spamhaus.dqsKey is required when options.spamhaus.mode is 'dqs'",
98
+ );
99
+ }
100
+ // DQS keys are used as a DNS label; reject dots/whitespace and other characters.
101
+ if (!/^[a-z0-9-]+$/i.test(dqsKey)) {
102
+ throw new Error(
103
+ "options.spamhaus.dqsKey must be a valid DNS label (letters/numbers/hyphen)",
104
+ );
105
+ }
106
+ }
107
+
108
+ const spamhausPublicKeys = new Set(["zen.spamhaus.org", "sbl.spamhaus.org"]);
109
+ const spamhausDqsKeys = new Set(["zen.dq.spamhaus.net", "sbl.dq.spamhaus.net"]);
110
+
111
+ const listKeys = Object.keys(blacklists).filter((listKey) => {
112
+ if (spamhausMode === "zen") {
113
+ return !spamhausDqsKeys.has(listKey);
114
+ }
115
+ if (spamhausMode === "dqs") {
116
+ return !spamhausPublicKeys.has(listKey);
117
+ }
118
+ return true;
119
+ });
120
+
121
+ return listKeys.map((listKey) => {
122
+ const address =
123
+ spamhausMode === "dqs" && spamhausDqsKeys.has(listKey)
124
+ ? `${reversed}.${dqsKey}.${listKey}`
125
+ : `${reversed}.${listKey}`;
126
+
83
127
  return {
84
- address: `${reversed}.${listKey}`,
85
- listKey: listKey,
128
+ address,
129
+ listKey,
86
130
  };
87
131
  });
88
132
  }
@@ -119,6 +163,41 @@ export function parseLookupResult(listKey, resultCode) {
119
163
  }
120
164
 
121
165
  try {
166
+ // Some lists use A-record responses to indicate query errors/blocks.
167
+ // Treat these as NOT listed, otherwise they become false positives.
168
+ const spamhausKeys = new Set([
169
+ "zen.spamhaus.org",
170
+ "sbl.spamhaus.org",
171
+ "zen.dq.spamhaus.net",
172
+ "sbl.dq.spamhaus.net",
173
+ ]);
174
+ if (spamhausKeys.has(listKey) && resultCode === "127.255.255.254") {
175
+ return {
176
+ name: listDef.name,
177
+ listed: false,
178
+ reason:
179
+ listDef.mappings?.[resultCode] ||
180
+ "Query blocked by Spamhaus (rate limit / missing authorization / policy)",
181
+ code: resultCode,
182
+ delistUrl: listDef.delistUrl,
183
+ };
184
+ }
185
+
186
+ if (
187
+ listKey === "black.uribl.com" &&
188
+ (resultCode === "127.0.0.1" || resultCode === "127.0.0.255")
189
+ ) {
190
+ return {
191
+ name: listDef.name,
192
+ listed: false,
193
+ reason:
194
+ listDef.mappings?.[resultCode] ||
195
+ "Query blocked by URIBL (possibly due to high volume)",
196
+ code: resultCode,
197
+ delistUrl: listDef.delistUrl,
198
+ };
199
+ }
200
+
122
201
  if (listKey === "nodes.junkemailfilter.com" && resultCode === "127.0.0.1") {
123
202
  return {
124
203
  name: listDef.name,
@@ -129,7 +208,9 @@ export function parseLookupResult(listKey, resultCode) {
129
208
  };
130
209
  }
131
210
 
132
- const reason = listDef.mappings?.[resultCode] || "Listed (Unknown Reason)";
211
+ const reason =
212
+ listDef.mappings?.[resultCode] ||
213
+ `Listed (Unmapped return code: ${resultCode})`;
133
214
 
134
215
  return {
135
216
  name: listDef.name,