fets
Version:
TypeScript HTTP Framework focusing on e2e type-safety, easy setup, performance & great developer experience
245 lines (244 loc) • 10.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRouterBase = createRouterBase;
exports.createRouter = createRouter;
const tslib_1 = require("tslib");
const qs_1 = require("qs");
const DefaultFetchAPI = tslib_1.__importStar(require("@whatwg-node/fetch"));
const server_1 = require("@whatwg-node/server");
const landing_page_html_js_1 = tslib_1.__importDefault(require("./landing-page-html.js"));
const define_routes_js_1 = require("./plugins/define-routes.js");
const openapi_js_1 = require("./plugins/openapi.js");
const typebox_js_1 = require("./plugins/typebox.js");
const utils_js_1 = require("./plugins/utils.js");
const utils_js_2 = require("./utils.js");
function createRouterBase({ fetchAPI: givenFetchAPI, base: basePath = '/', plugins = [], openAPI, swaggerUI, landingPage = true, } = {}, openAPIDocument) {
const fetchAPI = givenFetchAPI
? {
...DefaultFetchAPI,
...givenFetchAPI,
}
: DefaultFetchAPI;
const __onRouterInitHooks = [];
const onRouteHooks = [];
const onRouteHandleHooks = [];
for (const plugin of plugins) {
if (plugin.onRouterInit) {
__onRouterInitHooks.push(plugin.onRouterInit);
}
if (plugin.onRoute) {
onRouteHooks.push(plugin.onRoute);
}
if (plugin.onRouteHandle) {
onRouteHandleHooks.push(plugin.onRouteHandle);
}
}
const routeByPatternByMethod = new Map();
const routeByPathByMethod = new Map();
const __routes = [];
function handleUnhandledRoute(requestPath) {
if (landingPage) {
return new fetchAPI.Response(landing_page_html_js_1.default
.replaceAll('__BASE_PATH__', basePath)
.replaceAll('__OAS_PATH__', openAPI?.endpoint || '/openapi.json')
.replaceAll('__SWAGGER_UI_PATH__', swaggerUI?.endpoint || '/docs')
.replaceAll('__REQUEST_PATH__', requestPath), {
headers: {
'Content-Type': 'text/html',
},
});
}
return undefined;
}
return {
openAPIDocument,
handle(request, context) {
let url = new Proxy(utils_js_1.EMPTY_OBJECT, {
get(_target, prop, _receiver) {
url = new fetchAPI.URL(request.url, 'http://localhost');
return Reflect.get(url, prop, url);
},
});
let parsedQuery;
Object.defineProperties(request, {
parsedUrl: {
get() {
return url;
},
configurable: true,
},
query: {
get() {
if (parsedQuery == null) {
const searchPart = request.url.split('?')[1];
if (searchPart == null) {
return utils_js_1.EMPTY_OBJECT;
}
parsedQuery = (0, qs_1.parse)(searchPart);
}
return parsedQuery;
},
configurable: true,
},
});
const pathPatternMapByMethod = routeByPathByMethod.get(request.method);
if (pathPatternMapByMethod) {
const route = pathPatternMapByMethod.get(url.pathname);
if (route) {
for (const onRouteHandleHook of onRouteHandleHooks) {
onRouteHandleHook({
route,
// @ts-expect-error - We know it's a TypedRequest
request,
});
}
// @ts-expect-error - We know it's a TypedRequest
const handlerResult$ = route.handler(request, context);
if ((0, server_1.isPromise)(handlerResult$)) {
return handlerResult$.then(handlerResult => {
if (handlerResult) {
return handlerResult;
}
return handleUnhandledRoute(request.url);
});
}
if (handlerResult$) {
return handlerResult$;
}
}
}
const methodPatternMaps = routeByPatternByMethod.get(request.method);
if (methodPatternMaps) {
const patternHandlerResult$ = (0, utils_js_2.asyncIterationUntilReturn)(methodPatternMaps.entries(), ([pattern, route]) => {
// Do not parse URL if not needed
const match = pattern.exec(url);
if (match != null) {
let paramsProxy;
Object.defineProperty(request, 'params', {
get() {
if (paramsProxy == null) {
paramsProxy = new Proxy(match.pathname.groups, {
get(_, prop) {
const value = match.pathname.groups[prop.toString()];
if (value != null) {
return decodeURIComponent(value);
}
return value;
},
});
}
return paramsProxy;
},
configurable: true,
});
for (const onRouteHandleHook of onRouteHandleHooks) {
onRouteHandleHook({
route,
// @ts-expect-error - We know it's a TypedRequest
request,
});
}
// @ts-expect-error - We know it's a TypedRequest
return route.handler(request, context);
}
});
if ((0, server_1.isPromise)(patternHandlerResult$)) {
return patternHandlerResult$.then(patternHandlerResult => {
if (patternHandlerResult) {
return patternHandlerResult;
}
return handleUnhandledRoute(request.url);
});
}
if (patternHandlerResult$) {
return patternHandlerResult$;
}
}
return handleUnhandledRoute(request.url);
},
route(route) {
if (!route.internal) {
__routes.push(route);
}
for (const onRouteHook of onRouteHooks) {
onRouteHook({
basePath,
route,
routeByPathByMethod,
routeByPatternByMethod,
openAPIDocument,
fetchAPI,
});
}
return this;
},
use(prefixOrSubRouter, subRouter) {
let prefix = '';
let actualSubRouter;
if (typeof prefixOrSubRouter === 'string') {
prefix = prefixOrSubRouter === '/' ? '' : prefixOrSubRouter;
actualSubRouter = subRouter;
}
else {
actualSubRouter = prefixOrSubRouter;
}
const subBase = actualSubRouter.__base === '/' ? '' : actualSubRouter.__base;
for (const subRoute of actualSubRouter.__routes) {
const subPath = subRoute.path === '/' ? '' : subRoute.path;
const newPath = `${prefix}${subBase}${subPath}` || '/';
this.route({ ...subRoute, path: newPath });
}
return this;
},
__client: {},
__onRouterInitHooks,
__routes,
__base: basePath,
};
}
function createRouter(options = {}) {
const { openAPI: { endpoint: oasEndpoint = '/openapi.json', includeValidationErrors, ...openAPIDocument } = {}, swaggerUI: { endpoint: swaggerUIEndpoint = '/docs', ...swaggerUIOpts } = {}, plugins: userPlugins = [], base = '/', } = options;
openAPIDocument.openapi = openAPIDocument.openapi || '3.0.1';
const oasInfo = (openAPIDocument.info ||= {});
oasInfo.title ||= 'feTS API';
oasInfo.description ||= 'An API written with feTS';
oasInfo.version ||= '1.0.0';
if (base !== '/') {
openAPIDocument.servers = openAPIDocument.servers || [
{
url: base,
},
];
}
const plugins = [
...(oasEndpoint || swaggerUIEndpoint
? [
(0, openapi_js_1.useOpenAPI)({
oasEndpoint,
swaggerUIEndpoint,
swaggerUIOpts,
includeValidationErrors,
}),
]
: []),
(0, typebox_js_1.useTypeBox)(),
(0, server_1.useErrorHandling)(options?.onError),
(0, define_routes_js_1.useDefineRoutes)(),
...userPlugins,
];
const finalOpts = {
...options,
swaggerUI: {
endpoint: swaggerUIEndpoint,
...swaggerUIOpts,
},
base,
plugins,
};
const routerBaseObject = createRouterBase(finalOpts, openAPIDocument);
const router = (0, server_1.createServerAdapter)(routerBaseObject, finalOpts);
for (const onRouterInitHook of routerBaseObject.__onRouterInitHooks) {
onRouterInitHook(router);
}
return router;
}