evolit 0.1.0-alpha.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/LICENSE +201 -0
- package/README.md +352 -0
- package/package.json +63 -0
- package/src/app-discovery.js +312 -0
- package/src/build.js +417 -0
- package/src/cli.js +83 -0
- package/src/client-assets.js +1881 -0
- package/src/compiler.js +353 -0
- package/src/config.js +20 -0
- package/src/constants.js +49 -0
- package/src/deployment-runtime.js +463 -0
- package/src/fs-utils.js +71 -0
- package/src/index.js +167 -0
- package/src/render.js +516 -0
- package/src/request-context-browser.js +11 -0
- package/src/request-context.js +230 -0
- package/src/response-cache.js +214 -0
- package/src/route-config.js +60 -0
- package/src/scaffold.js +107 -0
- package/src/server-api.js +51 -0
- package/src/server.js +230 -0
- package/src/ssr-adapter.js +189 -0
- package/templates/default/app/about/page.litsx +20 -0
- package/templates/default/app/assets.d.ts +1 -0
- package/templates/default/app/components/feature-card.litsx +35 -0
- package/templates/default/app/global.css +27 -0
- package/templates/default/app/layout.litsx +18 -0
- package/templates/default/app/page.litsx +36 -0
- package/templates/default/jsconfig.json +22 -0
package/src/render.js
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { discoverAppRouteHandlers, discoverAppRoutes, matchRoute } from "./app-discovery.js";
|
|
4
|
+
import { importCompiledModule } from "./compiler.js";
|
|
5
|
+
import { APP_DIRECTORY, MODULE_EXTENSIONS } from "./constants.js";
|
|
6
|
+
import { pathExists } from "./fs-utils.js";
|
|
7
|
+
import { mergeRouteConfig, normalizeRouteCachePolicy } from "./route-config.js";
|
|
8
|
+
import {
|
|
9
|
+
createRequestContext,
|
|
10
|
+
applyRequestContextToResponse,
|
|
11
|
+
getRequestContextResponse,
|
|
12
|
+
isEvolitHttpSignal,
|
|
13
|
+
runWithRequestContext,
|
|
14
|
+
} from "./request-context.js";
|
|
15
|
+
import {
|
|
16
|
+
LITSX_COMPONENT,
|
|
17
|
+
LITSX_SERVER_COMPONENT,
|
|
18
|
+
} from "@litsx/core/elements";
|
|
19
|
+
|
|
20
|
+
function collectSearchParams(url) {
|
|
21
|
+
const values = {};
|
|
22
|
+
|
|
23
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
24
|
+
if (Object.hasOwn(values, key)) {
|
|
25
|
+
const previous = values[key];
|
|
26
|
+
values[key] = Array.isArray(previous) ? [...previous, value] : [previous, value];
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
values[key] = value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return values;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createRequestCacheKey(url) {
|
|
37
|
+
return `${url.pathname}${url.search}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function resolveDefaultExport(moduleRecord, filePath) {
|
|
41
|
+
if (typeof moduleRecord.default !== "function") {
|
|
42
|
+
throw new Error(`Expected a default function export in ${filePath}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return moduleRecord.default;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertServerComponent(moduleExport, filePath) {
|
|
49
|
+
if (moduleExport?.[LITSX_SERVER_COMPONENT] === true) {
|
|
50
|
+
return moduleExport;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (moduleExport?.[LITSX_COMPONENT] === true) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
[
|
|
56
|
+
`Expected ${filePath} to export an async server component.`,
|
|
57
|
+
"Synchronous LitSX page and layout functions compile to client components, not SSR route handlers.",
|
|
58
|
+
"Use `export default async function ...` for app pages and layouts.",
|
|
59
|
+
].join(" "),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return moduleExport;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function mergeMetadata(modules) {
|
|
67
|
+
return modules.reduce((metadata, moduleRecord) => {
|
|
68
|
+
if (moduleRecord?.metadata && typeof moduleRecord.metadata === "object") {
|
|
69
|
+
return { ...metadata, ...moduleRecord.metadata };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return metadata;
|
|
73
|
+
}, {});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createBoundaryError(error, { mode, route, reportRouteError }) {
|
|
77
|
+
const originalError = error instanceof Error ? error : new Error(String(error));
|
|
78
|
+
|
|
79
|
+
if (mode === "development") {
|
|
80
|
+
return originalError;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const digest = randomUUID();
|
|
84
|
+
const report = {
|
|
85
|
+
digest,
|
|
86
|
+
error: originalError,
|
|
87
|
+
route,
|
|
88
|
+
};
|
|
89
|
+
if (typeof reportRouteError === "function") {
|
|
90
|
+
reportRouteError(report);
|
|
91
|
+
} else {
|
|
92
|
+
console.error(`[evolit] Route rendering failed (${digest}) at ${route.pathname}`, originalError);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const boundaryError = new Error("An unexpected server error occurred.");
|
|
96
|
+
boundaryError.digest = digest;
|
|
97
|
+
return boundaryError;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getStaticParamsGenerator(moduleRecord) {
|
|
101
|
+
if (moduleRecord?.generateStaticParams == null) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof moduleRecord.generateStaticParams !== "function") {
|
|
106
|
+
throw new Error("Expected generateStaticParams to export a function.");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return moduleRecord.generateStaticParams;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function loadRouteModules(route, projectRoot, mode, options = {}) {
|
|
113
|
+
const moduleOptions = createRouteModuleOptions(projectRoot, mode, options);
|
|
114
|
+
const pageModule = await importCompiledModule(route.page, moduleOptions);
|
|
115
|
+
const layoutModules = await Promise.all(
|
|
116
|
+
route.layouts.map((layoutPath) => importCompiledModule(layoutPath, moduleOptions)),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
pageModule,
|
|
121
|
+
layoutModules,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function createRouteModuleOptions(projectRoot, mode, options = {}) {
|
|
126
|
+
const resolvedStaticAssetPublicUrls =
|
|
127
|
+
typeof options.getStaticAssetPublicUrls === "function"
|
|
128
|
+
? options.getStaticAssetPublicUrls()
|
|
129
|
+
: (options.staticAssetPublicUrls ?? null);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
projectRoot,
|
|
133
|
+
mode,
|
|
134
|
+
ssr: true,
|
|
135
|
+
staticAssetPublicUrls: resolvedStaticAssetPublicUrls,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function findAppModule(projectRoot, stem) {
|
|
140
|
+
const appRoot = path.join(projectRoot, APP_DIRECTORY);
|
|
141
|
+
|
|
142
|
+
for (const extension of MODULE_EXTENSIONS) {
|
|
143
|
+
const candidate = path.join(appRoot, `${stem}${extension}`);
|
|
144
|
+
if (await pathExists(candidate)) {
|
|
145
|
+
return candidate;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getNearestBoundary(route, boundaryName) {
|
|
153
|
+
return (route?.[boundaryName] ?? []).at(-1)?.module ?? null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function renderComponentTree(component, layoutComponents, requestContext, request, extraProps = {}) {
|
|
157
|
+
let renderedTree = await component({
|
|
158
|
+
params: requestContext.params,
|
|
159
|
+
searchParams: requestContext.searchParams,
|
|
160
|
+
request,
|
|
161
|
+
...extraProps,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
for (let index = layoutComponents.length - 1; index >= 0; index -= 1) {
|
|
165
|
+
renderedTree = await layoutComponents[index]({
|
|
166
|
+
params: requestContext.params,
|
|
167
|
+
searchParams: requestContext.searchParams,
|
|
168
|
+
request,
|
|
169
|
+
children: renderedTree,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return renderedTree;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function resolveStaticParamsForRoute(route, projectRoot, mode = "production", options = {}) {
|
|
177
|
+
const { pageModule, layoutModules } = await loadRouteModules(route, projectRoot, mode, options);
|
|
178
|
+
const generators = [...layoutModules, pageModule]
|
|
179
|
+
.map((moduleRecord) => getStaticParamsGenerator(moduleRecord))
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
|
|
182
|
+
let paramsList = [{}];
|
|
183
|
+
|
|
184
|
+
for (const generateStaticParams of generators) {
|
|
185
|
+
const nextParamsList = [];
|
|
186
|
+
|
|
187
|
+
for (const params of paramsList) {
|
|
188
|
+
const generatedEntries = await generateStaticParams({
|
|
189
|
+
params: Object.freeze({ ...params }),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (!Array.isArray(generatedEntries)) {
|
|
193
|
+
throw new Error("Expected generateStaticParams() to return an array.");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const entry of generatedEntries) {
|
|
197
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
198
|
+
throw new Error("Expected each generateStaticParams() entry to be an object.");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
nextParamsList.push({
|
|
202
|
+
...params,
|
|
203
|
+
...entry,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
paramsList = nextParamsList;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
hasGenerateStaticParams: generators.length > 0,
|
|
213
|
+
paramsList,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function createRouteResolver(projectRoot, mode = "development", resolverOptions = {}) {
|
|
218
|
+
const routes = await discoverAppRoutes(projectRoot);
|
|
219
|
+
const routeHandlers = await discoverAppRouteHandlers(projectRoot);
|
|
220
|
+
const rootLayoutPath = await findAppModule(projectRoot, "layout");
|
|
221
|
+
const rootNotFoundPath = await findAppModule(projectRoot, "not-found");
|
|
222
|
+
|
|
223
|
+
async function loadBoundaryComponent(boundaryPath) {
|
|
224
|
+
if (!boundaryPath) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const moduleRecord = await importCompiledModule(
|
|
229
|
+
boundaryPath,
|
|
230
|
+
createRouteModuleOptions(projectRoot, mode, resolverOptions),
|
|
231
|
+
);
|
|
232
|
+
return {
|
|
233
|
+
moduleRecord,
|
|
234
|
+
component: assertServerComponent(
|
|
235
|
+
await resolveDefaultExport(moduleRecord, boundaryPath),
|
|
236
|
+
boundaryPath,
|
|
237
|
+
),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function renderNotFound(request, options = {}) {
|
|
242
|
+
const route = options.route ?? null;
|
|
243
|
+
const requestContext = options.requestContext ?? createRequestContext({
|
|
244
|
+
request,
|
|
245
|
+
params: options.params ?? {},
|
|
246
|
+
searchParams: options.searchParams ?? collectSearchParams(new URL(request.url)),
|
|
247
|
+
});
|
|
248
|
+
const boundaryPath = getNearestBoundary(route, "notFoundBoundaries") ?? rootNotFoundPath;
|
|
249
|
+
|
|
250
|
+
if (!boundaryPath) {
|
|
251
|
+
return {
|
|
252
|
+
type: "not-found",
|
|
253
|
+
status: 404,
|
|
254
|
+
boundaryModule: null,
|
|
255
|
+
route,
|
|
256
|
+
params: requestContext.params,
|
|
257
|
+
searchParams: requestContext.searchParams,
|
|
258
|
+
cachePolicy: { mode: "dynamic" },
|
|
259
|
+
cacheKey: createRequestCacheKey(new URL(request.url)),
|
|
260
|
+
responseHeaders: getRequestContextResponse(requestContext).headers,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const boundary = await loadBoundaryComponent(boundaryPath);
|
|
265
|
+
const layoutModules = options.layoutModules ?? await Promise.all(
|
|
266
|
+
(rootLayoutPath ? [rootLayoutPath] : []).map((layoutPath) =>
|
|
267
|
+
importCompiledModule(
|
|
268
|
+
layoutPath,
|
|
269
|
+
createRouteModuleOptions(projectRoot, mode, resolverOptions),
|
|
270
|
+
),
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
const layoutComponents = options.layoutComponents ?? await Promise.all(
|
|
274
|
+
layoutModules.map((moduleRecord, index) => {
|
|
275
|
+
const layoutPath = route?.layouts[index] ?? rootLayoutPath;
|
|
276
|
+
return resolveDefaultExport(moduleRecord, layoutPath).then((entry) =>
|
|
277
|
+
assertServerComponent(entry, layoutPath),
|
|
278
|
+
);
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
const tree = await runWithRequestContext(requestContext, () =>
|
|
282
|
+
renderComponentTree(boundary.component, layoutComponents, requestContext, requestContext.routeRequest),
|
|
283
|
+
);
|
|
284
|
+
const contextResponse = getRequestContextResponse(requestContext);
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
type: "not-found",
|
|
288
|
+
status: 404,
|
|
289
|
+
tree,
|
|
290
|
+
boundaryModule: boundaryPath,
|
|
291
|
+
metadata: mergeMetadata([...layoutModules, boundary.moduleRecord]),
|
|
292
|
+
route,
|
|
293
|
+
params: requestContext.params,
|
|
294
|
+
searchParams: requestContext.searchParams,
|
|
295
|
+
cachePolicy: { mode: "dynamic" },
|
|
296
|
+
cacheKey: createRequestCacheKey(new URL(request.url)),
|
|
297
|
+
responseHeaders: contextResponse.headers,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function resolveRouteHandler(request, match, options = {}) {
|
|
302
|
+
const url = new URL(request.url);
|
|
303
|
+
if (options.renderTree === false) {
|
|
304
|
+
return {
|
|
305
|
+
type: "handler",
|
|
306
|
+
status: 200,
|
|
307
|
+
route: match.route,
|
|
308
|
+
params: match.params,
|
|
309
|
+
searchParams: collectSearchParams(url),
|
|
310
|
+
cachePolicy: { mode: "dynamic" },
|
|
311
|
+
cacheKey: createRequestCacheKey(url),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const handlerModule = await importCompiledModule(
|
|
316
|
+
match.route.handler,
|
|
317
|
+
createRouteModuleOptions(projectRoot, mode, resolverOptions),
|
|
318
|
+
);
|
|
319
|
+
const method = (request.method || "GET").toUpperCase();
|
|
320
|
+
const allowedMethods = Object.keys(handlerModule)
|
|
321
|
+
.filter((name) => /^[A-Z]+$/.test(name) && typeof handlerModule[name] === "function")
|
|
322
|
+
.sort();
|
|
323
|
+
const handler = handlerModule[method];
|
|
324
|
+
if (typeof handler !== "function") {
|
|
325
|
+
const response = new Response(null, {
|
|
326
|
+
status: 405,
|
|
327
|
+
headers: { allow: allowedMethods.join(", ") },
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
type: "handler",
|
|
331
|
+
status: response.status,
|
|
332
|
+
response,
|
|
333
|
+
route: match.route,
|
|
334
|
+
params: match.params,
|
|
335
|
+
searchParams: collectSearchParams(url),
|
|
336
|
+
cachePolicy: { mode: "dynamic" },
|
|
337
|
+
cacheKey: createRequestCacheKey(url),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const requestContext = createRequestContext({
|
|
342
|
+
request,
|
|
343
|
+
params: match.params,
|
|
344
|
+
searchParams: collectSearchParams(url),
|
|
345
|
+
});
|
|
346
|
+
const response = applyRequestContextToResponse(
|
|
347
|
+
await runWithRequestContext(requestContext, () => handler(requestContext.routeRequest, {
|
|
348
|
+
params: requestContext.params,
|
|
349
|
+
searchParams: requestContext.searchParams,
|
|
350
|
+
})),
|
|
351
|
+
requestContext,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
type: "handler",
|
|
356
|
+
status: response.status,
|
|
357
|
+
response,
|
|
358
|
+
route: match.route,
|
|
359
|
+
params: match.params,
|
|
360
|
+
searchParams: requestContext.searchParams,
|
|
361
|
+
cachePolicy: { mode: "dynamic" },
|
|
362
|
+
cacheKey: createRequestCacheKey(url),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function resolveMatchedRequest(request, options = {}) {
|
|
367
|
+
const url = new URL(request.url);
|
|
368
|
+
const handlerMatch = matchRoute(url.pathname, routeHandlers);
|
|
369
|
+
if (handlerMatch) {
|
|
370
|
+
return resolveRouteHandler(request, handlerMatch, options);
|
|
371
|
+
}
|
|
372
|
+
const match = matchRoute(url.pathname, routes);
|
|
373
|
+
|
|
374
|
+
if (!match) {
|
|
375
|
+
if (options.renderTree === false) {
|
|
376
|
+
return {
|
|
377
|
+
type: "not-found",
|
|
378
|
+
status: 404,
|
|
379
|
+
cachePolicy: { mode: "dynamic" },
|
|
380
|
+
cacheKey: createRequestCacheKey(url),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return renderNotFound(request);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const { pageModule, layoutModules } = await loadRouteModules(
|
|
388
|
+
match.route,
|
|
389
|
+
projectRoot,
|
|
390
|
+
mode,
|
|
391
|
+
resolverOptions,
|
|
392
|
+
);
|
|
393
|
+
const routeConfig = mergeRouteConfig([...layoutModules, pageModule]);
|
|
394
|
+
const cachePolicy = normalizeRouteCachePolicy(routeConfig);
|
|
395
|
+
|
|
396
|
+
if (options.renderTree === false) {
|
|
397
|
+
return {
|
|
398
|
+
type: "route",
|
|
399
|
+
status: 200,
|
|
400
|
+
route: match.route,
|
|
401
|
+
params: match.params,
|
|
402
|
+
searchParams: collectSearchParams(url),
|
|
403
|
+
routeConfig,
|
|
404
|
+
cachePolicy,
|
|
405
|
+
cacheKey: createRequestCacheKey(url),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const pageComponent = assertServerComponent(
|
|
410
|
+
await resolveDefaultExport(pageModule, match.route.page),
|
|
411
|
+
match.route.page,
|
|
412
|
+
);
|
|
413
|
+
const layoutComponents = await Promise.all(
|
|
414
|
+
layoutModules.map((moduleRecord, index) =>
|
|
415
|
+
resolveDefaultExport(moduleRecord, match.route.layouts[index]).then((entry) =>
|
|
416
|
+
assertServerComponent(entry, match.route.layouts[index]),
|
|
417
|
+
),
|
|
418
|
+
),
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const requestContext = createRequestContext({
|
|
422
|
+
request,
|
|
423
|
+
params: match.params,
|
|
424
|
+
searchParams: collectSearchParams(url),
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const tree = await runWithRequestContext(requestContext, () =>
|
|
429
|
+
renderComponentTree(pageComponent, layoutComponents, requestContext, requestContext.routeRequest),
|
|
430
|
+
);
|
|
431
|
+
const contextResponse = getRequestContextResponse(requestContext);
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
type: "route",
|
|
435
|
+
status: 200,
|
|
436
|
+
tree,
|
|
437
|
+
metadata: mergeMetadata([...layoutModules, pageModule]),
|
|
438
|
+
route: match.route,
|
|
439
|
+
params: match.params,
|
|
440
|
+
searchParams: requestContext.searchParams,
|
|
441
|
+
routeConfig,
|
|
442
|
+
cachePolicy: contextResponse.didUseDynamicRequestData ? { mode: "dynamic" } : cachePolicy,
|
|
443
|
+
cacheKey: createRequestCacheKey(url),
|
|
444
|
+
responseHeaders: contextResponse.headers,
|
|
445
|
+
};
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (isEvolitHttpSignal(error)) {
|
|
448
|
+
if (error.type === "not-found") {
|
|
449
|
+
return renderNotFound(request, {
|
|
450
|
+
route: match.route,
|
|
451
|
+
requestContext,
|
|
452
|
+
layoutModules,
|
|
453
|
+
layoutComponents,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const contextResponse = getRequestContextResponse(requestContext);
|
|
458
|
+
return {
|
|
459
|
+
type: error.type,
|
|
460
|
+
status: error.status,
|
|
461
|
+
location: error.location,
|
|
462
|
+
route: match.route,
|
|
463
|
+
params: match.params,
|
|
464
|
+
searchParams: requestContext.searchParams,
|
|
465
|
+
routeConfig,
|
|
466
|
+
cachePolicy: { mode: "dynamic" },
|
|
467
|
+
cacheKey: createRequestCacheKey(url),
|
|
468
|
+
responseHeaders: contextResponse.headers,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const errorBoundaryPath = getNearestBoundary(match.route, "errorBoundaries");
|
|
473
|
+
if (!errorBoundaryPath) {
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const boundary = await loadBoundaryComponent(errorBoundaryPath);
|
|
478
|
+
const boundaryError = createBoundaryError(error, {
|
|
479
|
+
mode,
|
|
480
|
+
route: match.route,
|
|
481
|
+
reportRouteError: resolverOptions.reportRouteError,
|
|
482
|
+
});
|
|
483
|
+
const tree = await runWithRequestContext(requestContext, () =>
|
|
484
|
+
renderComponentTree(boundary.component, layoutComponents, requestContext, requestContext.routeRequest, {
|
|
485
|
+
error: boundaryError,
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
const contextResponse = getRequestContextResponse(requestContext);
|
|
489
|
+
return {
|
|
490
|
+
type: "error",
|
|
491
|
+
status: 500,
|
|
492
|
+
tree,
|
|
493
|
+
boundaryModule: errorBoundaryPath,
|
|
494
|
+
metadata: mergeMetadata([...layoutModules, boundary.moduleRecord]),
|
|
495
|
+
route: match.route,
|
|
496
|
+
params: match.params,
|
|
497
|
+
searchParams: requestContext.searchParams,
|
|
498
|
+
routeConfig,
|
|
499
|
+
cachePolicy: { mode: "dynamic" },
|
|
500
|
+
cacheKey: createRequestCacheKey(url),
|
|
501
|
+
responseHeaders: contextResponse.headers,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
routes,
|
|
508
|
+
routeHandlers,
|
|
509
|
+
async resolveRoutePolicy(request) {
|
|
510
|
+
return resolveMatchedRequest(request, { renderTree: false });
|
|
511
|
+
},
|
|
512
|
+
async resolveRequest(request) {
|
|
513
|
+
return resolveMatchedRequest(request, { renderTree: true });
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function throwServerOnlyApi(name) {
|
|
2
|
+
throw new Error(`${name}() is only available while rendering a evolit server route.`);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function cookies() { return throwServerOnlyApi("cookies"); }
|
|
6
|
+
export function headers() { return throwServerOnlyApi("headers"); }
|
|
7
|
+
export function notFound() { return throwServerOnlyApi("notFound"); }
|
|
8
|
+
export function permanentRedirect() { return throwServerOnlyApi("permanentRedirect"); }
|
|
9
|
+
export function redirect() { return throwServerOnlyApi("redirect"); }
|
|
10
|
+
export function requestUrl() { return throwServerOnlyApi("requestUrl"); }
|
|
11
|
+
export function responseHeaders() { return throwServerOnlyApi("responseHeaders"); }
|