Skip to content

Commit

Permalink
add waitUntil to the context type (#1465)
Browse files Browse the repository at this point in the history
Co-authored-by: Arda TANRIKULU <[email protected]>
  • Loading branch information
EmrysMyrddin and ardatan authored Jul 25, 2024
1 parent ea24fac commit 9f6546f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-pots-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@whatwg-node/server': patch
---

Fix context type to expose the `waitUntil` method.
33 changes: 20 additions & 13 deletions packages/server/src/createServerAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ServerAdapterBaseObject,
ServerAdapterObject,
ServerAdapterRequestHandler,
type ServerAdapterInitialContext,
} from './types.js';
import {
completeAssign,
Expand Down Expand Up @@ -99,8 +100,8 @@ function createServerAdapter<
? serverAdapterBaseObject
: serverAdapterBaseObject.handle;

const onRequestHooks: OnRequestHook<TServerContext>[] = [];
const onResponseHooks: OnResponseHook<TServerContext>[] = [];
const onRequestHooks: OnRequestHook<TServerContext & ServerAdapterInitialContext>[] = [];
const onResponseHooks: OnResponseHook<TServerContext & ServerAdapterInitialContext>[] = [];

if (options?.plugins != null) {
for (const plugin of options.plugins) {
Expand All @@ -113,9 +114,9 @@ function createServerAdapter<
}
}

const handleRequest: ServerAdapterRequestHandler<TServerContext> =
const handleRequest: ServerAdapterRequestHandler<TServerContext & ServerAdapterInitialContext> =
onRequestHooks.length > 0 || onResponseHooks.length > 0
? function handleRequest(request: Request, serverContext: TServerContext) {
? function handleRequest(request, serverContext) {
let requestHandler: ServerAdapterRequestHandler<any> = givenHandleRequest;
let response: Response | undefined;
if (onRequestHooks.length === 0) {
Expand Down Expand Up @@ -154,7 +155,9 @@ function createServerAdapter<
if (onResponseHooks.length === 0) {
return response;
}
const onResponseHookPayload: OnResponseEventPayload<TServerContext> = {
const onResponseHookPayload: OnResponseEventPayload<
TServerContext & ServerAdapterInitialContext
> = {
request,
response,
serverContext,
Expand Down Expand Up @@ -208,34 +211,38 @@ function createServerAdapter<

function requestListener(
nodeRequest: NodeRequest,
serverResponse: NodeResponse,
nodeResponse: NodeResponse,
...ctx: Partial<TServerContext>[]
) {
const waitUntilPromises: Promise<unknown>[] = [];
const defaultServerContext = {
req: nodeRequest,
res: serverResponse,
res: nodeResponse,
waitUntil(cb: Promise<unknown>) {
waitUntilPromises.push(cb.catch(err => console.error(err)));
},
};
nodeRequestResponseMap.set(nodeRequest, serverResponse);
let response$: Response | Promise<Response> | undefined;
try {
response$ = handleNodeRequest(nodeRequest, defaultServerContext as any, ...ctx);
response$ = handleNodeRequestAndResponse(
nodeRequest,
nodeResponse,
defaultServerContext as any,
...ctx,
);
} catch (err: any) {
response$ = handleErrorFromRequestHandler(err, fetchAPI.Response);
}
if (isPromise(response$)) {
return response$
.catch((e: any) => handleErrorFromRequestHandler(e, fetchAPI.Response))
.then(response => sendNodeResponse(response, serverResponse, nodeRequest))
.then(response => sendNodeResponse(response, nodeResponse, nodeRequest))
.catch(err => {
console.error(`Unexpected error while handling request: ${err.message || err}`);
});
}
try {
return sendNodeResponse(response$, serverResponse, nodeRequest);
return sendNodeResponse(response$, nodeResponse, nodeRequest);
} catch (err: any) {
console.error(`Unexpected error while handling request: ${err.message || err}`);
}
Expand Down Expand Up @@ -322,7 +329,7 @@ function createServerAdapter<

function handleRequestWithWaitUntil(request: Request, ...ctx: Partial<TServerContext>[]) {
const filteredCtxParts: any[] = ctx.filter(partCtx => partCtx != null);
let waitUntilPromises: Promise<void>[] | undefined;
let waitUntilPromises: Promise<unknown>[] | undefined;
const serverContext =
filteredCtxParts.length > 1
? completeAssign({}, ...filteredCtxParts)
Expand Down Expand Up @@ -407,7 +414,7 @@ function createServerAdapter<
};

const adapterObj: ServerAdapterObject<TServerContext> = {
handleRequest,
handleRequest: handleRequestWithWaitUntil,
fetch: fetchFn,
handleNodeRequest,
handleNodeRequestAndResponse,
Expand Down
14 changes: 9 additions & 5 deletions packages/server/src/plugins/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { FetchAPI, ServerAdapterRequestHandler } from '../types.js';
import {
FetchAPI,
ServerAdapterRequestHandler,
type ServerAdapterInitialContext,
} from '../types.js';

export interface ServerAdapterPlugin<TServerContext = {}> {
onRequest?: OnRequestHook<TServerContext>;
onResponse?: OnResponseHook<TServerContext>;
onRequest?: OnRequestHook<TServerContext & ServerAdapterInitialContext>;
onResponse?: OnResponseHook<TServerContext & ServerAdapterInitialContext>;
}

export type OnRequestHook<TServerContext> = (
Expand All @@ -12,7 +16,7 @@ export type OnRequestHook<TServerContext> = (
export interface OnRequestEventPayload<TServerContext> {
request: Request;
setRequest(newRequest: Request): void;
serverContext: TServerContext | undefined;
serverContext: TServerContext;
fetchAPI: FetchAPI;
requestHandler: ServerAdapterRequestHandler<TServerContext>;
setRequestHandler(newRequestHandler: ServerAdapterRequestHandler<TServerContext>): void;
Expand All @@ -26,7 +30,7 @@ export type OnResponseHook<TServerContext> = (

export interface OnResponseEventPayload<TServerContext> {
request: Request;
serverContext: TServerContext | undefined;
serverContext: TServerContext;
response: Response;
setResponse(newResponse: Response): void;
fetchAPI: FetchAPI;
Expand Down
7 changes: 3 additions & 4 deletions packages/server/src/plugins/useErrorHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ export function useErrorHandling<TServerContext>(
return {
onRequest({ requestHandler, setRequestHandler, fetchAPI }) {
const errorHandler = onError || createDefaultErrorHandler<TServerContext>(fetchAPI.Response);
setRequestHandler(function handlerWithErrorHandling(
request: Request,
serverContext: TServerContext,
): Promise<Response> | Response {
setRequestHandler(function handlerWithErrorHandling(request, serverContext):
| Promise<Response>
| Response {
try {
const response$ = requestHandler(request, serverContext);
if (isPromise(response$)) {
Expand Down
55 changes: 44 additions & 11 deletions packages/server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export interface ServerAdapterObject<TServerContext> extends EventListenerObject
/**
* A basic request listener that takes a `Request` with the server context and returns a `Response`.
*/
handleRequest: (request: Request, ctx: TServerContext) => Promise<Response> | Response;
handleRequest: (
request: Request,
ctx: TServerContext & Partial<ServerAdapterInitialContext>,
) => Promise<Response> | Response;
/**
* WHATWG Fetch spec compliant `fetch` function that can be used for testing purposes.
*/
Expand All @@ -48,18 +51,20 @@ export interface ServerAdapterObject<TServerContext> extends EventListenerObject
): Promise<Response> | Response;
/**
* This function takes Node's request object and returns a WHATWG Fetch spec compliant `Response` object.
*
* @deprecated Use `handleNodeRequestAndResponse` instead.
**/
handleNodeRequest(
nodeRequest: NodeRequest,
...ctx: Partial<TServerContext>[]
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<Response> | Response;
/**
* This function takes Node's request and response objects and returns a WHATWG Fetch spec compliant `Response` object.
*/
handleNodeRequestAndResponse(
nodeRequest: NodeRequest,
nodeResponseOrContainer: { raw: NodeResponse } | NodeResponse,
...ctx: Partial<TServerContext>[]
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<Response> | Response;
/**
* A request listener function that can be used with any Node server variation.
Expand All @@ -68,14 +73,31 @@ export interface ServerAdapterObject<TServerContext> extends EventListenerObject

handleUWS: UWSHandler;

handle(req: NodeRequest, res: NodeResponse, ...ctx: Partial<TServerContext>[]): Promise<void>;
handle(requestLike: RequestLike, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response;
handle(request: Request, ...ctx: Partial<TServerContext>[]): Promise<Response> | Response;
handle(fetchEvent: FetchEvent & Partial<TServerContext>, ...ctx: Partial<TServerContext>[]): void;
handle(res: UWSResponse, req: UWSRequest, ...ctx: Partial<TServerContext>[]): Promise<void>;
handle(
container: { request: Request } & Partial<TServerContext>,
req: NodeRequest,
res: NodeResponse,
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<void>;
handle(
requestLike: RequestLike,
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<Response> | Response;
handle(
request: Request,
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<Response> | Response;
handle(
fetchEvent: FetchEvent & Partial<TServerContext & ServerAdapterInitialContext>,
...ctx: Partial<TServerContext>[]
): void;
handle(
res: UWSResponse,
req: UWSRequest,
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<void>;
handle(
container: { request: Request } & Partial<TServerContext & ServerAdapterInitialContext>,
...ctx: Partial<TServerContext & ServerAdapterInitialContext>[]
): Promise<Response> | Response;
}

Expand All @@ -101,14 +123,25 @@ export type ServerAdapter<

export type ServerAdapterRequestHandler<TServerContext> = (
request: Request,
ctx: TServerContext,
ctx: TServerContext & ServerAdapterInitialContext,
) => Promise<Response> | Response;

export type ServerAdapterNodeContext = {
req: NodeRequest;
res: NodeResponse;
};

export type WaitUntilFn = (promise: Promise<void> | void) => void;
export type WaitUntilFn = (promise: Promise<unknown>) => void;

export type FetchAPI = ReturnType<typeof import('@whatwg-node/fetch').createFetch>;

export type ServerAdapterInitialContext = {
/**
* Register a promise that should be waited in the background for before the server process is exited.
*
* This also avoids unhandled promise rejections, which would otherwise cause the process to exit on some environment like Node.
* @param promise The promise to wait for
* @returns
*/
waitUntil: WaitUntilFn;
};

0 comments on commit 9f6546f

Please sign in to comment.