@whatwg-node/server 0.10.0-alpha-20240726141316-c6ce93b3598457ebe73b3b725986723af8f5e609 → 0.10.0-alpha-20241123133536-975c9068dde45574fcfa26567e4bab96f45d1f85

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createServerAdapter = createServerAdapter;
4
4
  const tslib_1 = require("tslib");
5
5
  /* eslint-disable @typescript-eslint/ban-types */
6
+ const disposablestack_1 = require("@whatwg-node/disposablestack");
6
7
  const DefaultFetchAPI = tslib_1.__importStar(require("@whatwg-node/fetch"));
7
8
  const utils_js_1 = require("./utils.js");
8
9
  const uwebsockets_js_1 = require("./uwebsockets.js");
9
- async function handleWaitUntils(waitUntilPromises) {
10
- await Promise.allSettled(waitUntilPromises);
11
- }
12
10
  // Required for envs like nextjs edge runtime
13
11
  function isRequestAccessible(serverContext) {
14
12
  try {
@@ -29,6 +27,36 @@ function createServerAdapter(serverAdapterBaseObject, options) {
29
27
  : serverAdapterBaseObject.handle;
30
28
  const onRequestHooks = [];
31
29
  const onResponseHooks = [];
30
+ const waitUntilPromises = new Set();
31
+ const disposableStack = new disposablestack_1.AsyncDisposableStack();
32
+ const signals = new Set();
33
+ function registerSignal(signal) {
34
+ signals.add(signal);
35
+ signal.addEventListener('abort', () => {
36
+ signals.delete(signal);
37
+ });
38
+ }
39
+ disposableStack.defer(() => {
40
+ for (const signal of signals) {
41
+ signal.sendAbort();
42
+ }
43
+ });
44
+ function handleWaitUntils() {
45
+ return Promise.allSettled(waitUntilPromises).then(() => { }, () => { });
46
+ }
47
+ disposableStack.defer(handleWaitUntils);
48
+ function waitUntil(promiseLike) {
49
+ // If it is a Node.js environment, we should register the disposable stack to handle process termination events
50
+ if (globalThis.process) {
51
+ (0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(disposableStack);
52
+ }
53
+ waitUntilPromises.add(promiseLike.then(() => {
54
+ waitUntilPromises.delete(promiseLike);
55
+ }, err => {
56
+ console.error(`Unexpected error while waiting: ${err.message || err}`);
57
+ waitUntilPromises.delete(promiseLike);
58
+ }));
59
+ }
32
60
  if (options?.plugins != null) {
33
61
  for (const plugin of options.plugins) {
34
62
  if (plugin.onRequest) {
@@ -107,20 +135,21 @@ function createServerAdapter(serverAdapterBaseObject, options) {
107
135
  }
108
136
  : givenHandleRequest;
109
137
  // TODO: Remove this on the next major version
110
- function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
111
- const nodeResponse = nodeResponseOrContainer.raw || nodeResponseOrContainer;
138
+ function handleNodeRequest(nodeRequest, ...ctx) {
112
139
  const serverContext = ctx.length > 1 ? (0, utils_js_1.completeAssign)(...ctx) : ctx[0] || {};
113
- const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, nodeResponse, fetchAPI.Request);
140
+ const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI, registerSignal);
114
141
  return handleRequest(request, serverContext);
115
142
  }
143
+ function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
144
+ const nodeResponse = nodeResponseOrContainer.raw || nodeResponseOrContainer;
145
+ utils_js_1.nodeRequestResponseMap.set(nodeRequest, nodeResponse);
146
+ return handleNodeRequest(nodeRequest, ...ctx);
147
+ }
116
148
  function requestListener(nodeRequest, nodeResponse, ...ctx) {
117
- const waitUntilPromises = [];
118
149
  const defaultServerContext = {
119
150
  req: nodeRequest,
120
151
  res: nodeResponse,
121
- waitUntil(cb) {
122
- waitUntilPromises.push(cb.catch(err => console.error(err)));
123
- },
152
+ waitUntil,
124
153
  };
125
154
  let response$;
126
155
  try {
@@ -145,19 +174,17 @@ function createServerAdapter(serverAdapterBaseObject, options) {
145
174
  }
146
175
  }
147
176
  function handleUWS(res, req, ...ctx) {
148
- const waitUntilPromises = [];
149
177
  const defaultServerContext = {
150
178
  res,
151
179
  req,
152
- waitUntil(cb) {
153
- waitUntilPromises.push(cb.catch(err => console.error(err)));
154
- },
180
+ waitUntil,
155
181
  };
156
182
  const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
157
183
  const serverContext = filteredCtxParts.length > 0
158
184
  ? (0, utils_js_1.completeAssign)(defaultServerContext, ...ctx)
159
185
  : defaultServerContext;
160
186
  const signal = new utils_js_1.ServerAdapterRequestAbortSignal();
187
+ registerSignal(signal);
161
188
  const originalResEnd = res.end.bind(res);
162
189
  let resEnded = false;
163
190
  res.end = function (data) {
@@ -189,7 +216,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
189
216
  .catch((e) => (0, utils_js_1.handleErrorFromRequestHandler)(e, fetchAPI.Response))
190
217
  .then(response => {
191
218
  if (!signal.aborted && !resEnded) {
192
- return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal);
219
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal, fetchAPI);
193
220
  }
194
221
  })
195
222
  .catch(err => {
@@ -198,7 +225,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
198
225
  }
199
226
  try {
200
227
  if (!signal.aborted && !resEnded) {
201
- return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal);
228
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal, fetchAPI);
202
229
  }
203
230
  }
204
231
  catch (err) {
@@ -218,17 +245,12 @@ function createServerAdapter(serverAdapterBaseObject, options) {
218
245
  }
219
246
  function handleRequestWithWaitUntil(request, ...ctx) {
220
247
  const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
221
- let waitUntilPromises;
222
248
  const serverContext = filteredCtxParts.length > 1
223
249
  ? (0, utils_js_1.completeAssign)({}, ...filteredCtxParts)
224
250
  : (0, utils_js_1.isolateObject)(filteredCtxParts[0], filteredCtxParts[0] == null || filteredCtxParts[0].waitUntil == null
225
- ? (waitUntilPromises = [])
251
+ ? waitUntil
226
252
  : undefined);
227
- const response$ = handleRequest(request, serverContext);
228
- if (waitUntilPromises?.length) {
229
- return handleWaitUntils(waitUntilPromises).then(() => response$);
230
- }
231
- return response$;
253
+ return handleRequest(request, serverContext);
232
254
  }
233
255
  const fetchFn = (input, ...maybeCtx) => {
234
256
  if (typeof input === 'string' || 'href' in input) {
@@ -275,11 +297,19 @@ function createServerAdapter(serverAdapterBaseObject, options) {
275
297
  const adapterObj = {
276
298
  handleRequest: handleRequestWithWaitUntil,
277
299
  fetch: fetchFn,
300
+ handleNodeRequest,
278
301
  handleNodeRequestAndResponse,
279
302
  requestListener,
280
303
  handleEvent,
281
304
  handleUWS,
282
305
  handle: genericRequestHandler,
306
+ disposableStack,
307
+ [disposablestack_1.DisposableSymbols.asyncDispose]() {
308
+ return disposableStack.disposeAsync();
309
+ },
310
+ dispose() {
311
+ return disposableStack.disposeAsync();
312
+ },
283
313
  };
284
314
  const serverAdapter = new Proxy(genericRequestHandler, {
285
315
  // It should have all the attributes of the handler function and the server instance
@@ -21,7 +21,7 @@ function useContentEncoding() {
21
21
  for (const contentEncoding of contentEncodings) {
22
22
  newBody = newBody.pipeThrough(new fetchAPI.DecompressionStream(contentEncoding));
23
23
  }
24
- const newRequest = new fetchAPI.Request(request.url, {
24
+ request = new fetchAPI.Request(request.url, {
25
25
  body: newBody,
26
26
  cache: request.cache,
27
27
  credentials: request.credentials,
@@ -38,7 +38,7 @@ function useContentEncoding() {
38
38
  // @ts-ignore - not in the TS types yet
39
39
  duplex: 'half',
40
40
  });
41
- setRequest(newRequest);
41
+ setRequest(request);
42
42
  }
43
43
  }
44
44
  const acceptEncoding = request.headers.get('accept-encoding');
@@ -46,7 +46,8 @@ function useContentEncoding() {
46
46
  encodingMap.set(request, acceptEncoding.split(','));
47
47
  }
48
48
  },
49
- onResponse({ request, response, setResponse, fetchAPI }) {
49
+ onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
50
+ const waitUntil = serverContext.waitUntil?.bind(serverContext) || (() => { });
50
51
  // Hack for avoiding to create whatwg-node to create a readable stream until it's needed
51
52
  if (response['bodyInit'] || response.body) {
52
53
  const encodings = encodingMap.get(request);
@@ -60,21 +61,15 @@ function useContentEncoding() {
60
61
  const bufOfRes = response._buffer;
61
62
  if (bufOfRes) {
62
63
  const writer = compressionStream.writable.getWriter();
63
- writer.write(bufOfRes);
64
- writer.close();
65
- const reader = compressionStream.readable.getReader();
66
- return Promise.resolve().then(async () => {
67
- const chunks = [];
68
- while (true) {
69
- const { done, value } = await reader.read();
70
- if (done) {
71
- reader.releaseLock();
72
- break;
73
- }
74
- else if (value) {
75
- chunks.push(...value);
76
- }
77
- }
64
+ waitUntil(writer.write(bufOfRes));
65
+ waitUntil(writer.close());
66
+ const uint8Arrays$ = (0, utils_js_1.isReadable)(compressionStream.readable['readable'])
67
+ ? collectReadableValues(compressionStream.readable['readable'])
68
+ : (0, utils_js_1.isAsyncIterable)(compressionStream.readable)
69
+ ? collectAsyncIterableValues(compressionStream.readable)
70
+ : collectReadableStreamValues(compressionStream.readable);
71
+ return uint8Arrays$.then(uint8Arrays => {
72
+ const chunks = uint8Arrays.flatMap(uint8Array => [...uint8Array]);
78
73
  const uint8Array = new Uint8Array(chunks);
79
74
  const newHeaders = new fetchAPI.Headers(response.headers);
80
75
  newHeaders.set('content-encoding', supportedEncoding);
@@ -85,6 +80,7 @@ function useContentEncoding() {
85
80
  });
86
81
  utils_js_1.decompressedResponseMap.set(compressedResponse, response);
87
82
  setResponse(compressedResponse);
83
+ waitUntil(compressionStream.writable.close());
88
84
  });
89
85
  }
90
86
  }
@@ -105,3 +101,33 @@ function useContentEncoding() {
105
101
  },
106
102
  };
107
103
  }
104
+ function collectReadableValues(readable) {
105
+ const values = [];
106
+ readable.on('data', value => values.push(value));
107
+ return new Promise((resolve, reject) => {
108
+ readable.once('end', () => resolve(values));
109
+ readable.once('error', reject);
110
+ });
111
+ }
112
+ async function collectAsyncIterableValues(asyncIterable) {
113
+ const values = [];
114
+ for await (const value of asyncIterable) {
115
+ values.push(value);
116
+ }
117
+ return values;
118
+ }
119
+ async function collectReadableStreamValues(readableStream) {
120
+ const reader = readableStream.getReader();
121
+ const values = [];
122
+ while (true) {
123
+ const { done, value } = await reader.read();
124
+ if (done) {
125
+ reader.releaseLock();
126
+ break;
127
+ }
128
+ else if (value) {
129
+ values.push(value);
130
+ }
131
+ }
132
+ return values;
133
+ }
@@ -24,13 +24,17 @@ function createDefaultErrorResponse(ResponseCtor) {
24
24
  return new ResponseCtor(null, { status: 500 });
25
25
  }
26
26
  class HTTPError extends Error {
27
+ status;
28
+ message;
29
+ headers;
30
+ details;
31
+ name = 'HTTPError';
27
32
  constructor(status = 500, message, headers = {}, details) {
28
33
  super(message);
29
34
  this.status = status;
30
35
  this.message = message;
31
36
  this.headers = headers;
32
37
  this.details = details;
33
- this.name = 'HTTPError';
34
38
  Error.captureStackTrace(this, HTTPError);
35
39
  }
36
40
  }
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.ServerAdapterRequestAbortSignal = void 0;
3
+ exports.decompressedResponseMap = exports.nodeRequestResponseMap = exports.ServerAdapterRequestAbortSignal = void 0;
4
4
  exports.isAsyncIterable = isAsyncIterable;
5
5
  exports.normalizeNodeRequest = normalizeNodeRequest;
6
6
  exports.isReadable = isReadable;
@@ -19,7 +19,7 @@ exports.createDeferredPromise = createDeferredPromise;
19
19
  exports.handleAbortSignalAndPromiseResponse = handleAbortSignalAndPromiseResponse;
20
20
  exports.getSupportedEncodings = getSupportedEncodings;
21
21
  exports.handleResponseDecompression = handleResponseDecompression;
22
- const fetch_1 = require("@whatwg-node/fetch");
22
+ exports.ensureDisposableStackRegisteredForTerminateEvents = ensureDisposableStackRegisteredForTerminateEvents;
23
23
  function isAsyncIterable(body) {
24
24
  return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
25
25
  }
@@ -70,11 +70,9 @@ function isRequestBody(body) {
70
70
  return false;
71
71
  }
72
72
  class ServerAdapterRequestAbortSignal extends EventTarget {
73
- constructor() {
74
- super(...arguments);
75
- this.aborted = false;
76
- this._onabort = null;
77
- }
73
+ aborted = false;
74
+ _onabort = null;
75
+ reason;
78
76
  throwIfAborted() {
79
77
  if (this.aborted) {
80
78
  throw this.reason;
@@ -103,17 +101,20 @@ class ServerAdapterRequestAbortSignal extends EventTarget {
103
101
  }
104
102
  exports.ServerAdapterRequestAbortSignal = ServerAdapterRequestAbortSignal;
105
103
  let bunNodeCompatModeWarned = false;
106
- function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
104
+ exports.nodeRequestResponseMap = new WeakMap();
105
+ function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
107
106
  const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
108
107
  let fullUrl = buildFullUrl(rawRequest);
109
108
  if (nodeRequest.query) {
110
- const url = new fetch_1.URL(fullUrl);
109
+ const url = new fetchAPI.URL(fullUrl);
111
110
  for (const key in nodeRequest.query) {
112
111
  url.searchParams.set(key, nodeRequest.query[key]);
113
112
  }
114
113
  fullUrl = url.toString();
115
114
  }
116
115
  let signal;
116
+ const nodeResponse = exports.nodeRequestResponseMap.get(nodeRequest);
117
+ exports.nodeRequestResponseMap.delete(nodeRequest);
117
118
  let normalizedHeaders = nodeRequest.headers;
118
119
  if (nodeRequest.headers?.[':method']) {
119
120
  normalizedHeaders = {};
@@ -126,8 +127,10 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
126
127
  if (nodeResponse?.once) {
127
128
  let sendAbortSignal;
128
129
  // If ponyfilled
129
- if (RequestCtor !== globalThis.Request) {
130
- signal = new ServerAdapterRequestAbortSignal();
130
+ if (fetchAPI.Request !== globalThis.Request) {
131
+ const newSignal = new ServerAdapterRequestAbortSignal();
132
+ registerSignal?.(newSignal);
133
+ signal = newSignal;
131
134
  sendAbortSignal = () => signal.sendAbort();
132
135
  }
133
136
  else {
@@ -148,7 +151,7 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
148
151
  });
149
152
  }
150
153
  if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
151
- return new RequestCtor(fullUrl, {
154
+ return new fetchAPI.Request(fullUrl, {
152
155
  method: nodeRequest.method,
153
156
  headers: normalizedHeaders,
154
157
  signal,
@@ -163,14 +166,14 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
163
166
  const maybeParsedBody = nodeRequest.body;
164
167
  if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
165
168
  if (isRequestBody(maybeParsedBody)) {
166
- return new RequestCtor(fullUrl, {
169
+ return new fetchAPI.Request(fullUrl, {
167
170
  method: nodeRequest.method,
168
171
  headers: normalizedHeaders,
169
172
  body: maybeParsedBody,
170
173
  signal,
171
174
  });
172
175
  }
173
- const request = new RequestCtor(fullUrl, {
176
+ const request = new fetchAPI.Request(fullUrl, {
174
177
  method: nodeRequest.method,
175
178
  headers: normalizedHeaders,
176
179
  signal,
@@ -198,7 +201,7 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
198
201
  console.warn(`You use Bun Node compatibility mode, which is not recommended!
199
202
  It will affect your performance. Please check our Bun integration recipe, and avoid using 'http' for your server implementation.`);
200
203
  }
201
- return new RequestCtor(fullUrl, {
204
+ return new fetchAPI.Request(fullUrl, {
202
205
  method: nodeRequest.method,
203
206
  headers: normalizedHeaders,
204
207
  duplex: 'half',
@@ -222,7 +225,7 @@ It will affect your performance. Please check our Bun integration recipe, and av
222
225
  });
223
226
  }
224
227
  // perf: instead of spreading the object, we can just pass it as is and it performs better
225
- return new RequestCtor(fullUrl, {
228
+ return new fetchAPI.Request(fullUrl, {
226
229
  method: nodeRequest.method,
227
230
  headers: normalizedHeaders,
228
231
  body: rawRequest,
@@ -260,11 +263,26 @@ function endResponse(serverResponse) {
260
263
  serverResponse.end(null, null, null);
261
264
  }
262
265
  async function sendAsyncIterable(serverResponse, asyncIterable) {
266
+ let closed = false;
267
+ const closeEventListener = () => {
268
+ closed = true;
269
+ };
270
+ serverResponse.once('error', closeEventListener);
271
+ serverResponse.once('close', closeEventListener);
272
+ serverResponse.once('finish', () => {
273
+ serverResponse.removeListener('close', closeEventListener);
274
+ });
263
275
  for await (const chunk of asyncIterable) {
276
+ if (closed) {
277
+ break;
278
+ }
264
279
  if (!serverResponse
265
280
  // @ts-expect-error http and http2 writes are actually compatible
266
281
  .write(chunk)) {
267
- break;
282
+ if (closed) {
283
+ break;
284
+ }
285
+ await new Promise(resolve => serverResponse.once('drain', resolve));
268
286
  }
269
287
  }
270
288
  endResponse(serverResponse);
@@ -275,7 +293,7 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
275
293
  }
276
294
  if (!fetchResponse) {
277
295
  serverResponse.statusCode = 404;
278
- serverResponse.end();
296
+ endResponse(serverResponse);
279
297
  return;
280
298
  }
281
299
  serverResponse.statusCode = fetchResponse.status;
@@ -324,10 +342,31 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
324
342
  fetchBody.pipe(serverResponse);
325
343
  return;
326
344
  }
345
+ if (isReadableStream(fetchBody)) {
346
+ return sendReadableStream(serverResponse, fetchBody);
347
+ }
327
348
  if (isAsyncIterable(fetchBody)) {
328
349
  return sendAsyncIterable(serverResponse, fetchBody);
329
350
  }
330
351
  }
352
+ async function sendReadableStream(serverResponse, readableStream) {
353
+ const reader = readableStream.getReader();
354
+ serverResponse.req.once('error', err => {
355
+ reader.cancel(err);
356
+ });
357
+ while (true) {
358
+ const { done, value } = await reader.read();
359
+ if (done) {
360
+ break;
361
+ }
362
+ if (!serverResponse
363
+ // @ts-expect-error http and http2 writes are actually compatible
364
+ .write(value)) {
365
+ await new Promise(resolve => serverResponse.once('drain', resolve));
366
+ }
367
+ }
368
+ endResponse(serverResponse);
369
+ }
331
370
  function isRequestInit(val) {
332
371
  return (val != null &&
333
372
  typeof val === 'object' &&
@@ -404,77 +443,18 @@ function handleErrorFromRequestHandler(error, ResponseCtor) {
404
443
  status: error.status || 500,
405
444
  });
406
445
  }
407
- function isolateObject(originalCtx, waitUntilPromises) {
446
+ function isolateObject(originalCtx, waitUntilFn) {
408
447
  if (originalCtx == null) {
409
- return {};
410
- }
411
- const extraProps = {};
412
- const deletedProps = new Set();
413
- return new Proxy(originalCtx, {
414
- get(originalCtx, prop) {
415
- if (waitUntilPromises != null && prop === 'waitUntil') {
416
- return function waitUntil(promise) {
417
- waitUntilPromises.push(promise.catch(err => console.error(err)));
418
- };
419
- }
420
- const extraPropVal = extraProps[prop];
421
- if (extraPropVal != null) {
422
- if (typeof extraPropVal === 'function') {
423
- return extraPropVal.bind(extraProps);
424
- }
425
- return extraPropVal;
426
- }
427
- if (deletedProps.has(prop)) {
428
- return undefined;
429
- }
430
- return originalCtx[prop];
431
- },
432
- set(_originalCtx, prop, value) {
433
- extraProps[prop] = value;
434
- return true;
435
- },
436
- has(originalCtx, prop) {
437
- if (waitUntilPromises != null && prop === 'waitUntil') {
438
- return true;
439
- }
440
- if (deletedProps.has(prop)) {
441
- return false;
442
- }
443
- if (prop in extraProps) {
444
- return true;
445
- }
446
- return prop in originalCtx;
447
- },
448
- defineProperty(_originalCtx, prop, descriptor) {
449
- return Reflect.defineProperty(extraProps, prop, descriptor);
450
- },
451
- deleteProperty(_originalCtx, prop) {
452
- if (prop in extraProps) {
453
- return Reflect.deleteProperty(extraProps, prop);
454
- }
455
- deletedProps.add(prop);
456
- return true;
457
- },
458
- ownKeys(originalCtx) {
459
- const extraKeys = Reflect.ownKeys(extraProps);
460
- const originalKeys = Reflect.ownKeys(originalCtx);
461
- const deletedKeys = Array.from(deletedProps);
462
- const allKeys = new Set(extraKeys.concat(originalKeys.filter(keys => !deletedKeys.includes(keys))));
463
- if (waitUntilPromises != null) {
464
- allKeys.add('waitUntil');
465
- }
466
- return Array.from(allKeys);
467
- },
468
- getOwnPropertyDescriptor(originalCtx, prop) {
469
- if (prop in extraProps) {
470
- return Reflect.getOwnPropertyDescriptor(extraProps, prop);
471
- }
472
- if (deletedProps.has(prop)) {
473
- return undefined;
474
- }
475
- return Reflect.getOwnPropertyDescriptor(originalCtx, prop);
476
- },
477
- });
448
+ if (waitUntilFn == null) {
449
+ return {};
450
+ }
451
+ return {
452
+ waitUntil: waitUntilFn,
453
+ };
454
+ }
455
+ return completeAssign(Object.create(originalCtx), {
456
+ waitUntil: waitUntilFn,
457
+ }, originalCtx);
478
458
  }
479
459
  function createDeferredPromise() {
480
460
  let resolveFn;
@@ -516,22 +496,27 @@ function getSupportedEncodings(fetchAPI) {
516
496
  let supportedEncodings = supportedEncodingsByFetchAPI.get(fetchAPI);
517
497
  if (!supportedEncodings) {
518
498
  const possibleEncodings = ['deflate', 'gzip', 'deflate-raw', 'br'];
519
- supportedEncodings = possibleEncodings.filter(encoding => {
520
- // deflate-raw is not supported in Node.js >v20
521
- if (globalThis.process?.version?.startsWith('v2') &&
522
- fetchAPI.DecompressionStream === globalThis.DecompressionStream &&
523
- encoding === 'deflate-raw') {
524
- return false;
525
- }
526
- try {
527
- // eslint-disable-next-line no-new
528
- new fetchAPI.DecompressionStream(encoding);
529
- return true;
530
- }
531
- catch {
532
- return false;
533
- }
534
- });
499
+ if (fetchAPI.DecompressionStream?.['supportedFormats']) {
500
+ supportedEncodings = fetchAPI.DecompressionStream['supportedFormats'];
501
+ }
502
+ else {
503
+ supportedEncodings = possibleEncodings.filter(encoding => {
504
+ // deflate-raw is not supported in Node.js >v20
505
+ if (globalThis.process?.version?.startsWith('v2') &&
506
+ fetchAPI.DecompressionStream === globalThis.DecompressionStream &&
507
+ encoding === 'deflate-raw') {
508
+ return false;
509
+ }
510
+ try {
511
+ // eslint-disable-next-line no-new
512
+ new fetchAPI.DecompressionStream(encoding);
513
+ return true;
514
+ }
515
+ catch {
516
+ return false;
517
+ }
518
+ });
519
+ }
535
520
  supportedEncodingsByFetchAPI.set(fetchAPI, supportedEncodings);
536
521
  }
537
522
  return supportedEncodings;
@@ -562,3 +547,30 @@ function handleResponseDecompression(response, fetchAPI) {
562
547
  }
563
548
  return decompressedResponse;
564
549
  }
550
+ const terminateEvents = ['SIGINT', 'SIGTERM', 'exit'];
551
+ const disposableStacks = new Set();
552
+ let eventListenerRegistered = false;
553
+ function ensureEventListenerForDisposableStacks() {
554
+ if (eventListenerRegistered) {
555
+ return;
556
+ }
557
+ eventListenerRegistered = true;
558
+ for (const event of terminateEvents) {
559
+ globalThis.process.once(event, function terminateHandler() {
560
+ return Promise.allSettled([...disposableStacks].map(stack => stack.disposeAsync().catch(e => {
561
+ console.error('Error while disposing:', e);
562
+ })));
563
+ });
564
+ }
565
+ }
566
+ function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) {
567
+ if (globalThis.process) {
568
+ ensureEventListenerForDisposableStacks();
569
+ if (!disposableStacks.has(disposableStack)) {
570
+ disposableStacks.add(disposableStack);
571
+ disposableStack.defer(() => {
572
+ disposableStacks.delete(disposableStack);
573
+ });
574
+ }
575
+ }
576
+ }