@whatwg-node/node-fetch 0.7.23-alpha-20250726021906-27dea3e7c5dae7285bf9c837cce54f387e269df2 → 0.7.23-alpha-20250726022028-80b722590ec2d4fb9c98bfa7a74d8daf31fcf4bf

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
@@ -27,9 +27,11 @@ class PonyfillBody {
27
27
  bodyUsed = false;
28
28
  contentType = null;
29
29
  contentLength = null;
30
+ _signal = null;
30
31
  constructor(bodyInit, options = {}) {
31
32
  this.bodyInit = bodyInit;
32
33
  this.options = options;
34
+ this._signal = options.signal || null;
33
35
  const { bodyFactory, contentType, contentLength, bodyType, buffer } = processBodyInit(bodyInit);
34
36
  this._bodyFactory = bodyFactory;
35
37
  this.contentType = contentType;
@@ -215,6 +217,9 @@ class PonyfillBody {
215
217
  limits: formDataLimits,
216
218
  defCharset: 'utf-8',
217
219
  });
220
+ if (this._signal) {
221
+ (0, node_stream_1.addAbortSignal)(this._signal, bb);
222
+ }
218
223
  let completed = false;
219
224
  const complete = (err) => {
220
225
  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;
package/cjs/fetchCurl.js CHANGED
@@ -22,7 +22,7 @@ function fetchCurl(fetchRequest) {
22
22
  }
23
23
  curlHandle.enable(CurlFeature.StreamResponse);
24
24
  let signal;
25
- if (fetchRequest._signal === null) {
25
+ if (fetchRequest._signal == null) {
26
26
  signal = undefined;
27
27
  }
28
28
  else if (fetchRequest._signal) {
@@ -104,8 +104,12 @@ function fetchCurl(fetchRequest) {
104
104
  }
105
105
  });
106
106
  curlHandle.once('stream', function streamListener(stream, status, headersBuf) {
107
- const outputStream = stream.pipe(new node_stream_1.PassThrough(), {
108
- end: true,
107
+ const passThrough = new node_stream_1.PassThrough();
108
+ (0, utils_js_1.pipeThrough)({
109
+ src: stream,
110
+ dest: passThrough,
111
+ signal,
112
+ onError: deferredPromise.reject,
109
113
  });
110
114
  const headersFlat = headersBuf
111
115
  .toString('utf8')
@@ -118,7 +122,7 @@ function fetchCurl(fetchRequest) {
118
122
  if (!stream.destroyed) {
119
123
  stream.resume();
120
124
  }
121
- outputStream.destroy();
125
+ passThrough.destroy();
122
126
  deferredPromise.reject(new Error('redirect is not allowed'));
123
127
  }
124
128
  return true;
@@ -126,14 +130,14 @@ function fetchCurl(fetchRequest) {
126
130
  return false;
127
131
  });
128
132
  const headersInit = headersFlat.map(headerFlat => headerFlat.split(/:\s(.+)/).slice(0, 2));
129
- const ponyfillResponse = new Response_js_1.PonyfillResponse(outputStream, {
133
+ const ponyfillResponse = new Response_js_1.PonyfillResponse(passThrough, {
130
134
  status,
131
135
  headers: headersInit,
132
136
  url: curlHandle.getInfo(Curl.info.REDIRECT_URL)?.toString() || fetchRequest.url,
133
137
  redirected: Number(curlHandle.getInfo(Curl.info.REDIRECT_COUNT)) > 0,
134
138
  });
135
139
  deferredPromise.resolve(ponyfillResponse);
136
- streamResolved = outputStream;
140
+ streamResolved = passThrough;
137
141
  });
138
142
  setImmediate(() => {
139
143
  curlHandle.perform();
@@ -92,10 +92,14 @@ function fetchNodeHttp(fetchRequest) {
92
92
  return;
93
93
  }
94
94
  }
95
- outputStream ||= new node_stream_1.PassThrough();
96
- nodeResponse.pipe(outputStream, {
97
- end: true,
98
- });
95
+ if (outputStream != null) {
96
+ (0, utils_js_1.pipeThrough)({
97
+ src: nodeResponse,
98
+ dest: outputStream,
99
+ signal,
100
+ onError: reject,
101
+ });
102
+ }
99
103
  const statusCode = nodeResponse.statusCode || 200;
100
104
  let statusText = nodeResponse.statusMessage || node_http_1.STATUS_CODES[statusCode];
101
105
  if (statusText == null) {
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';
@@ -24,9 +24,11 @@ export class PonyfillBody {
24
24
  bodyUsed = false;
25
25
  contentType = null;
26
26
  contentLength = null;
27
+ _signal = null;
27
28
  constructor(bodyInit, options = {}) {
28
29
  this.bodyInit = bodyInit;
29
30
  this.options = options;
31
+ this._signal = options.signal || null;
30
32
  const { bodyFactory, contentType, contentLength, bodyType, buffer } = processBodyInit(bodyInit);
31
33
  this._bodyFactory = bodyFactory;
32
34
  this.contentType = contentType;
@@ -212,6 +214,9 @@ export class PonyfillBody {
212
214
  limits: formDataLimits,
213
215
  defCharset: 'utf-8',
214
216
  });
217
+ if (this._signal) {
218
+ addAbortSignal(this._signal, bb);
219
+ }
215
220
  let completed = false;
216
221
  const complete = (err) => {
217
222
  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;
package/esm/fetchCurl.js CHANGED
@@ -2,7 +2,7 @@ import { PassThrough, Readable } from 'node:stream';
2
2
  import { rootCertificates } from 'node:tls';
3
3
  import { createDeferredPromise } from '@whatwg-node/promise-helpers';
4
4
  import { PonyfillResponse } from './Response.js';
5
- import { defaultHeadersSerializer, isNodeReadable, shouldRedirect } from './utils.js';
5
+ import { defaultHeadersSerializer, isNodeReadable, pipeThrough, shouldRedirect } from './utils.js';
6
6
  export function fetchCurl(fetchRequest) {
7
7
  const { Curl, CurlFeature, CurlPause, CurlProgressFunc } = globalThis['libcurl'];
8
8
  const curlHandle = new Curl();
@@ -19,7 +19,7 @@ export function fetchCurl(fetchRequest) {
19
19
  }
20
20
  curlHandle.enable(CurlFeature.StreamResponse);
21
21
  let signal;
22
- if (fetchRequest._signal === null) {
22
+ if (fetchRequest._signal == null) {
23
23
  signal = undefined;
24
24
  }
25
25
  else if (fetchRequest._signal) {
@@ -101,8 +101,12 @@ export function fetchCurl(fetchRequest) {
101
101
  }
102
102
  });
103
103
  curlHandle.once('stream', function streamListener(stream, status, headersBuf) {
104
- const outputStream = stream.pipe(new PassThrough(), {
105
- end: true,
104
+ const passThrough = new PassThrough();
105
+ pipeThrough({
106
+ src: stream,
107
+ dest: passThrough,
108
+ signal,
109
+ onError: deferredPromise.reject,
106
110
  });
107
111
  const headersFlat = headersBuf
108
112
  .toString('utf8')
@@ -115,7 +119,7 @@ export function fetchCurl(fetchRequest) {
115
119
  if (!stream.destroyed) {
116
120
  stream.resume();
117
121
  }
118
- outputStream.destroy();
122
+ passThrough.destroy();
119
123
  deferredPromise.reject(new Error('redirect is not allowed'));
120
124
  }
121
125
  return true;
@@ -123,14 +127,14 @@ export function fetchCurl(fetchRequest) {
123
127
  return false;
124
128
  });
125
129
  const headersInit = headersFlat.map(headerFlat => headerFlat.split(/:\s(.+)/).slice(0, 2));
126
- const ponyfillResponse = new PonyfillResponse(outputStream, {
130
+ const ponyfillResponse = new PonyfillResponse(passThrough, {
127
131
  status,
128
132
  headers: headersInit,
129
133
  url: curlHandle.getInfo(Curl.info.REDIRECT_URL)?.toString() || fetchRequest.url,
130
134
  redirected: Number(curlHandle.getInfo(Curl.info.REDIRECT_COUNT)) > 0,
131
135
  });
132
136
  deferredPromise.resolve(ponyfillResponse);
133
- streamResolved = outputStream;
137
+ streamResolved = passThrough;
134
138
  });
135
139
  setImmediate(() => {
136
140
  curlHandle.perform();
@@ -1,12 +1,12 @@
1
1
  import { request as httpRequest, STATUS_CODES } from 'node:http';
2
2
  import { request as httpsRequest } from 'node:https';
3
- import { PassThrough, Readable } from 'node:stream';
3
+ import { Readable } from 'node:stream';
4
4
  import { createBrotliDecompress, createGunzip, createInflate, createInflateRaw } from 'node:zlib';
5
5
  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;
@@ -89,10 +89,14 @@ export function fetchNodeHttp(fetchRequest) {
89
89
  return;
90
90
  }
91
91
  }
92
- outputStream ||= new PassThrough();
93
- nodeResponse.pipe(outputStream, {
94
- end: true,
95
- });
92
+ if (outputStream != null) {
93
+ pipeThrough({
94
+ src: nodeResponse,
95
+ dest: outputStream,
96
+ signal,
97
+ onError: reject,
98
+ });
99
+ }
96
100
  const statusCode = nodeResponse.statusCode || 200;
97
101
  let statusText = nodeResponse.statusMessage || STATUS_CODES[statusCode];
98
102
  if (statusText == null) {
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-20250726021906-27dea3e7c5dae7285bf9c837cce54f387e269df2",
3
+ "version": "0.7.23-alpha-20250726022028-80b722590ec2d4fb9c98bfa7a74d8daf31fcf4bf",
4
4
  "description": "Fetch API implementation for Node",
5
5
  "sideEffects": false,
6
6
  "dependencies": {
@@ -24,6 +24,7 @@ export declare class PonyfillBody<TJSON = any> implements Body {
24
24
  bodyUsed: boolean;
25
25
  contentType: string | null;
26
26
  contentLength: number | null;
27
+ _signal?: AbortSignal | null;
27
28
  constructor(bodyInit: BodyPonyfillInit | null, options?: PonyfillBodyOptions);
28
29
  private bodyType?;
29
30
  private _bodyFactory;
package/typings/Body.d.ts CHANGED
@@ -24,6 +24,7 @@ export declare class PonyfillBody<TJSON = any> implements Body {
24
24
  bodyUsed: boolean;
25
25
  contentType: string | null;
26
26
  contentLength: number | null;
27
+ _signal?: AbortSignal | null;
27
28
  constructor(bodyInit: BodyPonyfillInit | null, options?: PonyfillBodyOptions);
28
29
  private bodyType?;
29
30
  private _bodyFactory;
@@ -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;