@whatwg-node/server 0.9.57-alpha-20241123125835-498ddbef3a79038cbe04d8cef8c2cc1d83e7573c → 0.9.57-alpha-20241125145154-b2c748560e2a94065f9a25e55132e073f78bea40

Sign up to get free protection for your applications and to get access to all the features.
@@ -41,29 +41,41 @@ function createServerAdapter(serverAdapterBaseObject, options) {
41
41
  signal.sendAbort();
42
42
  }
43
43
  });
44
- function handleWaitUntils() {
45
- return Promise.allSettled(waitUntilPromises).then(() => { }, () => { });
46
- }
47
- disposableStack.defer(handleWaitUntils);
44
+ disposableStack.defer(() => {
45
+ if (waitUntilPromises.size > 0) {
46
+ return Promise.allSettled(waitUntilPromises).then(() => {
47
+ waitUntilPromises.clear();
48
+ }, () => {
49
+ waitUntilPromises.clear();
50
+ });
51
+ }
52
+ });
48
53
  function waitUntil(promiseLike) {
49
54
  // If it is a Node.js environment, we should register the disposable stack to handle process termination events
50
55
  if (globalThis.process) {
51
56
  (0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(disposableStack);
52
57
  }
53
- waitUntilPromises.add(promiseLike.then(() => {
58
+ waitUntilPromises.add(promiseLike);
59
+ promiseLike.then(() => {
54
60
  waitUntilPromises.delete(promiseLike);
55
61
  }, err => {
56
62
  console.error(`Unexpected error while waiting: ${err.message || err}`);
57
63
  waitUntilPromises.delete(promiseLike);
58
- }));
64
+ });
59
65
  }
60
66
  if (options?.plugins != null) {
61
67
  for (const plugin of options.plugins) {
62
- if (plugin.onRequest) {
63
- onRequestHooks.push(plugin.onRequest);
64
- }
65
- if (plugin.onResponse) {
66
- onResponseHooks.push(plugin.onResponse);
68
+ if (plugin != null) {
69
+ if (plugin.onRequest) {
70
+ onRequestHooks.push(plugin.onRequest);
71
+ }
72
+ if (plugin.onResponse) {
73
+ onResponseHooks.push(plugin.onResponse);
74
+ }
75
+ const disposeFn = plugin[disposablestack_1.DisposableSymbols.asyncDispose] || plugin[disposablestack_1.DisposableSymbols.dispose];
76
+ if (disposeFn != null) {
77
+ disposableStack.defer(disposeFn);
78
+ }
67
79
  }
68
80
  }
69
81
  }
@@ -74,12 +86,13 @@ function createServerAdapter(serverAdapterBaseObject, options) {
74
86
  if (onRequestHooks.length === 0) {
75
87
  return handleEarlyResponse();
76
88
  }
77
- let url = new Proxy(EMPTY_OBJECT, {
78
- get(_target, prop, _receiver) {
79
- url = new fetchAPI.URL(request.url, 'http://localhost');
80
- return Reflect.get(url, prop, url);
81
- },
82
- });
89
+ let url = request['parsedUrl'] ||
90
+ new Proxy(EMPTY_OBJECT, {
91
+ get(_target, prop, _receiver) {
92
+ url = new fetchAPI.URL(request.url, 'http://localhost');
93
+ return Reflect.get(url, prop, url);
94
+ },
95
+ });
83
96
  const onRequestHooksIteration$ = (0, utils_js_1.iterateAsyncVoid)(onRequestHooks, (onRequestHook, stopEarly) => onRequestHook({
84
97
  request,
85
98
  setRequest(newRequest) {
@@ -305,7 +318,16 @@ function createServerAdapter(serverAdapterBaseObject, options) {
305
318
  handle: genericRequestHandler,
306
319
  disposableStack,
307
320
  [disposablestack_1.DisposableSymbols.asyncDispose]() {
308
- return disposableStack.disposeAsync();
321
+ if (!disposableStack.disposed) {
322
+ return disposableStack.disposeAsync();
323
+ }
324
+ return (0, uwebsockets_js_1.fakePromise)(undefined);
325
+ },
326
+ dispose() {
327
+ if (!disposableStack.disposed) {
328
+ return disposableStack.disposeAsync();
329
+ }
330
+ return (0, uwebsockets_js_1.fakePromise)(undefined);
309
331
  },
310
332
  };
311
333
  const serverAdapter = new Proxy(genericRequestHandler, {
@@ -47,7 +47,6 @@ function useContentEncoding() {
47
47
  }
48
48
  },
49
49
  onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
50
- const waitUntil = serverContext.waitUntil?.bind(serverContext) || (() => { });
51
50
  // Hack for avoiding to create whatwg-node to create a readable stream until it's needed
52
51
  if (response['bodyInit'] || response.body) {
53
52
  const encodings = encodingMap.get(request);
@@ -61,8 +60,10 @@ function useContentEncoding() {
61
60
  const bufOfRes = response._buffer;
62
61
  if (bufOfRes) {
63
62
  const writer = compressionStream.writable.getWriter();
64
- waitUntil(writer.write(bufOfRes));
65
- waitUntil(writer.close());
63
+ const write$ = writer.write(bufOfRes);
64
+ serverContext.waitUntil?.(write$);
65
+ const close$ = writer.close();
66
+ serverContext.waitUntil?.(close$);
66
67
  const uint8Arrays$ = (0, utils_js_1.isReadable)(compressionStream.readable['readable'])
67
68
  ? collectReadableValues(compressionStream.readable['readable'])
68
69
  : (0, utils_js_1.isAsyncIterable)(compressionStream.readable)
@@ -80,7 +81,8 @@ function useContentEncoding() {
80
81
  });
81
82
  utils_js_1.decompressedResponseMap.set(compressedResponse, response);
82
83
  setResponse(compressedResponse);
83
- waitUntil(compressionStream.writable.close());
84
+ const close$ = compressionStream.writable.close();
85
+ serverContext.waitUntil?.(close$);
84
86
  });
85
87
  }
86
88
  }
package/cjs/utils.js CHANGED
@@ -557,9 +557,10 @@ function ensureEventListenerForDisposableStacks() {
557
557
  eventListenerRegistered = true;
558
558
  for (const event of terminateEvents) {
559
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
- })));
560
+ return Promise.allSettled([...disposableStacks].map(stack => !stack.disposed &&
561
+ stack.disposeAsync().catch(e => {
562
+ console.error('Error while disposing:', e);
563
+ })));
563
564
  });
564
565
  }
565
566
  }
@@ -2,7 +2,7 @@
2
2
  import { AsyncDisposableStack, DisposableSymbols } from '@whatwg-node/disposablestack';
3
3
  import * as DefaultFetchAPI from '@whatwg-node/fetch';
4
4
  import { completeAssign, ensureDisposableStackRegisteredForTerminateEvents, handleAbortSignalAndPromiseResponse, handleErrorFromRequestHandler, isFetchEvent, isNodeRequest, isolateObject, isPromise, isRequestInit, isServerResponse, iterateAsyncVoid, nodeRequestResponseMap, normalizeNodeRequest, sendNodeResponse, ServerAdapterRequestAbortSignal, } from './utils.js';
5
- import { getRequestFromUWSRequest, isUWSResponse, sendResponseToUwsOpts, } from './uwebsockets.js';
5
+ import { fakePromise, getRequestFromUWSRequest, isUWSResponse, sendResponseToUwsOpts, } from './uwebsockets.js';
6
6
  // Required for envs like nextjs edge runtime
7
7
  function isRequestAccessible(serverContext) {
8
8
  try {
@@ -37,29 +37,41 @@ function createServerAdapter(serverAdapterBaseObject, options) {
37
37
  signal.sendAbort();
38
38
  }
39
39
  });
40
- function handleWaitUntils() {
41
- return Promise.allSettled(waitUntilPromises).then(() => { }, () => { });
42
- }
43
- disposableStack.defer(handleWaitUntils);
40
+ disposableStack.defer(() => {
41
+ if (waitUntilPromises.size > 0) {
42
+ return Promise.allSettled(waitUntilPromises).then(() => {
43
+ waitUntilPromises.clear();
44
+ }, () => {
45
+ waitUntilPromises.clear();
46
+ });
47
+ }
48
+ });
44
49
  function waitUntil(promiseLike) {
45
50
  // If it is a Node.js environment, we should register the disposable stack to handle process termination events
46
51
  if (globalThis.process) {
47
52
  ensureDisposableStackRegisteredForTerminateEvents(disposableStack);
48
53
  }
49
- waitUntilPromises.add(promiseLike.then(() => {
54
+ waitUntilPromises.add(promiseLike);
55
+ promiseLike.then(() => {
50
56
  waitUntilPromises.delete(promiseLike);
51
57
  }, err => {
52
58
  console.error(`Unexpected error while waiting: ${err.message || err}`);
53
59
  waitUntilPromises.delete(promiseLike);
54
- }));
60
+ });
55
61
  }
56
62
  if (options?.plugins != null) {
57
63
  for (const plugin of options.plugins) {
58
- if (plugin.onRequest) {
59
- onRequestHooks.push(plugin.onRequest);
60
- }
61
- if (plugin.onResponse) {
62
- onResponseHooks.push(plugin.onResponse);
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
+ }
63
75
  }
64
76
  }
65
77
  }
@@ -70,12 +82,13 @@ function createServerAdapter(serverAdapterBaseObject, options) {
70
82
  if (onRequestHooks.length === 0) {
71
83
  return handleEarlyResponse();
72
84
  }
73
- let url = new Proxy(EMPTY_OBJECT, {
74
- get(_target, prop, _receiver) {
75
- url = new fetchAPI.URL(request.url, 'http://localhost');
76
- return Reflect.get(url, prop, url);
77
- },
78
- });
85
+ let url = request['parsedUrl'] ||
86
+ new Proxy(EMPTY_OBJECT, {
87
+ get(_target, prop, _receiver) {
88
+ url = new fetchAPI.URL(request.url, 'http://localhost');
89
+ return Reflect.get(url, prop, url);
90
+ },
91
+ });
79
92
  const onRequestHooksIteration$ = iterateAsyncVoid(onRequestHooks, (onRequestHook, stopEarly) => onRequestHook({
80
93
  request,
81
94
  setRequest(newRequest) {
@@ -301,7 +314,16 @@ function createServerAdapter(serverAdapterBaseObject, options) {
301
314
  handle: genericRequestHandler,
302
315
  disposableStack,
303
316
  [DisposableSymbols.asyncDispose]() {
304
- return disposableStack.disposeAsync();
317
+ if (!disposableStack.disposed) {
318
+ return disposableStack.disposeAsync();
319
+ }
320
+ return fakePromise(undefined);
321
+ },
322
+ dispose() {
323
+ if (!disposableStack.disposed) {
324
+ return disposableStack.disposeAsync();
325
+ }
326
+ return fakePromise(undefined);
305
327
  },
306
328
  };
307
329
  const serverAdapter = new Proxy(genericRequestHandler, {
@@ -44,7 +44,6 @@ export function useContentEncoding() {
44
44
  }
45
45
  },
46
46
  onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
47
- const waitUntil = serverContext.waitUntil?.bind(serverContext) || (() => { });
48
47
  // Hack for avoiding to create whatwg-node to create a readable stream until it's needed
49
48
  if (response['bodyInit'] || response.body) {
50
49
  const encodings = encodingMap.get(request);
@@ -58,8 +57,10 @@ export function useContentEncoding() {
58
57
  const bufOfRes = response._buffer;
59
58
  if (bufOfRes) {
60
59
  const writer = compressionStream.writable.getWriter();
61
- waitUntil(writer.write(bufOfRes));
62
- waitUntil(writer.close());
60
+ const write$ = writer.write(bufOfRes);
61
+ serverContext.waitUntil?.(write$);
62
+ const close$ = writer.close();
63
+ serverContext.waitUntil?.(close$);
63
64
  const uint8Arrays$ = isReadable(compressionStream.readable['readable'])
64
65
  ? collectReadableValues(compressionStream.readable['readable'])
65
66
  : isAsyncIterable(compressionStream.readable)
@@ -77,7 +78,8 @@ export function useContentEncoding() {
77
78
  });
78
79
  decompressedResponseMap.set(compressedResponse, response);
79
80
  setResponse(compressedResponse);
80
- waitUntil(compressionStream.writable.close());
81
+ const close$ = compressionStream.writable.close();
82
+ serverContext.waitUntil?.(close$);
81
83
  });
82
84
  }
83
85
  }
package/esm/utils.js CHANGED
@@ -534,9 +534,10 @@ function ensureEventListenerForDisposableStacks() {
534
534
  eventListenerRegistered = true;
535
535
  for (const event of terminateEvents) {
536
536
  globalThis.process.once(event, function terminateHandler() {
537
- return Promise.allSettled([...disposableStacks].map(stack => stack.disposeAsync().catch(e => {
538
- console.error('Error while disposing:', e);
539
- })));
537
+ return Promise.allSettled([...disposableStacks].map(stack => !stack.disposed &&
538
+ stack.disposeAsync().catch(e => {
539
+ console.error('Error while disposing:', e);
540
+ })));
540
541
  });
541
542
  }
542
543
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatwg-node/server",
3
- "version": "0.9.57-alpha-20241123125835-498ddbef3a79038cbe04d8cef8c2cc1d83e7573c",
3
+ "version": "0.9.57-alpha-20241125145154-b2c748560e2a94065f9a25e55132e073f78bea40",
4
4
  "description": "Fetch API compliant HTTP Server adapter",
5
5
  "sideEffects": false,
6
6
  "dependencies": {
@@ -1,8 +1,10 @@
1
1
  import { FetchAPI, ServerAdapterRequestHandler, type ServerAdapterInitialContext } from '../types.cjs';
2
- export interface ServerAdapterPlugin<TServerContext = {}> {
2
+ export type ServerAdapterPlugin<TServerContext = {}> = {
3
3
  onRequest?: OnRequestHook<TServerContext & ServerAdapterInitialContext>;
4
4
  onResponse?: OnResponseHook<TServerContext & ServerAdapterInitialContext>;
5
- }
5
+ [Symbol.dispose]?: () => void;
6
+ [Symbol.asyncDispose]?: () => PromiseLike<void> | void;
7
+ } | undefined;
6
8
  export type OnRequestHook<TServerContext> = (payload: OnRequestEventPayload<TServerContext>) => Promise<void> | void;
7
9
  export interface OnRequestEventPayload<TServerContext> {
8
10
  request: Request;
@@ -1,8 +1,10 @@
1
1
  import { FetchAPI, ServerAdapterRequestHandler, type ServerAdapterInitialContext } from '../types.js';
2
- export interface ServerAdapterPlugin<TServerContext = {}> {
2
+ export type ServerAdapterPlugin<TServerContext = {}> = {
3
3
  onRequest?: OnRequestHook<TServerContext & ServerAdapterInitialContext>;
4
4
  onResponse?: OnResponseHook<TServerContext & ServerAdapterInitialContext>;
5
- }
5
+ [Symbol.dispose]?: () => void;
6
+ [Symbol.asyncDispose]?: () => PromiseLike<void> | void;
7
+ } | undefined;
6
8
  export type OnRequestHook<TServerContext> = (payload: OnRequestEventPayload<TServerContext>) => Promise<void> | void;
7
9
  export interface OnRequestEventPayload<TServerContext> {
8
10
  request: Request;
@@ -57,6 +57,7 @@ export interface ServerAdapterObject<TServerContext> extends EventListenerObject
57
57
  request: Request;
58
58
  } & Partial<TServerContext & ServerAdapterInitialContext>, ...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]): Promise<Response> | Response;
59
59
  disposableStack: AsyncDisposableStack;
60
+ dispose(): Promise<void> | void;
60
61
  }
61
62
  export interface RequestLike {
62
63
  url: string;
@@ -57,6 +57,7 @@ export interface ServerAdapterObject<TServerContext> extends EventListenerObject
57
57
  request: Request;
58
58
  } & Partial<TServerContext & ServerAdapterInitialContext>, ...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]): Promise<Response> | Response;
59
59
  disposableStack: AsyncDisposableStack;
60
+ dispose(): Promise<void> | void;
60
61
  }
61
62
  export interface RequestLike {
62
63
  url: string;