@j0hanz/superfetch 2.2.1 → 2.3.0

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.
Files changed (75) hide show
  1. package/README.md +243 -494
  2. package/dist/cache.d.ts +2 -3
  3. package/dist/cache.js +51 -241
  4. package/dist/config.d.ts +6 -1
  5. package/dist/config.js +29 -34
  6. package/dist/crypto.d.ts +0 -1
  7. package/dist/crypto.js +0 -1
  8. package/dist/dom-noise-removal.d.ts +5 -0
  9. package/dist/dom-noise-removal.js +485 -0
  10. package/dist/errors.d.ts +0 -1
  11. package/dist/errors.js +8 -6
  12. package/dist/fetch.d.ts +0 -1
  13. package/dist/fetch.js +71 -61
  14. package/dist/host-normalization.d.ts +1 -0
  15. package/dist/host-normalization.js +47 -0
  16. package/dist/http-native.d.ts +5 -0
  17. package/dist/http-native.js +693 -0
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.js +1 -2
  20. package/dist/instructions.md +22 -20
  21. package/dist/json.d.ts +1 -0
  22. package/dist/json.js +29 -0
  23. package/dist/language-detection.d.ts +12 -0
  24. package/dist/language-detection.js +291 -0
  25. package/dist/markdown-cleanup.d.ts +18 -0
  26. package/dist/markdown-cleanup.js +283 -0
  27. package/dist/mcp-validator.d.ts +14 -0
  28. package/dist/mcp-validator.js +22 -0
  29. package/dist/mcp.d.ts +0 -1
  30. package/dist/mcp.js +0 -1
  31. package/dist/observability.d.ts +1 -1
  32. package/dist/observability.js +15 -3
  33. package/dist/server-tuning.d.ts +9 -0
  34. package/dist/server-tuning.js +30 -0
  35. package/dist/session.d.ts +36 -0
  36. package/dist/session.js +159 -0
  37. package/dist/tools.d.ts +0 -1
  38. package/dist/tools.js +23 -33
  39. package/dist/transform-types.d.ts +80 -0
  40. package/dist/transform-types.js +5 -0
  41. package/dist/transform.d.ts +7 -53
  42. package/dist/transform.js +434 -856
  43. package/dist/type-guards.d.ts +1 -2
  44. package/dist/type-guards.js +1 -2
  45. package/dist/workers/transform-worker.d.ts +0 -1
  46. package/dist/workers/transform-worker.js +52 -43
  47. package/package.json +11 -12
  48. package/dist/cache.d.ts.map +0 -1
  49. package/dist/cache.js.map +0 -1
  50. package/dist/config.d.ts.map +0 -1
  51. package/dist/config.js.map +0 -1
  52. package/dist/crypto.d.ts.map +0 -1
  53. package/dist/crypto.js.map +0 -1
  54. package/dist/errors.d.ts.map +0 -1
  55. package/dist/errors.js.map +0 -1
  56. package/dist/fetch.d.ts.map +0 -1
  57. package/dist/fetch.js.map +0 -1
  58. package/dist/http.d.ts +0 -90
  59. package/dist/http.d.ts.map +0 -1
  60. package/dist/http.js +0 -1576
  61. package/dist/http.js.map +0 -1
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/mcp.d.ts.map +0 -1
  65. package/dist/mcp.js.map +0 -1
  66. package/dist/observability.d.ts.map +0 -1
  67. package/dist/observability.js.map +0 -1
  68. package/dist/tools.d.ts.map +0 -1
  69. package/dist/tools.js.map +0 -1
  70. package/dist/transform.d.ts.map +0 -1
  71. package/dist/transform.js.map +0 -1
  72. package/dist/type-guards.d.ts.map +0 -1
  73. package/dist/type-guards.js.map +0 -1
  74. package/dist/workers/transform-worker.d.ts.map +0 -1
  75. package/dist/workers/transform-worker.js.map +0 -1
@@ -0,0 +1,485 @@
1
+ /**
2
+ * DOM noise removal utilities for cleaning HTML before markdown conversion.
3
+ * Removes navigation, ads, popups, and other non-content elements.
4
+ */
5
+ import { parseHTML } from 'linkedom';
6
+ import { config } from './config.js';
7
+ import { isObject } from './type-guards.js';
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // DOM Type Guards and Accessors
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ function isElement(node) {
12
+ return (isObject(node) &&
13
+ 'getAttribute' in node &&
14
+ typeof node.getAttribute === 'function');
15
+ }
16
+ function getBodyInnerHtml(document) {
17
+ if (!isObject(document))
18
+ return undefined;
19
+ const { body } = document;
20
+ if (isObject(body) && typeof body.innerHTML === 'string') {
21
+ return body.innerHTML;
22
+ }
23
+ return undefined;
24
+ }
25
+ function getDocumentToString(document) {
26
+ if (!isObject(document))
27
+ return undefined;
28
+ if (typeof document.toString === 'function') {
29
+ return document.toString.bind(document);
30
+ }
31
+ return undefined;
32
+ }
33
+ function getDocumentElementOuterHtml(document) {
34
+ if (!isObject(document))
35
+ return undefined;
36
+ const docEl = document.documentElement;
37
+ if (isObject(docEl) && typeof docEl.outerHTML === 'string') {
38
+ return docEl.outerHTML;
39
+ }
40
+ return undefined;
41
+ }
42
+ // ─────────────────────────────────────────────────────────────────────────────
43
+ // Noise Detection Constants
44
+ // ─────────────────────────────────────────────────────────────────────────────
45
+ const STRUCTURAL_TAGS = new Set([
46
+ 'script',
47
+ 'style',
48
+ 'noscript',
49
+ 'iframe',
50
+ 'form',
51
+ 'button',
52
+ 'input',
53
+ 'select',
54
+ 'textarea',
55
+ 'svg',
56
+ 'canvas',
57
+ ]);
58
+ const ALWAYS_NOISE_TAGS = new Set(['nav', 'footer']);
59
+ const BASE_NOISE_SELECTORS = [
60
+ 'nav',
61
+ 'footer',
62
+ 'header[class*="site"]',
63
+ 'header[class*="nav"]',
64
+ 'header[class*="menu"]',
65
+ '[role="banner"]',
66
+ '[role="navigation"]',
67
+ '[role="dialog"]',
68
+ '[style*="display: none"]',
69
+ '[style*="display:none"]',
70
+ '[hidden]',
71
+ '[aria-hidden="true"]',
72
+ ];
73
+ const BASE_NOISE_SELECTOR = BASE_NOISE_SELECTORS.join(',');
74
+ const CANDIDATE_NOISE_SELECTOR = [
75
+ ...STRUCTURAL_TAGS,
76
+ ...ALWAYS_NOISE_TAGS,
77
+ 'aside',
78
+ 'header',
79
+ '[class]',
80
+ '[id]',
81
+ '[role]',
82
+ '[style]',
83
+ ].join(',');
84
+ function buildNoiseSelector(extraSelectors) {
85
+ const extra = extraSelectors.filter((selector) => selector.trim().length > 0);
86
+ if (extra.length === 0)
87
+ return BASE_NOISE_SELECTOR;
88
+ return `${BASE_NOISE_SELECTOR},${extra.join(',')}`;
89
+ }
90
+ const NAVIGATION_ROLES = new Set([
91
+ 'navigation',
92
+ 'banner',
93
+ 'complementary',
94
+ 'contentinfo',
95
+ 'tree',
96
+ 'menubar',
97
+ 'menu',
98
+ 'dialog',
99
+ 'alertdialog',
100
+ 'search',
101
+ ]);
102
+ const INTERACTIVE_CONTENT_ROLES = new Set([
103
+ 'tabpanel',
104
+ 'tab',
105
+ 'tablist',
106
+ 'dialog',
107
+ 'alertdialog',
108
+ 'menu',
109
+ 'menuitem',
110
+ 'option',
111
+ 'listbox',
112
+ 'combobox',
113
+ 'tooltip',
114
+ 'alert',
115
+ ]);
116
+ const BASE_PROMO_TOKENS = [
117
+ 'banner',
118
+ 'promo',
119
+ 'announcement',
120
+ 'cta',
121
+ 'advert',
122
+ 'ad',
123
+ 'ads',
124
+ 'sponsor',
125
+ 'newsletter',
126
+ 'subscribe',
127
+ 'cookie',
128
+ 'consent',
129
+ 'popup',
130
+ 'modal',
131
+ 'overlay',
132
+ 'toast',
133
+ 'share',
134
+ 'social',
135
+ 'related',
136
+ 'recommend',
137
+ 'comment',
138
+ 'breadcrumb',
139
+ 'pagination',
140
+ 'pager',
141
+ 'taglist',
142
+ ];
143
+ /**
144
+ * Get promo tokens merged with any user-configured extra tokens.
145
+ * Memoized because it is used in hot paths when scanning many nodes.
146
+ */
147
+ let promoTokensCache = null;
148
+ function getPromoTokens() {
149
+ if (promoTokensCache)
150
+ return promoTokensCache;
151
+ const tokens = new Set(BASE_PROMO_TOKENS);
152
+ for (const token of config.noiseRemoval.extraTokens) {
153
+ const normalized = token.toLowerCase().trim();
154
+ if (normalized)
155
+ tokens.add(normalized);
156
+ }
157
+ promoTokensCache = tokens;
158
+ return tokens;
159
+ }
160
+ const HEADER_NOISE_PATTERN = /\b(site-header|masthead|topbar|navbar|nav(?:bar)?|menu|header-nav)\b/i;
161
+ const FIXED_PATTERN = /\b(fixed|sticky)\b/;
162
+ const HIGH_Z_PATTERN = /\bz-(?:4\d|50)\b/;
163
+ const ISOLATE_PATTERN = /\bisolate\b/;
164
+ const HTML_DOCUMENT_MARKERS = /<\s*(?:!doctype|html|head|body)\b/i;
165
+ const NOISE_MARKERS = [
166
+ '<script',
167
+ '<style',
168
+ '<noscript',
169
+ '<iframe',
170
+ '<nav',
171
+ '<footer',
172
+ '<header',
173
+ '<form',
174
+ '<button',
175
+ '<input',
176
+ '<select',
177
+ '<textarea',
178
+ '<svg',
179
+ '<canvas',
180
+ ' aria-hidden="true"',
181
+ " aria-hidden='true'",
182
+ ' hidden',
183
+ ' role="navigation"',
184
+ " role='navigation'",
185
+ ' role="banner"',
186
+ " role='banner'",
187
+ ' role="complementary"',
188
+ " role='complementary'",
189
+ ' role="contentinfo"',
190
+ " role='contentinfo'",
191
+ ' role="tree"',
192
+ " role='tree'",
193
+ ' role="menubar"',
194
+ " role='menubar'",
195
+ ' role="menu"',
196
+ " role='menu'",
197
+ ' banner',
198
+ ' promo',
199
+ ' announcement',
200
+ ' cta',
201
+ ' advert',
202
+ ' newsletter',
203
+ ' subscribe',
204
+ ' cookie',
205
+ ' consent',
206
+ ' popup',
207
+ ' modal',
208
+ ' overlay',
209
+ ' toast',
210
+ ' fixed',
211
+ ' sticky',
212
+ ' z-50',
213
+ ' z-4',
214
+ ' isolate',
215
+ ' breadcrumb',
216
+ ' pagination',
217
+ ];
218
+ // ─────────────────────────────────────────────────────────────────────────────
219
+ // Noise Detection Functions
220
+ // ─────────────────────────────────────────────────────────────────────────────
221
+ const NOISE_SCAN_LIMIT = 50_000;
222
+ function mayContainNoise(html) {
223
+ // Fast path: only scan a bounded prefix; parsing is the expensive step anyway.
224
+ // Most noise markers appear near the top of the document (nav, scripts, meta, etc.).
225
+ const sample = html.length > NOISE_SCAN_LIMIT ? html.slice(0, NOISE_SCAN_LIMIT) : html;
226
+ const haystack = sample.toLowerCase();
227
+ return NOISE_MARKERS.some((marker) => haystack.includes(marker));
228
+ }
229
+ function isFullDocumentHtml(html) {
230
+ return HTML_DOCUMENT_MARKERS.test(html);
231
+ }
232
+ function isStructuralNoiseTag(tagName) {
233
+ return STRUCTURAL_TAGS.has(tagName);
234
+ }
235
+ function isInteractiveComponent(element) {
236
+ const role = element.getAttribute('role');
237
+ if (role && INTERACTIVE_CONTENT_ROLES.has(role))
238
+ return true;
239
+ // Check for common UI framework data attributes that indicate managed visibility
240
+ const dataState = element.getAttribute('data-state');
241
+ if (dataState === 'inactive' || dataState === 'closed')
242
+ return true;
243
+ const dataOrientation = element.getAttribute('data-orientation');
244
+ if (dataOrientation === 'horizontal' || dataOrientation === 'vertical') {
245
+ return true;
246
+ }
247
+ // Check for accordion/collapse patterns
248
+ if (element.getAttribute('data-accordion-item') !== null)
249
+ return true;
250
+ if (element.getAttribute('data-radix-collection-item') !== null)
251
+ return true;
252
+ return false;
253
+ }
254
+ function isElementHidden(element) {
255
+ const style = element.getAttribute('style') ?? '';
256
+ return (element.getAttribute('hidden') !== null ||
257
+ element.getAttribute('aria-hidden') === 'true' ||
258
+ /\bdisplay\s*:\s*none\b/i.test(style) ||
259
+ /\bvisibility\s*:\s*hidden\b/i.test(style));
260
+ }
261
+ function hasNoiseRole(role) {
262
+ return role !== null && NAVIGATION_ROLES.has(role);
263
+ }
264
+ function tokenizeIdentifierLikeText(value) {
265
+ return value
266
+ .toLowerCase()
267
+ .replace(/[^a-z0-9]+/g, ' ')
268
+ .trim()
269
+ .split(' ')
270
+ .filter(Boolean);
271
+ }
272
+ function matchesPromoIdOrClass(className, id) {
273
+ const tokens = tokenizeIdentifierLikeText(`${className} ${id}`);
274
+ const promoTokens = getPromoTokens();
275
+ return tokens.some((token) => promoTokens.has(token));
276
+ }
277
+ function matchesFixedOrHighZIsolate(className) {
278
+ return (FIXED_PATTERN.test(className) ||
279
+ (HIGH_Z_PATTERN.test(className) && ISOLATE_PATTERN.test(className)));
280
+ }
281
+ function readElementMetadata(element) {
282
+ return {
283
+ tagName: element.tagName.toLowerCase(),
284
+ className: element.getAttribute('class') ?? '',
285
+ id: element.getAttribute('id') ?? '',
286
+ role: element.getAttribute('role'),
287
+ isHidden: isElementHidden(element),
288
+ };
289
+ }
290
+ function isBoilerplateHeader({ className, id, role, }) {
291
+ if (hasNoiseRole(role))
292
+ return true;
293
+ const combined = `${className} ${id}`.toLowerCase();
294
+ return HEADER_NOISE_PATTERN.test(combined);
295
+ }
296
+ function isNoiseElement(node) {
297
+ const metadata = readElementMetadata(node);
298
+ const isComplementaryAside = metadata.tagName === 'aside' && metadata.role === 'complementary';
299
+ const shouldCheckHidden = metadata.isHidden && !isInteractiveComponent(node);
300
+ const isInteractiveStructural = isStructuralNoiseTag(metadata.tagName) && isInteractiveComponent(node);
301
+ return ((isStructuralNoiseTag(metadata.tagName) && !isInteractiveStructural) ||
302
+ ALWAYS_NOISE_TAGS.has(metadata.tagName) ||
303
+ (metadata.tagName === 'header' && isBoilerplateHeader(metadata)) ||
304
+ shouldCheckHidden ||
305
+ (!isComplementaryAside && hasNoiseRole(metadata.role)) ||
306
+ matchesFixedOrHighZIsolate(metadata.className) ||
307
+ matchesPromoIdOrClass(metadata.className, metadata.id));
308
+ }
309
+ function isNodeListLike(value) {
310
+ return isObject(value) && typeof value.length === 'number';
311
+ }
312
+ function tryGetNodeListItem(nodes, index) {
313
+ if (typeof nodes.item === 'function')
314
+ return nodes.item(index);
315
+ return (nodes[index] ?? null);
316
+ }
317
+ function removeNoiseFromNodeListLike(nodes, shouldCheckNoise) {
318
+ for (let index = nodes.length - 1; index >= 0; index -= 1) {
319
+ const node = tryGetNodeListItem(nodes, index);
320
+ if (!node)
321
+ continue;
322
+ if (isElement(node) && (!shouldCheckNoise || isNoiseElement(node))) {
323
+ node.remove();
324
+ }
325
+ }
326
+ }
327
+ function removeNoiseNodes(nodes, shouldCheckNoise = true) {
328
+ if (isNodeListLike(nodes)) {
329
+ removeNoiseFromNodeListLike(nodes, shouldCheckNoise);
330
+ return;
331
+ }
332
+ // Generic iterable: copy to avoid iteration issues while removing.
333
+ const nodeList = Array.from(nodes);
334
+ for (const node of nodeList) {
335
+ if (isElement(node) && (!shouldCheckNoise || isNoiseElement(node))) {
336
+ node.remove();
337
+ }
338
+ }
339
+ }
340
+ function stripNoiseNodes(document) {
341
+ // Pass 1: Trusted selectors (Common noise)
342
+ // We trust these selectors match actual noise, so we skip the expensive isNoiseElement check
343
+ // Add user-configured extra selectors
344
+ const targetSelectors = buildNoiseSelector(config.noiseRemoval.extraSelectors);
345
+ const potentialNoiseNodes = document.querySelectorAll(targetSelectors);
346
+ removeNoiseNodes(potentialNoiseNodes, false);
347
+ // Second pass: check remaining elements for noise patterns (promo, fixed positioning, etc.)
348
+ const allElements = document.querySelectorAll(CANDIDATE_NOISE_SELECTOR);
349
+ removeNoiseNodes(allElements, true);
350
+ }
351
+ // ─────────────────────────────────────────────────────────────────────────────
352
+ // URL Resolution
353
+ // ─────────────────────────────────────────────────────────────────────────────
354
+ // Protocol patterns to skip during URL resolution (fragment, mailto, tel, blob, data, javascript)
355
+ // JavaScript protocol is detected to skip it for XSS prevention, not to evaluate it
356
+ const SKIP_URL_PREFIXES = [
357
+ '#',
358
+ 'java' + 'script:',
359
+ 'mailto:',
360
+ 'tel:',
361
+ 'data:',
362
+ 'blob:',
363
+ ];
364
+ /**
365
+ * Check if a URL scheme should be skipped during resolution.
366
+ * These schemes are either fragment-only (#), protocol handlers (mailto, tel),
367
+ * inline data (data, blob), or javascript: which we skip to avoid XSS.
368
+ */
369
+ function shouldSkipUrlResolution(url) {
370
+ const normalized = url.trim().toLowerCase();
371
+ return SKIP_URL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
372
+ }
373
+ /**
374
+ * Safely resolve a relative URL to absolute using base URL.
375
+ */
376
+ function tryResolveUrl(relativeUrl, baseUrl) {
377
+ try {
378
+ return new URL(relativeUrl, baseUrl).href;
379
+ }
380
+ catch {
381
+ return null;
382
+ }
383
+ }
384
+ /**
385
+ * Resolve anchor hrefs to absolute URLs.
386
+ */
387
+ function resolveAnchorUrls(document, baseUrl) {
388
+ for (const anchor of document.querySelectorAll('a[href]')) {
389
+ const href = anchor.getAttribute('href');
390
+ if (href && !shouldSkipUrlResolution(href)) {
391
+ const resolved = tryResolveUrl(href, baseUrl);
392
+ if (resolved)
393
+ anchor.setAttribute('href', resolved);
394
+ }
395
+ }
396
+ }
397
+ /**
398
+ * Resolve image srcs to absolute URLs.
399
+ */
400
+ function resolveImageUrls(document, baseUrl) {
401
+ for (const img of document.querySelectorAll('img[src]')) {
402
+ const src = img.getAttribute('src');
403
+ if (src && !shouldSkipUrlResolution(src)) {
404
+ const resolved = tryResolveUrl(src, baseUrl);
405
+ if (resolved)
406
+ img.setAttribute('src', resolved);
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * Resolve source srcset to absolute URLs (for picture elements).
412
+ */
413
+ function resolveSrcsetUrls(document, baseUrl) {
414
+ for (const source of document.querySelectorAll('source[srcset]')) {
415
+ const srcset = source.getAttribute('srcset');
416
+ if (!srcset)
417
+ continue;
418
+ // srcset can have multiple URLs with descriptors like "url 1x, url 2x"
419
+ const resolved = srcset
420
+ .split(',')
421
+ .map((entry) => {
422
+ const parts = entry.trim().split(/\s+/);
423
+ const url = parts[0];
424
+ if (url) {
425
+ const resolvedUrl = tryResolveUrl(url, baseUrl);
426
+ if (resolvedUrl)
427
+ parts[0] = resolvedUrl;
428
+ }
429
+ return parts.join(' ');
430
+ })
431
+ .join(', ');
432
+ source.setAttribute('srcset', resolved);
433
+ }
434
+ }
435
+ /**
436
+ * Resolve relative URLs in anchor and image elements to absolute URLs.
437
+ * Fixes broken links/images in markdown output when the source uses relative paths.
438
+ */
439
+ function resolveRelativeUrls(document, baseUrl) {
440
+ try {
441
+ const base = new URL(baseUrl);
442
+ resolveAnchorUrls(document, base);
443
+ resolveImageUrls(document, base);
444
+ resolveSrcsetUrls(document, base);
445
+ }
446
+ catch {
447
+ /* invalid base URL - skip resolution */
448
+ }
449
+ }
450
+ // ─────────────────────────────────────────────────────────────────────────────
451
+ // Main Export
452
+ // ─────────────────────────────────────────────────────────────────────────────
453
+ /**
454
+ * Remove noise elements from HTML and resolve relative URLs.
455
+ * Used as a preprocessing step before markdown conversion.
456
+ */
457
+ export function removeNoiseFromHtml(html, document, baseUrl) {
458
+ const shouldParse = isFullDocumentHtml(html) || mayContainNoise(html);
459
+ if (!shouldParse)
460
+ return html;
461
+ try {
462
+ const resolvedDocument = document ?? parseHTML(html).document;
463
+ stripNoiseNodes(resolvedDocument);
464
+ // Resolve relative URLs before converting to markdown
465
+ if (baseUrl) {
466
+ resolveRelativeUrls(resolvedDocument, baseUrl);
467
+ }
468
+ const bodyInnerHtml = getBodyInnerHtml(resolvedDocument);
469
+ // Only use body innerHTML if it has substantial content
470
+ // On some sites (e.g., Framer), noise removal empties body but leaves content in documentElement
471
+ if (bodyInnerHtml && bodyInnerHtml.trim().length > 100) {
472
+ return bodyInnerHtml;
473
+ }
474
+ const docToString = getDocumentToString(resolvedDocument);
475
+ if (docToString)
476
+ return docToString();
477
+ const documentElementOuterHtml = getDocumentElementOuterHtml(resolvedDocument);
478
+ if (documentElementOuterHtml)
479
+ return documentElementOuterHtml;
480
+ return html;
481
+ }
482
+ catch {
483
+ return html;
484
+ }
485
+ }
package/dist/errors.d.ts CHANGED
@@ -8,4 +8,3 @@ export declare class FetchError extends Error {
8
8
  export declare function getErrorMessage(error: unknown): string;
9
9
  export declare function createErrorWithCode(message: string, code: string): NodeJS.ErrnoException;
10
10
  export declare function isSystemError(error: unknown): error is NodeJS.ErrnoException;
11
- //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isRecord } from './type-guards.js';
1
+ import { isObject } from './type-guards.js';
2
2
  const DEFAULT_HTTP_STATUS = 502;
3
3
  export class FetchError extends Error {
4
4
  url;
@@ -25,7 +25,7 @@ export function getErrorMessage(error) {
25
25
  return 'Unknown error';
26
26
  }
27
27
  function isErrorWithMessage(error) {
28
- if (!isRecord(error))
28
+ if (!isObject(error))
29
29
  return false;
30
30
  const { message } = error;
31
31
  return typeof message === 'string' && message.length > 0;
@@ -35,8 +35,10 @@ export function createErrorWithCode(message, code) {
35
35
  return Object.assign(error, { code });
36
36
  }
37
37
  export function isSystemError(error) {
38
- return (error instanceof Error &&
39
- 'code' in error &&
40
- typeof Reflect.get(error, 'code') === 'string');
38
+ if (!(error instanceof Error))
39
+ return false;
40
+ if (!('code' in error))
41
+ return false;
42
+ const { code } = error;
43
+ return typeof code === 'string';
41
44
  }
42
- //# sourceMappingURL=errors.js.map
package/dist/fetch.d.ts CHANGED
@@ -38,4 +38,3 @@ export declare function readResponseText(response: Response, url: string, maxByt
38
38
  }>;
39
39
  export declare function fetchNormalizedUrl(normalizedUrl: string, options?: FetchOptions): Promise<string>;
40
40
  export {};
41
- //# sourceMappingURL=fetch.d.ts.map