Skip to content

Commit

Permalink
fix(server): handle nullish server context parts
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Dec 11, 2023
1 parent 37c60e6 commit eb326a6
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/popular-deers-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@whatwg-node/server": patch
---

Fix for undefined server context parts
59 changes: 31 additions & 28 deletions packages/server/src/createServerAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,7 @@ import {
} from './uwebsockets.js';

async function handleWaitUntils(waitUntilPromises: Promise<unknown>[]) {
const waitUntils = await Promise.allSettled(waitUntilPromises);
waitUntils.forEach(waitUntil => {
if (waitUntil.status === 'rejected') {
console.error(waitUntil.reason);
}
});
await Promise.allSettled(waitUntilPromises);
}

type RequestContainer = { request: Request };
Expand All @@ -58,14 +53,6 @@ function isRequestAccessible(serverContext: any): serverContext is RequestContai
}
}

function addWaitUntil(serverContext: any, waitUntilPromises: Promise<unknown>[]): void {
serverContext['waitUntil'] = function (promise: Promise<void> | void) {
if (promise != null) {
waitUntilPromises.push(promise);
}
};
}

export interface ServerAdapterOptions<TServerContext> {
plugins?: ServerAdapterPlugin<TServerContext>[];
fetchAPI?: Partial<FetchAPI>;
Expand Down Expand Up @@ -206,8 +193,10 @@ function createServerAdapter<
const defaultServerContext = {
req: nodeRequest,
res: serverResponse,
waitUntil(cb: Promise<unknown>) {
waitUntilPromises.push(cb.catch(err => console.error(err)));
},
};
addWaitUntil(defaultServerContext, waitUntilPromises);
let response$: Response | Promise<Response> | undefined;
try {
response$ = handleNodeRequest(nodeRequest, defaultServerContext as any, ...ctx);
Expand All @@ -234,10 +223,15 @@ function createServerAdapter<
const defaultServerContext = {
res,
req,
waitUntil(cb: Promise<unknown>) {
waitUntilPromises.push(cb.catch(err => console.error(err)));
},
};
addWaitUntil(defaultServerContext, waitUntilPromises);
const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
const serverContext =
ctx.length > 0 ? completeAssign(defaultServerContext, ...ctx) : defaultServerContext;
filteredCtxParts.length > 0
? completeAssign(defaultServerContext, ...ctx)
: defaultServerContext;
const request = getRequestFromUWSRequest({
req,
res,
Expand Down Expand Up @@ -277,23 +271,32 @@ function createServerAdapter<
if (!event.respondWith || !event.request) {
throw new TypeError(`Expected FetchEvent, got ${event}`);
}
const serverContext = ctx.length > 0 ? Object.assign({}, event, ...ctx) : event;
const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
const serverContext =
filteredCtxParts.length > 0
? completeAssign({}, event, ...filteredCtxParts)
: isolateObject(event);
const response$ = handleRequest(event.request, serverContext);
event.respondWith(response$);
}

function handleRequestWithWaitUntil(request: Request, ...ctx: Partial<TServerContext>[]) {
const serverContext = ctx.length > 1 ? completeAssign(...ctx) : isolateObject(ctx[0]);
if (serverContext.waitUntil == null) {
const waitUntilPromises: Promise<void>[] = [];
addWaitUntil(serverContext, waitUntilPromises);
const response$ = handleRequest(request, serverContext);
if (waitUntilPromises.length > 0) {
return handleWaitUntils(waitUntilPromises).then(() => response$);
}
return response$;
const filteredCtxParts: any[] = ctx.filter(partCtx => partCtx != null);
let waitUntilPromises: Promise<void>[] | undefined;
const serverContext =
filteredCtxParts.length > 1
? completeAssign(...filteredCtxParts)
: isolateObject(
filteredCtxParts[0],
filteredCtxParts[0] == null || filteredCtxParts[0].waitUntil == null
? (waitUntilPromises = [])
: undefined,
);
const response$ = handleRequest(request, serverContext);
if (waitUntilPromises?.length) {
return handleWaitUntils(waitUntilPromises).then(() => response$);
}
return handleRequest(request, serverContext);
return response$;
}

const fetchFn: ServerAdapterObject<TServerContext>['fetch'] = (
Expand Down
17 changes: 15 additions & 2 deletions packages/server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ export function handleErrorFromRequestHandler(error: any, ResponseCtor: typeof R

export function isolateObject<TIsolatedObject extends object>(
originalCtx: TIsolatedObject,
waitUntilPromises?: Promise<unknown>[],
): TIsolatedObject {
if (originalCtx == null) {
return {} as TIsolatedObject;
Expand All @@ -454,6 +455,11 @@ export function isolateObject<TIsolatedObject extends object>(
const deletedProps = new Set<string | symbol>();
return new Proxy(originalCtx, {
get(originalCtx, prop, receiver) {
if (waitUntilPromises != null && prop === 'waitUntil') {
return function waitUntil(promise: Promise<unknown>) {
waitUntilPromises.push(promise.catch(err => console.error(err)));
};
}
if (prop in extraProps) {
return Reflect.get(extraProps, prop, receiver);
}
Expand All @@ -468,6 +474,9 @@ export function isolateObject<TIsolatedObject extends object>(
return Reflect.set(extraProps, prop, value, receiver);
},
has(originalCtx, prop) {
if (waitUntilPromises != null && prop === 'waitUntil') {
return true;
}
if (deletedProps.has(prop)) {
return false;
}
Expand All @@ -490,9 +499,13 @@ export function isolateObject<TIsolatedObject extends object>(
const extraKeys = Reflect.ownKeys(extraProps);
const originalKeys = Reflect.ownKeys(originalCtx);
const deletedKeys = Array.from(deletedProps);
return Array.from(
new Set(extraKeys.concat(originalKeys.filter(keys => !deletedKeys.includes(keys)))),
const allKeys = new Set(
extraKeys.concat(originalKeys.filter(keys => !deletedKeys.includes(keys))),
);
if (waitUntilPromises != null) {
allKeys.add('waitUntil');
}
return Array.from(allKeys);
},
getOwnPropertyDescriptor(originalCtx, prop) {
if (prop in extraProps) {
Expand Down
11 changes: 11 additions & 0 deletions packages/server/test/server-context.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Request } from '@whatwg-node/fetch';
import { Response } from '@whatwg-node/server';
import { createServerAdapter } from '../src/createServerAdapter';

describe('Server Context', () => {
Expand All @@ -20,4 +22,13 @@ describe('Server Context', () => {
expect(seenCtx.has(exampleStaticCtx)).toBe(false);
expect(exampleStaticCtx.foo).toBe('bar');
});
it('filters empty ctx', async () => {
const adapter = createServerAdapter<any>(function handler(_req, ctx) {
return Response.json(ctx);
});
const ctxParts: any[] = [undefined, undefined, { foo: 'bar' }, undefined, { bar: 'baz' }];
const res = await adapter(new Request('https://example.com'), ...ctxParts);
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ foo: 'bar', bar: 'baz' });
});
});

0 comments on commit eb326a6

Please sign in to comment.