@warlock.js/web 5.6.0 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/esm/build/contribution.mjs +1 -1
- package/esm/build/discover-pages.mjs +3 -3
- package/esm/build/generate-pages-barrel.mjs +2 -2
- package/esm/build/page-routes-manifest.mjs +1 -1
- package/esm/build/public-files.mjs +1 -1
- package/esm/client/navigation/fetch-page-data.mjs +3 -10
- package/esm/client/navigation/fetch-page-data.mjs.map +1 -1
- package/esm/client/navigation/navigation-root.mjs +1 -1
- package/esm/hydration-payload.mjs +19 -10
- package/esm/hydration-payload.mjs.map +1 -1
- package/esm/loaders.d.mts +12 -7
- package/esm/route.d.mts +8 -32
- package/esm/server/create-page-route-handler.mjs +12 -6
- package/esm/server/create-page-route-handler.mjs.map +1 -1
- package/esm/server/execute-page-request.mjs +29 -37
- package/esm/server/execute-page-request.mjs.map +1 -1
- package/esm/server/execute-page-request.types.d.mts +15 -4
- package/esm/server/hydration-client-url.mjs +1 -1
- package/esm/server/index.d.mts +2 -1
- package/esm/server/index.mjs +4 -4
- package/esm/server/install-page-routes-from-manifest.d.mts +9 -0
- package/esm/server/install-page-routes-from-manifest.mjs +20 -20
- package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
- package/esm/server/install-page-routes.d.mts +2 -24
- package/esm/server/install-page-routes.mjs +48 -25
- package/esm/server/install-page-routes.mjs.map +1 -1
- package/esm/server/match-page-route.mjs +6 -10
- package/esm/server/match-page-route.mjs.map +1 -1
- package/esm/server/not-found-page.d.mts +1 -0
- package/esm/server/not-found-page.mjs +8 -1
- package/esm/server/not-found-page.mjs.map +1 -1
- package/esm/server/page-file-change.mjs +1 -1
- package/esm/server/page-module-shapes.d.mts +24 -0
- package/esm/server/page-route-reload.mjs +1 -1
- package/esm/server/register-production-public-files.mjs +1 -1
- package/esm/server/render-page.d.mts +3 -2
- package/esm/server/render-page.mjs +1 -0
- package/esm/server/render-page.mjs.map +1 -1
- package/esm/server/resolve-route-validation-input.mjs +2 -2
- package/esm/server/resolve-route-validation-input.mjs.map +1 -1
- package/esm/server/resolve-validation-data.mjs +1 -1
- package/esm/server/resolve-validation-data.mjs.map +1 -1
- package/esm/server/stylesheet-urls.mjs +78 -19
- package/esm/server/stylesheet-urls.mjs.map +1 -1
- package/esm/server/web-connector.mjs +2 -2
- package/esm/server/web-connector.mjs.map +1 -1
- package/esm/validation.d.mts +12 -1
- package/esm/vite/build-client.mjs +1 -1
- package/esm/vite/gate-a-resolve.mjs +1 -1
- package/esm/vite/hydration-entries.mjs +1 -1
- package/llms-full.txt +25 -19
- package/package.json +3 -3
- package/skills/create-a-page/SKILL.md +25 -19
- package/esm/server/route-validation-error.mjs +0 -32
- package/esm/server/route-validation-error.mjs.map +0 -1
|
@@ -4,9 +4,9 @@ import { resolvePageMetadata } from "./resolve-page-metadata.mjs";
|
|
|
4
4
|
import { connectPageContext, enterAdditionalSharedScope, requireRunner } from "./page-context.mjs";
|
|
5
5
|
import { matchRoute } from "./match-page-route.mjs";
|
|
6
6
|
import { resolveValidationData } from "./resolve-validation-data.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { RouteValidationError } from "./route-validation-error.mjs";
|
|
7
|
+
import { resolvePageValidationInput } from "./resolve-route-validation-input.mjs";
|
|
9
8
|
import { LEVEL_ORDER, buildErrorRecord, commitBuffers, createBufferedResponse, createLevelBuffer, designateBoundary, isLoaderShortCircuit } from "./settle-page-response.mjs";
|
|
9
|
+
import { RouteMiddlewareRemovedError } from "./install-page-routes.mjs";
|
|
10
10
|
import { Response } from "@warlock.js/core";
|
|
11
11
|
import { v } from "@warlock.js/seal";
|
|
12
12
|
|
|
@@ -35,7 +35,7 @@ async function executePageRequest(options) {
|
|
|
35
35
|
const runner = requireRunner();
|
|
36
36
|
wireRequestSearch();
|
|
37
37
|
const [pathname, queryString] = options.url.split("?");
|
|
38
|
-
const matched = matchRoute(pathname, options.routes);
|
|
38
|
+
const matched = options.matched ?? matchRoute(pathname, options.routes);
|
|
39
39
|
if (!matched) return void 0;
|
|
40
40
|
const query = Object.fromEntries(new URLSearchParams(queryString ?? ""));
|
|
41
41
|
const match = {
|
|
@@ -62,11 +62,10 @@ async function executePageRequest(options) {
|
|
|
62
62
|
params: match.params,
|
|
63
63
|
query
|
|
64
64
|
} };
|
|
65
|
-
const pageRoute = typeof triple.page.route === "object" ? triple.page.route : void 0;
|
|
66
65
|
/**
|
|
67
|
-
* THE
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* THE MIDDLEWARE SURFACES, TOGETHER — the one place both are documented,
|
|
67
|
+
* so they cannot again be found "separately and inconsistently" (canon
|
|
68
|
+
* `b79c4f55`, point 5):
|
|
70
69
|
*
|
|
71
70
|
* - A LAYOUT'S `middleware` export (`../routing/layout-policy.ts` — a
|
|
72
71
|
* middleware-only layout, one with no default export, is treated as a
|
|
@@ -74,17 +73,17 @@ async function executePageRequest(options) {
|
|
|
74
73
|
* on a page's chain contributes, outermost first
|
|
75
74
|
* (`install-page-routes.ts`'s `composeLayoutLevel` folds the whole
|
|
76
75
|
* chain into `triple.layout.middleware` before this runs).
|
|
77
|
-
* -
|
|
76
|
+
* - The PAGE's OWN top-level `middleware` export (`triple.page.middleware`)
|
|
78
77
|
* — a page's own answer to "what does this URL require", one level
|
|
79
78
|
* below the layout instead of borrowed from it (canon `f2e514c0`).
|
|
80
79
|
*
|
|
81
80
|
* ONE ordering rule covers both: `LEVEL_ORDER` (app, layout, page) runs
|
|
82
|
-
* outermost-first,
|
|
83
|
-
*
|
|
84
|
-
*
|
|
81
|
+
* outermost-first, so the page's own list always runs LAST — closest to
|
|
82
|
+
* the loader. A layout's auth gate can therefore never be bypassed by a
|
|
83
|
+
* page's own middleware.
|
|
85
84
|
*/
|
|
86
85
|
for (const level of LEVEL_ORDER) {
|
|
87
|
-
const middlewareForLevel =
|
|
86
|
+
const middlewareForLevel = triple[level].middleware ?? [];
|
|
88
87
|
for (const middleware of middlewareForLevel) {
|
|
89
88
|
let output;
|
|
90
89
|
try {
|
|
@@ -109,17 +108,24 @@ async function executePageRequest(options) {
|
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
const validation = triple.page.validation;
|
|
112
|
-
if (validation
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
111
|
+
if (validation) {
|
|
112
|
+
const legacyValidation = "schema" in validation || "validating" in validation;
|
|
113
|
+
const schema = legacyValidation ? validation.schema : v.object({
|
|
114
|
+
...validation.params === void 0 ? {} : { params: validation.params },
|
|
115
|
+
...validation.query === void 0 ? {} : { query: validation.query }
|
|
116
|
+
});
|
|
117
|
+
if (schema) {
|
|
118
|
+
const data = legacyValidation ? resolveValidationData(validation.validating, request) : resolvePageValidationInput(request);
|
|
119
|
+
const result = await v.validate(schema, data);
|
|
120
|
+
if (result.isValid && result.data) request.setValidatedData(result.data);
|
|
121
|
+
if (!result.isValid) {
|
|
122
|
+
bundle.shortCircuit = {
|
|
123
|
+
stage: "validation",
|
|
124
|
+
status: 400,
|
|
125
|
+
errors: result.errors
|
|
126
|
+
};
|
|
127
|
+
return finish(bundle);
|
|
128
|
+
}
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
const sealedShared = await sealShared(store);
|
|
@@ -140,20 +146,6 @@ async function executePageRequest(options) {
|
|
|
140
146
|
let signalCircuit;
|
|
141
147
|
for (let index = 0; index < LEVEL_ORDER.length; index++) {
|
|
142
148
|
const level = LEVEL_ORDER[index];
|
|
143
|
-
if (level === "page" && pageRoute?.validate) {
|
|
144
|
-
const routeInput = resolveRouteValidationInput(request);
|
|
145
|
-
const result = await v.validate(pageRoute.validate, routeInput);
|
|
146
|
-
if (result.isValid && result.data) request.setValidatedData({
|
|
147
|
-
...request.validated(),
|
|
148
|
-
...result.data
|
|
149
|
-
});
|
|
150
|
-
if (!result.isValid) {
|
|
151
|
-
signalIndex = index;
|
|
152
|
-
signalKind = "throw";
|
|
153
|
-
signalThrown = new RouteValidationError(result.errors);
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
149
|
const loader = triple[level].loader;
|
|
158
150
|
if (!loader) continue;
|
|
159
151
|
let value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute-page-request.mjs","names":[],"sources":["../../../../../../../web/src/server/execute-page-request.ts"],"sourcesContent":["import { Response } from \"@warlock.js/core\";\nimport { v } from \"@warlock.js/seal\";\nimport { enterSharedScope, sealShared } from \"../shared\";\nimport { connectRequestSearch } from \"../routing/query-string\";\nimport { enterAdditionalSharedScope, requireRunner } from \"./page-context\";\nimport { matchRoute } from \"./match-page-route\";\nimport { resolvePageMetadata } from \"./resolve-page-metadata\";\nimport { resolveValidationData } from \"./resolve-validation-data\";\nimport { resolveRouteValidationInput } from \"./resolve-route-validation-input\";\nimport { RouteValidationError } from \"./route-validation-error\";\nimport {\n buildErrorRecord,\n commitBuffers,\n createBufferedResponse,\n createLevelBuffer,\n designateBoundary,\n isLoaderShortCircuit,\n LEVEL_ORDER,\n type LevelBuffer,\n type PageResponseCommit,\n} from \"./settle-page-response\";\nimport type {\n ExecutePageRequestOptions,\n PageDataBundle,\n PageLevelName,\n PageRouteMatch,\n PipelineStore,\n} from \"./execute-page-request.types\";\n\nexport * from \"./execute-page-request.types\";\nexport { connectPageContext } from \"./page-context\";\nexport {\n buildErrorRecord,\n designateBoundary,\n type BufferedCookie,\n type BufferedHeader,\n type LoaderShortCircuitKind,\n type PageResponseCommit,\n} from \"./settle-page-response\";\n\n/** Widens `PageDataBundle` with the two fields stage 6/7 populate. */\ntype Bundle = PageDataBundle & {\n commit?: PageResponseCommit;\n shortCircuit?: PageDataBundle[\"shortCircuit\"] & {\n kind?: \"redirect\" | \"notFound\";\n url?: string;\n body?: unknown;\n };\n};\n\n/**\n * Self-wires `useQueryString`'s SSR seam to the SAME per-request store\n * `connectSharedStore`/`connectPageContext` already read (canon `1ca1e8ae`'s\n * scoping) — done HERE, once, rather than asking every server bootstrap\n * (dev's Vite-graph wiring in `web-connector.ts`, prod's\n * `installProductionPageRoutes`) to remember a THIRD `connect*` call for the\n * same store.\n *\n * The resolver calls `requireRunner()` on every read, not the `runner`\n * closed over by its caller: `runner.getStore()` is only valid for as long as\n * that particular runner is connected, and a test (or a later reconnect)\n * that swaps in a new one via `connectPageContext` must not leave this\n * resolver reading a stale runner's store.\n */\nlet requestSearchWired = false;\n\nfunction wireRequestSearch(): void {\n if (requestSearchWired) return;\n\n requestSearchWired = true;\n connectRequestSearch(() => requireRunner().getStore()?.request.url);\n}\n\nexport async function executePageRequest<TResult = PageDataBundle>(\n options: ExecutePageRequestOptions<TResult>,\n): Promise<TResult | Response | undefined> {\n const runner = requireRunner();\n\n wireRequestSearch();\n const [pathname, queryString] = options.url.split(\"?\");\n const matched = matchRoute(pathname, options.routes);\n\n if (!matched) return undefined;\n\n const query = Object.fromEntries(new URLSearchParams(queryString ?? \"\"));\n const match: PageRouteMatch = { entry: matched.entry, params: matched.params, query };\n const { triple } = matched.entry;\n const { request, response } = options.createHttp(match);\n const store: PipelineStore = runner.buildStore\n ? runner.buildStore({ request, response })\n : { request, response };\n\n return runner.run(store, async () => {\n enterSharedScope(store);\n enterAdditionalSharedScope(store);\n\n const finish = async (bundle: PageDataBundle): Promise<TResult> =>\n options.finish ? await options.finish(bundle) : (bundle as TResult);\n\n const bundle: Bundle = {\n route: {\n name: matched.entry.name,\n path: matched.entry.path,\n params: match.params,\n query,\n },\n };\n\n const pageRoute = typeof triple.page.route === \"object\" ? triple.page.route : undefined;\n\n /**\n * THE TWO MIDDLEWARE SURFACES, TOGETHER — the one place both are\n * documented, so they cannot again be found \"separately and\n * inconsistently\" (canon `b79c4f55`, point 5):\n *\n * - A LAYOUT'S `middleware` export (`../routing/layout-policy.ts` — a\n * middleware-only layout, one with no default export, is treated as a\n * deliberate authorization boundary and composes freely). Every layout\n * on a page's chain contributes, outermost first\n * (`install-page-routes.ts`'s `composeLayoutLevel` folds the whole\n * chain into `triple.layout.middleware` before this runs).\n * - A PAGE's OWN guards, declared on `route.middleware` (`../route.ts`)\n * — a page's own answer to \"what does this URL require\", one level\n * below the layout instead of borrowed from it (canon `f2e514c0`).\n *\n * ONE ordering rule covers both: `LEVEL_ORDER` (app, layout, page) runs\n * outermost-first, and `route.middleware` is appended to the PAGE level's\n * own list, so it always runs LAST — closest to the loader. A layout's\n * auth gate can therefore never be bypassed by a page's own middleware.\n */\n for (const level of LEVEL_ORDER) {\n const middlewareForLevel =\n level === \"page\"\n ? [...(triple.page.middleware ?? []), ...(pageRoute?.middleware ?? [])]\n : (triple[level].middleware ?? []);\n\n for (const middleware of middlewareForLevel) {\n let output: unknown;\n\n try {\n output = await middleware({ request, response });\n } catch (thrown) {\n bundle.error = buildErrorRecord(thrown, designateBoundary(level, triple), pathname);\n response.setStatusCode(500);\n return finish(bundle);\n }\n\n if (output !== undefined) {\n bundle.shortCircuit = {\n stage: \"middleware\",\n level,\n value: output,\n statusCode: response.statusCode,\n };\n return finish(bundle);\n }\n }\n }\n\n const validation = triple.page.validation;\n\n if (validation?.schema) {\n const data = resolveValidationData(validation.validating, request);\n const result = await v.validate(validation.schema, data);\n\n if (result.isValid && result.data) {\n request.setValidatedData(result.data);\n }\n\n if (!result.isValid) {\n bundle.shortCircuit = { stage: \"validation\", status: 422, errors: result.errors };\n return finish(bundle);\n }\n }\n\n const sealedShared = await sealShared(store);\n bundle.shared = sealedShared;\n\n const dataKeys: Record<PageLevelName, \"appData\" | \"layoutData\" | \"pageData\"> = {\n app: \"appData\",\n layout: \"layoutData\",\n page: \"pageData\",\n };\n\n // Stage 6 — LOADERS, root to leaf. Every level gets its OWN buffer (never\n // the live response), and a terminal result or throw prevents every lower\n // loader from starting.\n const buffers: Record<PageLevelName, LevelBuffer> = {\n app: createLevelBuffer(),\n layout: createLevelBuffer(),\n page: createLevelBuffer(),\n };\n\n let signalIndex = -1;\n let signalKind: \"throw\" | \"shortCircuit\" | undefined;\n let signalThrown: unknown;\n let signalCircuit:\n | { kind: \"redirect\" | \"notFound\"; statusCode: number; url?: string; body?: unknown }\n | undefined;\n\n for (let index = 0; index < LEVEL_ORDER.length; index++) {\n const level = LEVEL_ORDER[index];\n\n // `route.validate` — the PAGE's own declared schema, over `{ params,\n // query }` kept as two separate keys (canon `b79c4f55`, point 1). Runs\n // HERE, at the front of the page level's own turn: app and layout\n // loaders have already run (their data survives a rejection, exactly\n // as an ordinary page-level throw leaves them untouched) and the\n // page's OWN loader has not (mirrors the top-level `validation`\n // export's \"before the loader\" contract). A failure is folded into the\n // ordinary THROW signal below rather than given a fourth code path: it\n // designates a boundary and renders the application's error\n // page/boundary with status 400 (point 2) — a page is a document, not\n // an API endpoint, so this must never answer a raw JSON body.\n if (level === \"page\" && pageRoute?.validate) {\n const routeInput = resolveRouteValidationInput(request);\n const result = await v.validate(pageRoute.validate, routeInput);\n\n if (result.isValid && result.data) {\n // Merged, never overwritten: a page using BOTH the top-level\n // `validation` export and `route.validate` must see every field\n // either one produced, not just whichever ran last.\n request.setValidatedData({ ...request.validated(), ...result.data });\n }\n\n if (!result.isValid) {\n signalIndex = index;\n signalKind = \"throw\";\n signalThrown = new RouteValidationError(result.errors);\n break;\n }\n }\n\n const loader = triple[level].loader;\n\n if (!loader) continue;\n\n let value: unknown;\n\n try {\n value = await loader({\n request,\n response: createBufferedResponse(buffers[level]),\n shared: sealedShared,\n });\n } catch (thrown) {\n signalIndex = index;\n signalKind = \"throw\";\n signalThrown = thrown;\n break;\n }\n\n if (value instanceof Response) return value;\n\n if (isLoaderShortCircuit(value)) {\n signalIndex = index;\n signalKind = \"shortCircuit\";\n signalCircuit = value;\n break;\n }\n\n bundle[dataKeys[level]] = value;\n }\n\n let committedLevels: PageLevelName[];\n /** Set only when a THROW escalated to the app boundary — forces 500. */\n let forcedStatusCode: number | undefined;\n\n if (signalIndex === -1) {\n committedLevels = [...LEVEL_ORDER];\n } else if (signalKind === \"throw\") {\n // The throwing level's buffer is discarded; lower levels never ran.\n committedLevels = LEVEL_ORDER.slice(0, signalIndex);\n\n const boundary = designateBoundary(LEVEL_ORDER[signalIndex], triple);\n // A failure that OWNS its own status (a `RouteValidationError`'s 400)\n // carries it through here; an ordinary throw carries none and keeps\n // the pipeline's ordinary answer, 500.\n const ownStatusCode = (signalThrown as { statusCode?: number } | null)?.statusCode;\n bundle.error = buildErrorRecord(signalThrown, boundary, pathname, ownStatusCode);\n\n if (boundary.boundaryLevel === \"app\") {\n const status = ownStatusCode ?? 500;\n response.setStatusCode(status);\n forcedStatusCode = status;\n }\n } else {\n // Short-circuit: the signalling level's OWN buffer commits too\n // (inclusive); lower levels never ran.\n committedLevels = LEVEL_ORDER.slice(0, signalIndex + 1);\n\n const circuit = signalCircuit!;\n\n bundle.shortCircuit = {\n stage: \"loaders\",\n level: LEVEL_ORDER[signalIndex],\n kind: circuit.kind,\n statusCode: circuit.statusCode,\n url: circuit.url,\n body: circuit.body,\n } as unknown as PageDataBundle[\"shortCircuit\"];\n }\n\n bundle.commit = commitBuffers(response, buffers, committedLevels);\n\n // Forced AFTER the fold: an app-boundary escalation forces 500\n // regardless of what the surviving (rootward) buffers happened to set —\n // it is the framework's answer, not a loader's.\n if (forcedStatusCode !== undefined) bundle.commit.statusCode = forcedStatusCode;\n\n // Stage 8 — METADATA. Skipped entirely for a short-circuit (there is no\n // page to describe); a throw still runs it, same as before this stage 6/7\n // rewrite (a boundary still needs a title/robots answer).\n if (bundle.shortCircuit) {\n return finish(bundle);\n }\n\n const resolved = resolvePageMetadata({\n metadata: triple.page.metadata,\n data: bundle.pageData,\n error: bundle.error?.error,\n failed: Boolean(bundle.error),\n shared: sealedShared,\n });\n\n bundle.metadata = resolved.metadata;\n\n if (resolved.thrown !== undefined) {\n const boundary = designateBoundary(\"page\", triple);\n bundle.error = buildErrorRecord(resolved.thrown, boundary, bundle.route.path);\n\n if (boundary.boundaryLevel === \"app\") response.setStatusCode(500);\n }\n\n return finish(bundle);\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"execute-page-request.mjs","names":[],"sources":["../../../../../../../web/src/server/execute-page-request.ts"],"sourcesContent":["import { Response } from \"@warlock.js/core\";\r\nimport { v } from \"@warlock.js/seal\";\r\nimport { enterSharedScope, sealShared } from \"../shared\";\r\nimport { connectRequestSearch } from \"../routing/query-string\";\r\nimport { enterAdditionalSharedScope, requireRunner } from \"./page-context\";\r\nimport { matchRoute } from \"./match-page-route\";\r\nimport { resolvePageMetadata } from \"./resolve-page-metadata\";\r\nimport { resolveValidationData } from \"./resolve-validation-data\";\r\nimport { resolvePageValidationInput } from \"./resolve-route-validation-input\";\r\nimport {\r\n buildErrorRecord,\r\n commitBuffers,\r\n createBufferedResponse,\r\n createLevelBuffer,\r\n designateBoundary,\r\n isLoaderShortCircuit,\r\n LEVEL_ORDER,\r\n type LevelBuffer,\r\n type PageResponseCommit,\r\n} from \"./settle-page-response\";\r\nimport type {\r\n ExecutePageRequestOptions,\r\n PageDataBundle,\r\n PageLevelName,\r\n PageRouteMatch,\r\n PipelineStore,\r\n} from \"./execute-page-request.types\";\r\n\r\nexport * from \"./execute-page-request.types\";\r\nexport { connectPageContext } from \"./page-context\";\r\nexport {\r\n buildErrorRecord,\r\n designateBoundary,\r\n type BufferedCookie,\r\n type BufferedHeader,\r\n type LoaderShortCircuitKind,\r\n type PageResponseCommit,\r\n} from \"./settle-page-response\";\r\nexport { RouteMiddlewareRemovedError } from \"./install-page-routes\";\r\n\r\n/**\r\n * `route.middleware` shipped in 5.6.0 and was withdrawn (owner ruling,\r\n * 2026-09-08): a page declares middleware in exactly one place, the\r\n * top-level `middleware` export. Thrown the first time a matched route's\r\n * page module still carries a `middleware` key on `route` — loud, not a\r\n * silent no-op, so the guard the author thinks is running is never quietly\r\n * dropped.\r\n */\r\n/* RouteMiddlewareRemovedError moved to install-page-routes.ts, where pageFile is available.\r\nexport class RouteMiddlewareRemovedError extends Error {\r\n public constructor(\r\n public readonly routeName: string,\r\n public readonly routePath: string,\r\n ) {\r\n super(\r\n `Warlock route \"${routeName}\" (${routePath}) declares \\`route.middleware\\`, which no ` +\r\n \"longer runs — it was withdrawn after 5.6.0. Move it to the page's own top-level \" +\r\n \"`middleware` export instead: `export const middleware = [...]`.\",\r\n );\r\n this.name = \"RouteMiddlewareRemovedError\";\r\n }\r\n}\r\n*/\r\n\r\n/** Widens `PageDataBundle` with the two fields stage 6/7 populate. */\r\ntype Bundle = PageDataBundle & {\r\n commit?: PageResponseCommit;\r\n shortCircuit?: PageDataBundle[\"shortCircuit\"] & {\r\n kind?: \"redirect\" | \"notFound\";\r\n url?: string;\r\n body?: unknown;\r\n };\r\n};\r\n\r\n/**\r\n * Self-wires `useQueryString`'s SSR seam to the SAME per-request store\r\n * `connectSharedStore`/`connectPageContext` already read (canon `1ca1e8ae`'s\r\n * scoping) — done HERE, once, rather than asking every server bootstrap\r\n * (dev's Vite-graph wiring in `web-connector.ts`, prod's\r\n * `installProductionPageRoutes`) to remember a THIRD `connect*` call for the\r\n * same store.\r\n *\r\n * The resolver calls `requireRunner()` on every read, not the `runner`\r\n * closed over by its caller: `runner.getStore()` is only valid for as long as\r\n * that particular runner is connected, and a test (or a later reconnect)\r\n * that swaps in a new one via `connectPageContext` must not leave this\r\n * resolver reading a stale runner's store.\r\n */\r\nlet requestSearchWired = false;\r\n\r\nfunction wireRequestSearch(): void {\r\n if (requestSearchWired) return;\r\n\r\n requestSearchWired = true;\r\n connectRequestSearch(() => requireRunner().getStore()?.request.url);\r\n}\r\n\r\nexport async function executePageRequest<TResult = PageDataBundle>(\r\n options: ExecutePageRequestOptions<TResult>,\r\n): Promise<TResult | Response | undefined> {\r\n const runner = requireRunner();\r\n\r\n wireRequestSearch();\r\n const [pathname, queryString] = options.url.split(\"?\");\r\n // HTTP page handlers arrive here after core's router selected their route.\r\n // Keep its entry and decoded params authoritative; standalone rendering has\r\n // no such request, so it still resolves against the supplied route table.\r\n const matched = options.matched ?? matchRoute(pathname, options.routes);\r\n\r\n if (!matched) return undefined;\r\n\r\n const query = Object.fromEntries(new URLSearchParams(queryString ?? \"\"));\r\n const match: PageRouteMatch = { entry: matched.entry, params: matched.params, query };\r\n const { triple } = matched.entry;\r\n const { request, response } = options.createHttp(match);\r\n const store: PipelineStore = runner.buildStore\r\n ? runner.buildStore({ request, response })\r\n : { request, response };\r\n\r\n return runner.run(store, async () => {\r\n enterSharedScope(store);\r\n enterAdditionalSharedScope(store);\r\n\r\n const finish = async (bundle: PageDataBundle): Promise<TResult> =>\r\n options.finish ? await options.finish(bundle) : (bundle as TResult);\r\n\r\n const bundle: Bundle = {\r\n route: {\r\n name: matched.entry.name,\r\n path: matched.entry.path,\r\n params: match.params,\r\n query,\r\n },\r\n };\r\n\r\n // `route.middleware` shipped in 5.6.0 and was withdrawn (owner ruling,\r\n // 2026-09-08): a page declares middleware in exactly ONE place, the\r\n // top-level `middleware` export. A page module built before the ruling\r\n // that still exports `route.middleware` must fail loudly here rather than\r\n // have that guard silently stop running — the exact defect class this\r\n // workspace keeps paying to fix.\r\n /**\r\n * THE MIDDLEWARE SURFACES, TOGETHER — the one place both are documented,\r\n * so they cannot again be found \"separately and inconsistently\" (canon\r\n * `b79c4f55`, point 5):\r\n *\r\n * - A LAYOUT'S `middleware` export (`../routing/layout-policy.ts` — a\r\n * middleware-only layout, one with no default export, is treated as a\r\n * deliberate authorization boundary and composes freely). Every layout\r\n * on a page's chain contributes, outermost first\r\n * (`install-page-routes.ts`'s `composeLayoutLevel` folds the whole\r\n * chain into `triple.layout.middleware` before this runs).\r\n * - The PAGE's OWN top-level `middleware` export (`triple.page.middleware`)\r\n * — a page's own answer to \"what does this URL require\", one level\r\n * below the layout instead of borrowed from it (canon `f2e514c0`).\r\n *\r\n * ONE ordering rule covers both: `LEVEL_ORDER` (app, layout, page) runs\r\n * outermost-first, so the page's own list always runs LAST — closest to\r\n * the loader. A layout's auth gate can therefore never be bypassed by a\r\n * page's own middleware.\r\n */\r\n for (const level of LEVEL_ORDER) {\r\n const middlewareForLevel = triple[level].middleware ?? [];\r\n\r\n for (const middleware of middlewareForLevel) {\r\n let output: unknown;\r\n\r\n try {\r\n output = await middleware({ request, response });\r\n } catch (thrown) {\r\n bundle.error = buildErrorRecord(thrown, designateBoundary(level, triple), pathname);\r\n response.setStatusCode(500);\r\n return finish(bundle);\r\n }\r\n\r\n if (output !== undefined) {\r\n bundle.shortCircuit = {\r\n stage: \"middleware\",\r\n level,\r\n value: output,\r\n statusCode: response.statusCode,\r\n };\r\n return finish(bundle);\r\n }\r\n }\r\n }\r\n\r\n const validation = triple.page.validation;\r\n\r\n if (validation) {\r\n const legacyValidation = \"schema\" in validation || \"validating\" in validation;\r\n const schema = legacyValidation\r\n ? validation.schema\r\n : v.object({\r\n ...(validation.params === undefined ? {} : { params: validation.params }),\r\n ...(validation.query === undefined ? {} : { query: validation.query }),\r\n });\r\n\r\n if (schema) {\r\n const data = legacyValidation\r\n ? resolveValidationData(validation.validating, request)\r\n : resolvePageValidationInput(request);\r\n const result = await v.validate(schema, data);\r\n\r\n if (result.isValid && result.data) {\r\n request.setValidatedData(result.data);\r\n }\r\n\r\n if (!result.isValid) {\r\n bundle.shortCircuit = { stage: \"validation\", status: 400, errors: result.errors };\r\n return finish(bundle);\r\n }\r\n }\r\n }\r\n\r\n const sealedShared = await sealShared(store);\r\n bundle.shared = sealedShared;\r\n\r\n const dataKeys: Record<PageLevelName, \"appData\" | \"layoutData\" | \"pageData\"> = {\r\n app: \"appData\",\r\n layout: \"layoutData\",\r\n page: \"pageData\",\r\n };\r\n\r\n // Stage 6 — LOADERS, root to leaf. Every level gets its OWN buffer (never\r\n // the live response), and a terminal result or throw prevents every lower\r\n // loader from starting.\r\n const buffers: Record<PageLevelName, LevelBuffer> = {\r\n app: createLevelBuffer(),\r\n layout: createLevelBuffer(),\r\n page: createLevelBuffer(),\r\n };\r\n\r\n let signalIndex = -1;\r\n let signalKind: \"throw\" | \"shortCircuit\" | undefined;\r\n let signalThrown: unknown;\r\n let signalCircuit:\r\n | { kind: \"redirect\" | \"notFound\"; statusCode: number; url?: string; body?: unknown }\r\n | undefined;\r\n\r\n for (let index = 0; index < LEVEL_ORDER.length; index++) {\r\n const level = LEVEL_ORDER[index];\r\n\r\n // `route.validate` — the PAGE's own declared schema, over `{ params,\r\n // query }` kept as two separate keys (canon `b79c4f55`, point 1). Runs\r\n // HERE, at the front of the page level's own turn: app and layout\r\n // loaders have already run (their data survives a rejection, exactly\r\n // as an ordinary page-level throw leaves them untouched) and the\r\n // page's OWN loader has not (mirrors the top-level `validation`\r\n // export's \"before the loader\" contract). A failure is folded into the\r\n // ordinary THROW signal below rather than given a fourth code path: it\r\n // designates a boundary and renders the application's error\r\n // page/boundary with status 400 (point 2) — a page is a document, not\r\n // an API endpoint, so this must never answer a raw JSON body.\r\n const loader = triple[level].loader;\r\n\r\n if (!loader) continue;\r\n\r\n let value: unknown;\r\n\r\n try {\r\n value = await loader({\r\n request,\r\n response: createBufferedResponse(buffers[level]),\r\n shared: sealedShared,\r\n });\r\n } catch (thrown) {\r\n signalIndex = index;\r\n signalKind = \"throw\";\r\n signalThrown = thrown;\r\n break;\r\n }\r\n\r\n if (value instanceof Response) return value;\r\n\r\n if (isLoaderShortCircuit(value)) {\r\n signalIndex = index;\r\n signalKind = \"shortCircuit\";\r\n signalCircuit = value;\r\n break;\r\n }\r\n\r\n bundle[dataKeys[level]] = value;\r\n }\r\n\r\n let committedLevels: PageLevelName[];\r\n /** Set only when a THROW escalated to the app boundary — forces 500. */\r\n let forcedStatusCode: number | undefined;\r\n\r\n if (signalIndex === -1) {\r\n committedLevels = [...LEVEL_ORDER];\r\n } else if (signalKind === \"throw\") {\r\n // The throwing level's buffer is discarded; lower levels never ran.\r\n committedLevels = LEVEL_ORDER.slice(0, signalIndex);\r\n\r\n const boundary = designateBoundary(LEVEL_ORDER[signalIndex], triple);\r\n // A failure that OWNS its own status (a `RouteValidationError`'s 400)\r\n // carries it through here; an ordinary throw carries none and keeps\r\n // the pipeline's ordinary answer, 500.\r\n const ownStatusCode = (signalThrown as { statusCode?: number } | null)?.statusCode;\r\n bundle.error = buildErrorRecord(signalThrown, boundary, pathname, ownStatusCode);\r\n\r\n if (boundary.boundaryLevel === \"app\") {\r\n const status = ownStatusCode ?? 500;\r\n response.setStatusCode(status);\r\n forcedStatusCode = status;\r\n }\r\n } else {\r\n // Short-circuit: the signalling level's OWN buffer commits too\r\n // (inclusive); lower levels never ran.\r\n committedLevels = LEVEL_ORDER.slice(0, signalIndex + 1);\r\n\r\n const circuit = signalCircuit!;\r\n\r\n bundle.shortCircuit = {\r\n stage: \"loaders\",\r\n level: LEVEL_ORDER[signalIndex],\r\n kind: circuit.kind,\r\n statusCode: circuit.statusCode,\r\n url: circuit.url,\r\n body: circuit.body,\r\n } as unknown as PageDataBundle[\"shortCircuit\"];\r\n }\r\n\r\n bundle.commit = commitBuffers(response, buffers, committedLevels);\r\n\r\n // Forced AFTER the fold: an app-boundary escalation forces 500\r\n // regardless of what the surviving (rootward) buffers happened to set —\r\n // it is the framework's answer, not a loader's.\r\n if (forcedStatusCode !== undefined) bundle.commit.statusCode = forcedStatusCode;\r\n\r\n // Stage 8 — METADATA. Skipped entirely for a short-circuit (there is no\r\n // page to describe); a throw still runs it, same as before this stage 6/7\r\n // rewrite (a boundary still needs a title/robots answer).\r\n if (bundle.shortCircuit) {\r\n return finish(bundle);\r\n }\r\n\r\n const resolved = resolvePageMetadata({\r\n metadata: triple.page.metadata,\r\n data: bundle.pageData,\r\n error: bundle.error?.error,\r\n failed: Boolean(bundle.error),\r\n shared: sealedShared,\r\n });\r\n\r\n bundle.metadata = resolved.metadata;\r\n\r\n if (resolved.thrown !== undefined) {\r\n const boundary = designateBoundary(\"page\", triple);\r\n bundle.error = buildErrorRecord(resolved.thrown, boundary, bundle.route.path);\r\n\r\n if (boundary.boundaryLevel === \"app\") response.setStatusCode(500);\r\n }\r\n\r\n return finish(bundle);\r\n });\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,IAAI,qBAAqB;AAEzB,SAAS,oBAA0B;CACjC,IAAI,oBAAoB;CAExB,qBAAqB;CACrB,2BAA2B,cAAc,CAAC,CAAC,SAAS,CAAC,EAAE,QAAQ,GAAG;AACpE;AAEA,eAAsB,mBACpB,SACyC;CACzC,MAAM,SAAS,cAAc;CAE7B,kBAAkB;CAClB,MAAM,CAAC,UAAU,eAAe,QAAQ,IAAI,MAAM,GAAG;CAIrD,MAAM,UAAU,QAAQ,WAAW,WAAW,UAAU,QAAQ,MAAM;CAEtE,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,QAAQ,OAAO,YAAY,IAAI,gBAAgB,eAAe,EAAE,CAAC;CACvE,MAAM,QAAwB;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;EAAQ;CAAM;CACpF,MAAM,EAAE,WAAW,QAAQ;CAC3B,MAAM,EAAE,SAAS,aAAa,QAAQ,WAAW,KAAK;CACtD,MAAM,QAAuB,OAAO,aAChC,OAAO,WAAW;EAAE;EAAS;CAAS,CAAC,IACvC;EAAE;EAAS;CAAS;CAExB,OAAO,OAAO,IAAI,OAAO,YAAY;EACnC,iBAAiB,KAAK;EACtB,2CAA2B,KAAK;EAEhC,MAAM,SAAS,OAAO,WACpB,QAAQ,SAAS,MAAM,QAAQ,OAAO,MAAM,IAAK;EAEnD,MAAM,SAAiB,EACrB,OAAO;GACL,MAAM,QAAQ,MAAM;GACpB,MAAM,QAAQ,MAAM;GACpB,QAAQ,MAAM;GACd;EACF,EACF;;;;;;;;;;;;;;;;;;;;;EA4BA,KAAK,MAAM,SAAS,aAAa;GAC/B,MAAM,qBAAqB,OAAO,MAAM,CAAC,cAAc,CAAC;GAExD,KAAK,MAAM,cAAc,oBAAoB;IAC3C,IAAI;IAEJ,IAAI;KACF,SAAS,MAAM,WAAW;MAAE;MAAS;KAAS,CAAC;IACjD,SAAS,QAAQ;KACf,OAAO,QAAQ,iBAAiB,QAAQ,kBAAkB,OAAO,MAAM,GAAG,QAAQ;KAClF,SAAS,cAAc,GAAG;KAC1B,OAAO,OAAO,MAAM;IACtB;IAEA,IAAI,WAAW,QAAW;KACxB,OAAO,eAAe;MACpB,OAAO;MACP;MACA,OAAO;MACP,YAAY,SAAS;KACvB;KACA,OAAO,OAAO,MAAM;IACtB;GACF;EACF;EAEA,MAAM,aAAa,OAAO,KAAK;EAE/B,IAAI,YAAY;GACd,MAAM,mBAAmB,YAAY,cAAc,gBAAgB;GACnE,MAAM,SAAS,mBACX,WAAW,SACX,EAAE,OAAO;IACP,GAAI,WAAW,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,WAAW,OAAO;IACvE,GAAI,WAAW,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,WAAW,MAAM;GACtE,CAAC;GAEL,IAAI,QAAQ;IACV,MAAM,OAAO,mBACT,sBAAsB,WAAW,YAAY,OAAO,IACpD,2BAA2B,OAAO;IACtC,MAAM,SAAS,MAAM,EAAE,SAAS,QAAQ,IAAI;IAE5C,IAAI,OAAO,WAAW,OAAO,MAC3B,QAAQ,iBAAiB,OAAO,IAAI;IAGtC,IAAI,CAAC,OAAO,SAAS;KACnB,OAAO,eAAe;MAAE,OAAO;MAAc,QAAQ;MAAK,QAAQ,OAAO;KAAO;KAChF,OAAO,OAAO,MAAM;IACtB;GACF;EACF;EAEA,MAAM,eAAe,MAAM,WAAW,KAAK;EAC3C,OAAO,SAAS;EAEhB,MAAM,WAAyE;GAC7E,KAAK;GACL,QAAQ;GACR,MAAM;EACR;EAKA,MAAM,UAA8C;GAClD,KAAK,kBAAkB;GACvB,QAAQ,kBAAkB;GAC1B,MAAM,kBAAkB;EAC1B;EAEA,IAAI,cAAc;EAClB,IAAI;EACJ,IAAI;EACJ,IAAI;EAIJ,KAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,QAAQ,SAAS;GACvD,MAAM,QAAQ,YAAY;GAa1B,MAAM,SAAS,OAAO,MAAM,CAAC;GAE7B,IAAI,CAAC,QAAQ;GAEb,IAAI;GAEJ,IAAI;IACF,QAAQ,MAAM,OAAO;KACnB;KACA,UAAU,uBAAuB,QAAQ,MAAM;KAC/C,QAAQ;IACV,CAAC;GACH,SAAS,QAAQ;IACf,cAAc;IACd,aAAa;IACb,eAAe;IACf;GACF;GAEA,IAAI,iBAAiB,UAAU,OAAO;GAEtC,IAAI,qBAAqB,KAAK,GAAG;IAC/B,cAAc;IACd,aAAa;IACb,gBAAgB;IAChB;GACF;GAEA,OAAO,SAAS,UAAU;EAC5B;EAEA,IAAI;;EAEJ,IAAI;EAEJ,IAAI,gBAAgB,IAClB,kBAAkB,CAAC,GAAG,WAAW;OAC5B,IAAI,eAAe,SAAS;GAEjC,kBAAkB,YAAY,MAAM,GAAG,WAAW;GAElD,MAAM,WAAW,kBAAkB,YAAY,cAAc,MAAM;GAInE,MAAM,gBAAiB,cAAiD;GACxE,OAAO,QAAQ,iBAAiB,cAAc,UAAU,UAAU,aAAa;GAE/E,IAAI,SAAS,kBAAkB,OAAO;IACpC,MAAM,SAAS,iBAAiB;IAChC,SAAS,cAAc,MAAM;IAC7B,mBAAmB;GACrB;EACF,OAAO;GAGL,kBAAkB,YAAY,MAAM,GAAG,cAAc,CAAC;GAEtD,MAAM,UAAU;GAEhB,OAAO,eAAe;IACpB,OAAO;IACP,OAAO,YAAY;IACnB,MAAM,QAAQ;IACd,YAAY,QAAQ;IACpB,KAAK,QAAQ;IACb,MAAM,QAAQ;GAChB;EACF;EAEA,OAAO,SAAS,cAAc,UAAU,SAAS,eAAe;EAKhE,IAAI,qBAAqB,QAAW,OAAO,OAAO,aAAa;EAK/D,IAAI,OAAO,cACT,OAAO,OAAO,MAAM;EAGtB,MAAM,WAAW,oBAAoB;GACnC,UAAU,OAAO,KAAK;GACtB,MAAM,OAAO;GACb,OAAO,OAAO,OAAO;GACrB,QAAQ,QAAQ,OAAO,KAAK;GAC5B,QAAQ;EACV,CAAC;EAED,OAAO,WAAW,SAAS;EAE3B,IAAI,SAAS,WAAW,QAAW;GACjC,MAAM,WAAW,kBAAkB,QAAQ,MAAM;GACjD,OAAO,QAAQ,iBAAiB,SAAS,QAAQ,UAAU,OAAO,MAAM,IAAI;GAE5E,IAAI,SAAS,kBAAkB,OAAO,SAAS,cAAc,GAAG;EAClE;EAEA,OAAO,OAAO,MAAM;CACtB,CAAC;AACH"}
|
|
@@ -34,14 +34,19 @@ type PageTripleModule = {
|
|
|
34
34
|
register?: () => unknown;
|
|
35
35
|
route?: string | {
|
|
36
36
|
readonly path: string;
|
|
37
|
-
readonly name?: string;
|
|
38
|
-
|
|
39
|
-
readonly middleware?: readonly PipelineMiddleware[];
|
|
40
|
-
};
|
|
37
|
+
readonly name?: string;
|
|
38
|
+
}; /** This page's own guards, run LAST — see `LEVEL_ORDER` below. */
|
|
41
39
|
middleware?: readonly PipelineMiddleware[];
|
|
42
40
|
validation?: {
|
|
43
41
|
schema?: BaseValidator;
|
|
44
42
|
validating?: readonly string[];
|
|
43
|
+
params?: never;
|
|
44
|
+
query?: never;
|
|
45
|
+
} | {
|
|
46
|
+
params?: BaseValidator;
|
|
47
|
+
query?: BaseValidator;
|
|
48
|
+
schema?: never;
|
|
49
|
+
validating?: never;
|
|
45
50
|
};
|
|
46
51
|
loader?: PipelineLoader;
|
|
47
52
|
metadata?: PageMetadata<PipelineLoader>;
|
|
@@ -65,6 +70,12 @@ type PageRouteMatch = {
|
|
|
65
70
|
type ExecutePageRequestOptions<TResult = PageDataBundle> = {
|
|
66
71
|
url: string;
|
|
67
72
|
routes: readonly PageRouteEntry[];
|
|
73
|
+
/**
|
|
74
|
+
* An HTTP router has already selected this entry and decoded its params.
|
|
75
|
+
* Supplying it prevents the page pipeline from matching the same URL again;
|
|
76
|
+
* callers without an HTTP request continue to resolve against `routes`.
|
|
77
|
+
*/
|
|
78
|
+
matched?: Pick<PageRouteMatch, "entry" | "params">;
|
|
68
79
|
createHttp(match: PageRouteMatch): HttpContext;
|
|
69
80
|
finish?(bundle: PageDataBundle): TResult | Promise<TResult>;
|
|
70
81
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CLIENT_ASSET_URL_PREFIX } from "./client-asset-url-prefix.mjs";
|
|
2
2
|
import { HYDRATION_CLIENT_ENTRY_NAME } from "../vite/hydration-entries.mjs";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
5
|
|
|
6
6
|
//#region ../web/src/server/hydration-client-url.ts
|
|
7
7
|
/**
|
package/esm/server/index.d.mts
CHANGED
|
@@ -5,7 +5,8 @@ import { BufferedCookie, BufferedHeader, LoaderShortCircuitKind, PageResponseCom
|
|
|
5
5
|
import { ExecutePageRequestOptions, PageBoundaryDesignation, PageContextRunner, PageDataBundle, PageLevelName, PageRouteEntry, PageRouteMatch, PageShortCircuit, PageTripleModule, PipelineLoader, PipelineMiddleware, PipelineStore } from "./execute-page-request.types.mjs";
|
|
6
6
|
import { connectPageContext } from "./page-context.mjs";
|
|
7
7
|
import { executePageRequest } from "./execute-page-request.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { LayoutModuleShape, PageModuleShape, PageRouteExport } from "./page-module-shapes.mjs";
|
|
9
|
+
import { InstallPageRoutesOptions, InstalledPageRoute, installPageRoutes } from "./install-page-routes.mjs";
|
|
9
10
|
import { RenderPageRequestOptions, RenderedPage, renderPageRequest } from "./render-page.mjs";
|
|
10
11
|
import { PageModuleNotInManifestError, createPageModuleLoader } from "./create-page-module-loader.mjs";
|
|
11
12
|
import { InstallPageRoutesFromManifestOptions, InstalledManifestPageRoute, PageRouteHandlerFactory, installPageRoutesFromManifest } from "./install-page-routes-from-manifest.mjs";
|
package/esm/server/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { connectSharedStore } from "../shared.mjs";
|
|
2
2
|
import { PAYLOAD_SCRIPT_ID, escapePayload } from "../components/document-context.mjs";
|
|
3
3
|
import { connectPageContext } from "./page-context.mjs";
|
|
4
|
+
import { composeRoutePath } from "../routing/compose-route-path.mjs";
|
|
5
|
+
import { DuplicateNotFoundPageError, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, acceptsHtmlExplicitly, classifyUnmatchedRequest, createNotFoundRouteHandler, frameworkDefaultNotFoundDocument, isNotFoundPageFile } from "./not-found-page.mjs";
|
|
6
|
+
import { VITE_DIRECT_CSS_QUERY, devStylesheetUrls, productionStylesheetUrls } from "./stylesheet-urls.mjs";
|
|
7
|
+
import { installPageRoutes } from "./install-page-routes.mjs";
|
|
4
8
|
import { executePageRequest } from "./execute-page-request.mjs";
|
|
5
9
|
import { renderPageRequest } from "./render-page.mjs";
|
|
6
10
|
import { PageModuleNotInManifestError, createPageModuleLoader } from "./create-page-module-loader.mjs";
|
|
7
|
-
import { composeRoutePath } from "../routing/compose-route-path.mjs";
|
|
8
|
-
import { VITE_DIRECT_CSS_QUERY, devStylesheetUrls, productionStylesheetUrls } from "./stylesheet-urls.mjs";
|
|
9
|
-
import { DuplicateNotFoundPageError, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, acceptsHtmlExplicitly, classifyUnmatchedRequest, createNotFoundRouteHandler, frameworkDefaultNotFoundDocument, isNotFoundPageFile } from "./not-found-page.mjs";
|
|
10
11
|
import { installPageRoutesFromManifest } from "./install-page-routes-from-manifest.mjs";
|
|
11
|
-
import { installPageRoutes } from "./install-page-routes.mjs";
|
|
12
12
|
|
|
13
13
|
export { DuplicateNotFoundPageError, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, PAYLOAD_SCRIPT_ID, PageModuleNotInManifestError, VITE_DIRECT_CSS_QUERY, acceptsHtmlExplicitly, classifyUnmatchedRequest, composeRoutePath, connectPageContext, connectSharedStore, createNotFoundRouteHandler, createPageModuleLoader, devStylesheetUrls, escapePayload, executePageRequest, frameworkDefaultNotFoundDocument, installPageRoutes, installPageRoutesFromManifest, isNotFoundPageFile, productionStylesheetUrls, renderPageRequest };
|
|
@@ -3,6 +3,15 @@ import { PageRouteHandler, PageRouteHandlerOptions } from "./create-page-route-h
|
|
|
3
3
|
import { Router } from "@warlock.js/core";
|
|
4
4
|
|
|
5
5
|
//#region ../web/src/server/install-page-routes-from-manifest.d.ts
|
|
6
|
+
/** The exports this module reads off a layout module namespace. */
|
|
7
|
+
/**
|
|
8
|
+
* The default export — the thing that puts an element in the document, and
|
|
9
|
+
* therefore the ONLY export that decides whether a layout counts against the
|
|
10
|
+
* single-rendering-layout rule (`../routing/layout-policy.ts`). The manifest
|
|
11
|
+
* carries LOADED modules, so this is a fact rather than a guess, exactly as it
|
|
12
|
+
* is in dev's own `LayoutModuleShape`.
|
|
13
|
+
*/
|
|
14
|
+
/** The layout's guards, in the order it declared them. */
|
|
6
15
|
/**
|
|
7
16
|
* How a handler is built for one page. Defaults to `createPageRouteHandler`;
|
|
8
17
|
* taking it as an input keeps this module's own job — reading the manifest and
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { publishRouteTable } from "../routing/route-table.mjs";
|
|
2
|
-
import { createPageModuleLoader } from "./create-page-module-loader.mjs";
|
|
3
2
|
import { composeRoutePath } from "../routing/compose-route-path.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { DuplicateNotFoundPageError, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, isNotFoundPageFile, registerNotFoundPageRoute } from "./not-found-page.mjs";
|
|
5
4
|
import { deriveFilesystemRoutePath } from "../routing/filesystem-route.mjs";
|
|
6
|
-
import { resolveLayoutLevel } from "../routing/layout-level.mjs";
|
|
7
5
|
import { resolvePageRouteCache, resolvePageRouteIdentity } from "../routing/route-identity.mjs";
|
|
6
|
+
import { duplicateRoutePathMessage } from "../routing/duplicate-route-path.mjs";
|
|
7
|
+
import { resolveLayoutLevel } from "../routing/layout-level.mjs";
|
|
8
8
|
import { createPageRouteHandler } from "./create-page-route-handler.mjs";
|
|
9
9
|
import { foldLayoutLoaders } from "./fold-layout-loaders.mjs";
|
|
10
10
|
import { productionStylesheetUrls } from "./stylesheet-urls.mjs";
|
|
11
|
-
import {
|
|
11
|
+
import { createPageModuleLoader } from "./create-page-module-loader.mjs";
|
|
12
12
|
import "@warlock.js/core";
|
|
13
13
|
|
|
14
14
|
//#region ../web/src/server/install-page-routes-from-manifest.ts
|
|
@@ -190,22 +190,22 @@ function installPageRoutesFromManifest(options) {
|
|
|
190
190
|
layoutFile: layout?.sourceFile
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
193
|
+
registerNotFoundPageRoute({
|
|
194
|
+
router,
|
|
195
|
+
renderPage: notFoundPage === void 0 ? void 0 : createHandler({
|
|
196
|
+
path: "*",
|
|
197
|
+
name: NOT_FOUND_ROUTE_NAME,
|
|
198
|
+
appFile: app.sourceFile,
|
|
199
|
+
pageFile: notFoundPage.sourceFile,
|
|
200
|
+
layoutFile: void 0,
|
|
201
|
+
loadModule,
|
|
202
|
+
hydrationClientModuleUrl,
|
|
203
|
+
loadErrorPage,
|
|
204
|
+
stylesheetUrls: clientDir === void 0 ? [] : productionStylesheetUrls(clientDir, [app.sourceFile, notFoundPage.sourceFile]),
|
|
205
|
+
matchPath: (requestPath) => requestPath,
|
|
206
|
+
statusForRenderedOk: 404,
|
|
207
|
+
skipPageLoader: true
|
|
208
|
+
})
|
|
209
209
|
});
|
|
210
210
|
publishRouteTable(installed, "installPageRoutesFromManifest (production)");
|
|
211
211
|
return installed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-page-routes-from-manifest.mjs","names":[],"sources":["../../../../../../../web/src/server/install-page-routes-from-manifest.ts"],"sourcesContent":["/**\r\n * Page-route registration for a built application.\r\n *\r\n * `installPageRoutes` answers \"which pages exist?\" by walking the filesystem\r\n * and \"what is this module?\" by asking Vite to evaluate it. Neither question\r\n * can be asked of a running production process: there is no `app/` tree beside\r\n * the bundle and no Vite. Both answers were therefore moved to build time — the\r\n * generated `pages.ts` barrel statically imported every page, layout and the\r\n * app root and handed them over as a {@link PageManifest}, and this module\r\n * turns that table into registered routes.\r\n *\r\n * WHAT IS DELIBERATELY IDENTICAL TO DEVELOPMENT: the route a page ends up on,\r\n * and the guards that run before it renders. A page's `route` export and the\r\n * `prefix` and `middleware` exports of EVERY layout on its path are read off the\r\n * module namespaces here, at boot, and composed by the same rules dev composes\r\n * them by — a shared mechanism now, not a promise: {@link layoutLevelOf} calls\r\n * `../routing/layout-level.ts`'s `resolveLayoutLevel`, the same selection and\r\n * prefix-composition rule dev's own `resolveLayoutLevel` calls, and\r\n * {@link composeLayoutLevel} folds loaders through the same\r\n * `./fold-layout-loaders.ts` dev's does — so the URL a page answers on and the\r\n * chain that guards it are decided by the page's own source in both modes, and\r\n * a build cannot quietly disagree with the dev server about either.\r\n *\r\n * WHAT IS DELIBERATELY DIFFERENT: this is synchronous. Every module is already\r\n * in memory, so registration has nothing to await; the loader handed to each\r\n * handler is a lookup over the same table, not an evaluation step.\r\n */\r\nimport { composeRoutePath } from \"../routing/compose-route-path\";\r\nimport { duplicateRoutePathMessage } from \"../routing/duplicate-route-path\";\r\nimport { deriveFilesystemRoutePath } from \"../routing/filesystem-route\";\r\nimport { resolveLayoutLevel } from \"../routing/layout-level\";\r\nimport {\r\n resolvePageRouteCache,\r\n resolvePageRouteIdentity,\r\n type PageCacheOptIn,\r\n} from \"../routing/route-identity\";\r\nimport { publishRouteTable } from \"../routing/route-table\";\r\nimport { type Router } from \"@warlock.js/core\";\r\nimport { createPageModuleLoader } from \"./create-page-module-loader\";\r\nimport type { ErrorPageModule } from \"./error-page\";\r\nimport {\r\n createPageRouteHandler,\r\n type PageRouteHandler,\r\n type PageRouteHandlerOptions,\r\n} from \"./create-page-route-handler\";\r\nimport type { PipelineLoader, PipelineMiddleware } from \"./execute-page-request\";\r\nimport { foldLayoutLoaders } from \"./fold-layout-loaders\";\r\nimport { productionStylesheetUrls } from \"./stylesheet-urls\";\r\nimport {\r\n createNotFoundRouteHandler,\r\n DuplicateNotFoundPageError,\r\n isNotFoundPageFile,\r\n NotFoundPageDeclaresRouteError,\r\n NOT_FOUND_ROUTE_NAME,\r\n NOT_FOUND_ROUTE_PATH,\r\n} from \"./not-found-page\";\r\nimport type { PageManifest, PageManifestLayoutEntry, PageManifestPageEntry } from \"./page-manifest\";\r\n\r\n/** A page declares either a bare path or a path plus an explicit route name. */\r\ntype PageRouteExport = string | { path: string; name?: string; cache?: PageCacheOptIn };\r\n\r\n/** The only export this module reads off a page module namespace. */\r\ntype PageModuleShape = {\r\n route?: PageRouteExport;\r\n};\r\n\r\n/** The exports this module reads off a layout module namespace. */\r\ntype LayoutModuleShape = {\r\n prefix?: string;\r\n /**\r\n * The default export — the thing that puts an element in the document, and\r\n * therefore the ONLY export that decides whether a layout counts against the\r\n * single-rendering-layout rule (`../routing/layout-policy.ts`). The manifest\r\n * carries LOADED modules, so this is a fact rather than a guess, exactly as it\r\n * is in dev's own `LayoutModuleShape`.\r\n */\r\n default?: unknown;\r\n /** The layout's guards, in the order it declared them. */\r\n middleware?: readonly PipelineMiddleware[];\r\n loader?: PipelineLoader;\r\n};\r\n\r\n/**\r\n * How a handler is built for one page. Defaults to `createPageRouteHandler`;\r\n * taking it as an input keeps this module's own job — reading the manifest and\r\n * registering routes — provable without a render pipeline behind it.\r\n */\r\nexport type PageRouteHandlerFactory = (options: PageRouteHandlerOptions) => PageRouteHandler;\r\n\r\nexport type InstalledManifestPageRoute = {\r\n /** The canonical declared route path, before layout-prefix composition. */\r\n declaredPath: string;\r\n /** The composed path the route was registered on. */\r\n path: string;\r\n /** The resolved route name; shared namespace with API routes. */\r\n name: string;\r\n /** The page's manifest `sourceFile`. */\r\n file: string;\r\n /** The layout's manifest `sourceFile`, when the page has one. */\r\n layoutFile: string | undefined;\r\n};\r\n\r\nexport type InstallPageRoutesFromManifestOptions = {\r\n router: Router;\r\n /** The table the generated production barrel provided at import time. */\r\n manifest: PageManifest;\r\n /** Browser module loaded after the server-rendered application and payload. */\r\n hydrationClientModuleUrl?: string;\r\n /**\r\n * Where the client build wrote its output — `productionStylesheetUrls`'s own\r\n * `clientDir` argument, forwarded here rather than pre-read into a flat list:\r\n * each registered handler needs its OWN chain\r\n * (`[root, ...outer-to-inner matched layouts, page]`, matched by the\r\n * manifest's own `sourceFile` ids), not one list shared by every page.\r\n *\r\n * OPTIONAL for the same reason `PageManifest.clientDir` is: a build that\r\n * discovered zero pages emits no client bundle, so there is no directory to\r\n * read stylesheets from — and no page that could need one either.\r\n */\r\n clientDir?: string;\r\n /** Same helper `dev-error-transport.ts` exports — passed in, never imported. */\r\n createHandler?: PageRouteHandlerFactory;\r\n};\r\n\r\n/**\r\n * `sourceFile`'s path relative to the web root — `src/web/**`, the only page\r\n * root discovery enumerates (`discoverWebRoots`,\r\n * `web/src/build/discover-pages.ts:210-213`). Manifest `sourceFile`s are\r\n * app-root-relative (`\"src/web/...\"`, `page-manifest.ts`'s own doc comment),\r\n * so dropping the first two segments — `<srcDir>`, then the literal `\"web\"` —\r\n * recovers exactly what `deriveFilesystemRoutePath`/`deriveFilesystemRouteName`\r\n * expect: the same value dev computes as `install-page-routes.ts`'s\r\n * `filesystemPageFileFor`.\r\n */\r\nfunction webRelativeSourceFile(sourceFile: string): string {\r\n return sourceFile.split(\"/\").slice(2).join(\"/\");\r\n}\r\n\r\n/**\r\n * Exported for `../routing/route-name-parity.spec.ts`, which proves this and\r\n * dev's own call agree — both now call the same `../routing/route-identity.ts`\r\n * `resolvePageRouteIdentity`, so this wrapper's only job is supplying THIS\r\n * installer's identifiers: the manifest `sourceFile` doubles as the\r\n * `route.path` rejection context, and its web-root-relative form is the\r\n * filesystem-derivation input.\r\n */\r\nexport function resolveRoute(\r\n routeExport: PageRouteExport | undefined,\r\n sourceFile: string,\r\n): { path: string; name: string } {\r\n return resolvePageRouteIdentity(routeExport, webRelativeSourceFile(sourceFile), sourceFile);\r\n}\r\n\r\n/**\r\n * Every layout's declared `prefix`, keyed by its directory relative to the\r\n * web root — the same table dev builds as its own `LayoutLevel.prefixesByDirectory`,\r\n * and the one {@link deriveFilesystemRoutePath} uses to let a directory's own\r\n * layout rename the URL segment a bare directory name would otherwise\r\n * contribute.\r\n */\r\nfunction layoutPrefixesOf(page: PageManifestPageEntry): Record<string, string> {\r\n return Object.fromEntries(\r\n page.layouts.flatMap((layout) => {\r\n const prefix = (layout.module as LayoutModuleShape).prefix;\r\n\r\n if (prefix === undefined) return [];\r\n\r\n const relative = webRelativeSourceFile(layout.sourceFile);\r\n const slashIndex = relative.lastIndexOf(\"/\");\r\n const directory = slashIndex === -1 ? \"\" : relative.slice(0, slashIndex);\r\n\r\n return [[directory, prefix]];\r\n }),\r\n );\r\n}\r\n\r\n/**\r\n * The page's layout LEVEL, resolved from the whole chain the manifest carries\r\n * rather than from the one layout nearest to it. The selection and prefix\r\n * rules themselves do not depend on where a layout's module came from, so\r\n * they live in one place both installers call —\r\n * `../routing/layout-level.ts`'s `resolveLayoutLevel` — rather than being\r\n * re-derived here against loaded modules instead of Vite's. This function's\r\n * own job is reading THIS installer's inputs off the manifest (`renders` and\r\n * `prefix` off each already-loaded module, `host` recovered from the shared\r\n * result's `hostId`) and nothing else.\r\n *\r\n * The manifest carries the FULL chain, outermost first, and the render pipeline\r\n * has exactly one layout slot per page (`execute-page-request.ts`'s\r\n * `PageRouteEntry[\"triple\"]`), so the chain has to be collapsed into one module\r\n * before it reaches a handler. Two things collapse differently and both matter:\r\n *\r\n * - RENDERING is a selection: at most one layout on the chain may render, and\r\n * the policy picks it. `renders` is read off the loaded module\r\n * (`typeof module.default !== \"undefined\"`), never off the entry's presence in\r\n * the chain — a `middleware`-only layout has no default export and is not a\r\n * wrapper. Passing bare `sourceFile` strings had every layout read as a\r\n * rendering one, so boot refused a middleware-only guard chain that the build\r\n * had already accepted: an application that builds and will not start.\r\n * - MIDDLEWARE and PREFIX are compositions: every layout on the path\r\n * contributes, outermost first. A guard on an outer layout that the page's own\r\n * directory knows nothing about is exactly the guard that must still run, and\r\n * a prefix nobody composed is a URL nobody wrote down.\r\n *\r\n * A chain with more than one RENDERING layout is still refused here, at boot,\r\n * before a single request can observe the wrong document (raised by the\r\n * shared `resolveLayoutLevel` itself). Like the missing app-root refusal\r\n * below, that defends against stale or hand-edited build artifacts: the build\r\n * refuses to emit such a chain, but a manifest can reach a running process\r\n * without that build having produced it.\r\n */\r\ntype LayoutLevel = {\r\n /**\r\n * The layout entry the handler's layout slot is registered under, or\r\n * `undefined` when the page has no layout at all: the layout that RENDERS,\r\n * or — when none does — the nearest one, which is the slot production has\r\n * always used and so the choice that changes nothing but the middleware for a\r\n * chain with no wrapper in it.\r\n */\r\n host: PageManifestLayoutEntry | undefined;\r\n /** Every layout's `prefix`, composed outermost first — `discoverPages`' own reduction. */\r\n prefix: string;\r\n};\r\n\r\nfunction layoutLevelOf(page: PageManifestPageEntry): LayoutLevel {\r\n const level = resolveLayoutLevel(\r\n page.sourceFile,\r\n page.layouts.map((layout) => ({\r\n id: layout.sourceFile,\r\n renders: typeof (layout.module as LayoutModuleShape).default !== \"undefined\",\r\n prefix: (layout.module as LayoutModuleShape).prefix,\r\n })),\r\n );\r\n\r\n return {\r\n host: page.layouts.find((layout) => layout.sourceFile === level.hostId),\r\n prefix: level.prefix,\r\n };\r\n}\r\n\r\n/**\r\n * The layout slot's module for one page: the slot host's own namespace, with the\r\n * whole chain's middleware in place of its own — outermost first, which is the\r\n * order stage 3 runs the array in (`execute-page-request.ts:519-524`) and the\r\n * order an outer `optionalAuth` needs in order to have resolved an identity\r\n * before an inner `gate()` checks it.\r\n *\r\n * Deliberately NOT core's route-level `middleware` option: that runs before the\r\n * pipeline's App-level middleware, which would invert outermost-first — the one\r\n * property this composition exists to guarantee.\r\n *\r\n * Built once at registration, not per request: unlike dev, every module here is\r\n * already in memory and cannot change under a running process.\r\n */\r\nfunction composeLayoutLevel(\r\n page: PageManifestPageEntry,\r\n host: PageManifestLayoutEntry,\r\n): Record<string, unknown> {\r\n const hostIndex = page.layouts.indexOf(host);\r\n\r\n return {\r\n ...host.module,\r\n middleware: page.layouts.flatMap((layout) => [\r\n ...((layout.module as LayoutModuleShape).middleware ?? []),\r\n ]),\r\n loader: foldLayoutLoaders(\r\n page.layouts.map((layout) => (layout.module as LayoutModuleShape).loader),\r\n hostIndex,\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * Registers every page the manifest carries into `options.router`.\r\n *\r\n * An empty manifest registers nothing and is not an error: \"built with web, no\r\n * pages\" is a legal state of a built application, and treating it as a failure\r\n * would make an empty project unbootable. A manifest that DOES carry pages but\r\n * no app root is the opposite — every page renders inside the application root,\r\n * so that combination is a broken table rather than an empty one, and it is\r\n * refused before any route exists to serve a request with a missing root.\r\n *\r\n * Two pages composing to the same path is refused the moment the second one is\r\n * seen, naming both — a registration-time failure, rather than a route one of\r\n * them silently loses at runtime.\r\n */\r\nexport function installPageRoutesFromManifest(\r\n options: InstallPageRoutesFromManifestOptions,\r\n): InstalledManifestPageRoute[] {\r\n const {\r\n router,\r\n manifest,\r\n hydrationClientModuleUrl,\r\n clientDir,\r\n createHandler = createPageRouteHandler,\r\n } = options;\r\n\r\n if (manifest.pages.length === 0) return [];\r\n\r\n const app = manifest.app;\r\n\r\n if (app === undefined) {\r\n throw new Error(\r\n `installPageRoutesFromManifest: this build's page manifest carries ${manifest.pages.length} ` +\r\n \"page(s) but no application root. Every page renders inside the app component, so no \" +\r\n \"page can be registered without it. Re-run the build so the generated pages barrel \" +\r\n \"provides an `app` entry.\",\r\n );\r\n }\r\n\r\n // Ids are the manifest's own `sourceFile` strings and are passed on untouched:\r\n // the loader below matches them by exact string equality, so resolving,\r\n // joining or swapping separators on one side of that comparison would turn\r\n // every lookup into a miss.\r\n const loadModule = createPageModuleLoader(manifest);\r\n // The namespace is already statically imported by the generated barrel, but\r\n // do not hand it to the render pipeline until a request actually fails.\r\n const loadErrorPage =\r\n manifest.errorPage === undefined\r\n ? undefined\r\n : async () => manifest.errorPage!.module as ErrorPageModule;\r\n\r\n // Same partition development makes, on the same rule (the filename), so the\r\n // two modes cannot disagree about which file is the not-found page. It is\r\n // taken OUT of the registration loop rather than skipped inside it: every step\r\n // in there composes and claims a URL, and `404.page.tsx` has none.\r\n const notFoundPages = manifest.pages.filter((page) => isNotFoundPageFile(page.sourceFile));\r\n const pages = manifest.pages.filter((page) => !isNotFoundPageFile(page.sourceFile));\r\n\r\n if (notFoundPages.length > 1) {\r\n throw new DuplicateNotFoundPageError(notFoundPages.map((page) => page.sourceFile));\r\n }\r\n\r\n const notFoundPage = notFoundPages[0];\r\n\r\n if (notFoundPage !== undefined && (notFoundPage.module as PageModuleShape).route !== undefined) {\r\n throw new NotFoundPageDeclaresRouteError(notFoundPage.sourceFile);\r\n }\r\n\r\n const installed: InstalledManifestPageRoute[] = [];\r\n const fileByPath = new Map<string, string>();\r\n\r\n for (const page of pages) {\r\n const { host: layout, prefix: layoutPrefix } = layoutLevelOf(page);\r\n const routeExport = (page.module as PageModuleShape).route;\r\n\r\n const { path: routePath, name } = resolveRoute(routeExport, page.sourceFile);\r\n\r\n // Validated at INSTALL time — the same boot-time gate dev applies in its\r\n // own installer — so a malformed `cache` opt-in fails a production boot\r\n // instead of shipping a page whose freshness window the framework\r\n // silently guessed.\r\n const cache = resolvePageRouteCache(routeExport, page.sourceFile);\r\n\r\n // Explicit wins; otherwise the path is derived from the page's own source\r\n // location and the layouts on its path — the same rule dev applies at\r\n // registration and discovery applies at build (`discover-pages.ts`), read\r\n // here off the manifest's own `sourceFile`s instead of the filesystem.\r\n const effectivePath =\r\n routeExport === undefined\r\n ? deriveFilesystemRoutePath({\r\n pageFile: webRelativeSourceFile(page.sourceFile),\r\n layoutPrefixes: layoutPrefixesOf(page),\r\n })\r\n : composeRoutePath(layoutPrefix, routePath);\r\n const existingFile = fileByPath.get(effectivePath);\r\n\r\n if (existingFile) {\r\n throw new Error(\r\n duplicateRoutePathMessage({\r\n effectivePath,\r\n existingFile,\r\n newFile: page.sourceFile,\r\n composition: { layoutPrefix, routePath },\r\n }),\r\n );\r\n }\r\n\r\n fileByPath.set(effectivePath, page.sourceFile);\r\n\r\n // The layout slot's id resolves to the COMPOSED level — every layout's\r\n // middleware, in chain order — and every other id goes straight to the\r\n // manifest lookup. A one-layout chain has nothing to compose, so it is left\r\n // to resolve as the exact namespace object the manifest carries, untouched.\r\n const composedLayout =\r\n page.layouts.length > 1 && layout !== undefined\r\n ? composeLayoutLevel(page, layout)\r\n : undefined;\r\n\r\n // Every registered handler gets ITS OWN immutable, ordered, deduped CSS\r\n // chain: root, then every matched layout outer to inner (`page.layouts`,\r\n // the manifest's own chain — the same one dev walks as\r\n // `layoutLevel.chain`), then the page. `PageManifest.clientDir` is present\r\n // whenever `pages` is non-empty (`page-manifest.ts`), which this loop only\r\n // ever reaches when it is — `clientDir === undefined` is handled anyway,\r\n // rather than trusted away, because a caller can still pass this function\r\n // a manifest that violates its own generator's invariant.\r\n const stylesheetUrls =\r\n clientDir === undefined\r\n ? []\r\n : productionStylesheetUrls(clientDir, [\r\n app.sourceFile,\r\n ...page.layouts.map((pageLayout) => pageLayout.sourceFile),\r\n page.sourceFile,\r\n ]);\r\n\r\n router.get(\r\n effectivePath,\r\n createHandler({\r\n path: effectivePath,\r\n name,\r\n appFile: app.sourceFile,\r\n pageFile: page.sourceFile,\r\n layoutFile: layout?.sourceFile,\r\n loadModule:\r\n composedLayout === undefined\r\n ? loadModule\r\n : (moduleId) =>\r\n moduleId === layout?.sourceFile\r\n ? Promise.resolve(composedLayout)\r\n : loadModule(moduleId),\r\n loadRegistrationLayouts: () => Promise.resolve(page.layouts.map((layout) => layout.module)),\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n stylesheetUrls,\r\n cache,\r\n }),\r\n // `isPage` marks this route as SSR-served. Pages and API routes share one\r\n // router and one route-name namespace, so the router's duplicate-name\r\n // error reads this flag to say which claimant is the page.\r\n { name, isPage: true },\r\n );\r\n\r\n installed.push({\r\n declaredPath: routePath,\r\n path: effectivePath,\r\n name,\r\n file: page.sourceFile,\r\n layoutFile: layout?.sourceFile,\r\n });\r\n }\r\n\r\n /*\r\n THE CATCH-ALL — the same route dev registers, built the same way, differing\r\n only in where a module comes from. Registered last, and registered even when\r\n the build carried no `404.page.tsx`, so a production deployment answers 404\r\n with the right STATUS whether or not anyone has designed the page yet.\r\n */\r\n router.get(\r\n NOT_FOUND_ROUTE_PATH,\r\n createNotFoundRouteHandler({\r\n renderPage:\r\n notFoundPage === undefined\r\n ? undefined\r\n : createHandler({\r\n path: NOT_FOUND_ROUTE_PATH,\r\n name: NOT_FOUND_ROUTE_NAME,\r\n appFile: app.sourceFile,\r\n pageFile: notFoundPage.sourceFile,\r\n // No layout, and therefore no layout middleware — see the dev\r\n // installer for why the not-found path takes nothing that can\r\n // redirect or throw.\r\n layoutFile: undefined,\r\n loadModule,\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n // NO LAYOUT means no layout CSS either — just root and the\r\n // not-found page's own stylesheets, same reasoning as above.\r\n stylesheetUrls:\r\n clientDir === undefined\r\n ? []\r\n : productionStylesheetUrls(clientDir, [app.sourceFile, notFoundPage.sourceFile]),\r\n matchPath: (requestPath) => requestPath,\r\n statusForRenderedOk: 404,\r\n skipPageLoader: true,\r\n }),\r\n }),\r\n // `isPage` for the same reason the dev installer carries it — the router's\r\n // duplicate-name error reads the flag to say which claimant is the page.\r\n { name: NOT_FOUND_ROUTE_NAME, isPage: true },\r\n );\r\n\r\n /*\r\n Same publish as the dev installer, for the same reason: `href()` and the\r\n router must agree, and they only can if both read the one loop that\r\n registered the routes. Production installs once at boot, so the wholesale\r\n replacement is a single write before the first request.\r\n */\r\n publishRouteTable(installed, \"installPageRoutesFromManifest (production)\");\r\n\r\n return installed;\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"install-page-routes-from-manifest.mjs","names":[],"sources":["../../../../../../../web/src/server/install-page-routes-from-manifest.ts"],"sourcesContent":["/**\r\n * Page-route registration for a built application.\r\n *\r\n * `installPageRoutes` answers \"which pages exist?\" by walking the filesystem\r\n * and \"what is this module?\" by asking Vite to evaluate it. Neither question\r\n * can be asked of a running production process: there is no `app/` tree beside\r\n * the bundle and no Vite. Both answers were therefore moved to build time — the\r\n * generated `pages.ts` barrel statically imported every page, layout and the\r\n * app root and handed them over as a {@link PageManifest}, and this module\r\n * turns that table into registered routes.\r\n *\r\n * WHAT IS DELIBERATELY IDENTICAL TO DEVELOPMENT: the route a page ends up on,\r\n * and the guards that run before it renders. A page's `route` export and the\r\n * `prefix` and `middleware` exports of EVERY layout on its path are read off the\r\n * module namespaces here, at boot, and composed by the same rules dev composes\r\n * them by — a shared mechanism now, not a promise: {@link layoutLevelOf} calls\r\n * `../routing/layout-level.ts`'s `resolveLayoutLevel`, the same selection and\r\n * prefix-composition rule dev's own `resolveLayoutLevel` calls, and\r\n * {@link composeLayoutLevel} folds loaders through the same\r\n * `./fold-layout-loaders.ts` dev's does — so the URL a page answers on and the\r\n * chain that guards it are decided by the page's own source in both modes, and\r\n * a build cannot quietly disagree with the dev server about either.\r\n *\r\n * WHAT IS DELIBERATELY DIFFERENT: this is synchronous. Every module is already\r\n * in memory, so registration has nothing to await; the loader handed to each\r\n * handler is a lookup over the same table, not an evaluation step.\r\n */\r\nimport { composeRoutePath } from \"../routing/compose-route-path\";\r\nimport { duplicateRoutePathMessage } from \"../routing/duplicate-route-path\";\r\nimport { deriveFilesystemRoutePath } from \"../routing/filesystem-route\";\r\nimport { resolveLayoutLevel } from \"../routing/layout-level\";\r\nimport { resolvePageRouteCache, resolvePageRouteIdentity } from \"../routing/route-identity\";\r\nimport { publishRouteTable } from \"../routing/route-table\";\r\nimport { type Router } from \"@warlock.js/core\";\r\nimport { createPageModuleLoader } from \"./create-page-module-loader\";\r\nimport type { ErrorPageModule } from \"./error-page\";\r\nimport {\r\n createPageRouteHandler,\r\n type PageRouteHandler,\r\n type PageRouteHandlerOptions,\r\n} from \"./create-page-route-handler\";\r\nimport { foldLayoutLoaders } from \"./fold-layout-loaders\";\r\nimport { productionStylesheetUrls } from \"./stylesheet-urls\";\r\nimport {\r\n DuplicateNotFoundPageError,\r\n isNotFoundPageFile,\r\n NotFoundPageDeclaresRouteError,\r\n NOT_FOUND_ROUTE_NAME,\r\n NOT_FOUND_ROUTE_PATH,\r\n registerNotFoundPageRoute,\r\n} from \"./not-found-page\";\r\nimport type { PageManifest, PageManifestLayoutEntry, PageManifestPageEntry } from \"./page-manifest\";\r\nimport type { LayoutModuleShape, PageModuleShape, PageRouteExport } from \"./page-module-shapes\";\r\n\r\n/** The exports this module reads off a layout module namespace. */\r\n/**\r\n * The default export — the thing that puts an element in the document, and\r\n * therefore the ONLY export that decides whether a layout counts against the\r\n * single-rendering-layout rule (`../routing/layout-policy.ts`). The manifest\r\n * carries LOADED modules, so this is a fact rather than a guess, exactly as it\r\n * is in dev's own `LayoutModuleShape`.\r\n */\r\n/** The layout's guards, in the order it declared them. */\r\n\r\n/**\r\n * How a handler is built for one page. Defaults to `createPageRouteHandler`;\r\n * taking it as an input keeps this module's own job — reading the manifest and\r\n * registering routes — provable without a render pipeline behind it.\r\n */\r\nexport type PageRouteHandlerFactory = (options: PageRouteHandlerOptions) => PageRouteHandler;\r\n\r\nexport type InstalledManifestPageRoute = {\r\n /** The canonical declared route path, before layout-prefix composition. */\r\n declaredPath: string;\r\n /** The composed path the route was registered on. */\r\n path: string;\r\n /** The resolved route name; shared namespace with API routes. */\r\n name: string;\r\n /** The page's manifest `sourceFile`. */\r\n file: string;\r\n /** The layout's manifest `sourceFile`, when the page has one. */\r\n layoutFile: string | undefined;\r\n};\r\n\r\nexport type InstallPageRoutesFromManifestOptions = {\r\n router: Router;\r\n /** The table the generated production barrel provided at import time. */\r\n manifest: PageManifest;\r\n /** Browser module loaded after the server-rendered application and payload. */\r\n hydrationClientModuleUrl?: string;\r\n /**\r\n * Where the client build wrote its output — `productionStylesheetUrls`'s own\r\n * `clientDir` argument, forwarded here rather than pre-read into a flat list:\r\n * each registered handler needs its OWN chain\r\n * (`[root, ...outer-to-inner matched layouts, page]`, matched by the\r\n * manifest's own `sourceFile` ids), not one list shared by every page.\r\n *\r\n * OPTIONAL for the same reason `PageManifest.clientDir` is: a build that\r\n * discovered zero pages emits no client bundle, so there is no directory to\r\n * read stylesheets from — and no page that could need one either.\r\n */\r\n clientDir?: string;\r\n /** Same helper `dev-error-transport.ts` exports — passed in, never imported. */\r\n createHandler?: PageRouteHandlerFactory;\r\n};\r\n\r\n/**\r\n * `sourceFile`'s path relative to the web root — `src/web/**`, the only page\r\n * root discovery enumerates (`discoverWebRoots`,\r\n * `web/src/build/discover-pages.ts:210-213`). Manifest `sourceFile`s are\r\n * app-root-relative (`\"src/web/...\"`, `page-manifest.ts`'s own doc comment),\r\n * so dropping the first two segments — `<srcDir>`, then the literal `\"web\"` —\r\n * recovers exactly what `deriveFilesystemRoutePath`/`deriveFilesystemRouteName`\r\n * expect: the same value dev computes as `install-page-routes.ts`'s\r\n * `filesystemPageFileFor`.\r\n */\r\nfunction webRelativeSourceFile(sourceFile: string): string {\r\n return sourceFile.split(\"/\").slice(2).join(\"/\");\r\n}\r\n\r\n/**\r\n * Exported for `../routing/route-name-parity.spec.ts`, which proves this and\r\n * dev's own call agree — both now call the same `../routing/route-identity.ts`\r\n * `resolvePageRouteIdentity`, so this wrapper's only job is supplying THIS\r\n * installer's identifiers: the manifest `sourceFile` doubles as the\r\n * `route.path` rejection context, and its web-root-relative form is the\r\n * filesystem-derivation input.\r\n */\r\nexport function resolveRoute(\r\n routeExport: PageRouteExport | undefined,\r\n sourceFile: string,\r\n): { path: string; name: string } {\r\n return resolvePageRouteIdentity(routeExport, webRelativeSourceFile(sourceFile), sourceFile);\r\n}\r\n\r\n/**\r\n * Every layout's declared `prefix`, keyed by its directory relative to the\r\n * web root — the same table dev builds as its own `LayoutLevel.prefixesByDirectory`,\r\n * and the one {@link deriveFilesystemRoutePath} uses to let a directory's own\r\n * layout rename the URL segment a bare directory name would otherwise\r\n * contribute.\r\n */\r\nfunction layoutPrefixesOf(page: PageManifestPageEntry): Record<string, string> {\r\n return Object.fromEntries(\r\n page.layouts.flatMap((layout) => {\r\n const prefix = (layout.module as LayoutModuleShape).prefix;\r\n\r\n if (prefix === undefined) return [];\r\n\r\n const relative = webRelativeSourceFile(layout.sourceFile);\r\n const slashIndex = relative.lastIndexOf(\"/\");\r\n const directory = slashIndex === -1 ? \"\" : relative.slice(0, slashIndex);\r\n\r\n return [[directory, prefix]];\r\n }),\r\n );\r\n}\r\n\r\n/**\r\n * The page's layout LEVEL, resolved from the whole chain the manifest carries\r\n * rather than from the one layout nearest to it. The selection and prefix\r\n * rules themselves do not depend on where a layout's module came from, so\r\n * they live in one place both installers call —\r\n * `../routing/layout-level.ts`'s `resolveLayoutLevel` — rather than being\r\n * re-derived here against loaded modules instead of Vite's. This function's\r\n * own job is reading THIS installer's inputs off the manifest (`renders` and\r\n * `prefix` off each already-loaded module, `host` recovered from the shared\r\n * result's `hostId`) and nothing else.\r\n *\r\n * The manifest carries the FULL chain, outermost first, and the render pipeline\r\n * has exactly one layout slot per page (`execute-page-request.ts`'s\r\n * `PageRouteEntry[\"triple\"]`), so the chain has to be collapsed into one module\r\n * before it reaches a handler. Two things collapse differently and both matter:\r\n *\r\n * - RENDERING is a selection: at most one layout on the chain may render, and\r\n * the policy picks it. `renders` is read off the loaded module\r\n * (`typeof module.default !== \"undefined\"`), never off the entry's presence in\r\n * the chain — a `middleware`-only layout has no default export and is not a\r\n * wrapper. Passing bare `sourceFile` strings had every layout read as a\r\n * rendering one, so boot refused a middleware-only guard chain that the build\r\n * had already accepted: an application that builds and will not start.\r\n * - MIDDLEWARE and PREFIX are compositions: every layout on the path\r\n * contributes, outermost first. A guard on an outer layout that the page's own\r\n * directory knows nothing about is exactly the guard that must still run, and\r\n * a prefix nobody composed is a URL nobody wrote down.\r\n *\r\n * A chain with more than one RENDERING layout is still refused here, at boot,\r\n * before a single request can observe the wrong document (raised by the\r\n * shared `resolveLayoutLevel` itself). Like the missing app-root refusal\r\n * below, that defends against stale or hand-edited build artifacts: the build\r\n * refuses to emit such a chain, but a manifest can reach a running process\r\n * without that build having produced it.\r\n */\r\ntype LayoutLevel = {\r\n /**\r\n * The layout entry the handler's layout slot is registered under, or\r\n * `undefined` when the page has no layout at all: the layout that RENDERS,\r\n * or — when none does — the nearest one, which is the slot production has\r\n * always used and so the choice that changes nothing but the middleware for a\r\n * chain with no wrapper in it.\r\n */\r\n host: PageManifestLayoutEntry | undefined;\r\n /** Every layout's `prefix`, composed outermost first — `discoverPages`' own reduction. */\r\n prefix: string;\r\n};\r\n\r\nfunction layoutLevelOf(page: PageManifestPageEntry): LayoutLevel {\r\n const level = resolveLayoutLevel(\r\n page.sourceFile,\r\n page.layouts.map((layout) => ({\r\n id: layout.sourceFile,\r\n renders: typeof (layout.module as LayoutModuleShape).default !== \"undefined\",\r\n prefix: (layout.module as LayoutModuleShape).prefix,\r\n })),\r\n );\r\n\r\n return {\r\n host: page.layouts.find((layout) => layout.sourceFile === level.hostId),\r\n prefix: level.prefix,\r\n };\r\n}\r\n\r\n/**\r\n * The layout slot's module for one page: the slot host's own namespace, with the\r\n * whole chain's middleware in place of its own — outermost first, which is the\r\n * order stage 3 runs the array in (`execute-page-request.ts:519-524`) and the\r\n * order an outer `optionalAuth` needs in order to have resolved an identity\r\n * before an inner `gate()` checks it.\r\n *\r\n * Deliberately NOT core's route-level `middleware` option: that runs before the\r\n * pipeline's App-level middleware, which would invert outermost-first — the one\r\n * property this composition exists to guarantee.\r\n *\r\n * Built once at registration, not per request: unlike dev, every module here is\r\n * already in memory and cannot change under a running process.\r\n */\r\nfunction composeLayoutLevel(\r\n page: PageManifestPageEntry,\r\n host: PageManifestLayoutEntry,\r\n): Record<string, unknown> {\r\n const hostIndex = page.layouts.indexOf(host);\r\n\r\n return {\r\n ...host.module,\r\n middleware: page.layouts.flatMap((layout) => [\r\n ...((layout.module as LayoutModuleShape).middleware ?? []),\r\n ]),\r\n loader: foldLayoutLoaders(\r\n page.layouts.map((layout) => (layout.module as LayoutModuleShape).loader),\r\n hostIndex,\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * Registers every page the manifest carries into `options.router`.\r\n *\r\n * An empty manifest registers nothing and is not an error: \"built with web, no\r\n * pages\" is a legal state of a built application, and treating it as a failure\r\n * would make an empty project unbootable. A manifest that DOES carry pages but\r\n * no app root is the opposite — every page renders inside the application root,\r\n * so that combination is a broken table rather than an empty one, and it is\r\n * refused before any route exists to serve a request with a missing root.\r\n *\r\n * Two pages composing to the same path is refused the moment the second one is\r\n * seen, naming both — a registration-time failure, rather than a route one of\r\n * them silently loses at runtime.\r\n */\r\nexport function installPageRoutesFromManifest(\r\n options: InstallPageRoutesFromManifestOptions,\r\n): InstalledManifestPageRoute[] {\r\n const {\r\n router,\r\n manifest,\r\n hydrationClientModuleUrl,\r\n clientDir,\r\n createHandler = createPageRouteHandler,\r\n } = options;\r\n\r\n if (manifest.pages.length === 0) return [];\r\n\r\n const app = manifest.app;\r\n\r\n if (app === undefined) {\r\n throw new Error(\r\n `installPageRoutesFromManifest: this build's page manifest carries ${manifest.pages.length} ` +\r\n \"page(s) but no application root. Every page renders inside the app component, so no \" +\r\n \"page can be registered without it. Re-run the build so the generated pages barrel \" +\r\n \"provides an `app` entry.\",\r\n );\r\n }\r\n\r\n // Ids are the manifest's own `sourceFile` strings and are passed on untouched:\r\n // the loader below matches them by exact string equality, so resolving,\r\n // joining or swapping separators on one side of that comparison would turn\r\n // every lookup into a miss.\r\n const loadModule = createPageModuleLoader(manifest);\r\n // The namespace is already statically imported by the generated barrel, but\r\n // do not hand it to the render pipeline until a request actually fails.\r\n const loadErrorPage =\r\n manifest.errorPage === undefined\r\n ? undefined\r\n : async () => manifest.errorPage!.module as ErrorPageModule;\r\n\r\n // Same partition development makes, on the same rule (the filename), so the\r\n // two modes cannot disagree about which file is the not-found page. It is\r\n // taken OUT of the registration loop rather than skipped inside it: every step\r\n // in there composes and claims a URL, and `404.page.tsx` has none.\r\n const notFoundPages = manifest.pages.filter((page) => isNotFoundPageFile(page.sourceFile));\r\n const pages = manifest.pages.filter((page) => !isNotFoundPageFile(page.sourceFile));\r\n\r\n if (notFoundPages.length > 1) {\r\n throw new DuplicateNotFoundPageError(notFoundPages.map((page) => page.sourceFile));\r\n }\r\n\r\n const notFoundPage = notFoundPages[0];\r\n\r\n if (notFoundPage !== undefined && (notFoundPage.module as PageModuleShape).route !== undefined) {\r\n throw new NotFoundPageDeclaresRouteError(notFoundPage.sourceFile);\r\n }\r\n\r\n const installed: InstalledManifestPageRoute[] = [];\r\n const fileByPath = new Map<string, string>();\r\n\r\n for (const page of pages) {\r\n const { host: layout, prefix: layoutPrefix } = layoutLevelOf(page);\r\n const routeExport = (page.module as PageModuleShape).route;\r\n\r\n const { path: routePath, name } = resolveRoute(routeExport, page.sourceFile);\r\n\r\n // Validated at INSTALL time — the same boot-time gate dev applies in its\r\n // own installer — so a malformed `cache` opt-in fails a production boot\r\n // instead of shipping a page whose freshness window the framework\r\n // silently guessed.\r\n const cache = resolvePageRouteCache(routeExport, page.sourceFile);\r\n\r\n // Explicit wins; otherwise the path is derived from the page's own source\r\n // location and the layouts on its path — the same rule dev applies at\r\n // registration and discovery applies at build (`discover-pages.ts`), read\r\n // here off the manifest's own `sourceFile`s instead of the filesystem.\r\n const effectivePath =\r\n routeExport === undefined\r\n ? deriveFilesystemRoutePath({\r\n pageFile: webRelativeSourceFile(page.sourceFile),\r\n layoutPrefixes: layoutPrefixesOf(page),\r\n })\r\n : composeRoutePath(layoutPrefix, routePath);\r\n const existingFile = fileByPath.get(effectivePath);\r\n\r\n if (existingFile) {\r\n throw new Error(\r\n duplicateRoutePathMessage({\r\n effectivePath,\r\n existingFile,\r\n newFile: page.sourceFile,\r\n composition: { layoutPrefix, routePath },\r\n }),\r\n );\r\n }\r\n\r\n fileByPath.set(effectivePath, page.sourceFile);\r\n\r\n // The layout slot's id resolves to the COMPOSED level — every layout's\r\n // middleware, in chain order — and every other id goes straight to the\r\n // manifest lookup. A one-layout chain has nothing to compose, so it is left\r\n // to resolve as the exact namespace object the manifest carries, untouched.\r\n const composedLayout =\r\n page.layouts.length > 1 && layout !== undefined\r\n ? composeLayoutLevel(page, layout)\r\n : undefined;\r\n\r\n // Every registered handler gets ITS OWN immutable, ordered, deduped CSS\r\n // chain: root, then every matched layout outer to inner (`page.layouts`,\r\n // the manifest's own chain — the same one dev walks as\r\n // `layoutLevel.chain`), then the page. `PageManifest.clientDir` is present\r\n // whenever `pages` is non-empty (`page-manifest.ts`), which this loop only\r\n // ever reaches when it is — `clientDir === undefined` is handled anyway,\r\n // rather than trusted away, because a caller can still pass this function\r\n // a manifest that violates its own generator's invariant.\r\n const stylesheetUrls =\r\n clientDir === undefined\r\n ? []\r\n : productionStylesheetUrls(clientDir, [\r\n app.sourceFile,\r\n ...page.layouts.map((pageLayout) => pageLayout.sourceFile),\r\n page.sourceFile,\r\n ]);\r\n\r\n router.get(\r\n effectivePath,\r\n createHandler({\r\n path: effectivePath,\r\n name,\r\n appFile: app.sourceFile,\r\n pageFile: page.sourceFile,\r\n layoutFile: layout?.sourceFile,\r\n loadModule:\r\n composedLayout === undefined\r\n ? loadModule\r\n : (moduleId) =>\r\n moduleId === layout?.sourceFile\r\n ? Promise.resolve(composedLayout)\r\n : loadModule(moduleId),\r\n loadRegistrationLayouts: () => Promise.resolve(page.layouts.map((layout) => layout.module)),\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n stylesheetUrls,\r\n cache,\r\n }),\r\n // `isPage` marks this route as SSR-served. Pages and API routes share one\r\n // router and one route-name namespace, so the router's duplicate-name\r\n // error reads this flag to say which claimant is the page.\r\n { name, isPage: true },\r\n );\r\n\r\n installed.push({\r\n declaredPath: routePath,\r\n path: effectivePath,\r\n name,\r\n file: page.sourceFile,\r\n layoutFile: layout?.sourceFile,\r\n });\r\n }\r\n\r\n /*\r\n THE CATCH-ALL — the same route dev registers, built the same way, differing\r\n only in where a module comes from. Registered last, and registered even when\r\n the build carried no `404.page.tsx`, so a production deployment answers 404\r\n with the right STATUS whether or not anyone has designed the page yet.\r\n */\r\n registerNotFoundPageRoute({\r\n router,\r\n renderPage:\r\n notFoundPage === undefined\r\n ? undefined\r\n : createHandler({\r\n path: NOT_FOUND_ROUTE_PATH,\r\n name: NOT_FOUND_ROUTE_NAME,\r\n appFile: app.sourceFile,\r\n pageFile: notFoundPage.sourceFile,\r\n // No layout, and therefore no layout middleware — see the dev\r\n // installer for why the not-found path takes nothing that can\r\n // redirect or throw.\r\n layoutFile: undefined,\r\n loadModule,\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n // NO LAYOUT means no layout CSS either — just root and the\r\n // not-found page's own stylesheets, same reasoning as above.\r\n stylesheetUrls:\r\n clientDir === undefined\r\n ? []\r\n : productionStylesheetUrls(clientDir, [app.sourceFile, notFoundPage.sourceFile]),\r\n matchPath: (requestPath) => requestPath,\r\n statusForRenderedOk: 404,\r\n skipPageLoader: true,\r\n }),\r\n });\r\n // `isPage` for the same reason the dev installer carries it — the router's\r\n // duplicate-name error reads the flag to say which claimant is the page.\r\n\r\n /*\r\n Same publish as the dev installer, for the same reason: `href()` and the\r\n router must agree, and they only can if both read the one loop that\r\n registered the routes. Production installs once at boot, so the wholesale\r\n replacement is a single write before the first request.\r\n */\r\n publishRouteTable(installed, \"installPageRoutesFromManifest (production)\");\r\n\r\n return installed;\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { composeRoutePath } from "../routing/compose-route-path.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { PipelineLoader, PipelineMiddleware } from "./execute-page-request.types.mjs";
|
|
2
|
+
import { LayoutModuleShape, PageModuleShape, PageRouteExport } from "./page-module-shapes.mjs";
|
|
4
3
|
import { FastifyInstance, Router } from "@warlock.js/core";
|
|
5
4
|
import { ViteDevServer } from "vite";
|
|
6
5
|
|
|
7
6
|
//#region ../web/src/server/install-page-routes.d.ts
|
|
8
|
-
type PageRouteExport = string | {
|
|
9
|
-
path: string;
|
|
10
|
-
name?: string;
|
|
11
|
-
cache?: PageCacheOptIn;
|
|
12
|
-
};
|
|
13
|
-
type PageModuleShape = {
|
|
14
|
-
route?: PageRouteExport;
|
|
15
|
-
};
|
|
16
7
|
type InstalledPageRoute = {
|
|
17
8
|
/** The canonical declared route path, before layout-prefix composition. */declaredPath: string;
|
|
18
9
|
path: string;
|
|
@@ -20,19 +11,6 @@ type InstalledPageRoute = {
|
|
|
20
11
|
file: string;
|
|
21
12
|
layoutFile: string | undefined;
|
|
22
13
|
};
|
|
23
|
-
type LayoutModuleShape = {
|
|
24
|
-
/** Universal registration hook; invoked on this real namespace, never a composed wrapper. */register?: () => unknown;
|
|
25
|
-
prefix?: string;
|
|
26
|
-
/**
|
|
27
|
-
* The default export — the thing that puts an element in the document, and
|
|
28
|
-
* therefore the ONLY export that decides whether a layout counts against the
|
|
29
|
-
* single-rendering-layout rule (`../routing/layout-policy.ts`). In dev the
|
|
30
|
-
* module is loaded, so this is a fact rather than a guess.
|
|
31
|
-
*/
|
|
32
|
-
default?: unknown; /** The layout's guards, in the order it declared them. */
|
|
33
|
-
middleware?: readonly PipelineMiddleware[];
|
|
34
|
-
loader?: PipelineLoader;
|
|
35
|
-
};
|
|
36
14
|
type InstallPageRoutesOptions = {
|
|
37
15
|
router: Router;
|
|
38
16
|
vite: ViteDevServer; /** v5/app/src — pages live under "<appSrcRoot>/web/**". */
|
|
@@ -96,5 +74,5 @@ type InstallPageRoutesOptions = {
|
|
|
96
74
|
*/
|
|
97
75
|
declare function installPageRoutes(options: InstallPageRoutesOptions): Promise<InstalledPageRoute[]>;
|
|
98
76
|
//#endregion
|
|
99
|
-
export { InstallPageRoutesOptions, InstalledPageRoute,
|
|
77
|
+
export { InstallPageRoutesOptions, InstalledPageRoute, installPageRoutes };
|
|
100
78
|
//# sourceMappingURL=install-page-routes.d.mts.map
|