@whatwg-node/router
Version:
Fetch API compliant HTTP Router
132 lines (129 loc) • 4.96 kB
JavaScript
import * as DefaultFetchAPI from '@whatwg-node/fetch';
export { Response, URLPattern } from '@whatwg-node/fetch';
import { createServerAdapter } from '@whatwg-node/server';
export { useCORS, useErrorHandling } from '@whatwg-node/server';
const HTTP_METHODS = [
'GET',
'HEAD',
'POST',
'PUT',
'DELETE',
'CONNECT',
'OPTIONS',
'TRACE',
'PATCH',
];
function createRouterBase({ fetchAPI: givenFetchAPI, base: basePath = '/', } = {}) {
const fetchAPI = {
...givenFetchAPI,
...DefaultFetchAPI,
};
const routesByMethod = new Map();
function addHandlersToMethod(method, path, ...handlers) {
let methodPatternMaps = routesByMethod.get(method);
if (!methodPatternMaps) {
methodPatternMaps = new Map();
routesByMethod.set(method, methodPatternMaps);
}
let fullPath = '';
if (basePath === '/') {
fullPath = path;
}
else if (path === '/') {
fullPath = basePath;
}
else {
fullPath = `${basePath}${path}`;
}
const pattern = new fetchAPI.URLPattern({ pathname: fullPath });
methodPatternMaps.set(pattern, handlers);
}
async function handleRequest(request, context) {
const method = request.method;
let _parsedUrl;
function getParsedUrl() {
if (!_parsedUrl) {
_parsedUrl = new fetchAPI.URL(request.url);
}
return _parsedUrl;
}
const methodPatternMaps = routesByMethod.get(method);
if (methodPatternMaps) {
const queryProxy = new Proxy({}, {
get(_, prop) {
const parsedUrl = getParsedUrl();
const allQueries = parsedUrl.searchParams.getAll(prop.toString());
return allQueries.length === 1 ? allQueries[0] : allQueries;
},
has(_, prop) {
const parsedUrl = getParsedUrl();
return parsedUrl.searchParams.has(prop.toString());
},
});
for (const [pattern, handlers] of methodPatternMaps) {
const match = pattern.exec(request.url);
if (match) {
const routerRequest = new Proxy(request, {
get(target, prop) {
if (prop === 'parsedUrl') {
return getParsedUrl();
}
if (prop === 'params') {
return new Proxy(match.pathname.groups, {
get(_, prop) {
const value = match.pathname.groups[prop.toString()];
if (value != null) {
return decodeURIComponent(value);
}
return value;
},
});
}
if (prop === 'query') {
return queryProxy;
}
const targetProp = target[prop];
if (typeof targetProp === 'function') {
return targetProp.bind(target);
}
return targetProp;
},
has(target, prop) {
return (prop in target || prop === 'parsedUrl' || prop === 'params' || prop === 'query');
},
});
for (const handler of handlers) {
const result = await handler(routerRequest, context);
if (result) {
return result;
}
}
}
}
}
}
return new Proxy({}, {
get(_, prop) {
if (prop === 'handle') {
return handleRequest;
}
const method = prop.toString().toLowerCase();
return function routeMethodKeyFn(path, ...handlers) {
if (method === 'all') {
for (const httpMethod of HTTP_METHODS) {
addHandlersToMethod(httpMethod, path, ...handlers);
}
}
else {
addHandlersToMethod(method.toUpperCase(), path, ...handlers);
}
return this;
};
},
});
}
function createRouter(options) {
const routerBaseObject = createRouterBase(options);
return createServerAdapter(routerBaseObject, options);
}
export { createRouter, createRouterBase };