@gjsify/fetch 0.0.4 → 0.1.1

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 (88) hide show
  1. package/README.md +27 -2
  2. package/globals.mjs +12 -0
  3. package/lib/body.d.ts +69 -0
  4. package/lib/body.js +375 -0
  5. package/lib/errors/abort-error.d.ts +7 -0
  6. package/lib/errors/abort-error.js +9 -0
  7. package/lib/errors/base.d.ts +6 -0
  8. package/lib/errors/base.js +17 -0
  9. package/lib/errors/fetch-error.d.ts +16 -0
  10. package/lib/errors/fetch-error.js +23 -0
  11. package/lib/esm/body.js +104 -56
  12. package/lib/esm/errors/base.js +3 -1
  13. package/lib/esm/headers.js +116 -131
  14. package/lib/esm/index.js +145 -190
  15. package/lib/esm/request.js +42 -41
  16. package/lib/esm/response.js +19 -4
  17. package/lib/esm/utils/blob-from.js +2 -98
  18. package/lib/esm/utils/data-uri.js +23 -0
  19. package/lib/esm/utils/is.js +7 -3
  20. package/lib/esm/utils/multipart-parser.js +5 -2
  21. package/lib/esm/utils/referrer.js +10 -10
  22. package/lib/esm/utils/soup-helpers.js +22 -0
  23. package/lib/headers.d.ts +33 -0
  24. package/lib/headers.js +195 -0
  25. package/lib/index.d.ts +18 -0
  26. package/lib/index.js +205 -0
  27. package/lib/request.d.ts +101 -0
  28. package/lib/request.js +308 -0
  29. package/lib/response.d.ts +73 -0
  30. package/lib/response.js +158 -0
  31. package/lib/types/index.d.ts +1 -0
  32. package/lib/types/index.js +1 -0
  33. package/lib/types/system-error.d.ts +11 -0
  34. package/lib/types/system-error.js +2 -0
  35. package/lib/utils/blob-from.d.ts +2 -0
  36. package/lib/utils/blob-from.js +4 -0
  37. package/lib/utils/data-uri.d.ts +10 -0
  38. package/lib/utils/data-uri.js +27 -0
  39. package/lib/utils/get-search.d.ts +1 -0
  40. package/lib/utils/get-search.js +8 -0
  41. package/lib/utils/is-redirect.d.ts +7 -0
  42. package/lib/utils/is-redirect.js +10 -0
  43. package/lib/utils/is.d.ts +35 -0
  44. package/lib/utils/is.js +74 -0
  45. package/lib/utils/multipart-parser.d.ts +2 -0
  46. package/lib/utils/multipart-parser.js +396 -0
  47. package/lib/utils/referrer.d.ts +76 -0
  48. package/lib/utils/referrer.js +283 -0
  49. package/lib/utils/soup-helpers.d.ts +12 -0
  50. package/lib/utils/soup-helpers.js +25 -0
  51. package/package.json +23 -27
  52. package/src/body.ts +181 -169
  53. package/src/errors/base.ts +3 -1
  54. package/src/headers.ts +155 -202
  55. package/src/index.spec.ts +268 -3
  56. package/src/index.ts +199 -312
  57. package/src/request.ts +84 -75
  58. package/src/response.ts +48 -18
  59. package/src/test.mts +1 -1
  60. package/src/utils/blob-from.ts +4 -164
  61. package/src/utils/data-uri.ts +29 -0
  62. package/src/utils/is.ts +15 -15
  63. package/src/utils/multipart-parser.ts +3 -3
  64. package/src/utils/referrer.ts +11 -11
  65. package/src/utils/soup-helpers.ts +37 -0
  66. package/tsconfig.json +4 -4
  67. package/tsconfig.tsbuildinfo +1 -0
  68. package/lib/cjs/body.js +0 -255
  69. package/lib/cjs/errors/abort-error.js +0 -9
  70. package/lib/cjs/errors/base.js +0 -17
  71. package/lib/cjs/errors/fetch-error.js +0 -21
  72. package/lib/cjs/headers.js +0 -202
  73. package/lib/cjs/index.js +0 -224
  74. package/lib/cjs/request.js +0 -281
  75. package/lib/cjs/response.js +0 -133
  76. package/lib/cjs/types/index.js +0 -1
  77. package/lib/cjs/types/system-error.js +0 -1
  78. package/lib/cjs/utils/blob-from.js +0 -101
  79. package/lib/cjs/utils/get-search.js +0 -11
  80. package/lib/cjs/utils/is-redirect.js +0 -7
  81. package/lib/cjs/utils/is.js +0 -28
  82. package/lib/cjs/utils/multipart-parser.js +0 -353
  83. package/lib/cjs/utils/referrer.js +0 -153
  84. package/test.gjs.js +0 -34758
  85. package/test.gjs.mjs +0 -53172
  86. package/test.node.js +0 -1226
  87. package/test.node.mjs +0 -6273
  88. package/tsconfig.types.json +0 -8
package/README.md CHANGED
@@ -1,9 +1,34 @@
1
1
  # @gjsify/fetch
2
2
 
3
- Web and Node.js >= 18 fetch module for Gjs
3
+ GJS implementation of the Web Fetch API using Soup 3.0 and Gio. Provides fetch(), Request, Response, and Headers.
4
+
5
+ Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @gjsify/fetch
11
+ # or
12
+ yarn add @gjsify/fetch
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { fetch, Request, Response, Headers } from '@gjsify/fetch';
19
+
20
+ const response = await fetch('https://example.com');
21
+ const text = await response.text();
22
+ console.log(text);
23
+ ```
4
24
 
5
25
  ## Inspirations and credits
26
+
6
27
  - https://github.com/sonnyp/troll/blob/main/src/std/fetch.js
7
28
  - https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node-fetch
8
29
  - https://github.com/node-fetch/node-fetch
9
- - https://github.com/denoland/deno/tree/main/ext/fetch
30
+ - https://github.com/denoland/deno/tree/main/ext/fetch
31
+
32
+ ## License
33
+
34
+ MIT
package/globals.mjs ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Re-exports native Web API globals for use in Node.js builds.
3
+ *
4
+ * When tests import from the bare 'fetch' specifier, the build system
5
+ * aliases it to this module on Node.js (where fetch/Headers/Request/
6
+ * Response/FormData are native globals).
7
+ */
8
+ export const Headers = globalThis.Headers;
9
+ export const Request = globalThis.Request;
10
+ export const Response = globalThis.Response;
11
+ export const FormData = globalThis.FormData;
12
+ export default globalThis.fetch;
package/lib/body.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { Blob } from './utils/blob-from.js';
2
+ import { Readable, Writable } from 'node:stream';
3
+ import { Buffer } from 'node:buffer';
4
+ import { FormData } from '@gjsify/formdata';
5
+ import { FetchBaseError } from './errors/base.js';
6
+ import type { Request } from './request.js';
7
+ import type { Response } from './response.js';
8
+ declare const INTERNALS: unique symbol;
9
+ /**
10
+ * Body mixin
11
+ *
12
+ * Ref: https://fetch.spec.whatwg.org/#body
13
+ *
14
+ * @param body Readable stream
15
+ * @param opts Response options
16
+ */
17
+ export default class Body {
18
+ [INTERNALS]: {
19
+ body: null | Buffer | Readable | Blob;
20
+ stream: Readable | null;
21
+ boundary: string;
22
+ disturbed: boolean;
23
+ error: null | FetchBaseError;
24
+ };
25
+ size: number;
26
+ constructor(body: BodyInit | Readable | Blob | Buffer | null, options?: {
27
+ size?: number;
28
+ headers?: unknown;
29
+ });
30
+ get body(): ReadableStream<Uint8Array> | null;
31
+ get _stream(): Readable;
32
+ get bodyUsed(): boolean;
33
+ /**
34
+ * Decode response as ArrayBuffer
35
+ */
36
+ arrayBuffer(): Promise<ArrayBuffer>;
37
+ formData(): Promise<FormData>;
38
+ /**
39
+ * Return raw response as Blob
40
+ */
41
+ blob(): Promise<Blob>;
42
+ /**
43
+ * Decode response as json
44
+ */
45
+ json(): Promise<unknown>;
46
+ /**
47
+ * Decode response as text
48
+ */
49
+ text(): Promise<string>;
50
+ }
51
+ /**
52
+ * Clone body given Res/Req instance
53
+ */
54
+ export declare const clone: <T extends Request | Response>(instance: T, highWaterMark?: number) => Blob | Readable | Buffer<ArrayBufferLike>;
55
+ /**
56
+ * Extract a Content-Type value from a body.
57
+ */
58
+ export declare const extractContentType: (body: BodyInit | Readable | Blob | Buffer | null, request: Request | Response) => string | null;
59
+ /**
60
+ * Get total bytes of a body.
61
+ */
62
+ export declare const getTotalBytes: (request: Request) => number | null;
63
+ /**
64
+ * Write a Body to a Node.js WritableStream.
65
+ */
66
+ export declare const writeToStream: (dest: Writable, { body }: {
67
+ body: Readable | null;
68
+ }) => Promise<void>;
69
+ export {};
package/lib/body.js ADDED
@@ -0,0 +1,375 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Adapted from node-fetch (https://github.com/node-fetch/node-fetch/blob/main/src/body.js)
3
+ // Copyright (c) node-fetch contributors. MIT license.
4
+ // Modifications: Rewritten for GJS using libsoup 3.0 and @gjsify/url
5
+ import { URLSearchParams } from '@gjsify/url';
6
+ import { Blob } from './utils/blob-from.js';
7
+ import { PassThrough, pipeline as pipelineCb, Readable, Stream } from 'node:stream';
8
+ import { Buffer } from 'node:buffer';
9
+ import { FormData, formDataToBlob } from '@gjsify/formdata';
10
+ import { FetchError } from './errors/fetch-error.js';
11
+ import { FetchBaseError } from './errors/base.js';
12
+ import { isBlob, isURLSearchParameters } from './utils/is.js';
13
+ const pipeline = (source, dest) => new Promise((resolve, reject) => {
14
+ pipelineCb(source, dest, (err) => {
15
+ if (err)
16
+ reject(err);
17
+ else
18
+ resolve();
19
+ });
20
+ });
21
+ const INTERNALS = Symbol('Body internals');
22
+ function isAnyArrayBuffer(val) {
23
+ return val instanceof ArrayBuffer ||
24
+ (typeof SharedArrayBuffer !== 'undefined' && val instanceof SharedArrayBuffer);
25
+ }
26
+ function isBoxedPrimitive(val) {
27
+ return (val instanceof String ||
28
+ val instanceof Number ||
29
+ val instanceof Boolean ||
30
+ (typeof Symbol !== 'undefined' && val instanceof Symbol) ||
31
+ (typeof BigInt !== 'undefined' && val instanceof BigInt));
32
+ }
33
+ /**
34
+ * Body mixin
35
+ *
36
+ * Ref: https://fetch.spec.whatwg.org/#body
37
+ *
38
+ * @param body Readable stream
39
+ * @param opts Response options
40
+ */
41
+ export default class Body {
42
+ [INTERNALS] = {
43
+ body: null,
44
+ stream: null,
45
+ boundary: '',
46
+ disturbed: false,
47
+ error: null,
48
+ };
49
+ size = 0;
50
+ constructor(body, options = { size: 0 }) {
51
+ this.size = options.size || 0;
52
+ if (body === null || body === undefined) {
53
+ // Body is undefined or null
54
+ this[INTERNALS].body = null;
55
+ }
56
+ else if (isURLSearchParameters(body)) {
57
+ // Body is a URLSearchParams
58
+ this[INTERNALS].body = Buffer.from(body.toString());
59
+ }
60
+ else if (isBlob(body)) {
61
+ // Body is blob
62
+ this[INTERNALS].body = body;
63
+ }
64
+ else if (Buffer.isBuffer(body)) {
65
+ // Body is Buffer
66
+ this[INTERNALS].body = body;
67
+ }
68
+ else if (isAnyArrayBuffer(body)) {
69
+ // Body is ArrayBuffer
70
+ this[INTERNALS].body = Buffer.from(body);
71
+ }
72
+ else if (ArrayBuffer.isView(body)) {
73
+ // Body is ArrayBufferView
74
+ this[INTERNALS].body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
75
+ }
76
+ else if (body instanceof Readable) {
77
+ // Body is Node.js stream
78
+ this[INTERNALS].body = body;
79
+ }
80
+ else if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {
81
+ // Body is Web ReadableStream — convert to Node.js Readable
82
+ this[INTERNALS].body = readableStreamToReadable(body);
83
+ }
84
+ else if (body instanceof FormData) {
85
+ // Body is FormData
86
+ const blob = formDataToBlob(body);
87
+ this[INTERNALS].body = blob;
88
+ this[INTERNALS].boundary = blob.type?.split('boundary=')?.[1] ?? '';
89
+ }
90
+ else if (typeof body === 'string') {
91
+ // String body
92
+ this[INTERNALS].body = Buffer.from(body);
93
+ }
94
+ else if (body instanceof URLSearchParams) {
95
+ this[INTERNALS].body = Buffer.from(body.toString());
96
+ }
97
+ else {
98
+ console.warn(`Unknown body type "${typeof body}", try to parse the body to string!`);
99
+ this[INTERNALS].body = Buffer.from(String(body));
100
+ }
101
+ // Set up the internal stream
102
+ const b = this[INTERNALS].body;
103
+ if (Buffer.isBuffer(b)) {
104
+ this[INTERNALS].stream = Readable.from(b);
105
+ }
106
+ else if (isBlob(b)) {
107
+ this[INTERNALS].stream = Readable.from(blobToAsyncIterable(b));
108
+ }
109
+ else if (b instanceof Readable) {
110
+ this[INTERNALS].stream = b;
111
+ }
112
+ if (b instanceof Stream) {
113
+ b.on('error', (error_) => {
114
+ const error = error_ instanceof FetchBaseError
115
+ ? error_
116
+ : new FetchError(`Invalid response body while trying to fetch ${this.url}: ${error_.message}`, 'system', error_);
117
+ this[INTERNALS].error = error;
118
+ });
119
+ }
120
+ }
121
+ get body() {
122
+ const stream = this[INTERNALS].stream;
123
+ if (!stream)
124
+ return null;
125
+ // If ReadableStream is available, wrap the Readable into one
126
+ if (typeof ReadableStream !== 'undefined') {
127
+ return new ReadableStream({
128
+ start(controller) {
129
+ stream.on('data', (chunk) => {
130
+ controller.enqueue(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk));
131
+ });
132
+ stream.on('end', () => {
133
+ controller.close();
134
+ });
135
+ stream.on('error', (err) => {
136
+ controller.error(err);
137
+ });
138
+ },
139
+ cancel() {
140
+ stream.destroy();
141
+ }
142
+ });
143
+ }
144
+ return null;
145
+ }
146
+ get _stream() {
147
+ return this[INTERNALS].stream;
148
+ }
149
+ get bodyUsed() {
150
+ return this[INTERNALS].disturbed;
151
+ }
152
+ /**
153
+ * Decode response as ArrayBuffer
154
+ */
155
+ async arrayBuffer() {
156
+ const { buffer, byteOffset, byteLength } = await consumeBody(this);
157
+ return buffer.slice(byteOffset, byteOffset + byteLength);
158
+ }
159
+ async formData() {
160
+ const ct = this.headers?.get('content-type');
161
+ if (ct?.startsWith('application/x-www-form-urlencoded')) {
162
+ const formData = new FormData();
163
+ const parameters = new URLSearchParams(await this.text());
164
+ for (const [name, value] of parameters) {
165
+ formData.append(name, value);
166
+ }
167
+ return formData;
168
+ }
169
+ const { toFormData } = await import('./utils/multipart-parser.js');
170
+ return toFormData(this.body, ct);
171
+ }
172
+ /**
173
+ * Return raw response as Blob
174
+ */
175
+ async blob() {
176
+ const ct = (this.headers?.get('content-type')) || (this[INTERNALS].body && this[INTERNALS].body.type) || '';
177
+ const buf = await this.arrayBuffer();
178
+ return new Blob([buf], {
179
+ type: ct
180
+ });
181
+ }
182
+ /**
183
+ * Decode response as json
184
+ */
185
+ async json() {
186
+ const text = await this.text();
187
+ return JSON.parse(text);
188
+ }
189
+ /**
190
+ * Decode response as text
191
+ */
192
+ async text() {
193
+ const buffer = await consumeBody(this);
194
+ return new TextDecoder().decode(buffer);
195
+ }
196
+ }
197
+ // In browsers, all properties are enumerable.
198
+ Object.defineProperties(Body.prototype, {
199
+ body: { enumerable: true },
200
+ bodyUsed: { enumerable: true },
201
+ arrayBuffer: { enumerable: true },
202
+ blob: { enumerable: true },
203
+ json: { enumerable: true },
204
+ text: { enumerable: true },
205
+ });
206
+ /**
207
+ * Consume and convert an entire Body to a Buffer.
208
+ */
209
+ async function consumeBody(data) {
210
+ if (data[INTERNALS].disturbed) {
211
+ throw new TypeError(`body used already for: ${data.url}`);
212
+ }
213
+ data[INTERNALS].disturbed = true;
214
+ if (data[INTERNALS].error) {
215
+ throw data[INTERNALS].error;
216
+ }
217
+ const { _stream: body } = data;
218
+ // Body is null
219
+ if (body === null) {
220
+ return Buffer.alloc(0);
221
+ }
222
+ if (!(body instanceof Stream)) {
223
+ return Buffer.alloc(0);
224
+ }
225
+ // Body is stream — consume it
226
+ const accum = [];
227
+ let accumBytes = 0;
228
+ try {
229
+ for await (const chunk of body) {
230
+ if (data.size > 0 && accumBytes + chunk.length > data.size) {
231
+ const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
232
+ body.destroy(error);
233
+ throw error;
234
+ }
235
+ accumBytes += chunk.length;
236
+ accum.push(chunk);
237
+ }
238
+ }
239
+ catch (error) {
240
+ const err = error instanceof Error ? error : new Error(String(error));
241
+ const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${err.message}`, 'system', err);
242
+ throw error_;
243
+ }
244
+ try {
245
+ if (accum.every(c => typeof c === 'string')) {
246
+ return Buffer.from(accum.join(''));
247
+ }
248
+ return Buffer.concat(accum, accumBytes);
249
+ }
250
+ catch (error) {
251
+ const err = error instanceof Error ? error : new Error(String(error));
252
+ throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${err.message}`, 'system', err);
253
+ }
254
+ }
255
+ /**
256
+ * Clone body given Res/Req instance
257
+ */
258
+ export const clone = (instance, highWaterMark) => {
259
+ let p1;
260
+ let p2;
261
+ let { body } = instance[INTERNALS];
262
+ if (instance.bodyUsed) {
263
+ throw new Error('cannot clone body after it is used');
264
+ }
265
+ if ((body instanceof Stream) && (typeof body.getBoundary !== 'function')) {
266
+ p1 = new PassThrough({ highWaterMark });
267
+ p2 = new PassThrough({ highWaterMark });
268
+ body.pipe(p1);
269
+ body.pipe(p2);
270
+ instance[INTERNALS].stream = p1;
271
+ body = p2;
272
+ }
273
+ return body;
274
+ };
275
+ /**
276
+ * Extract a Content-Type value from a body.
277
+ */
278
+ export const extractContentType = (body, request) => {
279
+ if (body === null) {
280
+ return null;
281
+ }
282
+ if (typeof body === 'string') {
283
+ return 'text/plain;charset=UTF-8';
284
+ }
285
+ if (isURLSearchParameters(body)) {
286
+ return 'application/x-www-form-urlencoded;charset=UTF-8';
287
+ }
288
+ if (isBlob(body)) {
289
+ return body.type || null;
290
+ }
291
+ if (Buffer.isBuffer(body) || isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
292
+ return null;
293
+ }
294
+ if (body instanceof FormData) {
295
+ return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
296
+ }
297
+ if (body instanceof Stream) {
298
+ return null;
299
+ }
300
+ return 'text/plain;charset=UTF-8';
301
+ };
302
+ /**
303
+ * Get total bytes of a body.
304
+ */
305
+ export const getTotalBytes = (request) => {
306
+ const { body } = request[INTERNALS];
307
+ if (body === null) {
308
+ return 0;
309
+ }
310
+ if (isBlob(body)) {
311
+ return body.size;
312
+ }
313
+ if (Buffer.isBuffer(body)) {
314
+ return body.length;
315
+ }
316
+ if (body && typeof body.getLengthSync === 'function') {
317
+ const streamBody = body;
318
+ return streamBody.hasKnownLength && streamBody.hasKnownLength() ? streamBody.getLengthSync() : null;
319
+ }
320
+ return null;
321
+ };
322
+ /**
323
+ * Write a Body to a Node.js WritableStream.
324
+ */
325
+ export const writeToStream = async (dest, { body }) => {
326
+ if (body === null) {
327
+ dest.end();
328
+ }
329
+ else {
330
+ await pipeline(body, dest);
331
+ }
332
+ };
333
+ /**
334
+ * Convert a Web ReadableStream to a Node.js Readable.
335
+ */
336
+ function readableStreamToReadable(webStream) {
337
+ const reader = webStream.getReader();
338
+ return new Readable({
339
+ async read() {
340
+ try {
341
+ const { done, value } = await reader.read();
342
+ if (done) {
343
+ this.push(null);
344
+ }
345
+ else {
346
+ this.push(Buffer.from(value));
347
+ }
348
+ }
349
+ catch (err) {
350
+ this.destroy(err);
351
+ }
352
+ },
353
+ destroy(_err, callback) {
354
+ reader.cancel().then(() => callback(null), callback);
355
+ }
356
+ });
357
+ }
358
+ /**
359
+ * Convert a Blob to an async iterable for Readable.from().
360
+ */
361
+ async function* blobToAsyncIterable(blob) {
362
+ if (typeof blob.stream === 'function') {
363
+ const reader = blob.stream().getReader();
364
+ while (true) {
365
+ const { done, value } = await reader.read();
366
+ if (done)
367
+ break;
368
+ yield value;
369
+ }
370
+ }
371
+ else {
372
+ // Fallback: read the entire blob at once
373
+ yield new Uint8Array(await blob.arrayBuffer());
374
+ }
375
+ }
@@ -0,0 +1,7 @@
1
+ import { FetchBaseError } from './base.js';
2
+ /**
3
+ * AbortError interface for cancelled requests
4
+ */
5
+ export declare class AbortError extends FetchBaseError {
6
+ constructor(message: string, type?: string);
7
+ }
@@ -0,0 +1,9 @@
1
+ import { FetchBaseError } from './base.js';
2
+ /**
3
+ * AbortError interface for cancelled requests
4
+ */
5
+ export class AbortError extends FetchBaseError {
6
+ constructor(message, type = 'aborted') {
7
+ super(message, type);
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ export declare class FetchBaseError extends Error {
2
+ type?: 'aborted' | string;
3
+ constructor(message: string, type?: string);
4
+ get name(): string;
5
+ get [Symbol.toStringTag](): string;
6
+ }
@@ -0,0 +1,17 @@
1
+ export class FetchBaseError extends Error {
2
+ type;
3
+ constructor(message, type) {
4
+ super(message);
5
+ // Hide custom error implementation details from end-users
6
+ if (typeof Error.captureStackTrace === 'function') {
7
+ Error.captureStackTrace(this, this.constructor);
8
+ }
9
+ this.type = type;
10
+ }
11
+ get name() {
12
+ return this.constructor.name;
13
+ }
14
+ get [Symbol.toStringTag]() {
15
+ return this.constructor.name;
16
+ }
17
+ }
@@ -0,0 +1,16 @@
1
+ import { FetchBaseError } from './base.js';
2
+ import type { SystemError } from '../types/index.js';
3
+ /**
4
+ * FetchError interface for operational errors
5
+ */
6
+ export declare class FetchError extends FetchBaseError {
7
+ code: string;
8
+ errno: string;
9
+ erroredSysCall: string;
10
+ /**
11
+ * @param message Error message for human
12
+ * @param type Error type for machine
13
+ * @param systemError For Node.js system error
14
+ */
15
+ constructor(message: string, type?: string, systemError?: SystemError);
16
+ }
@@ -0,0 +1,23 @@
1
+ import { FetchBaseError } from './base.js';
2
+ /**
3
+ * FetchError interface for operational errors
4
+ */
5
+ export class FetchError extends FetchBaseError {
6
+ code;
7
+ errno;
8
+ erroredSysCall;
9
+ /**
10
+ * @param message Error message for human
11
+ * @param type Error type for machine
12
+ * @param systemError For Node.js system error
13
+ */
14
+ constructor(message, type, systemError) {
15
+ super(message, type);
16
+ // When err.type is `system`, err.erroredSysCall contains system error and err.code contains system error code
17
+ if (systemError) {
18
+ // eslint-disable-next-line no-multi-assign
19
+ this.code = this.errno = systemError.code;
20
+ this.erroredSysCall = systemError.syscall;
21
+ }
22
+ }
23
+ }