jeasx 3.0.0 → 3.0.1
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/CHANGELOG.md +31 -0
- package/README.md +3 -0
- package/package.json +4 -4
- package/serverless.js +3 -3
- package/serverless.js.map +2 -2
- package/serverless.ts +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-11-11 - Jeasx 3.0.1 released
|
|
4
|
+
|
|
5
|
+
🔒 This patch release includes a security update for Fastify ([GHSA-3qgw-9275-xf8w](https://github.com/fastify/fastify/security/advisories/GHSA-3qgw-9275-xf8w)). Please update.
|
|
6
|
+
|
|
7
|
+
### 🐛 Fixes
|
|
8
|
+
|
|
9
|
+
- **`null` from a route now means "no response".** Before, a route returning
|
|
10
|
+
`null` sent an empty 200. Now it behaves like `undefined`: the next route
|
|
11
|
+
is tried and the 404 fallback applies.
|
|
12
|
+
- **Dev server serves the same `dist` files as production.** In development,
|
|
13
|
+
a `dist` file is now hidden only when its last path segment is a route
|
|
14
|
+
output (`[name].ext`). Before, a file inside a bracketed directory, like
|
|
15
|
+
`[x].d/style.css`, was hidden in development but served in production.
|
|
16
|
+
|
|
17
|
+
### 🐛 Fixes in `jsx-async-runtime`
|
|
18
|
+
|
|
19
|
+
- **No more crash from deeply nested promises.** `jsx()` only guarded promises
|
|
20
|
+
one level deep in a plain object. A promise nested deeper (e.g.
|
|
21
|
+
`data={[{ p }]}`) could crash Node with an unhandled rejection when the
|
|
22
|
+
render failed on it. It is now marked as handled first.
|
|
23
|
+
- **Streams are unlocked after rendering.** `readStream()` never called
|
|
24
|
+
`releaseLock()`, so a `ReadableStream` child stayed locked after it was
|
|
25
|
+
fully rendered. The lock is now released when the stream ends, like
|
|
26
|
+
`for await` does.
|
|
27
|
+
|
|
28
|
+
### 📦 Dependency updates
|
|
29
|
+
|
|
30
|
+
- `fastify@5.12.8`
|
|
31
|
+
- `@fastify/send@4.2.1`
|
|
32
|
+
- `jsx-async-runtime@3.0.1`
|
|
33
|
+
|
|
3
34
|
## 2026-11-02 - Jeasx 3.0.0 released
|
|
4
35
|
|
|
5
36
|
> **The promise you made.** Await anything, stream everything.
|
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Jeasx - the ease of JSX with the power of SSR
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/jeasx)
|
|
4
|
+
[](LICENSE.txt)
|
|
5
|
+
|
|
3
6
|
Jeasx is a server-side rendering framework for Node.js. You write pages and
|
|
4
7
|
API endpoints as async JSX functions, and Jeasx renders them on the server and
|
|
5
8
|
streams the HTML to the browser. Routing comes from your file structure.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jeasx",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Jeasx - the ease of JSX with the power of SSR",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"async",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"build": "esbuild --platform=node --format=esm --sourcemap=linked --outdir=. serverless.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@fastify/send": "4.2.
|
|
32
|
+
"@fastify/send": "4.2.1",
|
|
33
33
|
"@types/node": "26.6.6",
|
|
34
34
|
"esbuild": "0.28.2",
|
|
35
|
-
"fastify": "5.12.
|
|
36
|
-
"jsx-async-runtime": "3.0.
|
|
35
|
+
"fastify": "5.12.8",
|
|
36
|
+
"jsx-async-runtime": "3.0.1"
|
|
37
37
|
},
|
|
38
38
|
"allowScripts": {
|
|
39
39
|
"esbuild": true
|
package/serverless.js
CHANGED
|
@@ -102,13 +102,13 @@ async function handler(request, reply) {
|
|
|
102
102
|
Object.assign(props, response);
|
|
103
103
|
response = void 0;
|
|
104
104
|
continue;
|
|
105
|
-
} else if (response
|
|
105
|
+
} else if (response == null) {
|
|
106
106
|
continue;
|
|
107
107
|
} else {
|
|
108
108
|
break;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
if (response
|
|
111
|
+
if (response == null && reply.statusCode === 200) {
|
|
112
112
|
reply.status(404);
|
|
113
113
|
}
|
|
114
114
|
return await renderResponse(context, response);
|
|
@@ -168,7 +168,7 @@ async function tryFile(request) {
|
|
|
168
168
|
}
|
|
169
169
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
170
170
|
for (const directory of ["dist", "public"]) {
|
|
171
|
-
if (directory === "dist" && /\]
|
|
171
|
+
if (directory === "dist" && /\[[^/]+\]\.[^/]*$/u.test(request.path)) {
|
|
172
172
|
continue;
|
|
173
173
|
}
|
|
174
174
|
try {
|
package/serverless.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["serverless.ts"],
|
|
4
|
-
"sourcesContent": ["import fastifySend, { BaseSendResult, SendOptions } from \"@fastify/send\";\nimport fastify, {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n FastifyServerOptions,\n} from \"fastify\";\nimport { isJsxNode, jsxToStream } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pathToFileURL } from \"node:url\";\nimport env from \"./env.js\";\n\nenv();\n\nconst CWD = process.cwd();\nconst CONFIG = (await import(pathToFileURL(join(CWD, \"jeasx.config.js\")).href)).default;\nconst NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === \"development\";\n\n// Map routes and files for non-development environments from metadata export.\n// Module paths are initialized at startup but overwritten\n// with resolved modules upon the first request.\nconst METADATA: {\n routes: Record<string, string | { default: Function }>;\n files: Record<string, string>;\n} = NODE_ENV_IS_DEVELOPMENT\n ? { routes: {}, files: {} }\n : (await import(pathToFileURL(join(CWD, \"dist\", \"[--metadata--].js\")).href)).default;\n\n// Without a prototype, lookups never return inherited keys like \"constructor\".\nconst MODULE_BY_ROUTE = Object.assign(\n Object.create(null),\n METADATA.routes,\n) as typeof METADATA.routes;\nconst FILE_BY_PATH = Object.assign(Object.create(null), METADATA.files) as typeof METADATA.files;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /** Decoded path without query parameters */\n path: string;\n /** Path to resolved route handler */\n route: string;\n }\n\n interface FastifyReply {\n /** Populated when serving a static file; otherwise undefined. */\n file?: BaseSendResult;\n }\n}\n\n// All config options may be sync or async functions.\n\n// Files are passed relative to CWD, so the root must always be CWD.\nconst FASTIFY_SEND_OPTIONS: SendOptions = {\n ...((await CONFIG.FASTIFY_SEND_OPTIONS?.()) as SendOptions),\n root: CWD,\n};\n\n// Enhance Fastify server from userland\nconst FASTIFY_SERVER = (CONFIG.FASTIFY_SERVER ?? ((fastify) => fastify)) as (\n fastify: FastifyInstance,\n) => FastifyInstance | Promise<FastifyInstance>;\n\n// Create and export a Fastify instance.\n// A Fastify instance is thenable: awaiting it loads the plugins registered so far\n// and resolves to the instance itself, so further plugins and routes can still be added.\nexport default await FASTIFY_SERVER(\n fastify((await CONFIG.FASTIFY_SERVER_OPTIONS?.()) as FastifyServerOptions)\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .decorateReply(\"file\", undefined)\n .addHook(\"onRequest\", async (request, reply) => {\n const path = decodePath(request.url);\n if (path === undefined) {\n reply.code(400).send();\n return reply;\n }\n request.path = path;\n })\n .all(\"*\", async (request: FastifyRequest, reply: FastifyReply) => {\n // Close the file stream once the response is finished or aborted,\n // otherwise its file handle leaks if nobody reads it. Waiting for the\n // response also covers routes which send the stream via reply.send().\n reply.raw.once(\"close\", () => reply.file?.stream.destroy());\n\n const payload = await handler(request, reply);\n if (\n reply.getHeader(\"content-type\") === undefined &&\n (typeof payload === \"string\" ||\n payload instanceof ReadableStream ||\n // Static files bring their own content type (or none, e.g. for 304).\n (payload instanceof Readable && payload !== reply.file?.stream) ||\n Buffer.isBuffer(payload))\n ) {\n reply.type(\"text/html; charset=utf-8\");\n }\n return payload;\n }),\n);\n\n// Used as marker for static file routes.\nconst STATIC_FILE_ROUTE = Symbol(\"static-file-route\");\n\n/**\n * Resolves route modules based on the request path and executes them.\n */\nasync function handler(request: FastifyRequest, reply: FastifyReply) {\n let response: unknown;\n\n // Global context object for route handlers\n const context: any = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n // Check for static file and store result for later processing.\n reply.file = await tryFile(request);\n\n // Execute route handlers for current request\n for (const route of generateRoutes(request.path)) {\n // Try to serve static file\n if (route === STATIC_FILE_ROUTE) {\n if (reply.file) {\n reply.status(reply.file.statusCode);\n reply.headers(reply.file.headers);\n response = reply.file.stream;\n break;\n }\n continue;\n }\n\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(pathToFileURL(join(CWD, module)).href);\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(`${pathToFileURL(modulePath).href}?${mtime}`);\n }\n } catch (e) {\n // Only stat() reports a missing route module (development).\n // ERR_MODULE_NOT_FOUND isn't ignored, as it is also thrown\n // for missing imports of an existing module.\n switch ((e as any)?.code) {\n case \"ENOENT\":\n case \"ENOTDIR\":\n continue;\n default:\n // Module exists, but fails to load.\n throw e;\n }\n }\n\n // Store current route in request.\n request.route = route;\n\n // Ensure module is a valid object before processing.\n if (module && typeof module === \"object\") {\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\n if (reply.sent) {\n return;\n } else if (route.endsWith(\"/[404]\")) {\n // Preserve existing status if a 404 page is requested directly\n // (e.g. \"/a/404\" answered by \"/a/[404]\", but not by \"/[404]\").\n // If no status is defined, set status to 404 automatically.\n if (\n reply.statusCode === 200 &&\n !(route.slice(0, -\"[404]\".length) + \"404\" === request.path.replace(/\\/$/, \"\"))\n ) {\n reply.status(404);\n }\n break;\n } else if (\n typeof response === \"string\" ||\n response instanceof Response ||\n response instanceof Readable ||\n response instanceof ReadableStream ||\n Buffer.isBuffer(response) ||\n isJsxNode(response)\n ) {\n break;\n } else if (typeof response === \"object\" && route.endsWith(\"/[...guard]\")) {\n // Add object entries from guard to props\n Object.assign(props, response);\n // Props are not a response: never send them if no other route answers.\n response = undefined;\n continue;\n } else if (response === undefined) {\n continue;\n } else {\n break;\n }\n }\n // No route answered and no [404] exists: don't reply with an empty 200.\n if (response === undefined && reply.statusCode === 200) {\n reply.status(404);\n }\n\n return await renderResponse(context, response);\n}\n\n// Names of special routes which must not be requested as \"/a/b/[c]\".\n// \"--metadata--\" is the route and file mapping of a production build, which must\n// not be served if the server runs in development mode on a production build.\nconst SPECIAL_SEGMENTS = new Set([\"index\", \"...guard\", \"...path\", \"--metadata--\"]);\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 * STATIC_FILE_ROUTE,\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): Generator<string | typeof STATIC_FILE_ROUTE> {\n // Transform given path into array of all its segments.\n // \"/a/b/c\" => [\"\", \"/a\", \"/a/b\", \"/a/b/c\"]\n const segments = [\"\"];\n let edgeSegment = \"\";\n for (const segment of path.split(\"/\")) {\n // Ignore redundant slashes.\n if (segment !== \"\") {\n edgeSegment += `/${segment}`;\n segments.push(edgeSegment);\n }\n }\n\n // [...guard]s are pushed from root to edge.\n for (let i = 0; i < segments.length; i++) {\n yield `${segments[i]}/[...guard]`;\n }\n\n // Append the marker for static files\n yield STATIC_FILE_ROUTE;\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n // Special routes aren't reachable by name (\"/index\", \"/...guard\", \"/...path\", \"/--metadata--\"),\n // except \"/a/404\", which is answered by \"/a/[404]\" on purpose.\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n const lastSegment = edgeSegment.substring(lastSlash);\n // Compared in lower case, as case-insensitive file systems (macOS, Windows) would\n // otherwise resolve e.g. \"/--METADATA--\" to \"[--metadata--].js\" in development.\n if (lastSlash > 0 && !SPECIAL_SEGMENTS.has(lastSegment.toLowerCase())) {\n yield `${edgeSegment.substring(0, lastSlash)}[${lastSegment}]`;\n }\n yield `${edgeSegment}/[index]`;\n\n // [...path]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[...path]`;\n }\n\n // [404]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[404]`;\n }\n}\n\n/**\n * Extracts the path from a request url (dropping the query string) and decodes it.\n * Backslashes become slashes (as in browsers) and repeated slashes are collapsed,\n * so a path like \"//evil.com\" can't be mistaken for another host, e.g. in a <base> tag.\n * Returns undefined for malformed encodings, null bytes and \".\" or \"..\" segments,\n * as browsers never send them: \"..\" could escape the project directory, and \".\"\n * would be resolved by the file system in development, but not in production.\n */\nfunction decodePath(url: string): string | undefined {\n try {\n const index = url.indexOf(\"?\");\n const rawPath = index === -1 ? url : url.slice(0, index);\n const path = decodeURIComponent(rawPath).replace(/[\\\\/]+/g, \"/\");\n if (path.includes(\"\\0\") || /(^|\\/)\\.\\.?(\\/|$)/.test(path)) {\n return undefined;\n }\n return path;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Renders JSX as stream and applies the optional response handler to the response.\n */\nasync function renderResponse(context: any, response: unknown) {\n const payload = isJsxNode(response) ? await jsxToStream.call(context, response) : response;\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\n/**\n * Returns stream and metadata for requested file.\n */\nasync function tryFile(request: FastifyRequest): Promise<BaseSendResult | undefined> {\n // Files can only be fetched; other methods (e.g. a POST to \"/index.css\") are left to the routes.\n if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n return undefined;\n }\n\n // Production: Retrieve files only from pre-initialized mapping.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `request.path` values.\n const file = FILE_BY_PATH[request.path];\n if (file) {\n return await sendFile(request, file);\n }\n\n if (NODE_ENV_IS_DEVELOPMENT) {\n for (const directory of [\"dist\", \"public\"]) {\n // Don't expose compiled server routes and their other outputs (sourcemaps, CSS).\n // Not anchored at the end, as some file systems ignore trailing dots and\n // stream names (Windows: \"[index].js.\", \"[index].js::$DATA\").\n if (directory === \"dist\" && /\\]\\./u.test(request.path)) {\n continue;\n }\n try {\n if ((await stat(join(CWD, directory, request.path))).isFile()) {\n // Dynamic path loading is restricted to development environments;\n // therefore, production-level path validation is not required here.\n return await sendFile(request, `${directory}${request.path}`);\n }\n } catch {\n continue;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Prepares a file for sending. Returns undefined if there is nothing to send,\n * so routes like [...path] or [404] can answer instead of the plain error page of fastifySend.\n */\nasync function sendFile(\n request: FastifyRequest,\n path: string,\n): Promise<BaseSendResult | undefined> {\n // fastifySend decodes the path, so it must be encoded.\n const result = await fastifySend(request.raw, encodeURI(path), FASTIFY_SEND_OPTIONS);\n // 404: file is missing or ignored, e.g. dotfiles by default.\n // Other errors (403 for denied dotfiles, 412, 416 for invalid ranges) belong to the file.\n if (result.type === \"error\" && result.statusCode === 404) {\n result.stream.destroy();\n return undefined;\n }\n return result;\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import fastifySend, { BaseSendResult, SendOptions } from \"@fastify/send\";\nimport fastify, {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n FastifyServerOptions,\n} from \"fastify\";\nimport { isJsxNode, jsxToStream } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pathToFileURL } from \"node:url\";\nimport env from \"./env.js\";\n\nenv();\n\nconst CWD = process.cwd();\nconst CONFIG = (await import(pathToFileURL(join(CWD, \"jeasx.config.js\")).href)).default;\nconst NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === \"development\";\n\n// Map routes and files for non-development environments from metadata export.\n// Module paths are initialized at startup but overwritten\n// with resolved modules upon the first request.\nconst METADATA: {\n routes: Record<string, string | { default: Function }>;\n files: Record<string, string>;\n} = NODE_ENV_IS_DEVELOPMENT\n ? { routes: {}, files: {} }\n : (await import(pathToFileURL(join(CWD, \"dist\", \"[--metadata--].js\")).href)).default;\n\n// Without a prototype, lookups never return inherited keys like \"constructor\".\nconst MODULE_BY_ROUTE = Object.assign(\n Object.create(null),\n METADATA.routes,\n) as typeof METADATA.routes;\nconst FILE_BY_PATH = Object.assign(Object.create(null), METADATA.files) as typeof METADATA.files;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /** Decoded path without query parameters */\n path: string;\n /** Path to resolved route handler */\n route: string;\n }\n\n interface FastifyReply {\n /** Populated when serving a static file; otherwise undefined. */\n file?: BaseSendResult;\n }\n}\n\n// All config options may be sync or async functions.\n\n// Files are passed relative to CWD, so the root must always be CWD.\nconst FASTIFY_SEND_OPTIONS: SendOptions = {\n ...((await CONFIG.FASTIFY_SEND_OPTIONS?.()) as SendOptions),\n root: CWD,\n};\n\n// Enhance Fastify server from userland\nconst FASTIFY_SERVER = (CONFIG.FASTIFY_SERVER ?? ((fastify) => fastify)) as (\n fastify: FastifyInstance,\n) => FastifyInstance | Promise<FastifyInstance>;\n\n// Create and export a Fastify instance.\n// A Fastify instance is thenable: awaiting it loads the plugins registered so far\n// and resolves to the instance itself, so further plugins and routes can still be added.\nexport default await FASTIFY_SERVER(\n fastify((await CONFIG.FASTIFY_SERVER_OPTIONS?.()) as FastifyServerOptions)\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .decorateReply(\"file\", undefined)\n .addHook(\"onRequest\", async (request, reply) => {\n const path = decodePath(request.url);\n if (path === undefined) {\n reply.code(400).send();\n return reply;\n }\n request.path = path;\n })\n .all(\"*\", async (request: FastifyRequest, reply: FastifyReply) => {\n // Close the file stream once the response is finished or aborted,\n // otherwise its file handle leaks if nobody reads it. Waiting for the\n // response also covers routes which send the stream via reply.send().\n reply.raw.once(\"close\", () => reply.file?.stream.destroy());\n\n const payload = await handler(request, reply);\n if (\n reply.getHeader(\"content-type\") === undefined &&\n (typeof payload === \"string\" ||\n payload instanceof ReadableStream ||\n // Static files bring their own content type (or none, e.g. for 304).\n (payload instanceof Readable && payload !== reply.file?.stream) ||\n Buffer.isBuffer(payload))\n ) {\n reply.type(\"text/html; charset=utf-8\");\n }\n return payload;\n }),\n);\n\n// Used as marker for static file routes.\nconst STATIC_FILE_ROUTE = Symbol(\"static-file-route\");\n\n/**\n * Resolves route modules based on the request path and executes them.\n */\nasync function handler(request: FastifyRequest, reply: FastifyReply) {\n let response: unknown;\n\n // Global context object for route handlers\n const context: any = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n // Check for static file and store result for later processing.\n reply.file = await tryFile(request);\n\n // Execute route handlers for current request\n for (const route of generateRoutes(request.path)) {\n // Try to serve static file\n if (route === STATIC_FILE_ROUTE) {\n if (reply.file) {\n reply.status(reply.file.statusCode);\n reply.headers(reply.file.headers);\n response = reply.file.stream;\n break;\n }\n continue;\n }\n\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(pathToFileURL(join(CWD, module)).href);\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(`${pathToFileURL(modulePath).href}?${mtime}`);\n }\n } catch (e) {\n // Only stat() reports a missing route module (development).\n // ERR_MODULE_NOT_FOUND isn't ignored, as it is also thrown\n // for missing imports of an existing module.\n switch ((e as any)?.code) {\n case \"ENOENT\":\n case \"ENOTDIR\":\n continue;\n default:\n // Module exists, but fails to load.\n throw e;\n }\n }\n\n // Store current route in request.\n request.route = route;\n\n // Ensure module is a valid object before processing.\n if (module && typeof module === \"object\") {\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\n if (reply.sent) {\n return;\n } else if (route.endsWith(\"/[404]\")) {\n // Preserve existing status if a 404 page is requested directly\n // (e.g. \"/a/404\" answered by \"/a/[404]\", but not by \"/[404]\").\n // If no status is defined, set status to 404 automatically.\n if (\n reply.statusCode === 200 &&\n !(route.slice(0, -\"[404]\".length) + \"404\" === request.path.replace(/\\/$/, \"\"))\n ) {\n reply.status(404);\n }\n break;\n } else if (\n typeof response === \"string\" ||\n response instanceof Response ||\n response instanceof Readable ||\n response instanceof ReadableStream ||\n Buffer.isBuffer(response) ||\n isJsxNode(response)\n ) {\n break;\n } else if (typeof response === \"object\" && route.endsWith(\"/[...guard]\")) {\n // Add object entries from guard to props\n Object.assign(props, response);\n // Props are not a response: never send them if no other route answers.\n response = undefined;\n continue;\n } else if (response == null) {\n continue;\n } else {\n break;\n }\n }\n // No route answered and no [404] exists: don't reply with an empty 200.\n if (response == null && reply.statusCode === 200) {\n reply.status(404);\n }\n\n return await renderResponse(context, response);\n}\n\n// Names of special routes which must not be requested as \"/a/b/[c]\".\n// \"--metadata--\" is the route and file mapping of a production build, which must\n// not be served if the server runs in development mode on a production build.\nconst SPECIAL_SEGMENTS = new Set([\"index\", \"...guard\", \"...path\", \"--metadata--\"]);\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 * STATIC_FILE_ROUTE,\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): Generator<string | typeof STATIC_FILE_ROUTE> {\n // Transform given path into array of all its segments.\n // \"/a/b/c\" => [\"\", \"/a\", \"/a/b\", \"/a/b/c\"]\n const segments = [\"\"];\n let edgeSegment = \"\";\n for (const segment of path.split(\"/\")) {\n // Ignore redundant slashes.\n if (segment !== \"\") {\n edgeSegment += `/${segment}`;\n segments.push(edgeSegment);\n }\n }\n\n // [...guard]s are pushed from root to edge.\n for (let i = 0; i < segments.length; i++) {\n yield `${segments[i]}/[...guard]`;\n }\n\n // Append the marker for static files\n yield STATIC_FILE_ROUTE;\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n // Special routes aren't reachable by name (\"/index\", \"/...guard\", \"/...path\", \"/--metadata--\"),\n // except \"/a/404\", which is answered by \"/a/[404]\" on purpose.\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n const lastSegment = edgeSegment.substring(lastSlash);\n // Compared in lower case, as case-insensitive file systems (macOS, Windows) would\n // otherwise resolve e.g. \"/--METADATA--\" to \"[--metadata--].js\" in development.\n if (lastSlash > 0 && !SPECIAL_SEGMENTS.has(lastSegment.toLowerCase())) {\n yield `${edgeSegment.substring(0, lastSlash)}[${lastSegment}]`;\n }\n yield `${edgeSegment}/[index]`;\n\n // [...path]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[...path]`;\n }\n\n // [404]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[404]`;\n }\n}\n\n/**\n * Extracts the path from a request url (dropping the query string) and decodes it.\n * Backslashes become slashes (as in browsers) and repeated slashes are collapsed,\n * so a path like \"//evil.com\" can't be mistaken for another host, e.g. in a <base> tag.\n * Returns undefined for malformed encodings, null bytes and \".\" or \"..\" segments,\n * as browsers never send them: \"..\" could escape the project directory, and \".\"\n * would be resolved by the file system in development, but not in production.\n */\nfunction decodePath(url: string): string | undefined {\n try {\n const index = url.indexOf(\"?\");\n const rawPath = index === -1 ? url : url.slice(0, index);\n const path = decodeURIComponent(rawPath).replace(/[\\\\/]+/g, \"/\");\n if (path.includes(\"\\0\") || /(^|\\/)\\.\\.?(\\/|$)/.test(path)) {\n return undefined;\n }\n return path;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Renders JSX as stream and applies the optional response handler to the response.\n */\nasync function renderResponse(context: any, response: unknown) {\n const payload = isJsxNode(response) ? await jsxToStream.call(context, response) : response;\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\n/**\n * Returns stream and metadata for requested file.\n */\nasync function tryFile(request: FastifyRequest): Promise<BaseSendResult | undefined> {\n // Files can only be fetched; other methods (e.g. a POST to \"/index.css\") are left to the routes.\n if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n return undefined;\n }\n\n // Production: Retrieve files only from pre-initialized mapping.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `request.path` values.\n const file = FILE_BY_PATH[request.path];\n if (file) {\n return await sendFile(request, file);\n }\n\n if (NODE_ENV_IS_DEVELOPMENT) {\n for (const directory of [\"dist\", \"public\"]) {\n // Don't expose compiled server routes and their other outputs (sourcemaps, CSS).\n // Only the last segment counts, like in the production build (see build.js).\n // The suffix after \"].\" is not anchored, as some file systems ignore trailing\n // dots and stream names (Windows: \"[index].js.\", \"[index].js::$DATA\").\n if (directory === \"dist\" && /\\[[^/]+\\]\\.[^/]*$/u.test(request.path)) {\n continue;\n }\n try {\n if ((await stat(join(CWD, directory, request.path))).isFile()) {\n // Dynamic path loading is restricted to development environments;\n // therefore, production-level path validation is not required here.\n return await sendFile(request, `${directory}${request.path}`);\n }\n } catch {\n continue;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Prepares a file for sending. Returns undefined if there is nothing to send,\n * so routes like [...path] or [404] can answer instead of the plain error page of fastifySend.\n */\nasync function sendFile(\n request: FastifyRequest,\n path: string,\n): Promise<BaseSendResult | undefined> {\n // fastifySend decodes the path, so it must be encoded.\n const result = await fastifySend(request.raw, encodeURI(path), FASTIFY_SEND_OPTIONS);\n // 404: file is missing or ignored, e.g. dotfiles by default.\n // Other errors (403 for denied dotfiles, 412, 416 for invalid ranges) belong to the file.\n if (result.type === \"error\" && result.statusCode === 404) {\n result.stream.destroy();\n return undefined;\n }\n return result;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["fastify"]
|
|
7
7
|
}
|
package/serverless.ts
CHANGED
|
@@ -214,14 +214,14 @@ async function handler(request: FastifyRequest, reply: FastifyReply) {
|
|
|
214
214
|
// Props are not a response: never send them if no other route answers.
|
|
215
215
|
response = undefined;
|
|
216
216
|
continue;
|
|
217
|
-
} else if (response
|
|
217
|
+
} else if (response == null) {
|
|
218
218
|
continue;
|
|
219
219
|
} else {
|
|
220
220
|
break;
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
// No route answered and no [404] exists: don't reply with an empty 200.
|
|
224
|
-
if (response
|
|
224
|
+
if (response == null && reply.statusCode === 200) {
|
|
225
225
|
reply.status(404);
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -344,9 +344,10 @@ async function tryFile(request: FastifyRequest): Promise<BaseSendResult | undefi
|
|
|
344
344
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
345
345
|
for (const directory of ["dist", "public"]) {
|
|
346
346
|
// Don't expose compiled server routes and their other outputs (sourcemaps, CSS).
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
|
|
347
|
+
// Only the last segment counts, like in the production build (see build.js).
|
|
348
|
+
// The suffix after "]." is not anchored, as some file systems ignore trailing
|
|
349
|
+
// dots and stream names (Windows: "[index].js.", "[index].js::$DATA").
|
|
350
|
+
if (directory === "dist" && /\[[^/]+\]\.[^/]*$/u.test(request.path)) {
|
|
350
351
|
continue;
|
|
351
352
|
}
|
|
352
353
|
try {
|