import grabRouteParams from "../../utils/grab-route-params"; import grabConstants from "../../utils/grab-constants"; import grabRouter from "../../utils/grab-router"; import isDevelopment from "../../utils/is-development"; import { log } from "../../utils/log"; import _ from "lodash"; import path from "path"; import grabDirNames from "../../utils/grab-dir-names"; import grabRelativePath from "../../utils/grab-relative-path"; import { RequestBodyTooLargeError } from "../../utils/read-request-body"; const { ROOT_DIR } = grabDirNames(); export default async function ({ req }) { try { const url = new URL(req.url); const is_dev = isDevelopment(); const { MBInBytes, ServerDefaultRequestBodyLimitBytes } = grabConstants(); const router = grabRouter(); const match = router.match(url.pathname); if (!match?.filePath) { const errMsg = `Route ${url.pathname} not found`; return Response.json({ success: false, msg: errMsg, }, { status: 404, headers: { "Content-Type": "application/json", }, }); } let module; const now = Date.now(); const relative_match_path = grabRelativePath(match.filePath); if (is_dev && global.BUNEXT_SSR_BUNDLER_CTX_MAP?.[relative_match_path]?.path) { const target_import = path.join(ROOT_DIR, global.BUNEXT_SSR_BUNDLER_CTX_MAP[relative_match_path].path); module = await import(`${target_import}?t=${now}`); } else { const import_path = is_dev ? `${match.filePath}?t=${now}` : match.filePath; module = await import(import_path); } const config = module.config; const maxBodyBytes = config?.max_request_body_mb ? config.max_request_body_mb * MBInBytes : ServerDefaultRequestBodyLimitBytes; const contentLength = req.headers.get("content-length"); if (contentLength && parseInt(contentLength, 10) > maxBodyBytes) { return bodyTooLargeResponse(); } let routeParams; try { routeParams = await grabRouteParams({ req, query: match.query, max_body_bytes: maxBodyBytes, }); } catch (error) { if (error instanceof RequestBodyTooLargeError) { return bodyTooLargeResponse(); } throw error; } const target_module = (module["default"] || module["handler"]); if (typeof target_module !== "function") { log.error(`API Route ${url.pathname} has no default or \`handler\` export`); return Response.json({ success: false, msg: is_dev ? `Route ${url.pathname} has no default or \`handler\` export` : "Internal Server Error", }, { status: 500 }); } let res; try { res = await target_module({ ...routeParams, }); } catch (error) { log.error(`API Route Error (${url.pathname}): ${error?.message}\n${error?.stack || ""}`); return Response.json({ success: false, msg: routeErrorMessage(error), }, { status: 500, headers: { "Content-Type": "application/json", }, }); } if (res instanceof Response) { if (is_dev) { res.headers.set("Cache-Control", "no-cache, no-store, must-revalidate"); } return res; } if (res) { let final_res = Response.json(_.omit(res, [ "bunext_api_route_res_options", "bunext_api_route_res_transform_fn", ]), { ...(res.bunext_api_route_res_options || undefined), }); if (res.bunext_api_route_res_transform_fn) { final_res = await res.bunext_api_route_res_transform_fn(final_res); } return final_res; } return Response.json({ err: `Route handler error` }); } catch (error) { const url = new URL(req.url); log.error(`API Route Error (${url.pathname}): ${error?.message}\n${error?.stack || ""}`); return Response.json({ success: false, msg: routeErrorMessage(error), }, { status: 500, headers: { "Content-Type": "application/json", }, }); } } function bodyTooLargeResponse() { return Response.json({ success: false, msg: "Request Body Too Large!", }, { status: 413 }); } /** * Error details can leak internals, so they are only exposed in * development. They are always logged server-side. */ function routeErrorMessage(error) { if (!isDevelopment()) { return "Internal Server Error"; } return `Route handler error: ${error?.message || "Internal Server Error"}`; }