@whatwg-node/node-fetch 0.7.23-alpha-20250726085929-27dea3e7c5dae7285bf9c837cce54f387e269df2 → 0.7.23-alpha-20250727165811-6c05e293622755103fa748bf8ebf677c55ed0143

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/cjs/Body.js CHANGED
@@ -36,11 +36,13 @@ class PonyfillBody {
36
36
  this.contentLength = contentLength;
37
37
  this.bodyType = bodyType;
38
38
  this._buffer = buffer;
39
+ this._signal = options.signal;
39
40
  }
40
41
  bodyType;
41
42
  _bodyFactory = () => null;
42
43
  _generatedBody = null;
43
44
  _buffer;
45
+ _signal;
44
46
  generateBody() {
45
47
  if (this._generatedBody?.readable?.destroyed && this._buffer) {
46
48
  this._generatedBody.readable = node_stream_1.Readable.from(this._buffer);
@@ -130,6 +132,9 @@ class PonyfillBody {
130
132
  this._chunks = [];
131
133
  return (0, utils_js_1.fakePromise)(this._chunks);
132
134
  }
135
+ if (_body.readable.destroyed) {
136
+ return (0, utils_js_1.fakePromise)((this._chunks = []));
137
+ }
133
138
  const chunks = [];
134
139
  return new Promise((resolve, reject) => {
135
140
  _body.readable.on('data', chunk => {
@@ -215,6 +220,9 @@ class PonyfillBody {
215
220
  limits: formDataLimits,
216
221
  defCharset: 'utf-8',
217
222
  });
223
+ if (this._signal) {
224
+ (0, node_stream_1.addAbortSignal)(this._signal, bb);
225
+ }
218
226
  let completed = false;
219
227
  const complete = (err) => {
220
228
  if (completed)
package/cjs/Request.js CHANGED
@@ -76,7 +76,6 @@ class PonyfillRequest extends Body_js_1.PonyfillBody {
76
76
  this.agent = requestInit.agent;
77
77
  }
78
78
  }
79
- this._signal = requestInit?.signal || undefined;
80
79
  }
81
80
  headersSerializer;
82
81
  cache;
@@ -92,7 +91,6 @@ class PonyfillRequest extends Body_js_1.PonyfillBody {
92
91
  referrer;
93
92
  referrerPolicy;
94
93
  _url;
95
- _signal;
96
94
  get signal() {
97
95
  this._signal ||= new AbortController().signal;
98
96
  return this._signal;
@@ -93,8 +93,19 @@ function fetchNodeHttp(fetchRequest) {
93
93
  }
94
94
  }
95
95
  outputStream ||= new node_stream_1.PassThrough();
96
- nodeResponse.pipe(outputStream, {
97
- end: true,
96
+ (0, utils_js_1.pipeThrough)({
97
+ src: nodeResponse,
98
+ dest: outputStream,
99
+ signal,
100
+ onError: e => {
101
+ if (!nodeResponse.destroyed) {
102
+ nodeResponse.destroy(e);
103
+ }
104
+ if (!outputStream.destroyed) {
105
+ outputStream.destroy(e);
106
+ }
107
+ reject(e);
108
+ },
98
109
  });
99
110
  const statusCode = nodeResponse.statusCode || 200;
100
111
  let statusText = nodeResponse.statusMessage || node_http_1.STATUS_CODES[statusCode];
package/cjs/utils.js CHANGED
@@ -7,6 +7,7 @@ exports.isArrayBufferView = isArrayBufferView;
7
7
  exports.isNodeReadable = isNodeReadable;
8
8
  exports.isIterable = isIterable;
9
9
  exports.shouldRedirect = shouldRedirect;
10
+ exports.pipeThrough = pipeThrough;
10
11
  exports.endStream = endStream;
11
12
  exports.safeWrite = safeWrite;
12
13
  const node_events_1 = require("node:events");
@@ -48,6 +49,42 @@ function isIterable(value) {
48
49
  function shouldRedirect(status) {
49
50
  return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
50
51
  }
52
+ function pipeThrough({ src, dest, signal, onError, }) {
53
+ if (onError) {
54
+ // listen for errors on the destination stream if necessary. if the readable
55
+ // stream (src) emits an error, the writable destination (dest) will be
56
+ // destroyed with that error (see below)
57
+ dest.once('error', onError);
58
+ }
59
+ src.once('error', (e) => {
60
+ // if the readable stream (src) emits an error during pipe, the writable
61
+ // destination (dest) is not closed automatically. that needs to be
62
+ // done manually. the readable stream is closed when error is emitted,
63
+ // so only the writable destination needs to be destroyed
64
+ dest.destroy(e);
65
+ });
66
+ if (signal) {
67
+ // this is faster than `import('node:signal').addAbortSignal(signal, src)`
68
+ const srcRef = new WeakRef(src);
69
+ const signalRef = new WeakRef(signal);
70
+ function cleanup() {
71
+ signalRef.deref()?.removeEventListener('abort', onAbort);
72
+ srcRef.deref()?.removeListener('end', cleanup);
73
+ srcRef.deref()?.removeListener('error', cleanup);
74
+ srcRef.deref()?.removeListener('close', cleanup);
75
+ }
76
+ function onAbort() {
77
+ srcRef.deref()?.destroy(new AbortError());
78
+ cleanup();
79
+ }
80
+ signal.addEventListener('abort', onAbort, { once: true });
81
+ // this is faster than `import('node:signal').finished(src, cleanup)`
82
+ src.once('end', cleanup);
83
+ src.once('error', cleanup);
84
+ src.once('close', cleanup);
85
+ }
86
+ src.pipe(dest, { end: true /* already default */ });
87
+ }
51
88
  function endStream(stream) {
52
89
  // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
53
90
  return stream.end(null, null, null);
@@ -58,3 +95,10 @@ function safeWrite(chunk, stream) {
58
95
  return (0, node_events_1.once)(stream, 'drain');
59
96
  }
60
97
  }
98
+ // https://github.com/nodejs/node/blob/f692878dec6354c0a82241f224906981861bc840/lib/internal/errors.js#L961-L973
99
+ class AbortError extends Error {
100
+ constructor(message = 'The operation was aborted', options = undefined) {
101
+ super(message, options);
102
+ this.name = 'AbortError';
103
+ }
104
+ }
package/esm/Body.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment */
2
2
  import { Buffer } from 'node:buffer';
3
- import { Readable } from 'node:stream';
3
+ import { addAbortSignal, Readable } from 'node:stream';
4
4
  import { Busboy } from '@fastify/busboy';
5
5
  import { handleMaybePromise } from '@whatwg-node/promise-helpers';
6
6
  import { hasArrayBufferMethod, hasBufferMethod, hasBytesMethod, PonyfillBlob } from './Blob.js';
@@ -33,11 +33,13 @@ export class PonyfillBody {
33
33
  this.contentLength = contentLength;
34
34
  this.bodyType = bodyType;
35
35
  this._buffer = buffer;
36
+ this._signal = options.signal;
36
37
  }
37
38
  bodyType;
38
39
  _bodyFactory = () => null;
39
40
  _generatedBody = null;
40
41
  _buffer;
42
+ _signal;
41
43
  generateBody() {
42
44
  if (this._generatedBody?.readable?.destroyed && this._buffer) {
43
45
  this._generatedBody.readable = Readable.from(this._buffer);
@@ -127,6 +129,9 @@ export class PonyfillBody {
127
129
  this._chunks = [];
128
130
  return fakePromise(this._chunks);
129
131
  }
132
+ if (_body.readable.destroyed) {
133
+ return fakePromise((this._chunks = []));
134
+ }
130
135
  const chunks = [];
131
136
  return new Promise((resolve, reject) => {
132
137
  _body.readable.on('data', chunk => {
@@ -212,6 +217,9 @@ export class PonyfillBody {
212
217
  limits: formDataLimits,
213
218
  defCharset: 'utf-8',
214
219
  });
220
+ if (this._signal) {
221
+ addAbortSignal(this._signal, bb);
222
+ }
215
223
  let completed = false;
216
224
  const complete = (err) => {
217
225
  if (completed)
package/esm/Request.js CHANGED
@@ -73,7 +73,6 @@ export class PonyfillRequest extends PonyfillBody {
73
73
  this.agent = requestInit.agent;
74
74
  }
75
75
  }
76
- this._signal = requestInit?.signal || undefined;
77
76
  }
78
77
  headersSerializer;
79
78
  cache;
@@ -89,7 +88,6 @@ export class PonyfillRequest extends PonyfillBody {
89
88
  referrer;
90
89
  referrerPolicy;
91
90
  _url;
92
- _signal;
93
91
  get signal() {
94
92
  this._signal ||= new AbortController().signal;
95
93
  return this._signal;
@@ -6,7 +6,7 @@ import { handleMaybePromise } from '@whatwg-node/promise-helpers';
6
6
  import { PonyfillRequest } from './Request.js';
7
7
  import { PonyfillResponse } from './Response.js';
8
8
  import { PonyfillURL } from './URL.js';
9
- import { endStream, getHeadersObj, isNodeReadable, safeWrite, shouldRedirect } from './utils.js';
9
+ import { endStream, getHeadersObj, isNodeReadable, pipeThrough, safeWrite, shouldRedirect, } from './utils.js';
10
10
  function getRequestFnForProtocol(url) {
11
11
  if (url.startsWith('http:')) {
12
12
  return httpRequest;
@@ -90,8 +90,19 @@ export function fetchNodeHttp(fetchRequest) {
90
90
  }
91
91
  }
92
92
  outputStream ||= new PassThrough();
93
- nodeResponse.pipe(outputStream, {
94
- end: true,
93
+ pipeThrough({
94
+ src: nodeResponse,
95
+ dest: outputStream,
96
+ signal,
97
+ onError: e => {
98
+ if (!nodeResponse.destroyed) {
99
+ nodeResponse.destroy(e);
100
+ }
101
+ if (!outputStream.destroyed) {
102
+ outputStream.destroy(e);
103
+ }
104
+ reject(e);
105
+ },
95
106
  });
96
107
  const statusCode = nodeResponse.statusCode || 200;
97
108
  let statusText = nodeResponse.statusMessage || STATUS_CODES[statusCode];
package/esm/utils.js CHANGED
@@ -36,6 +36,42 @@ export function isIterable(value) {
36
36
  export function shouldRedirect(status) {
37
37
  return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
38
38
  }
39
+ export function pipeThrough({ src, dest, signal, onError, }) {
40
+ if (onError) {
41
+ // listen for errors on the destination stream if necessary. if the readable
42
+ // stream (src) emits an error, the writable destination (dest) will be
43
+ // destroyed with that error (see below)
44
+ dest.once('error', onError);
45
+ }
46
+ src.once('error', (e) => {
47
+ // if the readable stream (src) emits an error during pipe, the writable
48
+ // destination (dest) is not closed automatically. that needs to be
49
+ // done manually. the readable stream is closed when error is emitted,
50
+ // so only the writable destination needs to be destroyed
51
+ dest.destroy(e);
52
+ });
53
+ if (signal) {
54
+ // this is faster than `import('node:signal').addAbortSignal(signal, src)`
55
+ const srcRef = new WeakRef(src);
56
+ const signalRef = new WeakRef(signal);
57
+ function cleanup() {
58
+ signalRef.deref()?.removeEventListener('abort', onAbort);
59
+ srcRef.deref()?.removeListener('end', cleanup);
60
+ srcRef.deref()?.removeListener('error', cleanup);
61
+ srcRef.deref()?.removeListener('close', cleanup);
62
+ }
63
+ function onAbort() {
64
+ srcRef.deref()?.destroy(new AbortError());
65
+ cleanup();
66
+ }
67
+ signal.addEventListener('abort', onAbort, { once: true });
68
+ // this is faster than `import('node:signal').finished(src, cleanup)`
69
+ src.once('end', cleanup);
70
+ src.once('error', cleanup);
71
+ src.once('close', cleanup);
72
+ }
73
+ src.pipe(dest, { end: true /* already default */ });
74
+ }
39
75
  export function endStream(stream) {
40
76
  // @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
41
77
  return stream.end(null, null, null);
@@ -46,3 +82,10 @@ export function safeWrite(chunk, stream) {
46
82
  return once(stream, 'drain');
47
83
  }
48
84
  }
85
+ // https://github.com/nodejs/node/blob/f692878dec6354c0a82241f224906981861bc840/lib/internal/errors.js#L961-L973
86
+ class AbortError extends Error {
87
+ constructor(message = 'The operation was aborted', options = undefined) {
88
+ super(message, options);
89
+ this.name = 'AbortError';
90
+ }
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatwg-node/node-fetch",
3
- "version": "0.7.23-alpha-20250726085929-27dea3e7c5dae7285bf9c837cce54f387e269df2",
3
+ "version": "0.7.23-alpha-20250727165811-6c05e293622755103fa748bf8ebf677c55ed0143",
4
4
  "description": "Fetch API implementation for Node",
5
5
  "sideEffects": false,
6
6
  "dependencies": {
@@ -29,6 +29,7 @@ export declare class PonyfillBody<TJSON = any> implements Body {
29
29
  private _bodyFactory;
30
30
  private _generatedBody;
31
31
  private _buffer?;
32
+ _signal?: AbortSignal | undefined;
32
33
  private generateBody;
33
34
  protected handleContentLengthHeader(this: PonyfillBody & {
34
35
  headers: Headers;
package/typings/Body.d.ts CHANGED
@@ -29,6 +29,7 @@ export declare class PonyfillBody<TJSON = any> implements Body {
29
29
  private _bodyFactory;
30
30
  private _generatedBody;
31
31
  private _buffer?;
32
+ _signal?: AbortSignal | undefined;
32
33
  private generateBody;
33
34
  protected handleContentLengthHeader(this: PonyfillBody & {
34
35
  headers: Headers;
@@ -26,7 +26,6 @@ export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> im
26
26
  referrer: string;
27
27
  referrerPolicy: ReferrerPolicy;
28
28
  _url: string | undefined;
29
- _signal: AbortSignal | undefined;
30
29
  get signal(): AbortSignal;
31
30
  get url(): string;
32
31
  _parsedUrl: URL | undefined;
@@ -26,7 +26,6 @@ export declare class PonyfillRequest<TJSON = any> extends PonyfillBody<TJSON> im
26
26
  referrer: string;
27
27
  referrerPolicy: ReferrerPolicy;
28
28
  _url: string | undefined;
29
- _signal: AbortSignal | undefined;
30
29
  get signal(): AbortSignal;
31
30
  get url(): string;
32
31
  _parsedUrl: URL | undefined;
@@ -6,6 +6,12 @@ export declare function isArrayBufferView(obj: any): obj is ArrayBufferView;
6
6
  export declare function isNodeReadable(obj: any): obj is Readable;
7
7
  export declare function isIterable(value: any): value is Iterable<unknown>;
8
8
  export declare function shouldRedirect(status?: number): boolean;
9
+ export declare function pipeThrough({ src, dest, signal, onError, }: {
10
+ src: Readable;
11
+ dest: Writable;
12
+ signal?: AbortSignal | undefined;
13
+ onError?: ((e: Error) => void) | undefined;
14
+ }): void;
9
15
  export declare function endStream(stream: {
10
16
  end: () => void;
11
17
  }): void;
@@ -6,6 +6,12 @@ export declare function isArrayBufferView(obj: any): obj is ArrayBufferView;
6
6
  export declare function isNodeReadable(obj: any): obj is Readable;
7
7
  export declare function isIterable(value: any): value is Iterable<unknown>;
8
8
  export declare function shouldRedirect(status?: number): boolean;
9
+ export declare function pipeThrough({ src, dest, signal, onError, }: {
10
+ src: Readable;
11
+ dest: Writable;
12
+ signal?: AbortSignal | undefined;
13
+ onError?: ((e: Error) => void) | undefined;
14
+ }): void;
9
15
  export declare function endStream(stream: {
10
16
  end: () => void;
11
17
  }): void;