jeasx 2.7.0 → 2.7.2
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/esbuild.config.js +52 -36
- package/package.json +2 -2
- package/serverless.js +37 -42
- package/serverless.js.map +2 -2
- package/serverless.ts +65 -67
package/esbuild.config.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as esbuild from "esbuild";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import env from "./env.js";
|
|
4
5
|
|
|
5
6
|
env();
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const CWD = process.cwd();
|
|
9
|
+
const CONFIG = (await import(`file://${join(CWD, "jeasx.config.js")}`)).default;
|
|
10
|
+
const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
|
|
8
11
|
const BUILD_TIME = `"${process.env.BUILD_TIME || Date.now().toString(36)}"`;
|
|
9
12
|
|
|
10
13
|
const BROWSER_PUBLIC_ENV = Object.keys(process.env)
|
|
@@ -17,43 +20,56 @@ const BROWSER_PUBLIC_ENV = Object.keys(process.env)
|
|
|
17
20
|
Object({ "process.env.BROWSER_PUBLIC_BUILD_TIME": BUILD_TIME }),
|
|
18
21
|
);
|
|
19
22
|
|
|
20
|
-
/** @type esbuild.BuildOptions
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
entryPoints: ["src/**/index.*"],
|
|
39
|
-
define: BROWSER_PUBLIC_ENV,
|
|
40
|
-
minify: process.env.NODE_ENV !== "development",
|
|
41
|
-
logLevel: "info",
|
|
42
|
-
color: true,
|
|
43
|
-
bundle: true,
|
|
44
|
-
outdir: "dist",
|
|
45
|
-
publicPath: "/",
|
|
46
|
-
assetNames: "[dir]/[name]-[hash]",
|
|
47
|
-
platform: "browser",
|
|
48
|
-
format: "esm",
|
|
49
|
-
...CONFIG.ESBUILD_BROWSER_OPTIONS?.(),
|
|
50
|
-
},
|
|
51
|
-
];
|
|
23
|
+
/** @type esbuild.BuildOptions */
|
|
24
|
+
const SERVER_OPTIONS = {
|
|
25
|
+
entryPoints: ["src/**/[*].*"],
|
|
26
|
+
define: { "process.env.BUILD_TIME": BUILD_TIME },
|
|
27
|
+
minify: !NODE_ENV_IS_DEVELOPMENT,
|
|
28
|
+
logLevel: "info",
|
|
29
|
+
color: true,
|
|
30
|
+
bundle: true,
|
|
31
|
+
metafile: true,
|
|
32
|
+
outdir: "dist",
|
|
33
|
+
publicPath: "/",
|
|
34
|
+
assetNames: "[dir]/[name]-[hash]",
|
|
35
|
+
platform: "neutral",
|
|
36
|
+
format: "esm",
|
|
37
|
+
packages: "external",
|
|
38
|
+
...CONFIG.ESBUILD_SERVER_OPTIONS?.(),
|
|
39
|
+
};
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
/** @type esbuild.BuildOptions */
|
|
42
|
+
const BROWSER_OPTIONS = {
|
|
43
|
+
entryPoints: ["src/**/index.*"],
|
|
44
|
+
define: BROWSER_PUBLIC_ENV,
|
|
45
|
+
minify: !NODE_ENV_IS_DEVELOPMENT,
|
|
46
|
+
logLevel: "info",
|
|
47
|
+
color: true,
|
|
48
|
+
bundle: true,
|
|
49
|
+
outdir: "dist",
|
|
50
|
+
publicPath: "/",
|
|
51
|
+
assetNames: "[dir]/[name]-[hash]",
|
|
52
|
+
platform: "browser",
|
|
53
|
+
format: "esm",
|
|
54
|
+
...CONFIG.ESBUILD_BROWSER_OPTIONS?.(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
[SERVER_OPTIONS, BROWSER_OPTIONS].forEach(async (options) => {
|
|
58
|
+
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
55
59
|
(await esbuild.context(options)).watch();
|
|
56
60
|
} else {
|
|
57
|
-
await esbuild.build(options);
|
|
61
|
+
const result = await esbuild.build(options);
|
|
62
|
+
if (options === SERVER_OPTIONS) {
|
|
63
|
+
// Create metadata file with all routes
|
|
64
|
+
if (result.metafile?.outputs) {
|
|
65
|
+
const routes = Object.keys(result.metafile.outputs)
|
|
66
|
+
.filter((path) => /\[.+\]\.js$/.test(path))
|
|
67
|
+
.map((path) => path.slice("dist".length, -".js".length));
|
|
68
|
+
await writeFile(
|
|
69
|
+
join(CWD, "dist", `[--metadata--].js`),
|
|
70
|
+
`export default ${JSON.stringify({ routes })};`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
58
74
|
}
|
|
59
75
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jeasx",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
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.
|
|
36
|
+
"@types/node": "25.9.2",
|
|
37
37
|
"esbuild": "0.28.0",
|
|
38
38
|
"fastify": "5.8.5",
|
|
39
39
|
"jsx-async-runtime": "2.1.2"
|
package/serverless.js
CHANGED
|
@@ -5,13 +5,17 @@ import fastifyStatic from "@fastify/static";
|
|
|
5
5
|
import fastify from "fastify";
|
|
6
6
|
import { jsxToString } from "jsx-async-runtime";
|
|
7
7
|
import { stat } from "node:fs/promises";
|
|
8
|
-
import { freemem } from "node:os";
|
|
9
8
|
import { join } from "node:path";
|
|
10
9
|
import env from "./env.js";
|
|
11
10
|
env();
|
|
12
|
-
const
|
|
11
|
+
const CWD = process.cwd();
|
|
12
|
+
const CONFIG = (await import(`file://${join(CWD, "jeasx.config.js")}`)).default;
|
|
13
13
|
const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
|
|
14
|
-
const
|
|
14
|
+
const MODULE_BY_ROUTE = {};
|
|
15
|
+
if (!NODE_ENV_IS_DEVELOPMENT) {
|
|
16
|
+
const { routes } = (await import(`file://${join(CWD, "dist", "[--metadata--].js")}`)).default;
|
|
17
|
+
routes.forEach((route) => MODULE_BY_ROUTE[route] = null);
|
|
18
|
+
}
|
|
15
19
|
const FASTIFY_SERVER = CONFIG.FASTIFY_SERVER ?? ((fastify2) => fastify2);
|
|
16
20
|
var serverless_default = FASTIFY_SERVER(
|
|
17
21
|
fastify({
|
|
@@ -25,10 +29,9 @@ var serverless_default = FASTIFY_SERVER(
|
|
|
25
29
|
}).register(fastifyMultipart, {
|
|
26
30
|
...CONFIG.FASTIFY_MULTIPART_OPTIONS?.()
|
|
27
31
|
}).register(fastifyStatic, {
|
|
28
|
-
root: ["public", "dist"].map((dir) => join(
|
|
32
|
+
root: ["public", "dist"].map((dir) => join(CWD, dir)),
|
|
29
33
|
wildcard: false,
|
|
30
34
|
globIgnore: ["/**/\\[*\\].js?(.map)"],
|
|
31
|
-
// ignore server routes
|
|
32
35
|
...CONFIG.FASTIFY_STATIC_OPTIONS?.()
|
|
33
36
|
}).decorateRequest("route", "").decorateRequest("path", "").addHook("onRequest", async (request) => {
|
|
34
37
|
const index = request.url.indexOf("?");
|
|
@@ -46,20 +49,19 @@ var serverless_default = FASTIFY_SERVER(
|
|
|
46
49
|
}
|
|
47
50
|
});
|
|
48
51
|
});
|
|
49
|
-
const modules = /* @__PURE__ */ new Map();
|
|
50
52
|
async function handler(request, reply) {
|
|
51
53
|
let response;
|
|
52
54
|
const context = {};
|
|
53
55
|
const props = { request, reply };
|
|
54
56
|
try {
|
|
55
57
|
for (const route of generateRoutes(request.path)) {
|
|
56
|
-
let module =
|
|
57
|
-
if (module ===
|
|
58
|
+
let module = MODULE_BY_ROUTE[route];
|
|
59
|
+
if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
|
|
58
60
|
continue;
|
|
59
61
|
}
|
|
60
|
-
if (module === void 0) {
|
|
62
|
+
if (module === null || NODE_ENV_IS_DEVELOPMENT && module === void 0) {
|
|
61
63
|
try {
|
|
62
|
-
const modulePath = join(
|
|
64
|
+
const modulePath = join(CWD, "dist", `${route}.js`);
|
|
63
65
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
64
66
|
if (typeof require === "function") {
|
|
65
67
|
if (require.cache[modulePath]) {
|
|
@@ -72,29 +74,21 @@ async function handler(request, reply) {
|
|
|
72
74
|
}
|
|
73
75
|
} else {
|
|
74
76
|
module = await import(`file://${modulePath}`);
|
|
75
|
-
|
|
77
|
+
MODULE_BY_ROUTE[route] = module;
|
|
76
78
|
}
|
|
77
79
|
} catch (e) {
|
|
78
80
|
switch (e.code) {
|
|
79
81
|
case "ENOENT":
|
|
80
82
|
case "ENOTDIR":
|
|
81
83
|
case "ERR_MODULE_NOT_FOUND":
|
|
82
|
-
if (!NODE_ENV_IS_DEVELOPMENT) {
|
|
83
|
-
modules.set(route, null);
|
|
84
|
-
}
|
|
85
84
|
continue;
|
|
86
85
|
default:
|
|
87
86
|
throw e;
|
|
88
87
|
}
|
|
89
|
-
} finally {
|
|
90
|
-
if (modules.size > ROUTE_CACHE_LIMIT) {
|
|
91
|
-
modules.delete(modules.keys().next().value);
|
|
92
|
-
}
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
request.route = route;
|
|
96
|
-
response =
|
|
97
|
-
typeof module.default === "function" ? await module.default.call(context, props) : module.default;
|
|
91
|
+
response = typeof module.default === "function" ? await module.default.call(context, props) : module.default;
|
|
98
92
|
if (reply.sent) {
|
|
99
93
|
return;
|
|
100
94
|
} else if (route.endsWith("/[404]")) {
|
|
@@ -126,29 +120,30 @@ async function handler(request, reply) {
|
|
|
126
120
|
}
|
|
127
121
|
}
|
|
128
122
|
function generateRoutes(path) {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
123
|
+
const routes = [];
|
|
124
|
+
const segments = [""];
|
|
125
|
+
let current = "";
|
|
126
|
+
for (const segment of path.split("/").filter(Boolean)) {
|
|
127
|
+
current += `/${segment}`;
|
|
128
|
+
segments.push(current);
|
|
129
|
+
}
|
|
130
|
+
segments.reverse();
|
|
131
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
132
|
+
routes.push(`${segments[i]}/[...guard]`);
|
|
133
|
+
}
|
|
134
|
+
const edgeSegment = segments[0];
|
|
135
|
+
const lastSlash = edgeSegment.lastIndexOf("/") + 1;
|
|
136
|
+
if (lastSlash > 0) {
|
|
137
|
+
routes.push(`${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`);
|
|
138
|
+
}
|
|
139
|
+
routes.push(`${edgeSegment}/[index]`);
|
|
140
|
+
for (let i = 0; i < segments.length; i++) {
|
|
141
|
+
routes.push(`${segments[i]}/[...path]`);
|
|
142
|
+
}
|
|
143
|
+
for (let i = 0; i < segments.length; i++) {
|
|
144
|
+
routes.push(`${segments[i]}/[404]`);
|
|
149
145
|
}
|
|
150
|
-
|
|
151
|
-
return edges;
|
|
146
|
+
return routes;
|
|
152
147
|
}
|
|
153
148
|
function isJSX(obj) {
|
|
154
149
|
return !!obj && typeof obj === "object" && "type" in obj && "props" in obj;
|
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 {
|
|
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,
|
|
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": "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",
|
|
6
6
|
"names": ["fastify"]
|
|
7
7
|
}
|
package/serverless.ts
CHANGED
|
@@ -10,15 +10,27 @@ import fastify, {
|
|
|
10
10
|
} from "fastify";
|
|
11
11
|
import { jsxToString } from "jsx-async-runtime";
|
|
12
12
|
import { stat } from "node:fs/promises";
|
|
13
|
-
import { freemem } from "node:os";
|
|
14
13
|
import { join } from "node:path";
|
|
15
14
|
import env from "./env.js";
|
|
16
15
|
|
|
17
16
|
env();
|
|
18
17
|
|
|
19
|
-
const
|
|
18
|
+
const CWD = process.cwd();
|
|
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 }> = {};
|
|
24
|
+
|
|
25
|
+
// Initialize the cache with `null` for all known modules.
|
|
26
|
+
// Modules are lazily loaded on their first request for a specific route.
|
|
27
|
+
// Only routes explicitly initialized with `null` will be loaded.
|
|
28
|
+
if (!NODE_ENV_IS_DEVELOPMENT) {
|
|
29
|
+
const { routes } = (await import(`file://${join(CWD, "dist", "[--metadata--].js")}`)).default as {
|
|
30
|
+
routes: string[];
|
|
31
|
+
};
|
|
32
|
+
routes.forEach((route) => (MODULE_BY_ROUTE[route] = null));
|
|
33
|
+
}
|
|
22
34
|
|
|
23
35
|
declare module "fastify" {
|
|
24
36
|
interface FastifyRequest {
|
|
@@ -51,9 +63,9 @@ export default FASTIFY_SERVER(
|
|
|
51
63
|
...(CONFIG.FASTIFY_MULTIPART_OPTIONS?.() as FastifyMultipartOptions),
|
|
52
64
|
})
|
|
53
65
|
.register(fastifyStatic, {
|
|
54
|
-
root: ["public", "dist"].map((dir) => join(
|
|
66
|
+
root: ["public", "dist"].map((dir) => join(CWD, dir)),
|
|
55
67
|
wildcard: false,
|
|
56
|
-
globIgnore: ["/**/\\[*\\].js?(.map)"],
|
|
68
|
+
globIgnore: ["/**/\\[*\\].js?(.map)"],
|
|
57
69
|
...(CONFIG.FASTIFY_STATIC_OPTIONS?.() as FastifyStaticOptions),
|
|
58
70
|
})
|
|
59
71
|
.decorateRequest("route", "")
|
|
@@ -80,9 +92,6 @@ export default FASTIFY_SERVER(
|
|
|
80
92
|
});
|
|
81
93
|
});
|
|
82
94
|
|
|
83
|
-
// Cache for resolved route modules, 'null' means no module exists.
|
|
84
|
-
const modules = new Map<string, { default: Function }>();
|
|
85
|
-
|
|
86
95
|
/**
|
|
87
96
|
* Resolves route module based on the request path and execute it.
|
|
88
97
|
*/
|
|
@@ -99,17 +108,19 @@ async function handler(request: FastifyRequest, reply: FastifyReply) {
|
|
|
99
108
|
// Execute route handlers for current request
|
|
100
109
|
for (const route of generateRoutes(request.path)) {
|
|
101
110
|
// Resolve module via cache
|
|
102
|
-
let module =
|
|
111
|
+
let module = MODULE_BY_ROUTE[route];
|
|
103
112
|
|
|
104
|
-
//
|
|
105
|
-
|
|
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.
|
|
116
|
+
if (module === undefined && !NODE_ENV_IS_DEVELOPMENT) {
|
|
106
117
|
continue;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
// Module was not loaded yet?
|
|
110
|
-
if (module === undefined) {
|
|
121
|
+
if (module === null || (NODE_ENV_IS_DEVELOPMENT && module === undefined)) {
|
|
111
122
|
try {
|
|
112
|
-
const modulePath = join(
|
|
123
|
+
const modulePath = join(CWD, "dist", `${route}.js`);
|
|
113
124
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
114
125
|
if (typeof require === "function") {
|
|
115
126
|
// Bun: Remove module from cache before importing
|
|
@@ -126,38 +137,30 @@ async function handler(request: FastifyRequest, reply: FastifyReply) {
|
|
|
126
137
|
} else {
|
|
127
138
|
// Load and cache module for non-development
|
|
128
139
|
module = await import(`file://${modulePath}`);
|
|
129
|
-
|
|
140
|
+
MODULE_BY_ROUTE[route] = module;
|
|
130
141
|
}
|
|
131
142
|
} catch (e) {
|
|
132
143
|
switch (e.code) {
|
|
133
144
|
case "ENOENT":
|
|
134
145
|
case "ENOTDIR":
|
|
135
146
|
case "ERR_MODULE_NOT_FOUND":
|
|
136
|
-
if (!NODE_ENV_IS_DEVELOPMENT) {
|
|
137
|
-
// Cache module as not found
|
|
138
|
-
modules.set(route, null);
|
|
139
|
-
}
|
|
140
147
|
continue;
|
|
141
148
|
default:
|
|
142
149
|
// Module exists, but fails to load.
|
|
143
150
|
throw e;
|
|
144
151
|
}
|
|
145
|
-
} finally {
|
|
146
|
-
// Remove oldest entry from cache if limit is reached
|
|
147
|
-
if (modules.size > ROUTE_CACHE_LIMIT) {
|
|
148
|
-
modules.delete(modules.keys().next().value);
|
|
149
|
-
}
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
// Store current route in request
|
|
154
156
|
request.route = route;
|
|
155
157
|
|
|
158
|
+
// Call functions with 'this' context and props as parameters
|
|
159
|
+
// otherwise return default export
|
|
156
160
|
response =
|
|
157
|
-
// Call functions with 'this' context and props as parameters
|
|
158
161
|
typeof module.default === "function"
|
|
159
162
|
? await module.default.call(context, props)
|
|
160
|
-
: module.default;
|
|
163
|
+
: module.default;
|
|
161
164
|
|
|
162
165
|
if (reply.sent) {
|
|
163
166
|
return;
|
|
@@ -198,56 +201,51 @@ async function handler(request: FastifyRequest, reply: FastifyReply) {
|
|
|
198
201
|
|
|
199
202
|
/**
|
|
200
203
|
* Generates all possible routes based on the given input path.
|
|
204
|
+
*
|
|
205
|
+
* Example routes for "/a/b/c":
|
|
206
|
+
*
|
|
207
|
+
* [
|
|
208
|
+
* "/[...guard]","/a/[...guard]","/a/b/[...guard]","/a/b/c/[...guard]",
|
|
209
|
+
* "/a/b/[c]","/a/b/c/[index]",
|
|
210
|
+
* "/a/b/c/[...path]","/a/b/[...path]","/a/[...path]","/[...path]",
|
|
211
|
+
* "/a/b/c/[404]","/a/b/[404]","/a/[404]","/[404]"
|
|
212
|
+
* ]
|
|
201
213
|
*/
|
|
202
214
|
function generateRoutes(path: string): string[] {
|
|
215
|
+
const routes = [];
|
|
216
|
+
|
|
217
|
+
// Transform given path into array of all its segments.
|
|
203
218
|
// "/a/b/c" => ["/a/b/c", "/a/b", "/a", ""]
|
|
204
|
-
const segments =
|
|
219
|
+
const segments = [""];
|
|
220
|
+
let current = "";
|
|
221
|
+
for (const segment of path.split("/").filter(Boolean)) {
|
|
222
|
+
current += `/${segment}`;
|
|
223
|
+
segments.push(current);
|
|
224
|
+
}
|
|
225
|
+
segments.reverse();
|
|
205
226
|
|
|
206
|
-
//
|
|
207
|
-
|
|
227
|
+
// [...guard]s are evaluated from top to bottom
|
|
228
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
229
|
+
routes.push(`${segments[i]}/[...guard]`);
|
|
230
|
+
}
|
|
208
231
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
];
|
|
217
|
-
}
|
|
232
|
+
// "/a/b/c" => ["/a/b/[c]", "/a/b/c/[index]"]
|
|
233
|
+
const edgeSegment = segments[0];
|
|
234
|
+
const lastSlash = edgeSegment.lastIndexOf("/") + 1;
|
|
235
|
+
if (lastSlash > 0) {
|
|
236
|
+
routes.push(`${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`);
|
|
237
|
+
}
|
|
238
|
+
routes.push(`${edgeSegment}/[index]`);
|
|
218
239
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
* @example
|
|
223
|
-
* generateSegments("/a/b/c") => ["/a/b/c", "/a/b", "/a", ""]
|
|
224
|
-
*/
|
|
225
|
-
function generateSegments(path: string): string[] {
|
|
226
|
-
return path
|
|
227
|
-
.split("/")
|
|
228
|
-
.filter((segment) => segment !== "")
|
|
229
|
-
.reduce((acc, segment) => {
|
|
230
|
-
acc.push((acc.length > 0 ? acc[acc.length - 1] : "") + "/" + segment);
|
|
231
|
-
return acc;
|
|
232
|
-
}, [])
|
|
233
|
-
.reverse()
|
|
234
|
-
.concat("");
|
|
235
|
-
}
|
|
240
|
+
for (let i = 0; i < segments.length; i++) {
|
|
241
|
+
routes.push(`${segments[i]}/[...path]`);
|
|
242
|
+
}
|
|
236
243
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
*
|
|
240
|
-
* An edge is either a route with a named segment (e.g. "/a/b/[c]")
|
|
241
|
-
* or a route with an "index" segment (e.g. "/a/b/c/[index]").
|
|
242
|
-
*/
|
|
243
|
-
function generateEdges(path: string): string[] {
|
|
244
|
-
const edges = [];
|
|
245
|
-
if (path) {
|
|
246
|
-
const lastSegment = path.lastIndexOf("/") + 1;
|
|
247
|
-
edges.push(`${path.substring(0, lastSegment)}[${path.substring(lastSegment)}]`);
|
|
244
|
+
for (let i = 0; i < segments.length; i++) {
|
|
245
|
+
routes.push(`${segments[i]}/[404]`);
|
|
248
246
|
}
|
|
249
|
-
|
|
250
|
-
return
|
|
247
|
+
|
|
248
|
+
return routes;
|
|
251
249
|
}
|
|
252
250
|
|
|
253
251
|
/**
|