@whatwg-node/server 0.10.0-alpha-20240726141316-c6ce93b3598457ebe73b3b725986723af8f5e609 → 0.10.0-alpha-20241125124208-52a1220ca8b2a3b86a8338c8f96b80dde432e1bb

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,37 @@ 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
+ disposableStack.defer(() => {
45
+ if (waitUntilPromises.size > 0) {
46
+ return Promise.allSettled(waitUntilPromises).then(() => { }, () => { });
47
+ }
48
+ });
49
+ 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
+ (0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(disposableStack);
53
+ }
54
+ waitUntilPromises.add(promiseLike.then(() => {
55
+ waitUntilPromises.delete(promiseLike);
56
+ }, err => {
57
+ console.error(`Unexpected error while waiting: ${err.message || err}`);
58
+ waitUntilPromises.delete(promiseLike);
59
+ }));
60
+ }
32
61
  if (options?.plugins != null) {
33
62
  for (const plugin of options.plugins) {
34
63
  if (plugin.onRequest) {
@@ -107,20 +136,21 @@ function createServerAdapter(serverAdapterBaseObject, options) {
107
136
  }
108
137
  : givenHandleRequest;
109
138
  // TODO: Remove this on the next major version
110
- function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
111
- const nodeResponse = nodeResponseOrContainer.raw || nodeResponseOrContainer;
139
+ function handleNodeRequest(nodeRequest, ...ctx) {
112
140
  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);
141
+ const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI, registerSignal);
114
142
  return handleRequest(request, serverContext);
115
143
  }
144
+ function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
145
+ const nodeResponse = nodeResponseOrContainer.raw || nodeResponseOrContainer;
146
+ utils_js_1.nodeRequestResponseMap.set(nodeRequest, nodeResponse);
147
+ return handleNodeRequest(nodeRequest, ...ctx);
148
+ }
116
149
  function requestListener(nodeRequest, nodeResponse, ...ctx) {
117
- const waitUntilPromises = [];
118
150
  const defaultServerContext = {
119
151
  req: nodeRequest,
120
152
  res: nodeResponse,
121
- waitUntil(cb) {
122
- waitUntilPromises.push(cb.catch(err => console.error(err)));
123
- },
153
+ waitUntil,
124
154
  };
125
155
  let response$;
126
156
  try {
@@ -145,19 +175,17 @@ function createServerAdapter(serverAdapterBaseObject, options) {
145
175
  }
146
176
  }
147
177
  function handleUWS(res, req, ...ctx) {
148
- const waitUntilPromises = [];
149
178
  const defaultServerContext = {
150
179
  res,
151
180
  req,
152
- waitUntil(cb) {
153
- waitUntilPromises.push(cb.catch(err => console.error(err)));
154
- },
181
+ waitUntil,
155
182
  };
156
183
  const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
157
184
  const serverContext = filteredCtxParts.length > 0
158
185
  ? (0, utils_js_1.completeAssign)(defaultServerContext, ...ctx)
159
186
  : defaultServerContext;
160
187
  const signal = new utils_js_1.ServerAdapterRequestAbortSignal();
188
+ registerSignal(signal);
161
189
  const originalResEnd = res.end.bind(res);
162
190
  let resEnded = false;
163
191
  res.end = function (data) {
@@ -189,7 +217,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
189
217
  .catch((e) => (0, utils_js_1.handleErrorFromRequestHandler)(e, fetchAPI.Response))
190
218
  .then(response => {
191
219
  if (!signal.aborted && !resEnded) {
192
- return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal);
220
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal, fetchAPI);
193
221
  }
194
222
  })
195
223
  .catch(err => {
@@ -198,7 +226,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
198
226
  }
199
227
  try {
200
228
  if (!signal.aborted && !resEnded) {
201
- return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal);
229
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal, fetchAPI);
202
230
  }
203
231
  }
204
232
  catch (err) {
@@ -218,17 +246,12 @@ function createServerAdapter(serverAdapterBaseObject, options) {
218
246
  }
219
247
  function handleRequestWithWaitUntil(request, ...ctx) {
220
248
  const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
221
- let waitUntilPromises;
222
249
  const serverContext = filteredCtxParts.length > 1
223
250
  ? (0, utils_js_1.completeAssign)({}, ...filteredCtxParts)
224
251
  : (0, utils_js_1.isolateObject)(filteredCtxParts[0], filteredCtxParts[0] == null || filteredCtxParts[0].waitUntil == null
225
- ? (waitUntilPromises = [])
252
+ ? waitUntil
226
253
  : undefined);
227
- const response$ = handleRequest(request, serverContext);
228
- if (waitUntilPromises?.length) {
229
- return handleWaitUntils(waitUntilPromises).then(() => response$);
230
- }
231
- return response$;
254
+ return handleRequest(request, serverContext);
232
255
  }
233
256
  const fetchFn = (input, ...maybeCtx) => {
234
257
  if (typeof input === 'string' || 'href' in input) {
@@ -275,11 +298,25 @@ function createServerAdapter(serverAdapterBaseObject, options) {
275
298
  const adapterObj = {
276
299
  handleRequest: handleRequestWithWaitUntil,
277
300
  fetch: fetchFn,
301
+ handleNodeRequest,
278
302
  handleNodeRequestAndResponse,
279
303
  requestListener,
280
304
  handleEvent,
281
305
  handleUWS,
282
306
  handle: genericRequestHandler,
307
+ disposableStack,
308
+ [disposablestack_1.DisposableSymbols.asyncDispose]() {
309
+ if (!disposableStack.disposed) {
310
+ return disposableStack.disposeAsync();
311
+ }
312
+ return (0, uwebsockets_js_1.fakePromise)(undefined);
313
+ },
314
+ dispose() {
315
+ if (!disposableStack.disposed) {
316
+ return disposableStack.disposeAsync();
317
+ }
318
+ return (0, uwebsockets_js_1.fakePromise)(undefined);
319
+ },
283
320
  };
284
321
  const serverAdapter = new Proxy(genericRequestHandler, {
285
322
  // 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,31 @@ 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.disposed &&
561
+ stack.disposeAsync().catch(e => {
562
+ console.error('Error while disposing:', e);
563
+ })));
564
+ });
565
+ }
566
+ }
567
+ function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) {
568
+ if (globalThis.process) {
569
+ ensureEventListenerForDisposableStacks();
570
+ if (!disposableStacks.has(disposableStack)) {
571
+ disposableStacks.add(disposableStack);
572
+ disposableStack.defer(() => {
573
+ disposableStacks.delete(disposableStack);
574
+ });
575
+ }
576
+ }
577
+ }