jeasx 2.7.3 → 2.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeasx",
3
- "version": "2.7.3",
3
+ "version": "2.7.4",
4
4
  "description": "Jeasx - the ease of JSX with the power of SSR",
5
5
  "keywords": [
6
6
  "async",
@@ -33,7 +33,7 @@
33
33
  "@fastify/formbody": "8.0.2",
34
34
  "@fastify/multipart": "10.0.0",
35
35
  "@fastify/static": "9.1.3",
36
- "@types/node": "25.9.3",
36
+ "@types/node": "26.0.0",
37
37
  "esbuild": "0.28.1",
38
38
  "fastify": "5.8.5",
39
39
  "jsx-async-runtime": "2.1.3"
package/serverless.js CHANGED
@@ -14,7 +14,9 @@ const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
14
14
  const MODULE_BY_ROUTE = {};
15
15
  if (!NODE_ENV_IS_DEVELOPMENT) {
16
16
  const { routes } = (await import(`file://${join(CWD, "dist", "[--metadata--].js")}`)).default;
17
- routes.forEach((route) => MODULE_BY_ROUTE[route] = null);
17
+ for (const route of routes) {
18
+ MODULE_BY_ROUTE[route] = join(CWD, "dist", `${route}.js`);
19
+ }
18
20
  }
19
21
  const FASTIFY_SERVER = CONFIG.FASTIFY_SERVER ?? ((fastify2) => fastify2);
20
22
  var serverless_default = FASTIFY_SERVER(
@@ -59,36 +61,38 @@ async function handler(request, reply) {
59
61
  if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
60
62
  continue;
61
63
  }
62
- if (module === null || NODE_ENV_IS_DEVELOPMENT && module === void 0) {
63
- try {
64
+ try {
65
+ if (typeof module === "string") {
66
+ module = MODULE_BY_ROUTE[route] = await import(`file://${module}`);
67
+ } else if (module === void 0 && NODE_ENV_IS_DEVELOPMENT) {
64
68
  const modulePath = join(CWD, "dist", `${route}.js`);
65
- if (NODE_ENV_IS_DEVELOPMENT) {
66
- if (typeof require === "function") {
67
- if (require.cache[modulePath]) {
68
- delete require.cache[modulePath];
69
- }
70
- module = await import(`file://${modulePath}`);
71
- } else {
72
- const mtime = (await stat(modulePath)).mtime.getTime();
73
- module = await import(`file://${modulePath}?${mtime}`);
74
- }
75
- } else {
76
- module = await import(`file://${modulePath}`);
77
- MODULE_BY_ROUTE[route] = module;
78
- }
79
- } catch (e) {
80
- switch (e.code) {
81
- case "ENOENT":
82
- case "ENOTDIR":
83
- case "ERR_MODULE_NOT_FOUND":
84
- continue;
85
- default:
86
- throw e;
69
+ if (typeof require === "function" && require.cache[modulePath]) {
70
+ delete require.cache[modulePath];
87
71
  }
72
+ const mtime = (await stat(modulePath)).mtime.getTime();
73
+ module = await import(`file://${modulePath}?${mtime}`);
74
+ }
75
+ } catch (e) {
76
+ switch (e.code) {
77
+ case "ENOENT":
78
+ case "ENOTDIR":
79
+ case "ERR_MODULE_NOT_FOUND":
80
+ continue;
81
+ default:
82
+ throw e;
88
83
  }
89
84
  }
85
+ if (!module || typeof module !== "object") {
86
+ continue;
87
+ }
90
88
  request.route = route;
91
- response = typeof module.default === "function" ? await module.default.call(context, props) : module.default;
89
+ response = typeof module.default === "function" ? (
90
+ // Call functions with context as `this` and props as parameters,
91
+ await module.default.call(context, props)
92
+ ) : (
93
+ // otherwise return default export.
94
+ module.default
95
+ );
92
96
  if (reply.sent) {
93
97
  return;
94
98
  } else if (route.endsWith("/[404]")) {
@@ -123,9 +127,11 @@ function generateRoutes(path) {
123
127
  const routes = [];
124
128
  const segments = [""];
125
129
  let current = "";
126
- for (const segment of path.split("/").filter(Boolean)) {
127
- current += `/${segment}`;
128
- segments.push(current);
130
+ for (const segment of path.split("/")) {
131
+ if (segment !== "") {
132
+ current += `/${segment}`;
133
+ segments.push(current);
134
+ }
129
135
  }
130
136
  segments.reverse();
131
137
  for (let i = segments.length - 1; i >= 0; i--) {
package/serverless.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["serverless.ts"],
4
- "sourcesContent": ["import fastifyCookie, { FastifyCookieOptions } from \"@fastify/cookie\";\nimport fastifyFormbody, { FastifyFormbodyOptions } from \"@fastify/formbody\";\nimport fastifyMultipart, { FastifyMultipartOptions } from \"@fastify/multipart\";\nimport fastifyStatic, { FastifyStaticOptions } from \"@fastify/static\";\nimport fastify, {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n FastifyServerOptions,\n} from \"fastify\";\nimport { jsxToString } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport env from \"./env.js\";\n\nenv();\n\nconst CWD = process.cwd();\nconst CONFIG = (await import(`file://${join(CWD, \"jeasx.config.js\")}`)).default;\nconst NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === \"development\";\n\n// Cache for route modules used in non-development environments.\nconst MODULE_BY_ROUTE: Record<string, { default: Function }> = {};\n\n// Initialize the cache with `null` for all known modules.\n// Modules are lazily loaded on their first request for a specific route.\n// Only routes explicitly initialized with `null` will be loaded.\nif (!NODE_ENV_IS_DEVELOPMENT) {\n const { routes } = (await import(`file://${join(CWD, \"dist\", \"[--metadata--].js\")}`)).default as {\n routes: string[];\n };\n routes.forEach((route) => (MODULE_BY_ROUTE[route] = null));\n}\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n path: string; // Path without query parameters\n route: string; // Path to resolved route handler\n }\n}\n\n// Enhance Fastify server from userland\nconst FASTIFY_SERVER = (CONFIG.FASTIFY_SERVER ?? ((fastify) => fastify)) as (\n fastify: FastifyInstance,\n) => FastifyInstance;\n\n// Create and export a Fastify instance\nexport default FASTIFY_SERVER(\n fastify({\n ...(CONFIG.FASTIFY_SERVER_OPTIONS?.() as FastifyServerOptions),\n }),\n)\n // Create encapsulation context\n .register((fastify) => {\n fastify\n .register(fastifyCookie, {\n ...(CONFIG.FASTIFY_COOKIE_OPTIONS?.() as FastifyCookieOptions),\n })\n .register(fastifyFormbody, {\n ...(CONFIG.FASTIFY_FORMBODY_OPTIONS?.() as FastifyFormbodyOptions),\n })\n .register(fastifyMultipart, {\n ...(CONFIG.FASTIFY_MULTIPART_OPTIONS?.() as FastifyMultipartOptions),\n })\n .register(fastifyStatic, {\n root: [\"public\", \"dist\"].map((dir) => join(CWD, dir)),\n wildcard: false,\n globIgnore: [\"/**/\\\\[*\\\\].js?(.map)\"],\n ...(CONFIG.FASTIFY_STATIC_OPTIONS?.() as FastifyStaticOptions),\n })\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .addHook(\"onRequest\", async (request) => {\n // Extract path from url\n const index = request.url.indexOf(\"?\");\n request.path = index === -1 ? request.url : request.url.slice(0, index);\n })\n .all(\"*\", async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const payload = await handler(request, reply);\n if (\n reply.getHeader(\"content-type\") === undefined &&\n (typeof payload === \"string\" || Buffer.isBuffer(payload))\n ) {\n reply.type(\"text/html; charset=utf-8\");\n }\n return payload;\n } catch (error) {\n request.log.error(error);\n throw error;\n }\n });\n });\n\n/**\n * Resolves route module based on the request path and execute it.\n */\nasync function handler(request: FastifyRequest, reply: FastifyReply) {\n let response: unknown;\n\n // Global context object for route handlers\n const context = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n try {\n // Execute route handlers for current request\n for (const route of generateRoutes(request.path)) {\n // Resolve module via cache\n let module = MODULE_BY_ROUTE[route];\n\n // Skip loading the module if the route path was not initialized.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `route` values.\n if (module === undefined && !NODE_ENV_IS_DEVELOPMENT) {\n continue;\n }\n\n // Module was not loaded yet?\n if (module === null || (NODE_ENV_IS_DEVELOPMENT && module === undefined)) {\n try {\n const modulePath = join(CWD, \"dist\", `${route}.js`);\n if (NODE_ENV_IS_DEVELOPMENT) {\n if (typeof require === \"function\") {\n // Bun: Remove module from cache before importing\n // as query parameter for import is ignored (see Node).\n if (require.cache[modulePath]) {\n delete require.cache[modulePath];\n }\n module = await import(`file://${modulePath}`);\n } else {\n // Node: Use timestamp as query parameter to update modules.\n const mtime = (await stat(modulePath)).mtime.getTime();\n module = await import(`file://${modulePath}?${mtime}`);\n }\n } else {\n // Load and cache module for non-development\n module = await import(`file://${modulePath}`);\n MODULE_BY_ROUTE[route] = module;\n }\n } catch (e) {\n switch (e.code) {\n case \"ENOENT\":\n case \"ENOTDIR\":\n case \"ERR_MODULE_NOT_FOUND\":\n continue;\n default:\n // Module exists, but fails to load.\n throw e;\n }\n }\n }\n\n // Store current route in request\n request.route = route;\n\n // Call functions with 'this' context and props as parameters\n // otherwise return default export\n response =\n typeof module.default === \"function\"\n ? await module.default.call(context, props)\n : module.default;\n\n if (reply.sent) {\n return;\n } else if (route.endsWith(\"/[404]\")) {\n // Preserve existing status if a 404 page is requested directly.\n // If no status is defined, set status to 404 automatically.\n if (reply.statusCode === 200 && !request.path.endsWith(\"/404\")) {\n reply.status(404);\n }\n break;\n } else if (typeof response === \"string\" || Buffer.isBuffer(response) || isJSX(response)) {\n break;\n } else if (\n route.endsWith(\"/[...guard]\") &&\n (response === undefined || typeof response === \"object\")\n ) {\n // Add object entries from guard to props\n Object.assign(props, response);\n continue;\n } else if (reply.statusCode === 404) {\n continue;\n } else {\n break;\n }\n }\n return await renderJSX(context, response);\n } catch (error) {\n const errorHandler = context[\"errorHandler\"];\n if (typeof errorHandler === \"function\") {\n reply.status(500);\n response = await errorHandler.call(context, error);\n return await renderJSX(context, response);\n } else {\n throw error;\n }\n }\n}\n\n/**\n * Generates all possible routes based on the given input path.\n *\n * Example routes for \"/a/b/c\":\n *\n * [\n * \"/[...guard]\",\"/a/[...guard]\",\"/a/b/[...guard]\",\"/a/b/c/[...guard]\",\n * \"/a/b/[c]\",\"/a/b/c/[index]\",\n * \"/a/b/c/[...path]\",\"/a/b/[...path]\",\"/a/[...path]\",\"/[...path]\",\n * \"/a/b/c/[404]\",\"/a/b/[404]\",\"/a/[404]\",\"/[404]\"\n * ]\n */\nfunction generateRoutes(path: string): string[] {\n const routes = [];\n\n // Transform given path into array of all its segments.\n // \"/a/b/c\" => [\"/a/b/c\", \"/a/b\", \"/a\", \"\"]\n const segments = [\"\"];\n let current = \"\";\n for (const segment of path.split(\"/\").filter(Boolean)) {\n current += `/${segment}`;\n segments.push(current);\n }\n segments.reverse();\n\n // [...guard]s are evaluated from top to bottom\n for (let i = segments.length - 1; i >= 0; i--) {\n routes.push(`${segments[i]}/[...guard]`);\n }\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n const edgeSegment = segments[0];\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n if (lastSlash > 0) {\n routes.push(`${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`);\n }\n routes.push(`${edgeSegment}/[index]`);\n\n for (let i = 0; i < segments.length; i++) {\n routes.push(`${segments[i]}/[...path]`);\n }\n\n for (let i = 0; i < segments.length; i++) {\n routes.push(`${segments[i]}/[404]`);\n }\n\n return routes;\n}\n\n/**\n * Determines if a given object is a JSX element.\n */\nfunction isJSX(obj: unknown): boolean {\n return !!obj && typeof obj === \"object\" && \"type\" in obj && \"props\" in obj;\n}\n\n/**\n * Renders JSX to string and applies optional response handler.\n */\nasync function renderJSX(context: object, response: unknown) {\n const payload = isJSX(response) ? await jsxToString.call(context, response) : response;\n\n // Post-process the payload with an optional response handler\n const responseHandler = context[\"responseHandler\"];\n return typeof responseHandler === \"function\"\n ? await responseHandler.call(context, payload)\n : payload;\n}\n"],
5
- "mappings": "AAAA,OAAO,mBAA6C;AACpD,OAAO,qBAAiD;AACxD,OAAO,sBAAmD;AAC1D,OAAO,mBAA6C;AACpD,OAAO,aAKA;AACP,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,OAAO,SAAS;AAEhB,IAAI;AAEJ,MAAM,MAAM,QAAQ,IAAI;AACxB,MAAM,UAAU,MAAM,OAAO,UAAU,KAAK,KAAK,iBAAiB,CAAC,KAAK;AACxE,MAAM,0BAA0B,QAAQ,IAAI,aAAa;AAGzD,MAAM,kBAAyD,CAAC;AAKhE,IAAI,CAAC,yBAAyB;AAC5B,QAAM,EAAE,OAAO,KAAK,MAAM,OAAO,UAAU,KAAK,KAAK,QAAQ,mBAAmB,CAAC,KAAK;AAGtF,SAAO,QAAQ,CAAC,UAAW,gBAAgB,KAAK,IAAI,IAAK;AAC3D;AAUA,MAAM,iBAAkB,OAAO,mBAAmB,CAACA,aAAYA;AAK/D,IAAO,qBAAQ;AAAA,EACb,QAAQ;AAAA,IACN,GAAI,OAAO,yBAAyB;AAAA,EACtC,CAAC;AACH,EAEG,SAAS,CAACA,aAAY;AACrB,EAAAA,SACG,SAAS,eAAe;AAAA,IACvB,GAAI,OAAO,yBAAyB;AAAA,EACtC,CAAC,EACA,SAAS,iBAAiB;AAAA,IACzB,GAAI,OAAO,2BAA2B;AAAA,EACxC,CAAC,EACA,SAAS,kBAAkB;AAAA,IAC1B,GAAI,OAAO,4BAA4B;AAAA,EACzC,CAAC,EACA,SAAS,eAAe;AAAA,IACvB,MAAM,CAAC,UAAU,MAAM,EAAE,IAAI,CAAC,QAAQ,KAAK,KAAK,GAAG,CAAC;AAAA,IACpD,UAAU;AAAA,IACV,YAAY,CAAC,uBAAuB;AAAA,IACpC,GAAI,OAAO,yBAAyB;AAAA,EACtC,CAAC,EACA,gBAAgB,SAAS,EAAE,EAC3B,gBAAgB,QAAQ,EAAE,EAC1B,QAAQ,aAAa,OAAO,YAAY;AAEvC,UAAM,QAAQ,QAAQ,IAAI,QAAQ,GAAG;AACrC,YAAQ,OAAO,UAAU,KAAK,QAAQ,MAAM,QAAQ,IAAI,MAAM,GAAG,KAAK;AAAA,EACxE,CAAC,EACA,IAAI,KAAK,OAAO,SAAyB,UAAwB;AAChE,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,SAAS,KAAK;AAC5C,UACE,MAAM,UAAU,cAAc,MAAM,WACnC,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO,IACvD;AACA,cAAM,KAAK,0BAA0B;AAAA,MACvC;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,IAAI,MAAM,KAAK;AACvB,YAAM;AAAA,IACR;AAAA,EACF,CAAC;AACL,CAAC;AAKH,eAAe,QAAQ,SAAyB,OAAqB;AACnE,MAAI;AAGJ,QAAM,UAAU,CAAC;AAGjB,QAAM,QAAQ,EAAE,SAAS,MAAM;AAE/B,MAAI;AAEF,eAAW,SAAS,eAAe,QAAQ,IAAI,GAAG;AAEhD,UAAI,SAAS,gBAAgB,KAAK;AAKlC,UAAI,WAAW,UAAa,CAAC,yBAAyB;AACpD;AAAA,MACF;AAGA,UAAI,WAAW,QAAS,2BAA2B,WAAW,QAAY;AACxE,YAAI;AACF,gBAAM,aAAa,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK;AAClD,cAAI,yBAAyB;AAC3B,gBAAI,OAAO,YAAY,YAAY;AAGjC,kBAAI,QAAQ,MAAM,UAAU,GAAG;AAC7B,uBAAO,QAAQ,MAAM,UAAU;AAAA,cACjC;AACA,uBAAS,MAAM,OAAO,UAAU,UAAU;AAAA,YAC5C,OAAO;AAEL,oBAAM,SAAS,MAAM,KAAK,UAAU,GAAG,MAAM,QAAQ;AACrD,uBAAS,MAAM,OAAO,UAAU,UAAU,IAAI,KAAK;AAAA,YACrD;AAAA,UACF,OAAO;AAEL,qBAAS,MAAM,OAAO,UAAU,UAAU;AAC1C,4BAAgB,KAAK,IAAI;AAAA,UAC3B;AAAA,QACF,SAAS,GAAG;AACV,kBAAQ,EAAE,MAAM;AAAA,YACd,KAAK;AAAA,YACL,KAAK;AAAA,YACL,KAAK;AACH;AAAA,YACF;AAEE,oBAAM;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAGA,cAAQ,QAAQ;AAIhB,iBACE,OAAO,OAAO,YAAY,aACtB,MAAM,OAAO,QAAQ,KAAK,SAAS,KAAK,IACxC,OAAO;AAEb,UAAI,MAAM,MAAM;AACd;AAAA,MACF,WAAW,MAAM,SAAS,QAAQ,GAAG;AAGnC,YAAI,MAAM,eAAe,OAAO,CAAC,QAAQ,KAAK,SAAS,MAAM,GAAG;AAC9D,gBAAM,OAAO,GAAG;AAAA,QAClB;AACA;AAAA,MACF,WAAW,OAAO,aAAa,YAAY,OAAO,SAAS,QAAQ,KAAK,MAAM,QAAQ,GAAG;AACvF;AAAA,MACF,WACE,MAAM,SAAS,aAAa,MAC3B,aAAa,UAAa,OAAO,aAAa,WAC/C;AAEA,eAAO,OAAO,OAAO,QAAQ;AAC7B;AAAA,MACF,WAAW,MAAM,eAAe,KAAK;AACnC;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF;AACA,WAAO,MAAM,UAAU,SAAS,QAAQ;AAAA,EAC1C,SAAS,OAAO;AACd,UAAM,eAAe,QAAQ,cAAc;AAC3C,QAAI,OAAO,iBAAiB,YAAY;AACtC,YAAM,OAAO,GAAG;AAChB,iBAAW,MAAM,aAAa,KAAK,SAAS,KAAK;AACjD,aAAO,MAAM,UAAU,SAAS,QAAQ;AAAA,IAC1C,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAcA,SAAS,eAAe,MAAwB;AAC9C,QAAM,SAAS,CAAC;AAIhB,QAAM,WAAW,CAAC,EAAE;AACpB,MAAI,UAAU;AACd,aAAW,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,GAAG;AACrD,eAAW,IAAI,OAAO;AACtB,aAAS,KAAK,OAAO;AAAA,EACvB;AACA,WAAS,QAAQ;AAGjB,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,WAAO,KAAK,GAAG,SAAS,CAAC,CAAC,aAAa;AAAA,EACzC;AAGA,QAAM,cAAc,SAAS,CAAC;AAC9B,QAAM,YAAY,YAAY,YAAY,GAAG,IAAI;AACjD,MAAI,YAAY,GAAG;AACjB,WAAO,KAAK,GAAG,YAAY,UAAU,GAAG,SAAS,CAAC,IAAI,YAAY,UAAU,SAAS,CAAC,GAAG;AAAA,EAC3F;AACA,SAAO,KAAK,GAAG,WAAW,UAAU;AAEpC,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,WAAO,KAAK,GAAG,SAAS,CAAC,CAAC,YAAY;AAAA,EACxC;AAEA,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,WAAO,KAAK,GAAG,SAAS,CAAC,CAAC,QAAQ;AAAA,EACpC;AAEA,SAAO;AACT;AAKA,SAAS,MAAM,KAAuB;AACpC,SAAO,CAAC,CAAC,OAAO,OAAO,QAAQ,YAAY,UAAU,OAAO,WAAW;AACzE;AAKA,eAAe,UAAU,SAAiB,UAAmB;AAC3D,QAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,YAAY,KAAK,SAAS,QAAQ,IAAI;AAG9E,QAAM,kBAAkB,QAAQ,iBAAiB;AACjD,SAAO,OAAO,oBAAoB,aAC9B,MAAM,gBAAgB,KAAK,SAAS,OAAO,IAC3C;AACN;",
4
+ "sourcesContent": ["import fastifyCookie, { FastifyCookieOptions } from \"@fastify/cookie\";\nimport fastifyFormbody, { FastifyFormbodyOptions } from \"@fastify/formbody\";\nimport fastifyMultipart, { FastifyMultipartOptions } from \"@fastify/multipart\";\nimport fastifyStatic, { FastifyStaticOptions } from \"@fastify/static\";\nimport fastify, {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n FastifyServerOptions,\n} from \"fastify\";\nimport { jsxToString } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport env from \"./env.js\";\n\nenv();\n\nconst CWD = process.cwd();\nconst CONFIG = (await import(`file://${join(CWD, \"jeasx.config.js\")}`)).default;\nconst NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === \"development\";\n\n// Mapping for route modules used in non-development environments.\nconst MODULE_BY_ROUTE: Record<string, { default: Function } | string> = {};\n\n// Initialize the mapping with absolute paths for all known modules.\n// Modules are lazily loaded on their first request for a specific route.\nif (!NODE_ENV_IS_DEVELOPMENT) {\n const { routes } = (await import(`file://${join(CWD, \"dist\", \"[--metadata--].js\")}`)).default as {\n routes: string[];\n };\n // Map route identifiers to their absolute file system paths in the build directory.\n for (const route of routes) {\n MODULE_BY_ROUTE[route] = join(CWD, \"dist\", `${route}.js`);\n }\n}\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n path: string; // Path without query parameters\n route: string; // Path to resolved route handler\n }\n}\n\n// Enhance Fastify server from userland\nconst FASTIFY_SERVER = (CONFIG.FASTIFY_SERVER ?? ((fastify) => fastify)) as (\n fastify: FastifyInstance,\n) => FastifyInstance;\n\n// Create and export a Fastify instance\nexport default FASTIFY_SERVER(\n fastify({\n ...(CONFIG.FASTIFY_SERVER_OPTIONS?.() as FastifyServerOptions),\n }),\n)\n // Create encapsulation context\n .register((fastify) => {\n fastify\n .register(fastifyCookie, {\n ...(CONFIG.FASTIFY_COOKIE_OPTIONS?.() as FastifyCookieOptions),\n })\n .register(fastifyFormbody, {\n ...(CONFIG.FASTIFY_FORMBODY_OPTIONS?.() as FastifyFormbodyOptions),\n })\n .register(fastifyMultipart, {\n ...(CONFIG.FASTIFY_MULTIPART_OPTIONS?.() as FastifyMultipartOptions),\n })\n .register(fastifyStatic, {\n root: [\"public\", \"dist\"].map((dir) => join(CWD, dir)),\n wildcard: false,\n globIgnore: [\"/**/\\\\[*\\\\].js?(.map)\"],\n ...(CONFIG.FASTIFY_STATIC_OPTIONS?.() as FastifyStaticOptions),\n })\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .addHook(\"onRequest\", async (request) => {\n // Extract path from url\n const index = request.url.indexOf(\"?\");\n request.path = index === -1 ? request.url : request.url.slice(0, index);\n })\n .all(\"*\", async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const payload = await handler(request, reply);\n if (\n reply.getHeader(\"content-type\") === undefined &&\n (typeof payload === \"string\" || Buffer.isBuffer(payload))\n ) {\n reply.type(\"text/html; charset=utf-8\");\n }\n return payload;\n } catch (error) {\n request.log.error(error);\n throw error;\n }\n });\n });\n\n/**\n * Resolves route module based on the request path and execute it.\n */\nasync function handler(request: FastifyRequest, reply: FastifyReply) {\n let response: unknown;\n\n // Global context object for route handlers\n const context = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n try {\n // Execute route handlers for current request\n for (const route of generateRoutes(request.path)) {\n // Resolve module or path to module\n let module = MODULE_BY_ROUTE[route];\n\n // Skip processing if the route path was not initialized.\n if (module === undefined && !NODE_ENV_IS_DEVELOPMENT) {\n continue;\n }\n\n // Module was not loaded yet?\n try {\n if (typeof module === \"string\") {\n // Production: Load and cache module only via pre-calculated path.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `route` values.\n module = MODULE_BY_ROUTE[route] = await import(`file://${module}`);\n } else if (module === undefined && NODE_ENV_IS_DEVELOPMENT) {\n // Only map module paths depending on `route` during development.\n const modulePath = join(CWD, \"dist\", `${route}.js`);\n if (typeof require === \"function\" && require.cache[modulePath]) {\n // Bun: Remove module from cache before importing\n // as query parameter for import is ignored.\n delete require.cache[modulePath];\n }\n // Use timestamp as query parameter to update modules.\n const mtime = (await stat(modulePath)).mtime.getTime();\n // Dynamic imports are restricted to development environments;\n // therefore, production-level path validation is not required here.\n module = await import(`file://${modulePath}?${mtime}`);\n }\n } catch (e) {\n switch (e.code) {\n case \"ENOENT\":\n case \"ENOTDIR\":\n case \"ERR_MODULE_NOT_FOUND\":\n continue;\n default:\n // Module exists, but fails to load.\n throw e;\n }\n }\n\n // Ensure module is a valid object before processing.\n if (!module || typeof module !== \"object\") {\n continue;\n }\n\n // Store current route in request.\n request.route = route;\n\n response =\n typeof module.default === \"function\"\n ? // Call functions with context as `this` and props as parameters,\n await module.default.call(context, props)\n : // otherwise return default export.\n module.default;\n\n if (reply.sent) {\n return;\n } else if (route.endsWith(\"/[404]\")) {\n // Preserve existing status if a 404 page is requested directly.\n // If no status is defined, set status to 404 automatically.\n if (reply.statusCode === 200 && !request.path.endsWith(\"/404\")) {\n reply.status(404);\n }\n break;\n } else if (typeof response === \"string\" || Buffer.isBuffer(response) || isJSX(response)) {\n break;\n } else if (\n route.endsWith(\"/[...guard]\") &&\n (response === undefined || typeof response === \"object\")\n ) {\n // Add object entries from guard to props\n Object.assign(props, response);\n continue;\n } else if (reply.statusCode === 404) {\n continue;\n } else {\n break;\n }\n }\n return await renderJSX(context, response);\n } catch (error) {\n const errorHandler = context[\"errorHandler\"];\n if (typeof errorHandler === \"function\") {\n reply.status(500);\n response = await errorHandler.call(context, error);\n return await renderJSX(context, response);\n } else {\n throw error;\n }\n }\n}\n\n/**\n * Generates all possible routes based on the given input path.\n *\n * Example routes for \"/a/b/c\":\n *\n * [\n * \"/[...guard]\",\"/a/[...guard]\",\"/a/b/[...guard]\",\"/a/b/c/[...guard]\",\n * \"/a/b/[c]\",\"/a/b/c/[index]\",\n * \"/a/b/c/[...path]\",\"/a/b/[...path]\",\"/a/[...path]\",\"/[...path]\",\n * \"/a/b/c/[404]\",\"/a/b/[404]\",\"/a/[404]\",\"/[404]\"\n * ]\n */\nfunction generateRoutes(path: string): string[] {\n const routes = [];\n\n // Transform given path into array of all its segments.\n // \"/a/b/c\" => [\"/a/b/c\", \"/a/b\", \"/a\", \"\"]\n const segments = [\"\"];\n let current = \"\";\n for (const segment of path.split(\"/\")) {\n // Ignore redundant slashes.\n if (segment !== \"\") {\n current += `/${segment}`;\n segments.push(current);\n }\n }\n segments.reverse();\n\n // [...guard]s are evaluated from top to bottom\n for (let i = segments.length - 1; i >= 0; i--) {\n routes.push(`${segments[i]}/[...guard]`);\n }\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n const edgeSegment = segments[0];\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n if (lastSlash > 0) {\n routes.push(`${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`);\n }\n routes.push(`${edgeSegment}/[index]`);\n\n for (let i = 0; i < segments.length; i++) {\n routes.push(`${segments[i]}/[...path]`);\n }\n\n for (let i = 0; i < segments.length; i++) {\n routes.push(`${segments[i]}/[404]`);\n }\n\n return routes;\n}\n\n/**\n * Determines if a given object is a JSX element.\n */\nfunction isJSX(obj: unknown): boolean {\n return !!obj && typeof obj === \"object\" && \"type\" in obj && \"props\" in obj;\n}\n\n/**\n * Renders JSX to string and applies optional response handler.\n */\nasync function renderJSX(context: object, response: unknown) {\n const payload = isJSX(response) ? await jsxToString.call(context, response) : response;\n\n // Post-process the payload with an optional response handler\n const responseHandler = context[\"responseHandler\"];\n return typeof responseHandler === \"function\"\n ? await responseHandler.call(context, payload)\n : payload;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["fastify"]
7
7
  }
package/serverless.ts CHANGED
@@ -19,17 +19,19 @@ const CWD = process.cwd();
19
19
  const CONFIG = (await import(`file://${join(CWD, "jeasx.config.js")}`)).default;
20
20
  const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
21
21
 
22
- // Cache for route modules used in non-development environments.
23
- const MODULE_BY_ROUTE: Record<string, { default: Function }> = {};
22
+ // Mapping for route modules used in non-development environments.
23
+ const MODULE_BY_ROUTE: Record<string, { default: Function } | string> = {};
24
24
 
25
- // Initialize the cache with `null` for all known modules.
25
+ // Initialize the mapping with absolute paths for all known modules.
26
26
  // Modules are lazily loaded on their first request for a specific route.
27
- // Only routes explicitly initialized with `null` will be loaded.
28
27
  if (!NODE_ENV_IS_DEVELOPMENT) {
29
28
  const { routes } = (await import(`file://${join(CWD, "dist", "[--metadata--].js")}`)).default as {
30
29
  routes: string[];
31
30
  };
32
- routes.forEach((route) => (MODULE_BY_ROUTE[route] = null));
31
+ // Map route identifiers to their absolute file system paths in the build directory.
32
+ for (const route of routes) {
33
+ MODULE_BY_ROUTE[route] = join(CWD, "dist", `${route}.js`);
34
+ }
33
35
  }
34
36
 
35
37
  declare module "fastify" {
@@ -107,60 +109,61 @@ async function handler(request: FastifyRequest, reply: FastifyReply) {
107
109
  try {
108
110
  // Execute route handlers for current request
109
111
  for (const route of generateRoutes(request.path)) {
110
- // Resolve module via cache
112
+ // Resolve module or path to module
111
113
  let module = MODULE_BY_ROUTE[route];
112
114
 
113
- // Skip loading the module if the route path was not initialized.
114
- // This avoids potential path traversal vulnerabilities caused
115
- // by unexpected `route` values.
115
+ // Skip processing if the route path was not initialized.
116
116
  if (module === undefined && !NODE_ENV_IS_DEVELOPMENT) {
117
117
  continue;
118
118
  }
119
119
 
120
120
  // Module was not loaded yet?
121
- if (module === null || (NODE_ENV_IS_DEVELOPMENT && module === undefined)) {
122
- try {
121
+ try {
122
+ if (typeof module === "string") {
123
+ // Production: Load and cache module only via pre-calculated path.
124
+ // This avoids potential path traversal vulnerabilities caused
125
+ // by unexpected `route` values.
126
+ module = MODULE_BY_ROUTE[route] = await import(`file://${module}`);
127
+ } else if (module === undefined && NODE_ENV_IS_DEVELOPMENT) {
128
+ // Only map module paths depending on `route` during development.
123
129
  const modulePath = join(CWD, "dist", `${route}.js`);
124
- if (NODE_ENV_IS_DEVELOPMENT) {
125
- if (typeof require === "function") {
126
- // Bun: Remove module from cache before importing
127
- // as query parameter for import is ignored (see Node).
128
- if (require.cache[modulePath]) {
129
- delete require.cache[modulePath];
130
- }
131
- module = await import(`file://${modulePath}`);
132
- } else {
133
- // Node: Use timestamp as query parameter to update modules.
134
- const mtime = (await stat(modulePath)).mtime.getTime();
135
- module = await import(`file://${modulePath}?${mtime}`);
136
- }
137
- } else {
138
- // Load and cache module for non-development
139
- module = await import(`file://${modulePath}`);
140
- MODULE_BY_ROUTE[route] = module;
141
- }
142
- } catch (e) {
143
- switch (e.code) {
144
- case "ENOENT":
145
- case "ENOTDIR":
146
- case "ERR_MODULE_NOT_FOUND":
147
- continue;
148
- default:
149
- // Module exists, but fails to load.
150
- throw e;
130
+ if (typeof require === "function" && require.cache[modulePath]) {
131
+ // Bun: Remove module from cache before importing
132
+ // as query parameter for import is ignored.
133
+ delete require.cache[modulePath];
151
134
  }
135
+ // Use timestamp as query parameter to update modules.
136
+ const mtime = (await stat(modulePath)).mtime.getTime();
137
+ // Dynamic imports are restricted to development environments;
138
+ // therefore, production-level path validation is not required here.
139
+ module = await import(`file://${modulePath}?${mtime}`);
140
+ }
141
+ } catch (e) {
142
+ switch (e.code) {
143
+ case "ENOENT":
144
+ case "ENOTDIR":
145
+ case "ERR_MODULE_NOT_FOUND":
146
+ continue;
147
+ default:
148
+ // Module exists, but fails to load.
149
+ throw e;
152
150
  }
153
151
  }
154
152
 
155
- // Store current route in request
153
+ // Ensure module is a valid object before processing.
154
+ if (!module || typeof module !== "object") {
155
+ continue;
156
+ }
157
+
158
+ // Store current route in request.
156
159
  request.route = route;
157
160
 
158
- // Call functions with 'this' context and props as parameters
159
- // otherwise return default export
160
161
  response =
161
162
  typeof module.default === "function"
162
- ? await module.default.call(context, props)
163
- : module.default;
163
+ ? // Call functions with context as `this` and props as parameters,
164
+ await module.default.call(context, props)
165
+ : // otherwise return default export.
166
+ module.default;
164
167
 
165
168
  if (reply.sent) {
166
169
  return;
@@ -218,9 +221,12 @@ function generateRoutes(path: string): string[] {
218
221
  // "/a/b/c" => ["/a/b/c", "/a/b", "/a", ""]
219
222
  const segments = [""];
220
223
  let current = "";
221
- for (const segment of path.split("/").filter(Boolean)) {
222
- current += `/${segment}`;
223
- segments.push(current);
224
+ for (const segment of path.split("/")) {
225
+ // Ignore redundant slashes.
226
+ if (segment !== "") {
227
+ current += `/${segment}`;
228
+ segments.push(current);
229
+ }
224
230
  }
225
231
  segments.reverse();
226
232