@whatwg-node/server 0.10.0-alpha-20241125144944-df106a17746126e3c8089afa16af852a4483c8b3 → 0.10.0-alpha-20250205010145-5ab5edf75d82f8174c999dc970ad9a042ea5547d

Sign up to get free protection for your applications and to get access to all the features.
package/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.decompressedResponseMap = exports.nodeRequestResponseMap = exports.ServerAdapterRequestAbortSignal = void 0;
3
+ exports.decompressedResponseMap = exports.nodeRequestResponseMap = void 0;
4
4
  exports.isAsyncIterable = isAsyncIterable;
5
5
  exports.normalizeNodeRequest = normalizeNodeRequest;
6
6
  exports.isReadable = isReadable;
@@ -69,40 +69,9 @@ function isRequestBody(body) {
69
69
  }
70
70
  return false;
71
71
  }
72
- class ServerAdapterRequestAbortSignal extends EventTarget {
73
- aborted = false;
74
- _onabort = null;
75
- reason;
76
- throwIfAborted() {
77
- if (this.aborted) {
78
- throw this.reason;
79
- }
80
- }
81
- sendAbort() {
82
- this.reason = new DOMException('This operation was aborted', 'AbortError');
83
- this.aborted = true;
84
- this.dispatchEvent(new Event('abort'));
85
- }
86
- get onabort() {
87
- return this._onabort;
88
- }
89
- set onabort(value) {
90
- this._onabort = value;
91
- if (value) {
92
- this.addEventListener('abort', value);
93
- }
94
- else {
95
- this.removeEventListener('abort', value);
96
- }
97
- }
98
- any(signals) {
99
- return AbortSignal.any([...signals]);
100
- }
101
- }
102
- exports.ServerAdapterRequestAbortSignal = ServerAdapterRequestAbortSignal;
103
72
  let bunNodeCompatModeWarned = false;
104
73
  exports.nodeRequestResponseMap = new WeakMap();
105
- function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
74
+ function normalizeNodeRequest(nodeRequest, fetchAPI) {
106
75
  const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
107
76
  let fullUrl = buildFullUrl(rawRequest);
108
77
  if (nodeRequest.query) {
@@ -112,7 +81,6 @@ function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
112
81
  }
113
82
  fullUrl = url.toString();
114
83
  }
115
- let signal;
116
84
  const nodeResponse = exports.nodeRequestResponseMap.get(nodeRequest);
117
85
  exports.nodeRequestResponseMap.delete(nodeRequest);
118
86
  let normalizedHeaders = nodeRequest.headers;
@@ -124,24 +92,12 @@ function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
124
92
  }
125
93
  }
126
94
  }
95
+ const controller = new AbortController();
127
96
  if (nodeResponse?.once) {
128
- let sendAbortSignal;
129
- // If ponyfilled
130
- if (fetchAPI.Request !== globalThis.Request) {
131
- const newSignal = new ServerAdapterRequestAbortSignal();
132
- registerSignal?.(newSignal);
133
- signal = newSignal;
134
- sendAbortSignal = () => signal.sendAbort();
135
- }
136
- else {
137
- const controller = new AbortController();
138
- signal = controller.signal;
139
- sendAbortSignal = () => controller.abort();
140
- }
141
97
  const closeEventListener = () => {
142
- if (signal && !signal.aborted) {
143
- rawRequest.aborted = true;
144
- sendAbortSignal();
98
+ if (!controller.signal.aborted) {
99
+ Object.defineProperty(rawRequest, 'aborted', { value: true });
100
+ controller.abort(nodeResponse.errored ?? undefined);
145
101
  }
146
102
  };
147
103
  nodeResponse.once('error', closeEventListener);
@@ -154,7 +110,7 @@ function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
154
110
  return new fetchAPI.Request(fullUrl, {
155
111
  method: nodeRequest.method,
156
112
  headers: normalizedHeaders,
157
- signal,
113
+ signal: controller.signal,
158
114
  });
159
115
  }
160
116
  /**
@@ -167,16 +123,16 @@ function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
167
123
  if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
168
124
  if (isRequestBody(maybeParsedBody)) {
169
125
  return new fetchAPI.Request(fullUrl, {
170
- method: nodeRequest.method,
126
+ method: nodeRequest.method || 'GET',
171
127
  headers: normalizedHeaders,
172
128
  body: maybeParsedBody,
173
- signal,
129
+ signal: controller.signal,
174
130
  });
175
131
  }
176
132
  const request = new fetchAPI.Request(fullUrl, {
177
- method: nodeRequest.method,
133
+ method: nodeRequest.method || 'GET',
178
134
  headers: normalizedHeaders,
179
- signal,
135
+ signal: controller.signal,
180
136
  });
181
137
  if (!request.headers.get('content-type')?.includes('json')) {
182
138
  request.headers.set('content-type', 'application/json; charset=utf-8');
@@ -221,16 +177,17 @@ It will affect your performance. Please check our Bun integration recipe, and av
221
177
  rawRequest.destroy(e);
222
178
  },
223
179
  }),
224
- signal,
180
+ signal: controller.signal,
225
181
  });
226
182
  }
227
183
  // perf: instead of spreading the object, we can just pass it as is and it performs better
228
184
  return new fetchAPI.Request(fullUrl, {
229
185
  method: nodeRequest.method,
230
186
  headers: normalizedHeaders,
187
+ signal: controller.signal,
188
+ // @ts-expect-error - AsyncIterable is supported as body
231
189
  body: rawRequest,
232
190
  duplex: 'half',
233
- signal,
234
191
  });
235
192
  }
236
193
  function isReadable(stream) {
@@ -276,13 +233,25 @@ async function sendAsyncIterable(serverResponse, asyncIterable) {
276
233
  if (closed) {
277
234
  break;
278
235
  }
279
- if (!serverResponse
280
- // @ts-expect-error http and http2 writes are actually compatible
281
- .write(chunk)) {
282
- if (closed) {
283
- break;
236
+ const shouldBreak = await new Promise(resolve => {
237
+ if (!serverResponse
238
+ // @ts-expect-error http and http2 writes are actually compatible
239
+ .write(chunk, err => {
240
+ if (err) {
241
+ resolve(true);
242
+ }
243
+ })) {
244
+ if (closed) {
245
+ resolve(true);
246
+ return;
247
+ }
248
+ serverResponse.once('drain', () => {
249
+ resolve(false);
250
+ });
284
251
  }
285
- await new Promise(resolve => serverResponse.once('drain', resolve));
252
+ });
253
+ if (shouldBreak) {
254
+ break;
286
255
  }
287
256
  }
288
257
  endResponse(serverResponse);
@@ -343,15 +312,15 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
343
312
  return;
344
313
  }
345
314
  if (isReadableStream(fetchBody)) {
346
- return sendReadableStream(serverResponse, fetchBody);
315
+ return sendReadableStream(nodeRequest, serverResponse, fetchBody);
347
316
  }
348
317
  if (isAsyncIterable(fetchBody)) {
349
318
  return sendAsyncIterable(serverResponse, fetchBody);
350
319
  }
351
320
  }
352
- async function sendReadableStream(serverResponse, readableStream) {
321
+ async function sendReadableStream(nodeRequest, serverResponse, readableStream) {
353
322
  const reader = readableStream.getReader();
354
- serverResponse.req.once('error', err => {
323
+ nodeRequest?.once?.('error', err => {
355
324
  reader.cancel(err);
356
325
  });
357
326
  while (true) {
@@ -474,17 +443,24 @@ function createDeferredPromise() {
474
443
  };
475
444
  }
476
445
  function handleAbortSignalAndPromiseResponse(response$, abortSignal) {
446
+ if (abortSignal?.aborted) {
447
+ throw abortSignal.reason;
448
+ }
477
449
  if (isPromise(response$) && abortSignal) {
478
450
  const deferred$ = createDeferredPromise();
479
- abortSignal.addEventListener('abort', function abortSignalFetchErrorHandler() {
451
+ function abortSignalFetchErrorHandler() {
480
452
  deferred$.reject(abortSignal.reason);
481
- });
453
+ }
454
+ abortSignal.addEventListener('abort', abortSignalFetchErrorHandler, { once: true });
482
455
  response$
483
456
  .then(function fetchSuccessHandler(res) {
484
457
  deferred$.resolve(res);
485
458
  })
486
459
  .catch(function fetchErrorHandler(err) {
487
460
  deferred$.reject(err);
461
+ })
462
+ .finally(() => {
463
+ abortSignal.removeEventListener('abort', abortSignalFetchErrorHandler);
488
464
  });
489
465
  return deferred$.promise;
490
466
  }
@@ -547,7 +523,7 @@ function handleResponseDecompression(response, fetchAPI) {
547
523
  }
548
524
  return decompressedResponse;
549
525
  }
550
- const terminateEvents = ['SIGINT', 'SIGTERM', 'exit'];
526
+ const terminateEvents = ['SIGINT', 'exit', 'SIGTERM'];
551
527
  const disposableStacks = new Set();
552
528
  let eventListenerRegistered = false;
553
529
  function ensureEventListenerForDisposableStacks() {
@@ -557,10 +533,7 @@ function ensureEventListenerForDisposableStacks() {
557
533
  eventListenerRegistered = true;
558
534
  for (const event of terminateEvents) {
559
535
  globalThis.process.once(event, function terminateHandler() {
560
- return Promise.allSettled([...disposableStacks].map(stack => !stack.disposed &&
561
- stack.disposeAsync().catch(e => {
562
- console.error('Error while disposing:', e);
563
- })));
536
+ return Promise.allSettled([...disposableStacks].map(stack => !stack.disposed && stack.disposeAsync()));
564
537
  });
565
538
  }
566
539
  }
@@ -9,7 +9,7 @@ const utils_js_1 = require("./utils.js");
9
9
  function isUWSResponse(res) {
10
10
  return !!res.onData;
11
11
  }
12
- function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
12
+ function getRequestFromUWSRequest({ req, res, fetchAPI, controller, }) {
13
13
  const method = req.getMethod();
14
14
  let duplex;
15
15
  const chunks = [];
@@ -43,9 +43,9 @@ function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
43
43
  let getReadableStream;
44
44
  if (method !== 'get' && method !== 'head') {
45
45
  duplex = 'half';
46
- signal.addEventListener('abort', () => {
46
+ controller.signal.addEventListener('abort', () => {
47
47
  stop();
48
- });
48
+ }, { once: true });
49
49
  let readableStream;
50
50
  getReadableStream = () => {
51
51
  if (!readableStream) {
@@ -97,7 +97,7 @@ function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
97
97
  get body() {
98
98
  return getBody();
99
99
  },
100
- signal,
100
+ signal: controller.signal,
101
101
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
102
102
  // @ts-ignore - not in the TS types yet
103
103
  duplex,
@@ -171,14 +171,14 @@ function createWritableFromUWS(uwsResponse, fetchAPI) {
171
171
  },
172
172
  });
173
173
  }
174
- function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal, fetchAPI) {
174
+ function sendResponseToUwsOpts(uwsResponse, fetchResponse, controller, fetchAPI) {
175
175
  if (!fetchResponse) {
176
176
  uwsResponse.writeStatus('404 Not Found');
177
177
  uwsResponse.end();
178
178
  return;
179
179
  }
180
180
  const bufferOfRes = fetchResponse._buffer;
181
- if (signal.aborted) {
181
+ if (controller.signal.aborted) {
182
182
  return;
183
183
  }
184
184
  uwsResponse.cork(() => {
@@ -208,17 +208,17 @@ function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal, fetchAPI) {
208
208
  if (bufferOfRes || !fetchResponse.body) {
209
209
  return;
210
210
  }
211
- signal.addEventListener('abort', () => {
211
+ controller.signal.addEventListener('abort', () => {
212
212
  if (!fetchResponse.body?.locked) {
213
- fetchResponse.body?.cancel(signal.reason);
213
+ fetchResponse.body?.cancel(controller.signal.reason);
214
214
  }
215
- });
215
+ }, { once: true });
216
216
  return fetchResponse.body
217
217
  .pipeTo(createWritableFromUWS(uwsResponse, fetchAPI), {
218
- signal,
218
+ signal: controller.signal,
219
219
  })
220
220
  .catch(err => {
221
- if (signal.aborted) {
221
+ if (controller.signal.aborted) {
222
222
  return;
223
223
  }
224
224
  throw err;
@@ -1,7 +1,6 @@
1
- /* eslint-disable @typescript-eslint/ban-types */
2
1
  import { AsyncDisposableStack, DisposableSymbols } from '@whatwg-node/disposablestack';
3
2
  import * as DefaultFetchAPI from '@whatwg-node/fetch';
4
- import { completeAssign, ensureDisposableStackRegisteredForTerminateEvents, handleAbortSignalAndPromiseResponse, handleErrorFromRequestHandler, isFetchEvent, isNodeRequest, isolateObject, isPromise, isRequestInit, isServerResponse, iterateAsyncVoid, nodeRequestResponseMap, normalizeNodeRequest, sendNodeResponse, ServerAdapterRequestAbortSignal, } from './utils.js';
3
+ import { completeAssign, ensureDisposableStackRegisteredForTerminateEvents, handleAbortSignalAndPromiseResponse, handleErrorFromRequestHandler, isFetchEvent, isNodeRequest, isolateObject, isPromise, isRequestInit, isServerResponse, iterateAsyncVoid, nodeRequestResponseMap, normalizeNodeRequest, sendNodeResponse, } from './utils.js';
5
4
  import { fakePromise, getRequestFromUWSRequest, isUWSResponse, sendResponseToUwsOpts, } from './uwebsockets.js';
6
5
  // Required for envs like nextjs edge runtime
7
6
  function isRequestAccessible(serverContext) {
@@ -24,54 +23,56 @@ function createServerAdapter(serverAdapterBaseObject, options) {
24
23
  const onRequestHooks = [];
25
24
  const onResponseHooks = [];
26
25
  const waitUntilPromises = new Set();
27
- const disposableStack = new AsyncDisposableStack();
28
- const signals = new Set();
29
- function registerSignal(signal) {
30
- signals.add(signal);
31
- signal.addEventListener('abort', () => {
32
- signals.delete(signal);
33
- });
34
- }
35
- disposableStack.defer(() => {
36
- for (const signal of signals) {
37
- signal.sendAbort();
38
- }
39
- });
40
- disposableStack.defer(() => {
41
- if (waitUntilPromises.size > 0) {
42
- return Promise.allSettled(waitUntilPromises).then(() => {
43
- waitUntilPromises.clear();
44
- }, () => {
45
- waitUntilPromises.clear();
26
+ let _disposableStack;
27
+ function ensureDisposableStack() {
28
+ if (!_disposableStack) {
29
+ _disposableStack = new AsyncDisposableStack();
30
+ if (options?.disposeOnProcessTerminate) {
31
+ ensureDisposableStackRegisteredForTerminateEvents(_disposableStack);
32
+ }
33
+ _disposableStack.defer(() => {
34
+ if (waitUntilPromises.size > 0) {
35
+ return Promise.allSettled(waitUntilPromises).then(() => {
36
+ waitUntilPromises.clear();
37
+ }, () => {
38
+ waitUntilPromises.clear();
39
+ });
40
+ }
46
41
  });
47
42
  }
48
- });
43
+ return _disposableStack;
44
+ }
49
45
  function waitUntil(promiseLike) {
50
- // If it is a Node.js environment, we should register the disposable stack to handle process termination events
51
- if (globalThis.process) {
52
- ensureDisposableStackRegisteredForTerminateEvents(disposableStack);
46
+ // Ensure that the disposable stack is created
47
+ if (isPromise(promiseLike)) {
48
+ ensureDisposableStack();
49
+ waitUntilPromises.add(promiseLike);
50
+ promiseLike.then(() => {
51
+ waitUntilPromises.delete(promiseLike);
52
+ }, err => {
53
+ console.error(`Unexpected error while waiting: ${err.message || err}`);
54
+ waitUntilPromises.delete(promiseLike);
55
+ });
53
56
  }
54
- waitUntilPromises.add(promiseLike);
55
- promiseLike.then(() => {
56
- waitUntilPromises.delete(promiseLike);
57
- }, err => {
58
- console.error(`Unexpected error while waiting: ${err.message || err}`);
59
- waitUntilPromises.delete(promiseLike);
60
- });
61
57
  }
62
58
  if (options?.plugins != null) {
63
59
  for (const plugin of options.plugins) {
64
- if (plugin != null) {
65
- if (plugin.onRequest) {
66
- onRequestHooks.push(plugin.onRequest);
67
- }
68
- if (plugin.onResponse) {
69
- onResponseHooks.push(plugin.onResponse);
70
- }
71
- const disposeFn = plugin[DisposableSymbols.asyncDispose] || plugin[DisposableSymbols.dispose];
72
- if (disposeFn != null) {
73
- disposableStack.defer(disposeFn);
74
- }
60
+ if (plugin.onRequest) {
61
+ onRequestHooks.push(plugin.onRequest);
62
+ }
63
+ if (plugin.onResponse) {
64
+ onResponseHooks.push(plugin.onResponse);
65
+ }
66
+ const disposeFn = plugin[DisposableSymbols.dispose];
67
+ if (disposeFn) {
68
+ ensureDisposableStack().defer(disposeFn);
69
+ }
70
+ const asyncDisposeFn = plugin[DisposableSymbols.asyncDispose];
71
+ if (asyncDisposeFn) {
72
+ ensureDisposableStack().defer(asyncDisposeFn);
73
+ }
74
+ if (plugin.onDispose) {
75
+ ensureDisposableStack().defer(plugin.onDispose);
75
76
  }
76
77
  }
77
78
  }
@@ -146,7 +147,11 @@ function createServerAdapter(serverAdapterBaseObject, options) {
146
147
  // TODO: Remove this on the next major version
147
148
  function handleNodeRequest(nodeRequest, ...ctx) {
148
149
  const serverContext = ctx.length > 1 ? completeAssign(...ctx) : ctx[0] || {};
149
- const request = normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal);
150
+ // Ensure `waitUntil` is available in the server context
151
+ if (!serverContext.waitUntil) {
152
+ serverContext.waitUntil = waitUntil;
153
+ }
154
+ const request = normalizeNodeRequest(nodeRequest, fetchAPI);
150
155
  return handleRequest(request, serverContext);
151
156
  }
152
157
  function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
@@ -192,8 +197,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
192
197
  const serverContext = filteredCtxParts.length > 0
193
198
  ? completeAssign(defaultServerContext, ...ctx)
194
199
  : defaultServerContext;
195
- const signal = new ServerAdapterRequestAbortSignal();
196
- registerSignal(signal);
200
+ const controller = new AbortController();
197
201
  const originalResEnd = res.end.bind(res);
198
202
  let resEnded = false;
199
203
  res.end = function (data) {
@@ -202,16 +206,16 @@ function createServerAdapter(serverAdapterBaseObject, options) {
202
206
  };
203
207
  const originalOnAborted = res.onAborted.bind(res);
204
208
  originalOnAborted(function () {
205
- signal.sendAbort();
209
+ controller.abort();
206
210
  });
207
211
  res.onAborted = function (cb) {
208
- signal.addEventListener('abort', cb);
212
+ controller.signal.addEventListener('abort', cb, { once: true });
209
213
  };
210
214
  const request = getRequestFromUWSRequest({
211
215
  req,
212
216
  res,
213
217
  fetchAPI,
214
- signal,
218
+ controller,
215
219
  });
216
220
  let response$;
217
221
  try {
@@ -224,8 +228,8 @@ function createServerAdapter(serverAdapterBaseObject, options) {
224
228
  return response$
225
229
  .catch((e) => handleErrorFromRequestHandler(e, fetchAPI.Response))
226
230
  .then(response => {
227
- if (!signal.aborted && !resEnded) {
228
- return sendResponseToUwsOpts(res, response, signal, fetchAPI);
231
+ if (!controller.signal.aborted && !resEnded) {
232
+ return sendResponseToUwsOpts(res, response, controller, fetchAPI);
229
233
  }
230
234
  })
231
235
  .catch(err => {
@@ -233,8 +237,8 @@ function createServerAdapter(serverAdapterBaseObject, options) {
233
237
  });
234
238
  }
235
239
  try {
236
- if (!signal.aborted && !resEnded) {
237
- return sendResponseToUwsOpts(res, response$, signal, fetchAPI);
240
+ if (!controller.signal.aborted && !resEnded) {
241
+ return sendResponseToUwsOpts(res, response$, controller, fetchAPI);
238
242
  }
239
243
  }
240
244
  catch (err) {
@@ -267,13 +271,17 @@ function createServerAdapter(serverAdapterBaseObject, options) {
267
271
  if (isRequestInit(initOrCtx)) {
268
272
  const request = new fetchAPI.Request(input, initOrCtx);
269
273
  const res$ = handleRequestWithWaitUntil(request, ...restOfCtx);
270
- return handleAbortSignalAndPromiseResponse(res$, initOrCtx?.signal);
274
+ const signal = initOrCtx.signal;
275
+ if (signal) {
276
+ return handleAbortSignalAndPromiseResponse(res$, signal);
277
+ }
278
+ return res$;
271
279
  }
272
280
  const request = new fetchAPI.Request(input);
273
281
  return handleRequestWithWaitUntil(request, ...maybeCtx);
274
282
  }
275
283
  const res$ = handleRequestWithWaitUntil(input, ...maybeCtx);
276
- return handleAbortSignalAndPromiseResponse(res$, input._signal);
284
+ return handleAbortSignalAndPromiseResponse(res$, input.signal);
277
285
  };
278
286
  const genericRequestHandler = (input, ...maybeCtx) => {
279
287
  // If it is a Node request
@@ -312,16 +320,18 @@ function createServerAdapter(serverAdapterBaseObject, options) {
312
320
  handleEvent,
313
321
  handleUWS,
314
322
  handle: genericRequestHandler,
315
- disposableStack,
323
+ get disposableStack() {
324
+ return ensureDisposableStack();
325
+ },
316
326
  [DisposableSymbols.asyncDispose]() {
317
- if (!disposableStack.disposed) {
318
- return disposableStack.disposeAsync();
327
+ if (_disposableStack && !_disposableStack.disposed) {
328
+ return _disposableStack.disposeAsync();
319
329
  }
320
330
  return fakePromise(undefined);
321
331
  },
322
332
  dispose() {
323
- if (!disposableStack.disposed) {
324
- return disposableStack.disposeAsync();
333
+ if (_disposableStack && !_disposableStack.disposed) {
334
+ return _disposableStack.disposeAsync();
325
335
  }
326
336
  return fakePromise(undefined);
327
337
  },
package/esm/index.js CHANGED
@@ -7,3 +7,4 @@ export * from './plugins/useErrorHandling.js';
7
7
  export * from './plugins/useContentEncoding.js';
8
8
  export * from './uwebsockets.js';
9
9
  export { Response } from '@whatwg-node/fetch';
10
+ export { DisposableSymbols } from '@whatwg-node/disposablestack';
@@ -1,4 +1,4 @@
1
- import { decompressedResponseMap, getSupportedEncodings, isAsyncIterable, isReadable, } from '../utils.js';
1
+ import { decompressedResponseMap, getSupportedEncodings } from '../utils.js';
2
2
  export function useContentEncoding() {
3
3
  const encodingMap = new WeakMap();
4
4
  return {
@@ -43,50 +43,19 @@ export function useContentEncoding() {
43
43
  encodingMap.set(request, acceptEncoding.split(','));
44
44
  }
45
45
  },
46
- onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
47
- // Hack for avoiding to create whatwg-node to create a readable stream until it's needed
48
- if (response['bodyInit'] || response.body) {
46
+ onResponse({ request, response, setResponse, fetchAPI }) {
47
+ if (response.body) {
49
48
  const encodings = encodingMap.get(request);
50
49
  if (encodings) {
51
50
  const supportedEncoding = encodings.find(encoding => getSupportedEncodings(fetchAPI).includes(encoding));
52
51
  if (supportedEncoding) {
53
52
  const compressionStream = new fetchAPI.CompressionStream(supportedEncoding);
54
- // To calculate final content-length
55
- const contentLength = response.headers.get('content-length');
56
- if (contentLength) {
57
- const bufOfRes = response._buffer;
58
- if (bufOfRes) {
59
- const writer = compressionStream.writable.getWriter();
60
- const write$ = writer.write(bufOfRes);
61
- serverContext.waitUntil?.(write$);
62
- const close$ = writer.close();
63
- serverContext.waitUntil?.(close$);
64
- const uint8Arrays$ = isReadable(compressionStream.readable['readable'])
65
- ? collectReadableValues(compressionStream.readable['readable'])
66
- : isAsyncIterable(compressionStream.readable)
67
- ? collectAsyncIterableValues(compressionStream.readable)
68
- : collectReadableStreamValues(compressionStream.readable);
69
- return uint8Arrays$.then(uint8Arrays => {
70
- const chunks = uint8Arrays.flatMap(uint8Array => [...uint8Array]);
71
- const uint8Array = new Uint8Array(chunks);
72
- const newHeaders = new fetchAPI.Headers(response.headers);
73
- newHeaders.set('content-encoding', supportedEncoding);
74
- newHeaders.set('content-length', uint8Array.byteLength.toString());
75
- const compressedResponse = new fetchAPI.Response(uint8Array, {
76
- ...response,
77
- headers: newHeaders,
78
- });
79
- decompressedResponseMap.set(compressedResponse, response);
80
- setResponse(compressedResponse);
81
- const close$ = compressionStream.writable.close();
82
- serverContext.waitUntil?.(close$);
83
- });
84
- }
85
- }
86
53
  const newHeaders = new fetchAPI.Headers(response.headers);
87
54
  newHeaders.set('content-encoding', supportedEncoding);
88
55
  newHeaders.delete('content-length');
89
- const compressedBody = response.body.pipeThrough(compressionStream);
56
+ const compressedBody = response.body.pipeThrough(compressionStream, {
57
+ signal: request.signal,
58
+ });
90
59
  const compressedResponse = new fetchAPI.Response(compressedBody, {
91
60
  status: response.status,
92
61
  statusText: response.statusText,
@@ -100,33 +69,3 @@ export function useContentEncoding() {
100
69
  },
101
70
  };
102
71
  }
103
- function collectReadableValues(readable) {
104
- const values = [];
105
- readable.on('data', value => values.push(value));
106
- return new Promise((resolve, reject) => {
107
- readable.once('end', () => resolve(values));
108
- readable.once('error', reject);
109
- });
110
- }
111
- async function collectAsyncIterableValues(asyncIterable) {
112
- const values = [];
113
- for await (const value of asyncIterable) {
114
- values.push(value);
115
- }
116
- return values;
117
- }
118
- async function collectReadableStreamValues(readableStream) {
119
- const reader = readableStream.getReader();
120
- const values = [];
121
- while (true) {
122
- const { done, value } = await reader.read();
123
- if (done) {
124
- reader.releaseLock();
125
- break;
126
- }
127
- else if (value) {
128
- values.push(value);
129
- }
130
- }
131
- return values;
132
- }