elegance-js 2.1.36 → 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/bin/bootstrap.js +18 -0
- package/bin/run.js +2 -0
- package/dist/build/common.d.ts +147 -0
- package/dist/build/common.d.ts.map +1 -0
- package/dist/build/common.js +599 -0
- package/dist/build/dev.d.ts +2 -0
- package/dist/build/dev.d.ts.map +1 -0
- package/dist/build/dev.js +234 -0
- package/dist/build/prod.d.ts +2 -0
- package/dist/build/prod.d.ts.map +1 -0
- package/dist/build/prod.js +212 -0
- package/dist/build/render.d.ts +29 -0
- package/dist/build/render.d.ts.map +1 -0
- package/dist/build/render.js +234 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +677 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +80 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +20 -0
- package/dist/elements.d.ts +2 -0
- package/dist/elements.d.ts.map +1 -0
- package/dist/elements.js +14 -0
- package/dist/error.d.ts +20 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +123 -0
- package/dist/globals.d.ts +6 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +106 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +72 -0
- package/dist/page-tools.d.ts +19 -0
- package/dist/page-tools.d.ts.map +1 -0
- package/dist/page-tools.js +141 -0
- package/dist/processing/oxc.d.ts +17 -0
- package/dist/processing/oxc.d.ts.map +1 -0
- package/dist/processing/oxc.js +938 -0
- package/dist/processing/taglist.d.ts +2 -0
- package/dist/processing/taglist.d.ts.map +1 -0
- package/dist/processing/taglist.js +215 -0
- package/dist/processing/tsx.d.ts +2 -0
- package/dist/processing/tsx.d.ts.map +1 -0
- package/dist/processing/tsx.js +131 -0
- package/dist/run.d.ts +3 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +147 -0
- package/dist/server/dev.d.ts +2 -0
- package/dist/server/dev.d.ts.map +1 -0
- package/dist/server/dev.js +10 -0
- package/dist/server/prod.d.ts +2 -0
- package/dist/server/prod.d.ts.map +1 -0
- package/dist/server/prod.js +42 -0
- package/dist/server/security.d.ts +64 -0
- package/dist/server/security.d.ts.map +1 -0
- package/dist/server/security.js +120 -0
- package/dist/server/server.d.ts +73 -99
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +830 -680
- package/dist/types/component.d.ts +85 -0
- package/dist/types/component.d.ts.map +1 -0
- package/dist/types/component.js +0 -0
- package/dist/types/config.d.ts +12 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +0 -0
- package/dist/types/elements.d.ts +412 -0
- package/dist/types/elements.d.ts.map +1 -0
- package/dist/types/elements.js +0 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/jsx.d.ts +976 -0
- package/dist/types/jsx.d.ts.map +1 -0
- package/dist/types/jsx.js +0 -0
- package/dist/types/server-actions.d.ts +60 -0
- package/dist/types/server-actions.d.ts.map +1 -0
- package/dist/types/server-actions.js +0 -0
- package/dist/user-utils.d.ts +23 -0
- package/dist/user-utils.d.ts.map +1 -0
- package/dist/user-utils.js +61 -0
- package/package.json +47 -28
- package/README.md +0 -4
- package/dist/client/effect.d.ts +0 -27
- package/dist/client/effect.js +0 -37
- package/dist/client/eventListener.d.ts +0 -39
- package/dist/client/eventListener.js +0 -52
- package/dist/client/loadHook.d.ts +0 -34
- package/dist/client/loadHook.js +0 -52
- package/dist/client/observer.d.ts +0 -36
- package/dist/client/observer.js +0 -66
- package/dist/client/runtime.d.ts +0 -105
- package/dist/client/runtime.js +0 -624
- package/dist/client/state.d.ts +0 -40
- package/dist/client/state.js +0 -110
- package/dist/compilation/compiler.d.ts +0 -158
- package/dist/compilation/compiler.js +0 -1163
- package/dist/components/ClientComponent.d.ts +0 -22
- package/dist/components/ClientComponent.js +0 -55
- package/dist/components/Link.d.ts +0 -16
- package/dist/components/Link.js +0 -21
- package/dist/components/Portal.d.ts +0 -2
- package/dist/components/Portal.js +0 -2
- package/dist/elements/element.d.ts +0 -87
- package/dist/elements/element.js +0 -33
- package/dist/elements/element_list.d.ts +0 -7
- package/dist/elements/element_list.js +0 -65
- package/dist/elements/raw.d.ts +0 -14
- package/dist/elements/raw.js +0 -78
- package/dist/elements/specific_props.d.ts +0 -750
- package/dist/elements/specific_props.js +0 -1
- package/dist/global.d.ts +0 -229
- package/dist/global.js +0 -1
- package/dist/index.d.ts +0 -16
- package/dist/index.js +0 -12
- package/dist/server/layout.d.ts +0 -34
- package/dist/server/layout.js +0 -6
- package/dist/server/log.d.ts +0 -12
- package/dist/server/log.js +0 -64
- package/dist/server/page.d.ts +0 -32
- package/dist/server/page.js +0 -6
- package/dist/server/runtime.d.ts +0 -6
- package/dist/server/runtime.js +0 -86
- package/scripts/bootstrap.js +0 -95
- package/scripts/bootstrap_files/elegance.txt +0 -40
- package/scripts/bootstrap_files/index.txt +0 -3
- package/scripts/bootstrap_files/layout.txt +0 -46
- package/scripts/bootstrap_files/middleware.txt +0 -18
- package/scripts/bootstrap_files/page.txt +0 -123
- package/scripts/bootstrap_files/route.txt +0 -6
- package/scripts/elegance_dev.ts +0 -42
- package/scripts/elegance_prod.ts +0 -42
- package/scripts/elegance_static.ts +0 -26
- package/scripts/prod.js +0 -13
- package/scripts/run.js +0 -13
- package/scripts/static.js +0 -13
package/dist/server/server.js
CHANGED
|
@@ -1,712 +1,862 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { readFile, readdir, writeFile, mkdir, stat } from "node:fs/promises";
|
|
3
|
+
import { join, dirname, extname, relative, basename } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
gzip as gzipCb,
|
|
6
|
+
brotliCompress as brotliCb,
|
|
7
|
+
constants as zlibConstants
|
|
8
|
+
} from "node:zlib";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
const gzipAsync = promisify(gzipCb);
|
|
11
|
+
const brotliAsync = promisify(brotliCb);
|
|
12
|
+
import { performance } from "node:perf_hooks";
|
|
13
|
+
import { generateSyntheticBundle } from "../processing/oxc.js";
|
|
14
|
+
import { generatePageHTML, generateDynamicPageHTML, createRenderContext, runWithRenderContext } from "../build/render.js";
|
|
15
|
+
import { loadRouteFromCache, runBuildHooks, preClientMjsPath } from "../build/common.js";
|
|
16
|
+
import { OUT_DIR, DIST_DIR, PAGES_DIR, loadPaths } from "../constants.js";
|
|
17
|
+
import { getConfig } from "../config.js";
|
|
18
|
+
import { createSecurityHeaders } from "./security.js";
|
|
19
|
+
import { isRichError, printError, richError } from "../error.js";
|
|
20
|
+
import { logger } from "../logger.js";
|
|
21
|
+
async function loadServerOptions() {
|
|
22
|
+
await loadPaths();
|
|
23
|
+
const config = await getConfig();
|
|
24
|
+
return config.server;
|
|
18
25
|
}
|
|
19
26
|
let serverOptions;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
const IS_DEV = process.env.ELEGANCE_DEV_MODE === "dev";
|
|
28
|
+
class LRU {
|
|
29
|
+
constructor(max) {
|
|
30
|
+
this.max = max;
|
|
31
|
+
}
|
|
32
|
+
max;
|
|
33
|
+
map = /* @__PURE__ */ new Map();
|
|
34
|
+
get(key) {
|
|
35
|
+
if (!this.map.has(key)) return void 0;
|
|
36
|
+
const v = this.map.get(key);
|
|
37
|
+
this.map.delete(key);
|
|
38
|
+
this.map.set(key, v);
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
set(key, val) {
|
|
42
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
43
|
+
else if (this.map.size >= this.max) this.map.delete(this.map.keys().next().value);
|
|
44
|
+
this.map.set(key, val);
|
|
45
|
+
}
|
|
46
|
+
has(key) {
|
|
47
|
+
return this.map.has(key);
|
|
48
|
+
}
|
|
49
|
+
delete(key) {
|
|
50
|
+
this.map.delete(key);
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
const staticCache = /* @__PURE__ */ new Map();
|
|
54
|
+
const dynamicModuleCache = new LRU(256);
|
|
55
|
+
const aotStaticCache = /* @__PURE__ */ new Map();
|
|
56
|
+
const statusCodePageCache = /* @__PURE__ */ new Map();
|
|
57
|
+
const middlewareChainCache = /* @__PURE__ */ new Map();
|
|
58
|
+
const encCache = new LRU(512);
|
|
59
|
+
let staticRouteMap = null;
|
|
60
|
+
let paramRoutes = null;
|
|
61
|
+
let apiRoutesCache = null;
|
|
62
|
+
let middlewareMapCache = null;
|
|
63
|
+
const MIME_TYPES = {
|
|
64
|
+
".html": "text/html",
|
|
65
|
+
".js": "application/javascript",
|
|
66
|
+
".mjs": "application/javascript",
|
|
67
|
+
".css": "text/css",
|
|
68
|
+
".png": "image/png",
|
|
69
|
+
".jpg": "image/jpeg",
|
|
70
|
+
".svg": "image/svg+xml",
|
|
71
|
+
".json": "application/json",
|
|
72
|
+
".ico": "image/x-icon",
|
|
73
|
+
".txt": "text/plain"
|
|
74
|
+
};
|
|
75
|
+
const GZIP_PARAMS = { level: 6 };
|
|
76
|
+
const BROTLI_PARAMS = { params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 4 } };
|
|
77
|
+
const BODY_TIMEOUT_MS = parseInt(process.env.BODY_TIMEOUT_MS ?? "10000", 10);
|
|
78
|
+
let SECURITY_HEADERS;
|
|
79
|
+
async function initSecurityHeaders() {
|
|
80
|
+
const config = await getConfig();
|
|
81
|
+
SECURITY_HEADERS = createSecurityHeaders(config.security);
|
|
82
|
+
}
|
|
83
|
+
function buildCachedFileHeaders(mime, etag, rawLen, gzipLen, brotliLen, cacheControl = "public, max-age=31536000, immutable") {
|
|
84
|
+
const base = {
|
|
85
|
+
...SECURITY_HEADERS,
|
|
86
|
+
"ETag": etag,
|
|
87
|
+
"Cache-Control": cacheControl,
|
|
88
|
+
"Vary": "Accept-Encoding"
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
raw: { ...base, "Content-Type": mime, "Content-Length": rawLen },
|
|
92
|
+
gzip: { ...base, "Content-Type": mime, "Content-Length": gzipLen, "Content-Encoding": "gzip" },
|
|
93
|
+
brotli: { ...base, "Content-Type": mime, "Content-Length": brotliLen, "Content-Encoding": "br" }
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function primeStaticCache() {
|
|
97
|
+
async function walk(dir) {
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await Promise.all(entries.map(async (e) => {
|
|
105
|
+
const full = join(dir, e.name);
|
|
106
|
+
if (e.isDirectory()) {
|
|
107
|
+
await walk(full);
|
|
60
108
|
return;
|
|
61
|
-
|
|
62
|
-
|
|
109
|
+
}
|
|
110
|
+
const urlPath = "/" + relative(DIST_DIR, full).replace(/\\/g, "/");
|
|
111
|
+
const [raw, filestat] = await Promise.all([readFile(full), stat(full)]);
|
|
112
|
+
const [gzip, brotli] = await Promise.all([
|
|
113
|
+
gzipAsync(raw, GZIP_PARAMS),
|
|
114
|
+
brotliAsync(raw, BROTLI_PARAMS)
|
|
115
|
+
]);
|
|
116
|
+
const mime = MIME_TYPES[extname(full)] ?? "application/octet-stream";
|
|
117
|
+
const etag = `"${filestat.size}-${filestat.mtimeMs}"`;
|
|
118
|
+
staticCache.set(urlPath, {
|
|
119
|
+
raw,
|
|
120
|
+
gzip,
|
|
121
|
+
brotli,
|
|
122
|
+
mime,
|
|
123
|
+
etag,
|
|
124
|
+
headers: buildCachedFileHeaders(mime, etag, raw.length, gzip.length, brotli.length)
|
|
125
|
+
});
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
await walk(DIST_DIR);
|
|
63
129
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
130
|
+
function compileRouteMatcher(pattern) {
|
|
131
|
+
const norm = (p) => (p.endsWith("/") && p !== "/" ? p.slice(0, -1) : p) || "/";
|
|
132
|
+
const normalisedPattern = norm(pattern);
|
|
133
|
+
if (!normalisedPattern.includes("[")) {
|
|
134
|
+
return (pathname) => norm(pathname) === normalisedPattern ? {} : null;
|
|
135
|
+
}
|
|
136
|
+
const paramMeta = [];
|
|
137
|
+
const reSource = normalisedPattern.split("/").filter(Boolean).map((seg) => {
|
|
138
|
+
if (seg.startsWith("[...") && seg.endsWith("]")) {
|
|
139
|
+
paramMeta.push({ name: seg.slice(4, -1), catchAll: true, optional: false });
|
|
140
|
+
return "(.+)";
|
|
141
|
+
}
|
|
142
|
+
if (seg.startsWith(":[") && seg.endsWith("]")) {
|
|
143
|
+
paramMeta.push({ name: seg.slice(2, -1), catchAll: false, optional: true });
|
|
144
|
+
return "([^/]+)?";
|
|
145
|
+
}
|
|
146
|
+
if (seg.startsWith("[") && seg.endsWith("]")) {
|
|
147
|
+
paramMeta.push({ name: seg.slice(1, -1), catchAll: false, optional: false });
|
|
148
|
+
return "([^/]+)";
|
|
149
|
+
}
|
|
150
|
+
return seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
151
|
+
}).join("/");
|
|
152
|
+
const re = new RegExp(`^/?${reSource}/?$`);
|
|
153
|
+
return (pathname) => {
|
|
154
|
+
const m = re.exec(pathname);
|
|
155
|
+
if (!m) return null;
|
|
156
|
+
const params = {};
|
|
157
|
+
for (let i = 0; i < paramMeta.length; i++) {
|
|
158
|
+
const { name, catchAll, optional } = paramMeta[i];
|
|
159
|
+
const val = m[i + 1];
|
|
160
|
+
if (catchAll) {
|
|
161
|
+
params[name] = val ? val.split("/") : [];
|
|
162
|
+
} else {
|
|
163
|
+
params[name] = optional && !val ? void 0 : val;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return params;
|
|
167
|
+
};
|
|
84
168
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
169
|
+
function routeEntryToMatched(entry) {
|
|
170
|
+
let matcher;
|
|
171
|
+
if (entry.kind === "enumerated") {
|
|
172
|
+
const patternMatcher = compileRouteMatcher(entry.patternPathname);
|
|
173
|
+
const norm = (p) => (p.endsWith("/") && p !== "/" ? p.slice(0, -1) : p) || "/";
|
|
174
|
+
const concreteNorm = norm(entry.pathname);
|
|
175
|
+
matcher = (pathname) => norm(pathname) === concreteNorm ? patternMatcher(pathname) : null;
|
|
176
|
+
} else {
|
|
177
|
+
matcher = compileRouteMatcher(entry.pathname);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
pathname: entry.pathname,
|
|
181
|
+
pageFile: entry.pageFile,
|
|
182
|
+
layouts: entry.layouts,
|
|
183
|
+
layoutCacheKeys: entry.layoutCacheKeys,
|
|
184
|
+
cacheKey: entry.cacheKey,
|
|
185
|
+
sharedChunkPaths: entry.sharedChunkPaths,
|
|
186
|
+
isDynamic: entry.kind === "dynamic",
|
|
187
|
+
patternPathname: entry.kind === "enumerated" ? entry.patternPathname : void 0,
|
|
188
|
+
matcher
|
|
189
|
+
};
|
|
87
190
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
191
|
+
async function warmDynamicCaches(manifest) {
|
|
192
|
+
const sMap = /* @__PURE__ */ new Map();
|
|
193
|
+
const pRoutes = [];
|
|
194
|
+
await Promise.all(manifest.routes.map(async (entry) => {
|
|
195
|
+
const route = routeEntryToMatched(entry);
|
|
196
|
+
if (entry.kind === "enumerated") {
|
|
197
|
+
pRoutes.push(route);
|
|
198
|
+
} else {
|
|
199
|
+
sMap.set(entry.pathname, route);
|
|
200
|
+
}
|
|
201
|
+
if (entry.kind === "dynamic") {
|
|
202
|
+
try {
|
|
203
|
+
const compiled = await loadRouteFromCache(route);
|
|
204
|
+
dynamicModuleCache.set(entry.pathname, compiled);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (isRichError(err)) throw err;
|
|
207
|
+
throw richError({
|
|
208
|
+
title: "Failed to Load Cached Route",
|
|
209
|
+
cause: `${err}`,
|
|
210
|
+
origin: entry.pageFile,
|
|
211
|
+
doShowStack: false
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (entry.kind !== "dynamic" && IS_DEV) {
|
|
216
|
+
try {
|
|
217
|
+
const compiled = await loadRouteFromCache(route);
|
|
218
|
+
aotStaticCache.set(entry.pathname, compiled);
|
|
219
|
+
} catch (err) {
|
|
220
|
+
if (isRichError(err)) throw err;
|
|
221
|
+
throw richError({
|
|
222
|
+
title: "Failed to Load Cached Route",
|
|
223
|
+
cause: `${err}`,
|
|
224
|
+
origin: entry.pageFile,
|
|
225
|
+
doShowStack: false
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}));
|
|
230
|
+
await Promise.all(manifest.statusCodePages.map(async (ep) => {
|
|
231
|
+
try {
|
|
232
|
+
const compiled = await loadRouteFromCache({
|
|
233
|
+
pageFile: ep.pageFile,
|
|
234
|
+
layouts: ep.layouts,
|
|
235
|
+
layoutCacheKeys: ep.layoutCacheKeys,
|
|
236
|
+
cacheKey: ep.cacheKey,
|
|
237
|
+
pathname: ""
|
|
238
|
+
});
|
|
239
|
+
const code = parseInt(basename(ep.pageFile), 10);
|
|
240
|
+
statusCodePageCache.set(
|
|
241
|
+
`${dirname(ep.pageFile)}:${isNaN(code) ? 0 : code}`,
|
|
242
|
+
{
|
|
243
|
+
compiled,
|
|
244
|
+
pageFile: ep.pageFile,
|
|
245
|
+
layouts: ep.layouts,
|
|
246
|
+
layoutCacheKeys: ep.layoutCacheKeys,
|
|
247
|
+
cacheKey: ep.cacheKey
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
if (isRichError(err)) throw err;
|
|
252
|
+
throw richError({
|
|
253
|
+
title: "Failed to Load Status Code Route",
|
|
254
|
+
cause: err,
|
|
255
|
+
origin: ep.pageFile,
|
|
256
|
+
doShowStack: false
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
return { staticRouteMap: sMap, paramRoutes: pRoutes };
|
|
107
261
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
let candidate;
|
|
116
|
-
if (currentPath === "/") {
|
|
117
|
-
candidate = `/${statusCode}`;
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
candidate = `${currentPath.replace(/\/$/, "")}/${statusCode}`;
|
|
121
|
-
}
|
|
122
|
-
const pageInfo = pages.get(candidate);
|
|
123
|
-
if (pageInfo) {
|
|
124
|
-
pageInfo.pathname = pathname;
|
|
125
|
-
return pageInfo;
|
|
126
|
-
}
|
|
127
|
-
if (currentPath === "/") {
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
const lastSlash = currentPath.lastIndexOf("/");
|
|
131
|
-
if (lastSlash <= 0) {
|
|
132
|
-
currentPath = "/";
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
currentPath = currentPath.slice(0, lastSlash);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
262
|
+
async function loadApiRoutes(manifest) {
|
|
263
|
+
if (apiRoutesCache) return apiRoutesCache;
|
|
264
|
+
apiRoutesCache = /* @__PURE__ */ new Map();
|
|
265
|
+
await Promise.all(manifest.apiRoutes.map(async (entry) => {
|
|
266
|
+
apiRoutesCache.set(entry.pathname, await import(entry.file));
|
|
267
|
+
}));
|
|
268
|
+
return apiRoutesCache;
|
|
138
269
|
}
|
|
139
|
-
async function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
270
|
+
async function loadMiddlewareMap(manifest) {
|
|
271
|
+
if (middlewareMapCache) return middlewareMapCache;
|
|
272
|
+
middlewareMapCache = /* @__PURE__ */ new Map();
|
|
273
|
+
await Promise.all(manifest.middlewares.map(async (entry) => {
|
|
274
|
+
const mod = await import(entry.file);
|
|
275
|
+
if (!mod.default) {
|
|
276
|
+
printError(richError({
|
|
277
|
+
title: "Invalid Middleware",
|
|
278
|
+
cause: "Could not get module.default within a middleware file, which is required for the middleware to function.",
|
|
279
|
+
hint: "Did you forget the *default* keyword when exporting your function?",
|
|
280
|
+
origin: entry.file,
|
|
281
|
+
doShowStack: false
|
|
282
|
+
}));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
middlewareMapCache.set(dirname(entry.file), [typeof mod.default === "function" ? mod.default : mod]);
|
|
286
|
+
}));
|
|
287
|
+
return middlewareMapCache;
|
|
149
288
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
289
|
+
function getMiddlewareChain(routePattern) {
|
|
290
|
+
if (middlewareChainCache.has(routePattern)) return middlewareChainCache.get(routePattern);
|
|
291
|
+
const dirMap = middlewareMapCache;
|
|
292
|
+
const parts = routePattern.replace(/^\//, "").split("/").filter(Boolean);
|
|
293
|
+
const chain = [];
|
|
294
|
+
let dir = PAGES_DIR;
|
|
295
|
+
if (dirMap.has(dir)) chain.push(...dirMap.get(dir));
|
|
296
|
+
for (const part of parts) {
|
|
297
|
+
dir = join(dir, part);
|
|
298
|
+
if (dirMap.has(dir)) chain.push(...dirMap.get(dir));
|
|
299
|
+
}
|
|
300
|
+
middlewareChainCache.set(routePattern, chain);
|
|
301
|
+
return chain;
|
|
156
302
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
303
|
+
function extractPathname(rawUrl) {
|
|
304
|
+
if (!rawUrl) return "/";
|
|
305
|
+
let end = rawUrl.length;
|
|
306
|
+
for (let i = 0; i < rawUrl.length; i++) {
|
|
307
|
+
const ch = rawUrl.charCodeAt(i);
|
|
308
|
+
if (ch === 63 || ch === 35) {
|
|
309
|
+
end = i;
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const path = rawUrl.slice(0, end) || "/";
|
|
314
|
+
if (path.indexOf("%") !== -1) {
|
|
315
|
+
try {
|
|
316
|
+
return decodeURIComponent(path);
|
|
317
|
+
} catch {
|
|
318
|
+
return path;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return path;
|
|
172
322
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
informationClone.pathname = pathname;
|
|
182
|
-
const result = await compilePage(serverOptions.allLayouts, informationClone, { req, res }, matchHit.params);
|
|
183
|
-
if (res.writableEnded || res.headersSent)
|
|
184
|
-
return;
|
|
185
|
-
res.statusCode = 200;
|
|
186
|
-
await sendResponse(req, res, result.pageHTML, "text/html");
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
const { pageHTML } = serverOptions.builtStaticPages.get(pageInformation.pathname);
|
|
190
|
-
res.statusCode = 200;
|
|
191
|
-
await sendResponse(req, res, pageHTML, "text/html");
|
|
323
|
+
function acceptEncoding(req) {
|
|
324
|
+
const raw = req.headers["accept-encoding"];
|
|
325
|
+
if (!raw) return null;
|
|
326
|
+
const cached = encCache.get(raw);
|
|
327
|
+
if (cached !== void 0) return cached;
|
|
328
|
+
const result = raw.includes("br") ? "br" : raw.includes("gzip") ? "gzip" : null;
|
|
329
|
+
encCache.set(raw, result);
|
|
330
|
+
return result;
|
|
192
331
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
332
|
+
async function buildRouteHTML(compiled, route, params, requestPathname, req, res) {
|
|
333
|
+
const ctx = createRenderContext();
|
|
334
|
+
for (const { id, initial } of compiled.atomSeeds) {
|
|
335
|
+
if (!ctx.atomValues.has(id)) {
|
|
336
|
+
ctx.atomValues.set(id, initial);
|
|
337
|
+
ctx.atomRegistry.push({ id });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const preClientCode = await readFile(preClientMjsPath(route.cacheKey), "utf-8");
|
|
341
|
+
let html = await runWithRenderContext(ctx, async () => {
|
|
342
|
+
const [rootNode, metaNodes] = await Promise.all([
|
|
343
|
+
compiled.default(params, req, res),
|
|
344
|
+
compiled.metadata(params, req, res)
|
|
345
|
+
]);
|
|
346
|
+
const getClientCode = () => generateSyntheticBundle(
|
|
347
|
+
preClientCode,
|
|
348
|
+
requestPathname,
|
|
349
|
+
ctx.regions,
|
|
350
|
+
route.layoutCacheKeys
|
|
351
|
+
);
|
|
352
|
+
return generateDynamicPageHTML(rootNode, metaNodes, route, getClientCode, ctx);
|
|
353
|
+
});
|
|
354
|
+
const chunks = route.sharedChunkPaths.filter((chunkPath) => preClientCode.includes(chunkPath));
|
|
355
|
+
if (chunks.length > 0) {
|
|
356
|
+
const preloadTags = chunks.map((p) => `<link rel="modulepreload" href="${p}">`).join("");
|
|
357
|
+
html = html.replace("</head>", `${preloadTags}</head>`);
|
|
358
|
+
}
|
|
359
|
+
return html;
|
|
218
360
|
}
|
|
219
|
-
async function
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
};
|
|
239
|
-
if (!useGzip) {
|
|
240
|
-
head['Content-Length'] = fileSize;
|
|
241
|
-
}
|
|
242
|
-
if (useGzip) {
|
|
243
|
-
head['Content-Encoding'] = 'gzip';
|
|
244
|
-
head['Vary'] = 'Accept-Encoding';
|
|
245
|
-
}
|
|
246
|
-
res.writeHead(200, head);
|
|
247
|
-
const stream = createReadStream(safePath);
|
|
248
|
-
if (useGzip) {
|
|
249
|
-
const gzip = zlib.createGzip();
|
|
250
|
-
stream.pipe(gzip).pipe(res);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
stream.pipe(res);
|
|
254
|
-
}
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const ranges = rangeHeader.replace(/bytes=/, '').split('-');
|
|
258
|
-
let start = parseInt(ranges[0], 10);
|
|
259
|
-
let end = ranges[1] ? parseInt(ranges[1], 10) : fileSize - 1;
|
|
260
|
-
if (isNaN(start))
|
|
261
|
-
start = 0;
|
|
262
|
-
if (isNaN(end) || end >= fileSize)
|
|
263
|
-
end = fileSize - 1;
|
|
264
|
-
if (start >= fileSize || start > end) {
|
|
265
|
-
res.writeHead(416, {
|
|
266
|
-
'Content-Range': `bytes */${fileSize}`,
|
|
267
|
-
});
|
|
268
|
-
res.end();
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
const contentLength = end - start + 1;
|
|
272
|
-
const headers = {
|
|
273
|
-
'Content-Type': mime,
|
|
274
|
-
'Accept-Ranges': 'bytes',
|
|
275
|
-
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
|
276
|
-
'Content-Length': contentLength,
|
|
277
|
-
};
|
|
278
|
-
res.writeHead(206, headers);
|
|
279
|
-
const stream = createReadStream(safePath, { start, end });
|
|
280
|
-
stream.pipe(res);
|
|
361
|
+
async function pipeRouteToResponse(compiled, route, params, requestPathname, req, res, statusCode = 200) {
|
|
362
|
+
const start = IS_DEV ? performance.now() : 0;
|
|
363
|
+
const html = await buildRouteHTML(compiled, route, params, requestPathname, req, res);
|
|
364
|
+
if (IS_DEV) {
|
|
365
|
+
logger.debug(`rendered ${requestPathname} (${(performance.now() - start).toFixed(1)}ms)`);
|
|
366
|
+
}
|
|
367
|
+
if (res.writableEnded || res.destroyed) return;
|
|
368
|
+
const enc = acceptEncoding(req);
|
|
369
|
+
const body = enc === "br" ? await brotliAsync(html, BROTLI_PARAMS) : enc === "gzip" ? await gzipAsync(html, GZIP_PARAMS) : Buffer.from(html);
|
|
370
|
+
const headers = {
|
|
371
|
+
...SECURITY_HEADERS,
|
|
372
|
+
"Content-Type": "text/html",
|
|
373
|
+
"Content-Length": body.length,
|
|
374
|
+
"Vary": "Accept-Encoding",
|
|
375
|
+
"Cache-Control": "no-store"
|
|
376
|
+
};
|
|
377
|
+
if (enc) headers["Content-Encoding"] = enc;
|
|
378
|
+
res.writeHead(statusCode, headers);
|
|
379
|
+
res.end(body);
|
|
281
380
|
}
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
381
|
+
async function lazyBuildStaticPage(compiled, route, params, requestPathname, req, res) {
|
|
382
|
+
const overallStart = performance.now();
|
|
383
|
+
const timings = {};
|
|
384
|
+
await runBuildHooks(compiled, "pre");
|
|
385
|
+
const ctx = createRenderContext();
|
|
386
|
+
for (const { id, initial } of compiled.atomSeeds) {
|
|
387
|
+
if (!ctx.atomValues.has(id)) {
|
|
388
|
+
ctx.atomValues.set(id, initial);
|
|
389
|
+
ctx.atomRegistry.push({ id });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let t = performance.now();
|
|
393
|
+
const bundleUrl = `${route.pathname === "/" ? "" : route.pathname}/bundle.js`;
|
|
394
|
+
const { fullHtml } = await runWithRenderContext(ctx, async () => {
|
|
395
|
+
const [rootNode, metaNodes] = await Promise.all([
|
|
396
|
+
compiled.default(params, req, res),
|
|
397
|
+
compiled.metadata(params, req, res)
|
|
398
|
+
]);
|
|
399
|
+
timings["render"] = performance.now() - t;
|
|
400
|
+
t = performance.now();
|
|
401
|
+
const result = await generatePageHTML(
|
|
402
|
+
rootNode,
|
|
403
|
+
metaNodes,
|
|
404
|
+
route,
|
|
405
|
+
bundleUrl,
|
|
406
|
+
ctx,
|
|
407
|
+
route.sharedChunkPaths
|
|
408
|
+
);
|
|
409
|
+
timings["html"] = performance.now() - t;
|
|
294
410
|
return result;
|
|
411
|
+
});
|
|
412
|
+
const preClientCode = await readFile(preClientMjsPath(route.cacheKey), "utf-8");
|
|
413
|
+
t = performance.now();
|
|
414
|
+
const syntheticCode = generateSyntheticBundle(
|
|
415
|
+
preClientCode,
|
|
416
|
+
requestPathname,
|
|
417
|
+
ctx.regions,
|
|
418
|
+
route.layoutCacheKeys
|
|
419
|
+
);
|
|
420
|
+
timings["bundle"] = performance.now() - t;
|
|
421
|
+
await runBuildHooks(compiled, "post");
|
|
422
|
+
t = performance.now();
|
|
423
|
+
const routeOutDir = route.pathname === "/" ? DIST_DIR : join(DIST_DIR, route.pathname);
|
|
424
|
+
await mkdir(routeOutDir, { recursive: true });
|
|
425
|
+
await Promise.all([
|
|
426
|
+
writeFile(join(routeOutDir, "index.html"), fullHtml),
|
|
427
|
+
writeFile(join(routeOutDir, "bundle.js"), syntheticCode)
|
|
428
|
+
]);
|
|
429
|
+
timings["write"] = performance.now() - t;
|
|
430
|
+
t = performance.now();
|
|
431
|
+
const htmlBuf = Buffer.from(fullHtml);
|
|
432
|
+
const jsBuf = Buffer.from(syntheticCode);
|
|
433
|
+
const [htmlGzip, htmlBrotli, jsGzip, jsBrotli] = await Promise.all([
|
|
434
|
+
gzipAsync(htmlBuf, GZIP_PARAMS),
|
|
435
|
+
brotliAsync(htmlBuf, BROTLI_PARAMS),
|
|
436
|
+
gzipAsync(jsBuf, GZIP_PARAMS),
|
|
437
|
+
brotliAsync(jsBuf, BROTLI_PARAMS)
|
|
438
|
+
]);
|
|
439
|
+
timings["compress"] = performance.now() - t;
|
|
440
|
+
const htmlMime = "text/html";
|
|
441
|
+
const jsMime = "application/javascript";
|
|
442
|
+
const now = Date.now();
|
|
443
|
+
const htmlEtag = `"${htmlBuf.length}-${now}"`;
|
|
444
|
+
const jsEtag = `"${jsBuf.length}-${now}"`;
|
|
445
|
+
const htmlCached = {
|
|
446
|
+
raw: htmlBuf,
|
|
447
|
+
gzip: htmlGzip,
|
|
448
|
+
brotli: htmlBrotli,
|
|
449
|
+
mime: htmlMime,
|
|
450
|
+
etag: htmlEtag,
|
|
451
|
+
headers: buildCachedFileHeaders(htmlMime, htmlEtag, htmlBuf.length, htmlGzip.length, htmlBrotli.length, "no-store")
|
|
452
|
+
};
|
|
453
|
+
const jsCached = {
|
|
454
|
+
raw: jsBuf,
|
|
455
|
+
gzip: jsGzip,
|
|
456
|
+
brotli: jsBrotli,
|
|
457
|
+
mime: jsMime,
|
|
458
|
+
etag: jsEtag,
|
|
459
|
+
headers: buildCachedFileHeaders(jsMime, jsEtag, jsBuf.length, jsGzip.length, jsBrotli.length, "no-store")
|
|
460
|
+
};
|
|
461
|
+
staticCache.set(resolveStaticIndexKey(route.pathname), htmlCached);
|
|
462
|
+
staticCache.set(bundleUrl, jsCached);
|
|
463
|
+
const total = performance.now() - overallStart;
|
|
464
|
+
const steps = Object.entries(timings).map(
|
|
465
|
+
([step, ms], i, arr) => ` ${i === arr.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} ${step}: ${ms.toFixed(1)}ms`
|
|
466
|
+
).join("\n");
|
|
467
|
+
logger.debug(`built ${route.pathname} (${total.toFixed(1)}ms total)
|
|
468
|
+
${steps}`);
|
|
469
|
+
serveCachedFile(req, res, htmlCached);
|
|
295
470
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const { middleware } = await import("file://" + fullPath);
|
|
304
|
-
if (!middleware || typeof middleware !== "function") {
|
|
305
|
-
throw new Error(`In file: "${fullPath}":\nThe export middleware is not of type "function". Got: ${typeof middleware}`);
|
|
306
|
-
}
|
|
307
|
-
const middlewareInformation = {
|
|
308
|
-
exports: {
|
|
309
|
-
middleware,
|
|
310
|
-
},
|
|
311
|
-
modulePath: fullPath,
|
|
312
|
-
pathname: pathname,
|
|
313
|
-
};
|
|
314
|
-
allMiddleware.set(pathname, middlewareInformation);
|
|
315
|
-
});
|
|
471
|
+
async function runMiddlewareChain(mws, req, res, final) {
|
|
472
|
+
let i = 0;
|
|
473
|
+
const next = async () => {
|
|
474
|
+
if (i < mws.length) await mws[i++](req, res, next);
|
|
475
|
+
else await final();
|
|
476
|
+
};
|
|
477
|
+
await next();
|
|
316
478
|
}
|
|
317
|
-
async function
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
479
|
+
async function respondWithStatusCode(code, requestPathname, params, req, res) {
|
|
480
|
+
if (serverOptions.allowStatusCodePages) {
|
|
481
|
+
let bestEntry = null;
|
|
482
|
+
let bestDepth = -1;
|
|
483
|
+
for (const [key, entry] of statusCodePageCache) {
|
|
484
|
+
const colonIdx = key.lastIndexOf(":");
|
|
485
|
+
const dirPath = key.slice(0, colonIdx);
|
|
486
|
+
const keyCode = parseInt(key.slice(colonIdx + 1), 10);
|
|
487
|
+
if (keyCode !== code && keyCode !== 0) continue;
|
|
488
|
+
const relDir = relative(PAGES_DIR, dirPath);
|
|
489
|
+
const dirPattern = relDir === "" ? "/" : `/${relDir.replace(/\\/g, "/")}`;
|
|
490
|
+
const trailPattern = dirPattern === "/" ? "/[...__trail]" : `${dirPattern}/[...__trail]`;
|
|
491
|
+
const exactMatches = compileRouteMatcher(dirPattern)(requestPathname);
|
|
492
|
+
const trailMatches = compileRouteMatcher(trailPattern)(requestPathname);
|
|
493
|
+
if (exactMatches === null && trailMatches === null) continue;
|
|
494
|
+
const depth = dirPattern === "/" ? 0 : dirPattern.split("/").filter(Boolean).length;
|
|
495
|
+
if (depth > bestDepth) {
|
|
496
|
+
bestEntry = entry;
|
|
497
|
+
bestDepth = depth;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (bestEntry) {
|
|
501
|
+
const route = {
|
|
502
|
+
pathname: requestPathname,
|
|
503
|
+
pageFile: bestEntry.pageFile,
|
|
504
|
+
layouts: bestEntry.layouts,
|
|
505
|
+
layoutCacheKeys: bestEntry.layoutCacheKeys,
|
|
506
|
+
cacheKey: bestEntry.cacheKey,
|
|
507
|
+
sharedChunkPaths: [],
|
|
508
|
+
isDynamic: true,
|
|
509
|
+
matcher: () => ({})
|
|
510
|
+
};
|
|
511
|
+
await pipeRouteToResponse(bestEntry.compiled, route, params, requestPathname, req, res, code);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
res.writeHead(code);
|
|
516
|
+
res.end();
|
|
517
|
+
}
|
|
518
|
+
function stripTrailingSlash(p) {
|
|
519
|
+
return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
520
|
+
}
|
|
521
|
+
function resolveStaticIndexKey(pathname) {
|
|
522
|
+
if (extname(pathname)) return pathname;
|
|
523
|
+
const clean = pathname.replace(/\/$/, "") || "/";
|
|
524
|
+
return clean === "/" ? "/index.html" : `${clean}/index.html`;
|
|
525
|
+
}
|
|
526
|
+
function serveCachedFile(req, res, cached, statusCode = 200) {
|
|
527
|
+
if (req.headers["if-none-match"] === cached.etag) {
|
|
528
|
+
res.writeHead(304);
|
|
529
|
+
res.end();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const enc = acceptEncoding(req);
|
|
533
|
+
if (enc === "br") {
|
|
534
|
+
res.writeHead(statusCode, cached.headers.brotli);
|
|
535
|
+
res.end(cached.brotli);
|
|
536
|
+
} else if (enc === "gzip") {
|
|
537
|
+
res.writeHead(statusCode, cached.headers.gzip);
|
|
538
|
+
res.end(cached.gzip);
|
|
539
|
+
} else {
|
|
540
|
+
res.writeHead(statusCode, cached.headers.raw);
|
|
541
|
+
res.end(cached.raw);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function getRequestBody(req) {
|
|
545
|
+
return new Promise((resolve, reject) => {
|
|
546
|
+
const chunks = [];
|
|
547
|
+
let totalLen = 0;
|
|
548
|
+
const timer = setTimeout(() => {
|
|
549
|
+
req.socket.destroy();
|
|
550
|
+
reject(new Error("Request body timeout"));
|
|
551
|
+
}, BODY_TIMEOUT_MS);
|
|
552
|
+
req.on("data", (chunk) => {
|
|
553
|
+
totalLen += chunk.length;
|
|
554
|
+
if (totalLen > 1e6) {
|
|
555
|
+
clearTimeout(timer);
|
|
556
|
+
req.socket.destroy();
|
|
557
|
+
reject(new Error("Request body too large"));
|
|
327
558
|
return;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
559
|
+
}
|
|
560
|
+
chunks.push(chunk);
|
|
561
|
+
});
|
|
562
|
+
req.on("end", () => {
|
|
563
|
+
clearTimeout(timer);
|
|
564
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
565
|
+
try {
|
|
566
|
+
resolve(JSON.parse(body));
|
|
567
|
+
} catch {
|
|
568
|
+
resolve(body);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
req.on("error", (err) => {
|
|
572
|
+
clearTimeout(timer);
|
|
573
|
+
reject(err);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
339
576
|
}
|
|
340
|
-
async function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
res.
|
|
354
|
-
res.
|
|
355
|
-
|
|
577
|
+
async function runServerAction(req, res) {
|
|
578
|
+
let actionId;
|
|
579
|
+
{
|
|
580
|
+
const header = req.headers["elegance-action"];
|
|
581
|
+
if (!header || Array.isArray(header)) {
|
|
582
|
+
res.statusCode = 400;
|
|
583
|
+
res.end();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
actionId = header;
|
|
587
|
+
}
|
|
588
|
+
const serverAction = globalThis.__serverActions.find((a) => a.id === actionId);
|
|
589
|
+
if (!serverAction) {
|
|
590
|
+
res.statusCode = 404;
|
|
591
|
+
res.end();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (req.method !== "POST") {
|
|
595
|
+
res.statusCode = 405;
|
|
596
|
+
res.end();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const requestParams = await getRequestBody(req);
|
|
600
|
+
if (!requestParams || typeof requestParams !== "object" || Array.isArray(requestParams)) {
|
|
601
|
+
res.statusCode = 400;
|
|
602
|
+
res.end();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (serverAction.props) {
|
|
606
|
+
for (const [key, value] of Object.entries(serverAction.props)) {
|
|
607
|
+
let badRequest2 = function(reason) {
|
|
608
|
+
res.statusCode = 400;
|
|
609
|
+
res.end(reason);
|
|
610
|
+
};
|
|
611
|
+
var badRequest = badRequest2;
|
|
612
|
+
const requestParam = requestParams[key];
|
|
613
|
+
if (requestParam === void 0 || requestParam === null) {
|
|
614
|
+
if (value.required) return badRequest2(`${key} is a required value`);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
switch (value.type) {
|
|
618
|
+
case "string":
|
|
619
|
+
if (typeof requestParam !== "string")
|
|
620
|
+
return badRequest2(`${key} is not of type string`);
|
|
621
|
+
if (value.length && requestParam.length < value.length[0])
|
|
622
|
+
return badRequest2(`${key} is too short (min ${value.length[0]})`);
|
|
623
|
+
if (value.length && requestParam.length > value.length[1])
|
|
624
|
+
return badRequest2(`${key} is too long (max ${value.length[1]})`);
|
|
625
|
+
break;
|
|
626
|
+
case "array":
|
|
627
|
+
if (!Array.isArray(requestParam))
|
|
628
|
+
return badRequest2(`${key} is not an Array`);
|
|
629
|
+
if (value.length && requestParam.length < value.length[0])
|
|
630
|
+
return badRequest2(`${key} is too short (min ${value.length[0]})`);
|
|
631
|
+
if (value.length && requestParam.length > value.length[1])
|
|
632
|
+
return badRequest2(`${key} is too long (max ${value.length[1]})`);
|
|
633
|
+
break;
|
|
634
|
+
case "boolean":
|
|
635
|
+
if (typeof requestParam !== "boolean")
|
|
636
|
+
return badRequest2(`${key} is not of type boolean`);
|
|
637
|
+
break;
|
|
638
|
+
case "number":
|
|
639
|
+
if (typeof requestParam !== "number")
|
|
640
|
+
return badRequest2(`${key} is not of type number`);
|
|
641
|
+
if (value.min && requestParam < value.min)
|
|
642
|
+
return badRequest2(`${key} is too small`);
|
|
643
|
+
if (value.max && requestParam > value.max)
|
|
644
|
+
return badRequest2(`${key} is too large`);
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
if (typeof requestParam !== value.type) {
|
|
648
|
+
res.statusCode = 400;
|
|
649
|
+
res.end();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
const returnValue = await serverAction.callback({ ...requestParams, req, res });
|
|
656
|
+
if (res.writableEnded || res.destroyed) return;
|
|
657
|
+
res.end(JSON.stringify(returnValue));
|
|
658
|
+
return;
|
|
659
|
+
} catch {
|
|
660
|
+
res.statusCode = 500;
|
|
661
|
+
res.end();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
356
664
|
}
|
|
357
|
-
async function
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
665
|
+
async function handleRequest(req, res) {
|
|
666
|
+
req.socket.setNoDelay(IS_DEV);
|
|
667
|
+
const pathname = extractPathname(req.url);
|
|
668
|
+
if (IS_DEV) logger.info(`- ${req.method} : ${pathname}`);
|
|
669
|
+
if (pathname === "/__action") {
|
|
670
|
+
runServerAction(req, res);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const cached = staticCache.get(pathname);
|
|
674
|
+
if (cached) {
|
|
675
|
+
serveCachedFile(req, res, cached);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const apiModule = apiRoutesCache.get(stripTrailingSlash(pathname));
|
|
679
|
+
if (apiModule) {
|
|
680
|
+
const handler = apiModule[req.method];
|
|
681
|
+
if (typeof handler !== "function") {
|
|
682
|
+
res.writeHead(405);
|
|
683
|
+
res.end();
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
runMiddlewareChain(getMiddlewareChain(pathname), req, res, async () => {
|
|
687
|
+
handler(req, res).catch((e) => {
|
|
688
|
+
res.writeHead(500);
|
|
366
689
|
res.end();
|
|
690
|
+
printError(richError({
|
|
691
|
+
title: "API Route Threw",
|
|
692
|
+
cause: "An API Route threw an error whilst it was being executed.",
|
|
693
|
+
origin: pathname,
|
|
694
|
+
doShowStack: false
|
|
695
|
+
}));
|
|
696
|
+
logger.error(e);
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const normalizedPathname = stripTrailingSlash(pathname) || "/";
|
|
702
|
+
let matchedRoute = staticRouteMap.get(normalizedPathname) ?? null;
|
|
703
|
+
let params = {};
|
|
704
|
+
if (!matchedRoute) {
|
|
705
|
+
for (const r of paramRoutes) {
|
|
706
|
+
const match = r.matcher(pathname);
|
|
707
|
+
if (match) {
|
|
708
|
+
matchedRoute = r;
|
|
709
|
+
params = match;
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
await runMiddlewareChain(getMiddlewareChain(matchedRoute?.pathname ?? pathname), req, res, async () => {
|
|
715
|
+
if (!matchedRoute) {
|
|
716
|
+
const staticIndex = staticCache.get(resolveStaticIndexKey(pathname));
|
|
717
|
+
if (staticIndex) {
|
|
718
|
+
serveCachedFile(req, res, staticIndex);
|
|
367
719
|
return;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
720
|
+
}
|
|
721
|
+
await respondWithStatusCode(404, pathname, params, req, res);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (matchedRoute.isDynamic) {
|
|
725
|
+
const compiled = dynamicModuleCache.get(matchedRoute.pathname);
|
|
726
|
+
if (!compiled) {
|
|
727
|
+
printError(richError({
|
|
728
|
+
title: "No Cached Module for Dynamic Route",
|
|
729
|
+
cause: "A dynamic route was not warmed during server startup, this is likely an internal error.",
|
|
730
|
+
origin: matchedRoute.pathname,
|
|
731
|
+
doShowStack: true
|
|
732
|
+
}));
|
|
733
|
+
await respondWithStatusCode(500, pathname, params, req, res);
|
|
372
734
|
return;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
735
|
+
}
|
|
736
|
+
await pipeRouteToResponse(compiled, matchedRoute, params, pathname, req, res, 200);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (IS_DEV) {
|
|
740
|
+
const builtHtml = staticCache.get(resolveStaticIndexKey(matchedRoute.pathname));
|
|
741
|
+
if (builtHtml) {
|
|
742
|
+
serveCachedFile(req, res, builtHtml);
|
|
378
743
|
return;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
744
|
+
}
|
|
745
|
+
const compiled = aotStaticCache.get(matchedRoute.pathname);
|
|
746
|
+
if (!compiled) {
|
|
747
|
+
printError(richError({
|
|
748
|
+
title: "Internal Error",
|
|
749
|
+
cause: "A static route was not cached during lazy compilation on the server.",
|
|
750
|
+
origin: matchedRoute.pathname,
|
|
751
|
+
doShowStack: false
|
|
752
|
+
}));
|
|
753
|
+
await respondWithStatusCode(500, pathname, params, req, res);
|
|
383
754
|
return;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const optional = part.startsWith(':');
|
|
416
|
-
const currentPart = optional ? part.slice(1) : part;
|
|
417
|
-
const isCatchAll = currentPart.startsWith('*') && currentPart.endsWith('*');
|
|
418
|
-
const isDynamic = currentPart.startsWith('[') && currentPart.endsWith(']');
|
|
419
|
-
let matcher;
|
|
420
|
-
if (isCatchAll) {
|
|
421
|
-
matcher = '[^/]+(?:/[^/]+)*';
|
|
422
|
-
}
|
|
423
|
-
else if (isDynamic) {
|
|
424
|
-
matcher = '[^/]+';
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
matcher = escapeRegExp(currentPart);
|
|
428
|
-
}
|
|
429
|
-
if (isCatchAll || isDynamic) {
|
|
430
|
-
const paramName = currentPart.slice(1, -1);
|
|
431
|
-
matcher = `(?<${paramName}>${matcher})`;
|
|
432
|
-
}
|
|
433
|
-
let sep;
|
|
434
|
-
if (hasPart) {
|
|
435
|
-
sep = previousCanSkip ? '/?' : '/';
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
sep = '';
|
|
439
|
-
}
|
|
440
|
-
let addition = sep + matcher;
|
|
441
|
-
if (optional) {
|
|
442
|
-
if (hasPart || sep !== '') {
|
|
443
|
-
addition = '(?:' + sep + matcher + ')?';
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
addition = '(?:' + matcher + ')?';
|
|
447
|
-
}
|
|
448
|
-
previousCanSkip = true;
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
previousCanSkip = false;
|
|
452
|
-
}
|
|
453
|
-
patternRegex += addition;
|
|
454
|
-
hasPart = true;
|
|
455
|
-
}
|
|
456
|
-
if (patternRegex === '^/') {
|
|
457
|
-
patternRegex = '^/?';
|
|
458
|
-
}
|
|
459
|
-
patternRegex += '$';
|
|
460
|
-
return patternRegex;
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
await lazyBuildStaticPage(compiled, matchedRoute, params, pathname, req, res);
|
|
758
|
+
} catch (e) {
|
|
759
|
+
respondWithStatusCode(500, pathname, {}, req, res);
|
|
760
|
+
if (isRichError(e)) throw e;
|
|
761
|
+
throw richError({
|
|
762
|
+
title: "Failed to lazy build a static page.",
|
|
763
|
+
cause: e,
|
|
764
|
+
doShowStack: true
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const pageCached = staticCache.get(resolveStaticIndexKey(matchedRoute.pathname));
|
|
770
|
+
if (pageCached) {
|
|
771
|
+
serveCachedFile(req, res, pageCached);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
await respondWithStatusCode(404, pathname, params, req, res);
|
|
775
|
+
}).catch((e) => {
|
|
776
|
+
if (isRichError(e)) {
|
|
777
|
+
printError(e);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
printError(richError({
|
|
781
|
+
title: "Failed to run request.",
|
|
782
|
+
cause: e,
|
|
783
|
+
doShowStack: true
|
|
784
|
+
}));
|
|
785
|
+
});
|
|
461
786
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (match) {
|
|
485
|
-
const getBasePart = (p) => p.startsWith(':') ? p.slice(1) : p;
|
|
486
|
-
const isDynamicPart = (p) => p.startsWith(':') || p.startsWith('[') || p.startsWith('*');
|
|
487
|
-
const fixedCount = patternParts.filter(p => p !== '' && !isDynamicPart(p)).length;
|
|
488
|
-
const dynamicSingleCount = patternParts.filter(p => {
|
|
489
|
-
const pp = getBasePart(p);
|
|
490
|
-
return pp.startsWith('[') && pp.endsWith(']');
|
|
491
|
-
}).length;
|
|
492
|
-
const catchallCount = patternParts.filter(p => {
|
|
493
|
-
const pp = getBasePart(p);
|
|
494
|
-
return pp.startsWith('*') && pp.endsWith('*');
|
|
495
|
-
}).length;
|
|
496
|
-
const optionalCount = patternParts.filter(p => p.startsWith(':')).length;
|
|
497
|
-
const totalDynamic = dynamicSingleCount + catchallCount;
|
|
498
|
-
candidates.push({ pattern, fixedCount, dynamicSingleCount, catchallCount, optionalCount, totalDynamic, match });
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
if (candidates.length === 0) {
|
|
502
|
-
return null;
|
|
503
|
-
}
|
|
504
|
-
candidates.sort((a, b) => {
|
|
505
|
-
if (a.fixedCount !== b.fixedCount) {
|
|
506
|
-
return b.fixedCount - a.fixedCount;
|
|
507
|
-
}
|
|
508
|
-
if (a.totalDynamic !== b.totalDynamic) {
|
|
509
|
-
return a.totalDynamic - b.totalDynamic;
|
|
510
|
-
}
|
|
511
|
-
if (a.catchallCount !== b.catchallCount) {
|
|
512
|
-
return a.catchallCount - b.catchallCount;
|
|
513
|
-
}
|
|
514
|
-
if (a.optionalCount !== b.optionalCount) {
|
|
515
|
-
return a.optionalCount - b.optionalCount;
|
|
516
|
-
}
|
|
517
|
-
return 0;
|
|
787
|
+
async function startMainServer() {
|
|
788
|
+
const port = serverOptions.port;
|
|
789
|
+
if (!IS_DEV) {
|
|
790
|
+
return new Promise((resolve, reject) => {
|
|
791
|
+
const srv = createServer(handleRequest);
|
|
792
|
+
srv.keepAliveTimeout = 65e3;
|
|
793
|
+
srv.headersTimeout = 66e3;
|
|
794
|
+
srv.once("error", (err) => {
|
|
795
|
+
if (err.code === "EADDRINUSE") {
|
|
796
|
+
printError(richError({
|
|
797
|
+
title: "Busy Port",
|
|
798
|
+
cause: `The port ${port} is already hogged by another process. Please kill the process hogging it, or change the port.`,
|
|
799
|
+
doShowStack: true
|
|
800
|
+
}));
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
reject(err);
|
|
804
|
+
});
|
|
805
|
+
srv.listen(port, "0.0.0.0", () => {
|
|
806
|
+
logger.success(`Live at: http://localhost:${port}`);
|
|
807
|
+
resolve(srv);
|
|
808
|
+
});
|
|
518
809
|
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
let port = serverOptions.port ?? 3000;
|
|
534
|
-
const server = createServer(requestHandler);
|
|
535
|
-
/** Prefer to sacrifice port desireability in-exchange for getting the thing running */
|
|
536
|
-
server.on("error", (error) => {
|
|
537
|
-
if (error.code === "EADDRINUSE") {
|
|
538
|
-
setTimeout(() => {
|
|
539
|
-
formattedLog(LogLevel.WARN, `${port} was not available, trying port ${port + 1}..`);
|
|
540
|
-
port += 1;
|
|
541
|
-
server.listen(port);
|
|
542
|
-
}, 500);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
server.listen({ port: serverOptions.port, hostname: serverOptions.hostname, }, () => {
|
|
546
|
-
if (compilerOptions.doHotReload) {
|
|
547
|
-
process.send?.(JSON.stringify({ message: "hot-reload-finish" }));
|
|
548
|
-
}
|
|
549
|
-
formattedLog(LogLevel.INFO, `Website Live at: http://${serverOptions.hostname}:${port}/`);
|
|
550
|
-
});
|
|
551
|
-
return {
|
|
552
|
-
port,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
/** Get the current query as `URLSearchParams` */
|
|
556
|
-
function getQuery() {
|
|
557
|
-
const store = compilerStore.getStore();
|
|
558
|
-
if (!store) {
|
|
559
|
-
throw new Error("getQuery() cannot be called outside of a page or layout.");
|
|
560
|
-
}
|
|
561
|
-
if (!store.req) {
|
|
562
|
-
throw new Error("getQuery() cannot be used inside of a static page, since it depends on the *request query*.");
|
|
563
|
-
}
|
|
564
|
-
if (!store.req.url) {
|
|
565
|
-
throw new Error("Invalid req.url");
|
|
566
|
-
}
|
|
567
|
-
return new URLSearchParams(new URL(`http://${process.env.HOST ?? 'localhost'}${store.req.url}`).searchParams);
|
|
568
|
-
}
|
|
569
|
-
/** Get the current page's request and response. */
|
|
570
|
-
function getRequest() {
|
|
571
|
-
const store = compilerStore.getStore();
|
|
572
|
-
if (!store) {
|
|
573
|
-
throw new Error("getQuery() cannot be called outside of a page or layout.");
|
|
574
|
-
}
|
|
575
|
-
if (!store.req || !store.res) {
|
|
576
|
-
throw new Error("getQuery() cannot be used inside of a static page, since it depends on the *request query*.");
|
|
810
|
+
}
|
|
811
|
+
for (let i = port; i < port + 100; i++) {
|
|
812
|
+
try {
|
|
813
|
+
return await new Promise((resolve, reject) => {
|
|
814
|
+
const srv = createServer(handleRequest);
|
|
815
|
+
srv.keepAliveTimeout = 65e3;
|
|
816
|
+
srv.headersTimeout = 66e3;
|
|
817
|
+
srv.once("error", reject);
|
|
818
|
+
srv.listen(i, "0.0.0.0", () => {
|
|
819
|
+
logger.success(`Live at: http://localhost:${i}`);
|
|
820
|
+
resolve(srv);
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
} catch {
|
|
577
824
|
}
|
|
578
|
-
|
|
825
|
+
}
|
|
826
|
+
throw new Error("Could not find an available port");
|
|
579
827
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const [name, ...valueParts] = trimmed.split('=');
|
|
597
|
-
if (name) {
|
|
598
|
-
const value = valueParts.join('=').trim();
|
|
599
|
-
cookieMap.set(name, decodeURIComponent(value));
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
return cookieMap;
|
|
604
|
-
};
|
|
605
|
-
return {
|
|
606
|
-
/**
|
|
607
|
-
* Get a cookie value by name
|
|
608
|
-
*/
|
|
609
|
-
get(name) {
|
|
610
|
-
return getCookies().get(name);
|
|
611
|
-
},
|
|
612
|
-
/**
|
|
613
|
-
* Check if a cookie exists
|
|
614
|
-
*/
|
|
615
|
-
has(name) {
|
|
616
|
-
return getCookies().has(name);
|
|
617
|
-
},
|
|
618
|
-
/**
|
|
619
|
-
* Get all cookies as a plain object
|
|
620
|
-
*/
|
|
621
|
-
getAll() {
|
|
622
|
-
return Object.fromEntries(getCookies());
|
|
623
|
-
},
|
|
624
|
-
/**
|
|
625
|
-
* Set a cookie
|
|
626
|
-
*
|
|
627
|
-
* @param name Cookie name
|
|
628
|
-
* @param value Cookie value
|
|
629
|
-
* @param options Optional cookie attributes
|
|
630
|
-
*/
|
|
631
|
-
set(name, value, options = {}) {
|
|
632
|
-
let cookieStr = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
633
|
-
if (options.maxAge !== undefined) {
|
|
634
|
-
cookieStr += `; Max-Age=${Math.floor(options.maxAge)}`;
|
|
635
|
-
}
|
|
636
|
-
if (options.expires) {
|
|
637
|
-
cookieStr += `; Expires=${options.expires.toUTCString()}`;
|
|
638
|
-
}
|
|
639
|
-
if (options.path) {
|
|
640
|
-
cookieStr += `; Path=${options.path}`;
|
|
641
|
-
}
|
|
642
|
-
if (options.domain) {
|
|
643
|
-
cookieStr += `; Domain=${options.domain}`;
|
|
644
|
-
}
|
|
645
|
-
if (options.secure) {
|
|
646
|
-
cookieStr += `; Secure`;
|
|
647
|
-
}
|
|
648
|
-
if (options.httpOnly) {
|
|
649
|
-
cookieStr += `; HttpOnly`;
|
|
650
|
-
}
|
|
651
|
-
if (options.sameSite) {
|
|
652
|
-
cookieStr += `; SameSite=${options.sameSite}`;
|
|
653
|
-
}
|
|
654
|
-
const existing = res.getHeader('Set-Cookie');
|
|
655
|
-
if (existing) {
|
|
656
|
-
if (Array.isArray(existing)) {
|
|
657
|
-
res.setHeader('Set-Cookie', [...existing, cookieStr]);
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
res.setHeader('Set-Cookie', [existing, cookieStr]);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
else {
|
|
664
|
-
res.setHeader('Set-Cookie', cookieStr);
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
/**
|
|
668
|
-
* Delete a cookie (sets it to expire immediately)
|
|
669
|
-
*/
|
|
670
|
-
delete(name, path = '/', domain) {
|
|
671
|
-
this.set(name, '', {
|
|
672
|
-
maxAge: 0,
|
|
673
|
-
expires: new Date(0),
|
|
674
|
-
path,
|
|
675
|
-
domain,
|
|
676
|
-
});
|
|
677
|
-
},
|
|
678
|
-
};
|
|
828
|
+
async function serve() {
|
|
829
|
+
serverOptions = await loadServerOptions();
|
|
830
|
+
const manifest = JSON.parse(
|
|
831
|
+
await readFile(join(OUT_DIR, "paths.json"), "utf-8")
|
|
832
|
+
);
|
|
833
|
+
const routes = await warmDynamicCaches(manifest);
|
|
834
|
+
staticRouteMap = routes.staticRouteMap;
|
|
835
|
+
paramRoutes = routes.paramRoutes;
|
|
836
|
+
await Promise.all([
|
|
837
|
+
primeStaticCache(),
|
|
838
|
+
serverOptions.serveAPI ? loadApiRoutes(manifest) : Promise.resolve(),
|
|
839
|
+
loadMiddlewareMap(manifest),
|
|
840
|
+
initSecurityHeaders()
|
|
841
|
+
]);
|
|
842
|
+
await startMainServer();
|
|
843
|
+
if (process.send) process.send({ type: "ready" });
|
|
679
844
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
await respondWithStatusCode(req, res, pathname, 401, "Not authorized.");
|
|
698
|
-
},
|
|
699
|
-
async forbidden() {
|
|
700
|
-
const { req, res } = getRequest();
|
|
701
|
-
const url = new URL(`http://${process.env.HOST ?? 'localhost'}${req.url}`);
|
|
702
|
-
const pathname = sanitizePathname(url.pathname);
|
|
703
|
-
await respondWithStatusCode(req, res, pathname, 403, "Forbidden.");
|
|
704
|
-
},
|
|
705
|
-
async internalError() {
|
|
706
|
-
const { req, res } = getRequest();
|
|
707
|
-
const url = new URL(`http://${process.env.HOST ?? 'localhost'}${req.url}`);
|
|
708
|
-
const pathname = sanitizePathname(url.pathname);
|
|
709
|
-
await respondWithStatusCode(req, res, pathname, 500, "Internal server error.");
|
|
710
|
-
}
|
|
845
|
+
export {
|
|
846
|
+
aotStaticCache,
|
|
847
|
+
apiRoutesCache,
|
|
848
|
+
compileRouteMatcher,
|
|
849
|
+
dynamicModuleCache,
|
|
850
|
+
encCache,
|
|
851
|
+
loadApiRoutes,
|
|
852
|
+
loadMiddlewareMap,
|
|
853
|
+
middlewareChainCache,
|
|
854
|
+
middlewareMapCache,
|
|
855
|
+
paramRoutes,
|
|
856
|
+
primeStaticCache,
|
|
857
|
+
serve,
|
|
858
|
+
staticCache,
|
|
859
|
+
staticRouteMap,
|
|
860
|
+
statusCodePageCache,
|
|
861
|
+
warmDynamicCaches
|
|
711
862
|
};
|
|
712
|
-
export { serveProject, getQuery, getRequest, getCookieStore, redirect, respondWith };
|