@whatwg-node/server
Version:
Fetch API compliant HTTP Server adapter
184 lines (183 loc) • 6.85 kB
JavaScript
/* eslint-disable @typescript-eslint/ban-types */
import * as DefaultFetchAPI from '@whatwg-node/fetch';
import { useFetchEvent } from './internal-plugins/useFetchEvent.js';
import { useNodeAdapter } from './internal-plugins/useNodeAdapter.js';
import { useUWSAdapter } from './internal-plugins/useUWSAdapter.js';
import { completeAssign, isRequestInit } from './utils.js';
const EMPTY_OBJECT = {};
function createServerAdapter(serverAdapterBaseObject, options) {
const fetchAPI = {
...DefaultFetchAPI,
...options?.fetchAPI,
};
const givenHandleRequest = typeof serverAdapterBaseObject === 'function'
? serverAdapterBaseObject
: serverAdapterBaseObject.handle;
const onRequestAdaptHooks = [];
const onRequestHooks = [];
const onResponseHooks = [];
const plugins = options?.plugins ?? [];
plugins.push(useUWSAdapter(), useNodeAdapter(), useFetchEvent());
for (const plugin of plugins) {
if (plugin.onRequestAdapt) {
onRequestAdaptHooks.push(plugin.onRequestAdapt);
}
if (plugin.onRequest) {
onRequestHooks.push(plugin.onRequest);
}
if (plugin.onResponse) {
onResponseHooks.push(plugin.onResponse);
}
}
async function handleRequest(request, serverContext) {
let url = new Proxy(EMPTY_OBJECT, {
get(_target, prop, _receiver) {
url = new fetchAPI.URL(request.url, 'http://localhost');
return Reflect.get(url, prop, url);
},
});
let requestHandler = givenHandleRequest;
let response;
let waitUntilPromises;
if (serverContext['waitUntil'] == null) {
waitUntilPromises = new Set();
serverContext['waitUntil'] = (promise) => {
waitUntilPromises.add(promise);
promise.then(() => {
waitUntilPromises.delete(promise);
});
};
}
for (const onRequestHook of onRequestHooks) {
await onRequestHook({
request,
serverContext,
fetchAPI,
url,
requestHandler,
setRequestHandler(newRequestHandler) {
requestHandler = newRequestHandler;
},
endResponse(newResponse) {
response = newResponse;
},
});
if (response) {
break;
}
}
if (!response) {
response = await requestHandler(request, serverContext);
}
if (!response) {
response = new fetchAPI.Response(undefined, {
status: 404,
statusText: 'Not Found',
});
}
for (const onResponseHook of onResponseHooks) {
await onResponseHook({
request,
response,
serverContext,
});
}
if (waitUntilPromises?.size) {
const waitUntils = await Promise.allSettled(waitUntilPromises);
waitUntils.forEach(waitUntil => {
if (waitUntil.status === 'rejected') {
console.error(waitUntil.reason);
}
});
}
return response;
}
const fetchFn = (input, ...maybeCtx) => {
if (typeof input === 'string' || 'href' in input) {
const [initOrCtx, ...restOfCtx] = maybeCtx;
if (isRequestInit(initOrCtx)) {
const serverContext = restOfCtx.length > 0 ? completeAssign(...restOfCtx) : {};
return handleRequest(new fetchAPI.Request(input, initOrCtx), serverContext);
}
const serverContext = maybeCtx.length > 0 ? completeAssign(...maybeCtx) : {};
return handleRequest(new fetchAPI.Request(input), serverContext);
}
const serverContext = maybeCtx.length > 0 ? completeAssign(...maybeCtx) : {};
return handleRequest(input, serverContext);
};
const genericRequestHandler = (...args) => {
let request;
let serverContext;
for (const onRequestAdapt of onRequestAdaptHooks) {
onRequestAdapt({
args,
setRequest(newRequest) {
request = newRequest;
},
setServerContext(newServerContext) {
serverContext = newServerContext;
},
fetchAPI,
});
}
if (request) {
if (!serverContext) {
serverContext = {};
}
return handleRequest(request, serverContext);
}
return fetchFn(...args);
};
const adapterObj = {
handleRequest,
fetch: fetchFn,
requestListener: genericRequestHandler,
handleNodeRequest: genericRequestHandler,
handleEvent: genericRequestHandler,
handle: genericRequestHandler,
};
const serverAdapter = new Proxy(genericRequestHandler, {
// It should have all the attributes of the handler function and the server instance
has: (_, prop) => {
return (prop in adapterObj ||
prop in genericRequestHandler ||
(serverAdapterBaseObject && prop in serverAdapterBaseObject));
},
get: (_, prop) => {
const adapterProp = adapterObj[prop];
if (adapterProp) {
if (adapterProp.bind) {
return adapterProp.bind(adapterObj);
}
return adapterProp;
}
const handleProp = genericRequestHandler[prop];
if (handleProp) {
if (handleProp.bind) {
return handleProp.bind(genericRequestHandler);
}
return handleProp;
}
if (serverAdapterBaseObject) {
const serverAdapterBaseObjectProp = serverAdapterBaseObject[prop];
if (serverAdapterBaseObjectProp) {
if (serverAdapterBaseObjectProp.bind) {
return function (...args) {
const returnedVal = serverAdapterBaseObject[prop](...args);
if (returnedVal === serverAdapterBaseObject) {
return serverAdapter;
}
return returnedVal;
};
}
return serverAdapterBaseObjectProp;
}
}
},
apply(_, __, args) {
return genericRequestHandler(...args);
},
});
return serverAdapter;
}
export { createServerAdapter };