jeasx 2.10.7 → 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/README.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Jeasx - the ease of JSX with the power of SSR
2
2
 
3
- This is the `Jeasx` core repository.
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
- Please visit <https://www.jeasx.dev> to learn more.
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 { glob, stat, writeFile } from "node:fs/promises";
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(`file://${join(CWD, "jeasx.config.js")}`)).default;
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
- ...CONFIG.ESBUILD_SERVER_OPTIONS?.(),
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
- (await esbuild.context(options)).watch();
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
- for await (const entry of glob("{dist,public}/**/*")) {
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\/.*\[.+\]\.js$/.test(path)) {
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 sourcemaps for server routes.
69
- if (/^dist\/.*\[.+\]\.js\.map$/.test(path)) {
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
- if ((await stat(join(CWD, path))).isFile()) {
75
- files[path.slice(path.indexOf("/"))] = path;
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 fs.rm("dist", { recursive: true, force: true, maxRetries: 3 });
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
- * Load environment variables from .env-files into process.env in the following order:
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": "2.10.7",
3
+ "version": "3.0.0",
4
4
  "description": "Jeasx - the ease of JSX with the power of SSR",
5
5
  "keywords": [
6
6
  "async",
@@ -30,10 +30,10 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@fastify/send": "4.2.0",
33
- "@types/node": "26.6.5",
33
+ "@types/node": "26.6.6",
34
34
  "esbuild": "0.28.2",
35
35
  "fastify": "5.12.7",
36
- "jsx-async-runtime": "2.3.0"
36
+ "jsx-async-runtime": "3.0.0"
37
37
  },
38
38
  "allowScripts": {
39
39
  "esbuild": true
package/serverless.js CHANGED
@@ -1,41 +1,42 @@
1
1
  import fastifySend from "@fastify/send";
2
2
  import fastify from "fastify";
3
- import { jsxToString } from "jsx-async-runtime";
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(`file://${join(CWD, "jeasx.config.js")}`)).default;
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
- routes: MODULE_BY_ROUTE,
14
- files: FILE_BY_PATH
15
- } = NODE_ENV_IS_DEVELOPMENT ? { routes: {}, files: {} } : (await import(`file://${join(CWD, "dist", "[--metadata--].js")}`)).default;
16
- const FASTIFY_SEND_OPTIONS = CONFIG.FASTIFY_SEND_OPTIONS?.();
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 index = request.url.indexOf("?");
21
- request.path = index === -1 ? request.url : request.url.slice(0, index);
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
- let payload;
24
- try {
25
- payload = await handler(request, reply);
26
- if (reply.getHeader("content-type") === void 0 && (typeof payload === "string" || Buffer.isBuffer(payload))) {
27
- reply.type("text/html; charset=utf-8");
28
- }
29
- return payload;
30
- } catch (error) {
31
- request.log.error(error);
32
- throw error;
33
- } finally {
34
- const stream = reply.file?.stream;
35
- if (stream && stream !== payload && stream.readableFlowing === null) {
36
- stream.destroy();
37
- }
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");
38
38
  }
39
+ return payload;
39
40
  })
40
41
  );
41
42
  const STATIC_FILE_ROUTE = /* @__PURE__ */ Symbol("static-file-route");
@@ -43,83 +44,76 @@ async function handler(request, reply) {
43
44
  let response;
44
45
  const context = {};
45
46
  const props = { request, reply };
46
- try {
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;
55
- }
56
- continue;
57
- }
58
- let module = MODULE_BY_ROUTE[route];
59
- if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
60
- 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;
61
55
  }
62
- try {
63
- if (typeof module === "string") {
64
- module = MODULE_BY_ROUTE[route] = await import(`file://${join(CWD, module)}`);
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];
69
- }
70
- const mtime = (await stat(modulePath)).mtime.getTime();
71
- module = await import(`file://${modulePath}?${mtime}`);
72
- }
73
- } catch (e) {
74
- switch (e?.code) {
75
- case "ENOENT":
76
- case "ENOTDIR":
77
- case "ERR_MODULE_NOT_FOUND":
78
- continue;
79
- default:
80
- 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];
81
69
  }
70
+ const mtime = (await stat(modulePath)).mtime.getTime();
71
+ module = await import(`${pathToFileURL(modulePath).href}?${mtime}`);
82
72
  }
83
- request.route = route;
84
- if (module && typeof module === "object") {
85
- response = typeof module.default === "function" ? (
86
- // Call functions with context as `this` and props as parameters,
87
- await module.default.call(context, props)
88
- ) : (
89
- // otherwise return default export.
90
- module.default
91
- );
92
- }
93
- if (reply.sent) {
94
- return;
95
- } else if (route.endsWith("/[404]")) {
96
- if (reply.statusCode === 200 && !request.path.endsWith("/404")) {
97
- reply.status(404);
98
- }
99
- break;
100
- } else if (typeof response === "string" || response instanceof Response || response instanceof Readable || response instanceof ReadableStream || Buffer.isBuffer(response) || isJSX(response)) {
101
- break;
102
- } else if (typeof response === "object" && route.endsWith("/[...guard]")) {
103
- Object.assign(props, response);
104
- continue;
105
- } else if (response === void 0 || reply.statusCode === 404) {
106
- continue;
107
- } else {
108
- break;
73
+ } catch (e) {
74
+ switch (e?.code) {
75
+ case "ENOENT":
76
+ case "ENOTDIR":
77
+ continue;
78
+ default:
79
+ throw e;
109
80
  }
110
81
  }
111
- return await renderResponse(context, response);
112
- } catch (error) {
113
- const errorHandler = context["errorHandler"];
114
- if (typeof errorHandler === "function") {
115
- reply.status(500);
116
- response = await errorHandler.call(context, error);
117
- return await renderResponse(context, response);
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;
118
107
  } else {
119
- throw error;
108
+ break;
120
109
  }
121
110
  }
111
+ if (response === void 0 && reply.statusCode === 200) {
112
+ reply.status(404);
113
+ }
114
+ return await renderResponse(context, response);
122
115
  }
116
+ const SPECIAL_SEGMENTS = /* @__PURE__ */ new Set(["index", "...guard", "...path", "--metadata--"]);
123
117
  function* generateRoutes(path) {
124
118
  const segments = [""];
125
119
  let edgeSegment = "";
@@ -134,8 +128,9 @@ function* generateRoutes(path) {
134
128
  }
135
129
  yield STATIC_FILE_ROUTE;
136
130
  const lastSlash = edgeSegment.lastIndexOf("/") + 1;
137
- if (lastSlash > 0) {
138
- yield `${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`;
131
+ const lastSegment = edgeSegment.substring(lastSlash);
132
+ if (lastSlash > 0 && !SPECIAL_SEGMENTS.has(lastSegment.toLowerCase())) {
133
+ yield `${edgeSegment.substring(0, lastSlash)}[${lastSegment}]`;
139
134
  }
140
135
  yield `${edgeSegment}/[index]`;
141
136
  for (let i = segments.length - 1; i >= 0; i--) {
@@ -145,28 +140,40 @@ function* generateRoutes(path) {
145
140
  yield `${segments[i]}/[404]`;
146
141
  }
147
142
  }
148
- function isJSX(obj) {
149
- return !!obj && typeof obj === "object" && "type" in obj && "props" in obj;
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
+ }
150
155
  }
151
156
  async function renderResponse(context, response) {
152
- const payload = isJSX(response) ? await jsxToString.call(context, response) : response;
157
+ const payload = isJsxNode(response) ? await jsxToStream.call(context, response) : response;
153
158
  const responseHandler = context["responseHandler"];
154
159
  return typeof responseHandler === "function" ? await responseHandler.call(context, payload) : payload;
155
160
  }
156
161
  async function tryFile(request) {
162
+ if (request.method !== "GET" && request.method !== "HEAD") {
163
+ return void 0;
164
+ }
157
165
  const file = FILE_BY_PATH[request.path];
158
166
  if (file) {
159
- return await fastifySend(request.raw, file, FASTIFY_SEND_OPTIONS);
167
+ return await sendFile(request, file);
160
168
  }
161
169
  if (NODE_ENV_IS_DEVELOPMENT) {
162
170
  for (const directory of ["dist", "public"]) {
171
+ if (directory === "dist" && /\]\./u.test(request.path)) {
172
+ continue;
173
+ }
163
174
  try {
164
175
  if ((await stat(join(CWD, directory, request.path))).isFile()) {
165
- return await fastifySend(
166
- request.raw,
167
- `${directory}${request.path}`,
168
- FASTIFY_SEND_OPTIONS
169
- );
176
+ return await sendFile(request, `${directory}${request.path}`);
170
177
  }
171
178
  } catch {
172
179
  continue;
@@ -175,6 +182,14 @@ async function tryFile(request) {
175
182
  }
176
183
  return void 0;
177
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
+ }
178
193
  export {
179
194
  serverless_default as default
180
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 let payload: unknown;\n try {\n 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 } finally {\n // Close file stream if it isn't sent and nobody started reading it,\n // otherwise its file handle leaks.\n const stream = reply.file?.stream;\n if (stream && stream !== payload && stream.readableFlowing === null) {\n stream.destroy();\n }\n }\n }),\n);\n\n// Used as marker for static file routes.\nconst STATIC_FILE_ROUTE = Symbol(\"static-file-route\");\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\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(`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 Response ||\n response instanceof Readable ||\n response instanceof ReadableStream ||\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 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 * STATIC_FILE,\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 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": "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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 === 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": "AAAA,OAAO,iBAAkD;AACzD,OAAO,aAKA;AACP,SAAS,WAAW,mBAAmB;AACvC,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,OAAO,SAAS;AAEhB,IAAI;AAEJ,MAAM,MAAM,QAAQ,IAAI;AACxB,MAAM,UAAU,MAAM,OAAO,cAAc,KAAK,KAAK,iBAAiB,CAAC,EAAE,OAAO;AAChF,MAAM,0BAA0B,QAAQ,IAAI,aAAa;AAKzD,MAAM,WAGF,0BACA,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,EAAE,KACvB,MAAM,OAAO,cAAc,KAAK,KAAK,QAAQ,mBAAmB,CAAC,EAAE,OAAO;AAG/E,MAAM,kBAAkB,OAAO;AAAA,EAC7B,uBAAO,OAAO,IAAI;AAAA,EAClB,SAAS;AACX;AACA,MAAM,eAAe,OAAO,OAAO,uBAAO,OAAO,IAAI,GAAG,SAAS,KAAK;AAmBtE,MAAM,uBAAoC;AAAA,EACxC,GAAK,MAAM,OAAO,uBAAuB;AAAA,EACzC,MAAM;AACR;AAGA,MAAM,iBAAkB,OAAO,mBAAmB,CAACA,aAAYA;AAO/D,IAAO,qBAAQ,MAAM;AAAA,EACnB,QAAS,MAAM,OAAO,yBAAyB,CAA0B,EACtE,gBAAgB,SAAS,EAAE,EAC3B,gBAAgB,QAAQ,EAAE,EAC1B,cAAc,QAAQ,MAAS,EAC/B,QAAQ,aAAa,OAAO,SAAS,UAAU;AAC9C,UAAM,OAAO,WAAW,QAAQ,GAAG;AACnC,QAAI,SAAS,QAAW;AACtB,YAAM,KAAK,GAAG,EAAE,KAAK;AACrB,aAAO;AAAA,IACT;AACA,YAAQ,OAAO;AAAA,EACjB,CAAC,EACA,IAAI,KAAK,OAAO,SAAyB,UAAwB;AAIhE,UAAM,IAAI,KAAK,SAAS,MAAM,MAAM,MAAM,OAAO,QAAQ,CAAC;AAE1D,UAAM,UAAU,MAAM,QAAQ,SAAS,KAAK;AAC5C,QACE,MAAM,UAAU,cAAc,MAAM,WACnC,OAAO,YAAY,YAClB,mBAAmB;AAAA,IAElB,mBAAmB,YAAY,YAAY,MAAM,MAAM,UACxD,OAAO,SAAS,OAAO,IACzB;AACA,YAAM,KAAK,0BAA0B;AAAA,IACvC;AACA,WAAO;AAAA,EACT,CAAC;AACL;AAGA,MAAM,oBAAoB,uBAAO,mBAAmB;AAKpD,eAAe,QAAQ,SAAyB,OAAqB;AACnE,MAAI;AAGJ,QAAM,UAAe,CAAC;AAGtB,QAAM,QAAQ,EAAE,SAAS,MAAM;AAG/B,QAAM,OAAO,MAAM,QAAQ,OAAO;AAGlC,aAAW,SAAS,eAAe,QAAQ,IAAI,GAAG;AAEhD,QAAI,UAAU,mBAAmB;AAC/B,UAAI,MAAM,MAAM;AACd,cAAM,OAAO,MAAM,KAAK,UAAU;AAClC,cAAM,QAAQ,MAAM,KAAK,OAAO;AAChC,mBAAW,MAAM,KAAK;AACtB;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,SAAS,gBAAgB,KAAK;AAGlC,QAAI,WAAW,UAAa,CAAC,yBAAyB;AACpD;AAAA,IACF;AAGA,QAAI;AACF,UAAI,OAAO,WAAW,UAAU;AAI9B,iBAAS,gBAAgB,KAAK,IAAI,MAAM,OAAO,cAAc,KAAK,KAAK,MAAM,CAAC,EAAE;AAAA,MAClF,WAAW,WAAW,UAAa,yBAAyB;AAE1D,cAAM,aAAa,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK;AAClD,YAAI,OAAO,YAAY,cAAc,QAAQ,MAAM,UAAU,GAAG;AAG9D,iBAAO,QAAQ,MAAM,UAAU;AAAA,QACjC;AAEA,cAAM,SAAS,MAAM,KAAK,UAAU,GAAG,MAAM,QAAQ;AAGrD,iBAAS,MAAM,OAAO,GAAG,cAAc,UAAU,EAAE,IAAI,IAAI,KAAK;AAAA,MAClE;AAAA,IACF,SAAS,GAAG;AAIV,cAAS,GAAW,MAAM;AAAA,QACxB,KAAK;AAAA,QACL,KAAK;AACH;AAAA,QACF;AAEE,gBAAM;AAAA,MACV;AAAA,IACF;AAGA,YAAQ,QAAQ;AAGhB,QAAI,UAAU,OAAO,WAAW,UAAU;AACxC,iBACE,OAAO,OAAO,YAAY;AAAA;AAAA,QAEtB,MAAM,OAAO,QAAQ,KAAK,SAAS,KAAK;AAAA;AAAA;AAAA,QAExC,OAAO;AAAA;AAAA,IACf;AAEA,QAAI,MAAM,MAAM;AACd;AAAA,IACF,WAAW,MAAM,SAAS,QAAQ,GAAG;AAInC,UACE,MAAM,eAAe,OACrB,EAAE,MAAM,MAAM,GAAG,CAAC,QAAQ,MAAM,IAAI,UAAU,QAAQ,KAAK,QAAQ,OAAO,EAAE,IAC5E;AACA,cAAM,OAAO,GAAG;AAAA,MAClB;AACA;AAAA,IACF,WACE,OAAO,aAAa,YACpB,oBAAoB,YACpB,oBAAoB,YACpB,oBAAoB,kBACpB,OAAO,SAAS,QAAQ,KACxB,UAAU,QAAQ,GAClB;AACA;AAAA,IACF,WAAW,OAAO,aAAa,YAAY,MAAM,SAAS,aAAa,GAAG;AAExE,aAAO,OAAO,OAAO,QAAQ;AAE7B,iBAAW;AACX;AAAA,IACF,WAAW,aAAa,QAAW;AACjC;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,UAAa,MAAM,eAAe,KAAK;AACtD,UAAM,OAAO,GAAG;AAAA,EAClB;AAEA,SAAO,MAAM,eAAe,SAAS,QAAQ;AAC/C;AAKA,MAAM,mBAAmB,oBAAI,IAAI,CAAC,SAAS,YAAY,WAAW,cAAc,CAAC;AAejF,UAAU,eAAe,MAA4D;AAGnF,QAAM,WAAW,CAAC,EAAE;AACpB,MAAI,cAAc;AAClB,aAAW,WAAW,KAAK,MAAM,GAAG,GAAG;AAErC,QAAI,YAAY,IAAI;AAClB,qBAAe,IAAI,OAAO;AAC1B,eAAS,KAAK,WAAW;AAAA,IAC3B;AAAA,EACF;AAGA,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,GAAG,SAAS,CAAC,CAAC;AAAA,EACtB;AAGA,QAAM;AAKN,QAAM,YAAY,YAAY,YAAY,GAAG,IAAI;AACjD,QAAM,cAAc,YAAY,UAAU,SAAS;AAGnD,MAAI,YAAY,KAAK,CAAC,iBAAiB,IAAI,YAAY,YAAY,CAAC,GAAG;AACrE,UAAM,GAAG,YAAY,UAAU,GAAG,SAAS,CAAC,IAAI,WAAW;AAAA,EAC7D;AACA,QAAM,GAAG,WAAW;AAGpB,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,GAAG,SAAS,CAAC,CAAC;AAAA,EACtB;AAGA,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,GAAG,SAAS,CAAC,CAAC;AAAA,EACtB;AACF;AAUA,SAAS,WAAW,KAAiC;AACnD,MAAI;AACF,UAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,UAAM,UAAU,UAAU,KAAK,MAAM,IAAI,MAAM,GAAG,KAAK;AACvD,UAAM,OAAO,mBAAmB,OAAO,EAAE,QAAQ,WAAW,GAAG;AAC/D,QAAI,KAAK,SAAS,IAAI,KAAK,oBAAoB,KAAK,IAAI,GAAG;AACzD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,eAAe,SAAc,UAAmB;AAC7D,QAAM,UAAU,UAAU,QAAQ,IAAI,MAAM,YAAY,KAAK,SAAS,QAAQ,IAAI;AAElF,QAAM,kBAAkB,QAAQ,iBAAiB;AACjD,SAAO,OAAO,oBAAoB,aAC9B,MAAM,gBAAgB,KAAK,SAAS,OAAO,IAC3C;AACN;AAKA,eAAe,QAAQ,SAA8D;AAEnF,MAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,QAAQ;AACzD,WAAO;AAAA,EACT;AAKA,QAAM,OAAO,aAAa,QAAQ,IAAI;AACtC,MAAI,MAAM;AACR,WAAO,MAAM,SAAS,SAAS,IAAI;AAAA,EACrC;AAEA,MAAI,yBAAyB;AAC3B,eAAW,aAAa,CAAC,QAAQ,QAAQ,GAAG;AAI1C,UAAI,cAAc,UAAU,QAAQ,KAAK,QAAQ,IAAI,GAAG;AACtD;AAAA,MACF;AACA,UAAI;AACF,aAAK,MAAM,KAAK,KAAK,KAAK,WAAW,QAAQ,IAAI,CAAC,GAAG,OAAO,GAAG;AAG7D,iBAAO,MAAM,SAAS,SAAS,GAAG,SAAS,GAAG,QAAQ,IAAI,EAAE;AAAA,QAC9D;AAAA,MACF,QAAQ;AACN;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAe,SACb,SACA,MACqC;AAErC,QAAM,SAAS,MAAM,YAAY,QAAQ,KAAK,UAAU,IAAI,GAAG,oBAAoB;AAGnF,MAAI,OAAO,SAAS,WAAW,OAAO,eAAe,KAAK;AACxD,WAAO,OAAO,QAAQ;AACtB,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
6
6
  "names": ["fastify"]
7
7
  }