@whatwg-node/server
Advanced tools
Comparing version 0.9.56 to 0.9.57-alpha-20241123125835-498ddbef3a79038cbe04d8cef8c2cc1d83e7573c
@@ -6,8 +6,6 @@ "use strict"; | ||
/* eslint-disable @typescript-eslint/ban-types */ | ||
const disposablestack_1 = require("@whatwg-node/disposablestack"); | ||
const DefaultFetchAPI = tslib_1.__importStar(require("@whatwg-node/fetch")); | ||
const utils_js_1 = require("./utils.js"); | ||
const uwebsockets_js_1 = require("./uwebsockets.js"); | ||
async function handleWaitUntils(waitUntilPromises) { | ||
await Promise.allSettled(waitUntilPromises); | ||
} | ||
// Required for envs like nextjs edge runtime | ||
@@ -33,2 +31,32 @@ function isRequestAccessible(serverContext) { | ||
const onResponseHooks = []; | ||
const waitUntilPromises = new Set(); | ||
const disposableStack = new disposablestack_1.AsyncDisposableStack(); | ||
const signals = new Set(); | ||
function registerSignal(signal) { | ||
signals.add(signal); | ||
signal.addEventListener('abort', () => { | ||
signals.delete(signal); | ||
}); | ||
} | ||
disposableStack.defer(() => { | ||
for (const signal of signals) { | ||
signal.sendAbort(); | ||
} | ||
}); | ||
function handleWaitUntils() { | ||
return Promise.allSettled(waitUntilPromises).then(() => { }, () => { }); | ||
} | ||
disposableStack.defer(handleWaitUntils); | ||
function waitUntil(promiseLike) { | ||
// If it is a Node.js environment, we should register the disposable stack to handle process termination events | ||
if (globalThis.process) { | ||
(0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(disposableStack); | ||
} | ||
waitUntilPromises.add(promiseLike.then(() => { | ||
waitUntilPromises.delete(promiseLike); | ||
}, err => { | ||
console.error(`Unexpected error while waiting: ${err.message || err}`); | ||
waitUntilPromises.delete(promiseLike); | ||
})); | ||
} | ||
if (options?.plugins != null) { | ||
@@ -114,3 +142,3 @@ for (const plugin of options.plugins) { | ||
const serverContext = ctx.length > 1 ? (0, utils_js_1.completeAssign)(...ctx) : ctx[0] || {}; | ||
const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI); | ||
const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI, registerSignal); | ||
return handleRequest(request, serverContext); | ||
@@ -124,9 +152,6 @@ } | ||
function requestListener(nodeRequest, nodeResponse, ...ctx) { | ||
const waitUntilPromises = []; | ||
const defaultServerContext = { | ||
req: nodeRequest, | ||
res: nodeResponse, | ||
waitUntil(cb) { | ||
waitUntilPromises.push(cb.catch(err => console.error(err))); | ||
}, | ||
waitUntil, | ||
}; | ||
@@ -156,9 +181,6 @@ let response$; | ||
function handleUWS(res, req, ...ctx) { | ||
const waitUntilPromises = []; | ||
const defaultServerContext = { | ||
res, | ||
req, | ||
waitUntil(cb) { | ||
waitUntilPromises.push(cb.catch(err => console.error(err))); | ||
}, | ||
waitUntil, | ||
}; | ||
@@ -170,2 +192,3 @@ const filteredCtxParts = ctx.filter(partCtx => partCtx != null); | ||
const signal = new utils_js_1.ServerAdapterRequestAbortSignal(); | ||
registerSignal(signal); | ||
const originalResEnd = res.end.bind(res); | ||
@@ -231,13 +254,8 @@ let resEnded = false; | ||
const filteredCtxParts = ctx.filter(partCtx => partCtx != null); | ||
let waitUntilPromises; | ||
const serverContext = filteredCtxParts.length > 1 | ||
? (0, utils_js_1.completeAssign)({}, ...filteredCtxParts) | ||
: (0, utils_js_1.isolateObject)(filteredCtxParts[0], filteredCtxParts[0] == null || filteredCtxParts[0].waitUntil == null | ||
? (waitUntilPromises = []) | ||
? waitUntil | ||
: undefined); | ||
const response$ = handleRequest(request, serverContext); | ||
if (waitUntilPromises?.length) { | ||
return handleWaitUntils(waitUntilPromises).then(() => response$); | ||
} | ||
return response$; | ||
return handleRequest(request, serverContext); | ||
} | ||
@@ -295,2 +313,6 @@ const fetchFn = (input, ...maybeCtx) => { | ||
handle: genericRequestHandler, | ||
disposableStack, | ||
[disposablestack_1.DisposableSymbols.asyncDispose]() { | ||
return disposableStack.disposeAsync(); | ||
}, | ||
}; | ||
@@ -297,0 +319,0 @@ const serverAdapter = new Proxy(genericRequestHandler, { |
@@ -22,2 +22,3 @@ "use strict"; | ||
exports.handleResponseDecompression = handleResponseDecompression; | ||
exports.ensureDisposableStackRegisteredForTerminateEvents = ensureDisposableStackRegisteredForTerminateEvents; | ||
function isAsyncIterable(body) { | ||
@@ -105,3 +106,3 @@ return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function'); | ||
exports.nodeRequestResponseMap = new WeakMap(); | ||
function normalizeNodeRequest(nodeRequest, fetchAPI) { | ||
function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) { | ||
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest; | ||
@@ -132,3 +133,5 @@ let fullUrl = buildFullUrl(rawRequest); | ||
if (fetchAPI.Request !== globalThis.Request) { | ||
signal = new ServerAdapterRequestAbortSignal(); | ||
const newSignal = new ServerAdapterRequestAbortSignal(); | ||
registerSignal?.(newSignal); | ||
signal = newSignal; | ||
sendAbortSignal = () => signal.sendAbort(); | ||
@@ -446,17 +449,13 @@ } | ||
} | ||
function isolateObject(originalCtx, waitUntilPromises) { | ||
function isolateObject(originalCtx, waitUntilFn) { | ||
if (originalCtx == null) { | ||
if (waitUntilPromises == null) { | ||
if (waitUntilFn == null) { | ||
return {}; | ||
} | ||
return { | ||
waitUntil(promise) { | ||
waitUntilPromises.push(promise.catch(err => console.error(err))); | ||
}, | ||
waitUntil: waitUntilFn, | ||
}; | ||
} | ||
return completeAssign(Object.create(originalCtx), { | ||
waitUntil(promise) { | ||
waitUntilPromises?.push(promise.catch(err => console.error(err))); | ||
}, | ||
waitUntil: waitUntilFn, | ||
}, originalCtx); | ||
@@ -555,1 +554,28 @@ } | ||
} | ||
const terminateEvents = ['SIGINT', 'SIGTERM', 'exit']; | ||
const disposableStacks = new Set(); | ||
let eventListenerRegistered = false; | ||
function ensureEventListenerForDisposableStacks() { | ||
if (eventListenerRegistered) { | ||
return; | ||
} | ||
eventListenerRegistered = true; | ||
for (const event of terminateEvents) { | ||
globalThis.process.once(event, function terminateHandler() { | ||
return Promise.allSettled([...disposableStacks].map(stack => stack.disposeAsync().catch(e => { | ||
console.error('Error while disposing:', e); | ||
}))); | ||
}); | ||
} | ||
} | ||
function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) { | ||
if (globalThis.process) { | ||
ensureEventListenerForDisposableStacks(); | ||
if (!disposableStacks.has(disposableStack)) { | ||
disposableStacks.add(disposableStack); | ||
disposableStack.defer(() => { | ||
disposableStacks.delete(disposableStack); | ||
}); | ||
} | ||
} | ||
} |
/* eslint-disable @typescript-eslint/ban-types */ | ||
import { AsyncDisposableStack, DisposableSymbols } from '@whatwg-node/disposablestack'; | ||
import * as DefaultFetchAPI from '@whatwg-node/fetch'; | ||
import { completeAssign, handleAbortSignalAndPromiseResponse, handleErrorFromRequestHandler, isFetchEvent, isNodeRequest, isolateObject, isPromise, isRequestInit, isServerResponse, iterateAsyncVoid, nodeRequestResponseMap, normalizeNodeRequest, sendNodeResponse, ServerAdapterRequestAbortSignal, } from './utils.js'; | ||
import { completeAssign, ensureDisposableStackRegisteredForTerminateEvents, handleAbortSignalAndPromiseResponse, handleErrorFromRequestHandler, isFetchEvent, isNodeRequest, isolateObject, isPromise, isRequestInit, isServerResponse, iterateAsyncVoid, nodeRequestResponseMap, normalizeNodeRequest, sendNodeResponse, ServerAdapterRequestAbortSignal, } from './utils.js'; | ||
import { getRequestFromUWSRequest, isUWSResponse, sendResponseToUwsOpts, } from './uwebsockets.js'; | ||
async function handleWaitUntils(waitUntilPromises) { | ||
await Promise.allSettled(waitUntilPromises); | ||
} | ||
// Required for envs like nextjs edge runtime | ||
@@ -28,2 +26,32 @@ function isRequestAccessible(serverContext) { | ||
const onResponseHooks = []; | ||
const waitUntilPromises = new Set(); | ||
const disposableStack = new AsyncDisposableStack(); | ||
const signals = new Set(); | ||
function registerSignal(signal) { | ||
signals.add(signal); | ||
signal.addEventListener('abort', () => { | ||
signals.delete(signal); | ||
}); | ||
} | ||
disposableStack.defer(() => { | ||
for (const signal of signals) { | ||
signal.sendAbort(); | ||
} | ||
}); | ||
function handleWaitUntils() { | ||
return Promise.allSettled(waitUntilPromises).then(() => { }, () => { }); | ||
} | ||
disposableStack.defer(handleWaitUntils); | ||
function waitUntil(promiseLike) { | ||
// If it is a Node.js environment, we should register the disposable stack to handle process termination events | ||
if (globalThis.process) { | ||
ensureDisposableStackRegisteredForTerminateEvents(disposableStack); | ||
} | ||
waitUntilPromises.add(promiseLike.then(() => { | ||
waitUntilPromises.delete(promiseLike); | ||
}, err => { | ||
console.error(`Unexpected error while waiting: ${err.message || err}`); | ||
waitUntilPromises.delete(promiseLike); | ||
})); | ||
} | ||
if (options?.plugins != null) { | ||
@@ -109,3 +137,3 @@ for (const plugin of options.plugins) { | ||
const serverContext = ctx.length > 1 ? completeAssign(...ctx) : ctx[0] || {}; | ||
const request = normalizeNodeRequest(nodeRequest, fetchAPI); | ||
const request = normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal); | ||
return handleRequest(request, serverContext); | ||
@@ -119,9 +147,6 @@ } | ||
function requestListener(nodeRequest, nodeResponse, ...ctx) { | ||
const waitUntilPromises = []; | ||
const defaultServerContext = { | ||
req: nodeRequest, | ||
res: nodeResponse, | ||
waitUntil(cb) { | ||
waitUntilPromises.push(cb.catch(err => console.error(err))); | ||
}, | ||
waitUntil, | ||
}; | ||
@@ -151,9 +176,6 @@ let response$; | ||
function handleUWS(res, req, ...ctx) { | ||
const waitUntilPromises = []; | ||
const defaultServerContext = { | ||
res, | ||
req, | ||
waitUntil(cb) { | ||
waitUntilPromises.push(cb.catch(err => console.error(err))); | ||
}, | ||
waitUntil, | ||
}; | ||
@@ -165,2 +187,3 @@ const filteredCtxParts = ctx.filter(partCtx => partCtx != null); | ||
const signal = new ServerAdapterRequestAbortSignal(); | ||
registerSignal(signal); | ||
const originalResEnd = res.end.bind(res); | ||
@@ -226,13 +249,8 @@ let resEnded = false; | ||
const filteredCtxParts = ctx.filter(partCtx => partCtx != null); | ||
let waitUntilPromises; | ||
const serverContext = filteredCtxParts.length > 1 | ||
? completeAssign({}, ...filteredCtxParts) | ||
: isolateObject(filteredCtxParts[0], filteredCtxParts[0] == null || filteredCtxParts[0].waitUntil == null | ||
? (waitUntilPromises = []) | ||
? waitUntil | ||
: undefined); | ||
const response$ = handleRequest(request, serverContext); | ||
if (waitUntilPromises?.length) { | ||
return handleWaitUntils(waitUntilPromises).then(() => response$); | ||
} | ||
return response$; | ||
return handleRequest(request, serverContext); | ||
} | ||
@@ -290,2 +308,6 @@ const fetchFn = (input, ...maybeCtx) => { | ||
handle: genericRequestHandler, | ||
disposableStack, | ||
[DisposableSymbols.asyncDispose]() { | ||
return disposableStack.disposeAsync(); | ||
}, | ||
}; | ||
@@ -292,0 +314,0 @@ const serverAdapter = new Proxy(genericRequestHandler, { |
@@ -82,3 +82,3 @@ export function isAsyncIterable(body) { | ||
export const nodeRequestResponseMap = new WeakMap(); | ||
export function normalizeNodeRequest(nodeRequest, fetchAPI) { | ||
export function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) { | ||
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest; | ||
@@ -109,3 +109,5 @@ let fullUrl = buildFullUrl(rawRequest); | ||
if (fetchAPI.Request !== globalThis.Request) { | ||
signal = new ServerAdapterRequestAbortSignal(); | ||
const newSignal = new ServerAdapterRequestAbortSignal(); | ||
registerSignal?.(newSignal); | ||
signal = newSignal; | ||
sendAbortSignal = () => signal.sendAbort(); | ||
@@ -423,17 +425,13 @@ } | ||
} | ||
export function isolateObject(originalCtx, waitUntilPromises) { | ||
export function isolateObject(originalCtx, waitUntilFn) { | ||
if (originalCtx == null) { | ||
if (waitUntilPromises == null) { | ||
if (waitUntilFn == null) { | ||
return {}; | ||
} | ||
return { | ||
waitUntil(promise) { | ||
waitUntilPromises.push(promise.catch(err => console.error(err))); | ||
}, | ||
waitUntil: waitUntilFn, | ||
}; | ||
} | ||
return completeAssign(Object.create(originalCtx), { | ||
waitUntil(promise) { | ||
waitUntilPromises?.push(promise.catch(err => console.error(err))); | ||
}, | ||
waitUntil: waitUntilFn, | ||
}, originalCtx); | ||
@@ -532,1 +530,28 @@ } | ||
} | ||
const terminateEvents = ['SIGINT', 'SIGTERM', 'exit']; | ||
const disposableStacks = new Set(); | ||
let eventListenerRegistered = false; | ||
function ensureEventListenerForDisposableStacks() { | ||
if (eventListenerRegistered) { | ||
return; | ||
} | ||
eventListenerRegistered = true; | ||
for (const event of terminateEvents) { | ||
globalThis.process.once(event, function terminateHandler() { | ||
return Promise.allSettled([...disposableStacks].map(stack => stack.disposeAsync().catch(e => { | ||
console.error('Error while disposing:', e); | ||
}))); | ||
}); | ||
} | ||
} | ||
export function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) { | ||
if (globalThis.process) { | ||
ensureEventListenerForDisposableStacks(); | ||
if (!disposableStacks.has(disposableStack)) { | ||
disposableStacks.add(disposableStack); | ||
disposableStack.defer(() => { | ||
disposableStacks.delete(disposableStack); | ||
}); | ||
} | ||
} | ||
} |
{ | ||
"name": "@whatwg-node/server", | ||
"version": "0.9.56", | ||
"version": "0.9.57-alpha-20241123125835-498ddbef3a79038cbe04d8cef8c2cc1d83e7573c", | ||
"description": "Fetch API compliant HTTP Server adapter", | ||
"sideEffects": false, | ||
"dependencies": { | ||
"@whatwg-node/disposablestack": "^0.0.5", | ||
"@whatwg-node/fetch": "^0.10.0", | ||
@@ -8,0 +9,0 @@ "tslib": "^2.6.3" |
@@ -16,3 +16,3 @@ import type { RequestListener } from 'http'; | ||
} | ||
export interface ServerAdapterObject<TServerContext> extends EventListenerObject { | ||
export interface ServerAdapterObject<TServerContext> extends EventListenerObject, AsyncDisposable { | ||
/** | ||
@@ -60,2 +60,3 @@ * A basic request listener that takes a `Request` with the server context and returns a `Response`. | ||
} & Partial<TServerContext & ServerAdapterInitialContext>, ...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]): Promise<Response> | Response; | ||
disposableStack: AsyncDisposableStack; | ||
} | ||
@@ -62,0 +63,0 @@ export interface RequestLike { |
@@ -34,3 +34,3 @@ import type { IncomingMessage, ServerResponse } from 'http'; | ||
export declare const nodeRequestResponseMap: WeakMap<NodeRequest, NodeResponse>; | ||
export declare function normalizeNodeRequest(nodeRequest: NodeRequest, fetchAPI: FetchAPI): Request; | ||
export declare function normalizeNodeRequest(nodeRequest: NodeRequest, fetchAPI: FetchAPI, registerSignal?: (signal: ServerAdapterRequestAbortSignal) => void): Request; | ||
export declare function isReadable(stream: any): stream is Readable; | ||
@@ -47,3 +47,3 @@ export declare function isNodeRequest(request: any): request is NodeRequest; | ||
export declare function handleErrorFromRequestHandler(error: any, ResponseCtor: typeof Response): Response; | ||
export declare function isolateObject<TIsolatedObject extends object>(originalCtx: TIsolatedObject, waitUntilPromises?: Promise<unknown>[]): TIsolatedObject; | ||
export declare function isolateObject<TIsolatedObject extends object>(originalCtx: TIsolatedObject, waitUntilFn?: (promiseLike: PromiseLike<unknown>) => void): TIsolatedObject; | ||
export interface DeferredPromise<T = void> { | ||
@@ -59,1 +59,2 @@ promise: Promise<T>; | ||
export declare function handleResponseDecompression(response: Response, fetchAPI: FetchAPI): Response; | ||
export declare function ensureDisposableStackRegisteredForTerminateEvents(disposableStack: AsyncDisposableStack): void; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
152074
3241
3