jeasx 2.10.6 → 3.0.0
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 +1454 -0
- package/README.md +28 -2
- package/build.js +37 -14
- package/cli.js +7 -1
- package/env.js +7 -1
- package/package.json +4 -4
- package/serverless.js +122 -100
- package/serverless.js.map +2 -2
- package/serverless.ts +225 -151
- package/start.js +13 -1
package/README.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Jeasx - the ease of JSX with the power of SSR
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Jeasx is a server-side rendering framework for Node.js. You write pages and
|
|
4
|
+
API endpoints as async JSX functions, and Jeasx renders them on the server and
|
|
5
|
+
streams the HTML to the browser. Routing comes from your file structure.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
It is built on [Fastify](https://fastify.dev), [esbuild](https://esbuild.github.io)
|
|
8
|
+
and [jsx-async-runtime](https://github.com/jeasx/jsx-async-runtime).
|
|
9
|
+
|
|
10
|
+
This is the Jeasx core repository. For guides, examples and the project
|
|
11
|
+
template, visit <https://www.jeasx.dev>. Live examples are at
|
|
12
|
+
<https://expo.jeasx.dev>.
|
|
13
|
+
|
|
14
|
+
## Development
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install
|
|
18
|
+
npm run build # compiles serverless.ts to serverless.js
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`serverless.js` and `serverless.js.map` are committed, so rebuild and commit
|
|
22
|
+
them together after changing `serverless.ts`.
|
|
23
|
+
|
|
24
|
+
To try out changes in a full app, use
|
|
25
|
+
[jeasx-develop](https://github.com/jeasx/jeasx-develop), an ejected version of
|
|
26
|
+
[Jeasx Expo](https://expo.jeasx.dev) that runs the framework code directly
|
|
27
|
+
in the project, so it's easy to change and test.
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
[MIT](LICENSE.txt) © Maik Jablonski
|
package/build.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as esbuild from "esbuild";
|
|
2
|
-
import {
|
|
3
|
-
import { join, sep } from "node:path";
|
|
2
|
+
import { readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
4
5
|
import env from "./env.js";
|
|
5
6
|
|
|
6
7
|
env();
|
|
7
8
|
|
|
8
9
|
const CWD = process.cwd();
|
|
9
|
-
const CONFIG = (await import(
|
|
10
|
+
const CONFIG = (await import(pathToFileURL(join(CWD, "jeasx.config.js")).href)).default;
|
|
10
11
|
const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
|
|
11
12
|
|
|
12
13
|
/** @type {esbuild.BuildOptions} */
|
|
@@ -14,7 +15,6 @@ const SERVER_OPTIONS = {
|
|
|
14
15
|
entryPoints: ["src/**/[*].*"],
|
|
15
16
|
minify: !NODE_ENV_IS_DEVELOPMENT,
|
|
16
17
|
logLevel: "info",
|
|
17
|
-
color: true,
|
|
18
18
|
bundle: true,
|
|
19
19
|
outdir: "dist",
|
|
20
20
|
publicPath: "/",
|
|
@@ -22,7 +22,8 @@ const SERVER_OPTIONS = {
|
|
|
22
22
|
format: "esm",
|
|
23
23
|
platform: "neutral",
|
|
24
24
|
packages: "external",
|
|
25
|
-
|
|
25
|
+
// Options may be async, so build-only plugins can be imported lazily (see jeasx.config.js).
|
|
26
|
+
...(await CONFIG.ESBUILD_SERVER_OPTIONS?.()),
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
/** @type {esbuild.BuildOptions} */
|
|
@@ -30,7 +31,6 @@ const BROWSER_OPTIONS = {
|
|
|
30
31
|
entryPoints: ["src/**/index.*"],
|
|
31
32
|
minify: !NODE_ENV_IS_DEVELOPMENT,
|
|
32
33
|
logLevel: "info",
|
|
33
|
-
color: true,
|
|
34
34
|
bundle: true,
|
|
35
35
|
outdir: "dist",
|
|
36
36
|
publicPath: "/",
|
|
@@ -38,12 +38,16 @@ const BROWSER_OPTIONS = {
|
|
|
38
38
|
format: "esm",
|
|
39
39
|
platform: "browser",
|
|
40
40
|
packages: "bundle",
|
|
41
|
-
...CONFIG.ESBUILD_BROWSER_OPTIONS?.(),
|
|
41
|
+
...(await CONFIG.ESBUILD_BROWSER_OPTIONS?.()),
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
for (const options of [SERVER_OPTIONS, BROWSER_OPTIONS]) {
|
|
45
45
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
46
|
-
|
|
46
|
+
const context = await esbuild.context(options);
|
|
47
|
+
// watch() doesn't wait for the first build, so the server would start with an empty "dist".
|
|
48
|
+
// Build errors are already logged and fixed while watching, so don't stop on them.
|
|
49
|
+
await context.rebuild().catch(() => {});
|
|
50
|
+
await context.watch();
|
|
47
51
|
} else {
|
|
48
52
|
await esbuild.build(options);
|
|
49
53
|
}
|
|
@@ -56,23 +60,42 @@ if (!NODE_ENV_IS_DEVELOPMENT) {
|
|
|
56
60
|
/** @type {Record<string,string>} */
|
|
57
61
|
const files = {};
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
// Unlike glob, readdir includes dotfiles (e.g. ".well-known"), so the "dotfiles"
|
|
64
|
+
// option of FASTIFY_SEND_OPTIONS decides about them like in development.
|
|
65
|
+
// "dist" comes first: if a file exists in both directories, "dist" wins.
|
|
66
|
+
// Only regular files are collected; symbolic links are skipped (unlike in development).
|
|
67
|
+
const entries = [];
|
|
68
|
+
for (const directory of ["dist", "public"]) {
|
|
69
|
+
const dirents = await readdir(join(CWD, directory), {
|
|
70
|
+
recursive: true,
|
|
71
|
+
withFileTypes: true,
|
|
72
|
+
}).catch(() => []);
|
|
73
|
+
for (const dirent of dirents) {
|
|
74
|
+
const path = join(dirent.parentPath, dirent.name);
|
|
75
|
+
if (dirent.isFile()) {
|
|
76
|
+
entries.push(relative(CWD, path));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
60
82
|
const path = entry.split(sep).join("/");
|
|
61
83
|
|
|
62
84
|
// Handle server routes.
|
|
63
|
-
if (/^dist\/.*\[
|
|
85
|
+
if (/^dist\/.*\[[^/]+\]\.js$/.test(path)) {
|
|
64
86
|
routes[path.slice("dist".length, -".js".length)] = path;
|
|
65
87
|
continue;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
// Skip
|
|
69
|
-
if (/^dist\/.*\[
|
|
90
|
+
// Skip all other outputs of server routes, e.g. sourcemaps or CSS of components.
|
|
91
|
+
if (/^dist\/.*\[[^/]+\]\.[^/]+$/.test(path)) {
|
|
70
92
|
continue;
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
// Treat all other entries as static files.
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
const key = path.slice(path.indexOf("/"));
|
|
97
|
+
if (!(path.startsWith("public/") && key in files)) {
|
|
98
|
+
files[key] = path;
|
|
76
99
|
}
|
|
77
100
|
}
|
|
78
101
|
|
package/cli.js
CHANGED
|
@@ -7,20 +7,26 @@ switch (process.argv[2]) {
|
|
|
7
7
|
break;
|
|
8
8
|
|
|
9
9
|
case "build":
|
|
10
|
+
await clean();
|
|
10
11
|
await import("./build.js");
|
|
11
12
|
break;
|
|
12
13
|
|
|
13
14
|
case "dev":
|
|
14
15
|
process.env.NODE_ENV = "development";
|
|
16
|
+
await clean();
|
|
15
17
|
await import("./build.js");
|
|
16
18
|
await import("./start.js");
|
|
17
19
|
break;
|
|
18
20
|
|
|
19
21
|
case "clean":
|
|
20
|
-
await
|
|
22
|
+
await clean();
|
|
21
23
|
break;
|
|
22
24
|
|
|
23
25
|
default:
|
|
24
26
|
console.info(`💡 Usage: jeasx [start|build|dev|clean]`);
|
|
25
27
|
process.exit(1);
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
async function clean() {
|
|
31
|
+
await fs.rm("dist", { recursive: true, force: true, maxRetries: 3 });
|
|
32
|
+
}
|
package/env.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Loads environment variables from .env files into process.env.
|
|
5
|
+
* Files are read in the order below. A variable that is already set,
|
|
6
|
+
* by the environment or by an earlier file, is never overwritten:
|
|
5
7
|
*
|
|
6
8
|
* 1. .env.<NODE_ENV>.local
|
|
7
9
|
* 2. .env.<NODE_ENV>
|
|
8
10
|
* 3. .env.local
|
|
9
11
|
* 4. .env
|
|
10
12
|
* 5. .env.defaults
|
|
13
|
+
*
|
|
14
|
+
* NODE_ENV itself must come from the environment (e.g. set by "jeasx dev"):
|
|
15
|
+
* the files above are chosen before any of them is read, so a NODE_ENV
|
|
16
|
+
* set in a file doesn't select .env.<NODE_ENV>(.local).
|
|
11
17
|
*/
|
|
12
18
|
export default function env() {
|
|
13
19
|
if (typeof process.loadEnvFile === "function") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jeasx",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
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.
|
|
33
|
-
"@types/node": "26.6.
|
|
32
|
+
"@fastify/send": "4.2.0",
|
|
33
|
+
"@types/node": "26.6.6",
|
|
34
34
|
"esbuild": "0.28.2",
|
|
35
35
|
"fastify": "5.12.7",
|
|
36
|
-
"jsx-async-runtime": "
|
|
36
|
+
"jsx-async-runtime": "3.0.0"
|
|
37
37
|
},
|
|
38
38
|
"allowScripts": {
|
|
39
39
|
"esbuild": true
|
package/serverless.js
CHANGED
|
@@ -1,118 +1,119 @@
|
|
|
1
1
|
import fastifySend from "@fastify/send";
|
|
2
2
|
import fastify from "fastify";
|
|
3
|
-
import {
|
|
3
|
+
import { isJsxNode, jsxToStream } from "jsx-async-runtime";
|
|
4
4
|
import { stat } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { Readable } from "node:stream";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
import env from "./env.js";
|
|
8
9
|
env();
|
|
9
10
|
const CWD = process.cwd();
|
|
10
|
-
const CONFIG = (await import(
|
|
11
|
+
const CONFIG = (await import(pathToFileURL(join(CWD, "jeasx.config.js")).href)).default;
|
|
11
12
|
const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const METADATA = NODE_ENV_IS_DEVELOPMENT ? { routes: {}, files: {} } : (await import(pathToFileURL(join(CWD, "dist", "[--metadata--].js")).href)).default;
|
|
14
|
+
const MODULE_BY_ROUTE = Object.assign(
|
|
15
|
+
/* @__PURE__ */ Object.create(null),
|
|
16
|
+
METADATA.routes
|
|
17
|
+
);
|
|
18
|
+
const FILE_BY_PATH = Object.assign(/* @__PURE__ */ Object.create(null), METADATA.files);
|
|
19
|
+
const FASTIFY_SEND_OPTIONS = {
|
|
20
|
+
...await CONFIG.FASTIFY_SEND_OPTIONS?.(),
|
|
21
|
+
root: CWD
|
|
22
|
+
};
|
|
17
23
|
const FASTIFY_SERVER = CONFIG.FASTIFY_SERVER ?? ((fastify2) => fastify2);
|
|
18
|
-
var serverless_default = FASTIFY_SERVER(
|
|
19
|
-
fastify(CONFIG.FASTIFY_SERVER_OPTIONS?.()).decorateRequest("route", "").decorateRequest("path", "").decorateReply("file", void 0).addHook("onRequest", async (request) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
24
|
+
var serverless_default = await FASTIFY_SERVER(
|
|
25
|
+
fastify(await CONFIG.FASTIFY_SERVER_OPTIONS?.()).decorateRequest("route", "").decorateRequest("path", "").decorateReply("file", void 0).addHook("onRequest", async (request, reply) => {
|
|
26
|
+
const path = decodePath(request.url);
|
|
27
|
+
if (path === void 0) {
|
|
28
|
+
reply.code(400).send();
|
|
29
|
+
return reply;
|
|
30
|
+
}
|
|
31
|
+
request.path = path;
|
|
22
32
|
}).all("*", async (request, reply) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return payload;
|
|
29
|
-
} catch (error) {
|
|
30
|
-
request.log.error(error);
|
|
31
|
-
throw error;
|
|
33
|
+
reply.raw.once("close", () => reply.file?.stream.destroy());
|
|
34
|
+
const payload = await handler(request, reply);
|
|
35
|
+
if (reply.getHeader("content-type") === void 0 && (typeof payload === "string" || payload instanceof ReadableStream || // Static files bring their own content type (or none, e.g. for 304).
|
|
36
|
+
payload instanceof Readable && payload !== reply.file?.stream || Buffer.isBuffer(payload))) {
|
|
37
|
+
reply.type("text/html; charset=utf-8");
|
|
32
38
|
}
|
|
39
|
+
return payload;
|
|
33
40
|
})
|
|
34
41
|
);
|
|
42
|
+
const STATIC_FILE_ROUTE = /* @__PURE__ */ Symbol("static-file-route");
|
|
35
43
|
async function handler(request, reply) {
|
|
36
44
|
let response;
|
|
37
45
|
const context = {};
|
|
38
46
|
const props = { request, reply };
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
let module = MODULE_BY_ROUTE[route];
|
|
52
|
-
if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
|
|
53
|
-
continue;
|
|
47
|
+
reply.file = await tryFile(request);
|
|
48
|
+
for (const route of generateRoutes(request.path)) {
|
|
49
|
+
if (route === STATIC_FILE_ROUTE) {
|
|
50
|
+
if (reply.file) {
|
|
51
|
+
reply.status(reply.file.statusCode);
|
|
52
|
+
reply.headers(reply.file.headers);
|
|
53
|
+
response = reply.file.stream;
|
|
54
|
+
break;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
case "ENOENT":
|
|
69
|
-
case "ENOTDIR":
|
|
70
|
-
case "ERR_MODULE_NOT_FOUND":
|
|
71
|
-
continue;
|
|
72
|
-
default:
|
|
73
|
-
throw e;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let module = MODULE_BY_ROUTE[route];
|
|
59
|
+
if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
if (typeof module === "string") {
|
|
64
|
+
module = MODULE_BY_ROUTE[route] = await import(pathToFileURL(join(CWD, module)).href);
|
|
65
|
+
} else if (module === void 0 && NODE_ENV_IS_DEVELOPMENT) {
|
|
66
|
+
const modulePath = join(CWD, "dist", `${route}.js`);
|
|
67
|
+
if (typeof require === "function" && require.cache[modulePath]) {
|
|
68
|
+
delete require.cache[modulePath];
|
|
74
69
|
}
|
|
70
|
+
const mtime = (await stat(modulePath)).mtime.getTime();
|
|
71
|
+
module = await import(`${pathToFileURL(modulePath).href}?${mtime}`);
|
|
75
72
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
module.default
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
if (reply.sent) {
|
|
87
|
-
return;
|
|
88
|
-
} else if (route.endsWith("/[404]")) {
|
|
89
|
-
if (reply.statusCode === 200 && !request.path.endsWith("/404")) {
|
|
90
|
-
reply.status(404);
|
|
91
|
-
}
|
|
92
|
-
break;
|
|
93
|
-
} else if (typeof response === "string" || response instanceof Readable || Buffer.isBuffer(response) || isJSX(response)) {
|
|
94
|
-
break;
|
|
95
|
-
} else if (typeof response === "object" && route.endsWith("/[...guard]")) {
|
|
96
|
-
Object.assign(props, response);
|
|
97
|
-
continue;
|
|
98
|
-
} else if (response === void 0 || reply.statusCode === 404) {
|
|
99
|
-
continue;
|
|
100
|
-
} else {
|
|
101
|
-
break;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
switch (e?.code) {
|
|
75
|
+
case "ENOENT":
|
|
76
|
+
case "ENOTDIR":
|
|
77
|
+
continue;
|
|
78
|
+
default:
|
|
79
|
+
throw e;
|
|
102
80
|
}
|
|
103
81
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
82
|
+
request.route = route;
|
|
83
|
+
if (module && typeof module === "object") {
|
|
84
|
+
response = typeof module.default === "function" ? (
|
|
85
|
+
// Call functions with context as `this` and props as parameters,
|
|
86
|
+
await module.default.call(context, props)
|
|
87
|
+
) : (
|
|
88
|
+
// otherwise return default export.
|
|
89
|
+
module.default
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (reply.sent) {
|
|
93
|
+
return;
|
|
94
|
+
} else if (route.endsWith("/[404]")) {
|
|
95
|
+
if (reply.statusCode === 200 && !(route.slice(0, -"[404]".length) + "404" === request.path.replace(/\/$/, ""))) {
|
|
96
|
+
reply.status(404);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
} else if (typeof response === "string" || response instanceof Response || response instanceof Readable || response instanceof ReadableStream || Buffer.isBuffer(response) || isJsxNode(response)) {
|
|
100
|
+
break;
|
|
101
|
+
} else if (typeof response === "object" && route.endsWith("/[...guard]")) {
|
|
102
|
+
Object.assign(props, response);
|
|
103
|
+
response = void 0;
|
|
104
|
+
continue;
|
|
105
|
+
} else if (response === void 0) {
|
|
106
|
+
continue;
|
|
111
107
|
} else {
|
|
112
|
-
|
|
108
|
+
break;
|
|
113
109
|
}
|
|
114
110
|
}
|
|
111
|
+
if (response === void 0 && reply.statusCode === 200) {
|
|
112
|
+
reply.status(404);
|
|
113
|
+
}
|
|
114
|
+
return await renderResponse(context, response);
|
|
115
115
|
}
|
|
116
|
+
const SPECIAL_SEGMENTS = /* @__PURE__ */ new Set(["index", "...guard", "...path", "--metadata--"]);
|
|
116
117
|
function* generateRoutes(path) {
|
|
117
118
|
const segments = [""];
|
|
118
119
|
let edgeSegment = "";
|
|
@@ -125,10 +126,11 @@ function* generateRoutes(path) {
|
|
|
125
126
|
for (let i = 0; i < segments.length; i++) {
|
|
126
127
|
yield `${segments[i]}/[...guard]`;
|
|
127
128
|
}
|
|
128
|
-
yield
|
|
129
|
+
yield STATIC_FILE_ROUTE;
|
|
129
130
|
const lastSlash = edgeSegment.lastIndexOf("/") + 1;
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
const lastSegment = edgeSegment.substring(lastSlash);
|
|
132
|
+
if (lastSlash > 0 && !SPECIAL_SEGMENTS.has(lastSegment.toLowerCase())) {
|
|
133
|
+
yield `${edgeSegment.substring(0, lastSlash)}[${lastSegment}]`;
|
|
132
134
|
}
|
|
133
135
|
yield `${edgeSegment}/[index]`;
|
|
134
136
|
for (let i = segments.length - 1; i >= 0; i--) {
|
|
@@ -138,28 +140,40 @@ function* generateRoutes(path) {
|
|
|
138
140
|
yield `${segments[i]}/[404]`;
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
+
function decodePath(url) {
|
|
144
|
+
try {
|
|
145
|
+
const index = url.indexOf("?");
|
|
146
|
+
const rawPath = index === -1 ? url : url.slice(0, index);
|
|
147
|
+
const path = decodeURIComponent(rawPath).replace(/[\\/]+/g, "/");
|
|
148
|
+
if (path.includes("\0") || /(^|\/)\.\.?(\/|$)/.test(path)) {
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
return path;
|
|
152
|
+
} catch {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
143
155
|
}
|
|
144
156
|
async function renderResponse(context, response) {
|
|
145
|
-
const payload =
|
|
157
|
+
const payload = isJsxNode(response) ? await jsxToStream.call(context, response) : response;
|
|
146
158
|
const responseHandler = context["responseHandler"];
|
|
147
159
|
return typeof responseHandler === "function" ? await responseHandler.call(context, payload) : payload;
|
|
148
160
|
}
|
|
149
161
|
async function tryFile(request) {
|
|
162
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
150
165
|
const file = FILE_BY_PATH[request.path];
|
|
151
166
|
if (file) {
|
|
152
|
-
return await
|
|
167
|
+
return await sendFile(request, file);
|
|
153
168
|
}
|
|
154
169
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
155
170
|
for (const directory of ["dist", "public"]) {
|
|
171
|
+
if (directory === "dist" && /\]\./u.test(request.path)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
156
174
|
try {
|
|
157
175
|
if ((await stat(join(CWD, directory, request.path))).isFile()) {
|
|
158
|
-
return await
|
|
159
|
-
request.raw,
|
|
160
|
-
`${directory}${request.path}`,
|
|
161
|
-
FASTIFY_SEND_OPTIONS
|
|
162
|
-
);
|
|
176
|
+
return await sendFile(request, `${directory}${request.path}`);
|
|
163
177
|
}
|
|
164
178
|
} catch {
|
|
165
179
|
continue;
|
|
@@ -168,6 +182,14 @@ async function tryFile(request) {
|
|
|
168
182
|
}
|
|
169
183
|
return void 0;
|
|
170
184
|
}
|
|
185
|
+
async function sendFile(request, path) {
|
|
186
|
+
const result = await fastifySend(request.raw, encodeURI(path), FASTIFY_SEND_OPTIONS);
|
|
187
|
+
if (result.type === "error" && result.statusCode === 404) {
|
|
188
|
+
result.stream.destroy();
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
171
193
|
export {
|
|
172
194
|
serverless_default as default
|
|
173
195
|
};
|
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 { jsxToString } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\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// 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 {\n routes: MODULE_BY_ROUTE,\n files: FILE_BY_PATH,\n}: {\n routes: Record<string, string | { default: Function }>;\n files: Record<string, string>;\n} = NODE_ENV_IS_DEVELOPMENT\n ? { routes: {}, files: {} }\n : (await import(`file://${join(CWD, \"dist\", \"[--metadata--].js\")}`)).default;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /** 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\nconst FASTIFY_SEND_OPTIONS = CONFIG.FASTIFY_SEND_OPTIONS?.() as SendOptions;\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(CONFIG.FASTIFY_SERVER_OPTIONS?.() as FastifyServerOptions)\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .decorateReply(\"file\", undefined)\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: any = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n try {\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 when route matches path.\n if (route === request.path) {\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(`file://${join(CWD, 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 as any)?.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 // 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 // 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 (\n typeof response === \"string\" ||\n response instanceof Readable ||\n Buffer.isBuffer(response) ||\n isJSX(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 continue;\n } else if (response === undefined || reply.statusCode === 404) {\n continue;\n } else {\n break;\n }\n }\n\n return await renderResponse(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 renderResponse(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\",\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> {\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 verbatim path for static file serving.\n yield path;\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n if (lastSlash > 0) {\n yield `${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`;\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 * 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 renderResponse(context: any, response: unknown) {\n const payload = isJSX(response)\n ? await jsxToString.call(context, response as JSX.Element)\n : 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\n/**\n * Returns stream and metadata for requested file.\n */\nasync function tryFile(request: FastifyRequest): Promise<BaseSendResult | undefined> {\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 fastifySend(request.raw, file, FASTIFY_SEND_OPTIONS);\n }\n\n if (NODE_ENV_IS_DEVELOPMENT) {\n for (const directory of [\"dist\", \"public\"]) {\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 fastifySend(\n request.raw,\n `${directory}${request.path}`,\n FASTIFY_SEND_OPTIONS,\n );\n }\n } catch {\n continue;\n }\n }\n }\n\n return undefined;\n}\n"],
|
|
5
|
-
"mappings": "AAAA,OAAO,iBAAkD;AACzD,OAAO,aAKA;AACP,SAAS,mBAAmB;
|
|
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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",
|
|
6
6
|
"names": ["fastify"]
|
|
7
7
|
}
|