@warlock.js/web 5.2.0 → 5.2.1

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.
Files changed (201) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/esm/build/contribution.d.mts +1 -1
  3. package/esm/build/contribution.mjs +1 -1
  4. package/esm/build/contribution.mjs.map +1 -1
  5. package/esm/build/discover-pages.mjs +1 -1
  6. package/esm/build/discover-pages.mjs.map +1 -1
  7. package/esm/build/generate-client-registry.mjs +1 -1
  8. package/esm/build/generate-client-registry.mjs.map +1 -1
  9. package/esm/build/generate-pages-barrel.mjs +1 -1
  10. package/esm/build/generate-pages-barrel.mjs.map +1 -1
  11. package/esm/build/page-default-export.mjs +1 -1
  12. package/esm/build/page-default-export.mjs.map +1 -1
  13. package/esm/build/page-routes-manifest.mjs +1 -1
  14. package/esm/build/page-routes-manifest.mjs.map +1 -1
  15. package/esm/build/public-files.mjs +1 -1
  16. package/esm/build/public-files.mjs.map +1 -1
  17. package/esm/build/read-route-exports.mjs +1 -1
  18. package/esm/build/read-route-exports.mjs.map +1 -1
  19. package/esm/client/build-hydrated-tree.mjs +1 -1
  20. package/esm/client/build-hydrated-tree.mjs.map +1 -1
  21. package/esm/client/hydrate-page.mjs +1 -1
  22. package/esm/client/hydrate-page.mjs.map +1 -1
  23. package/esm/client/navigation/current-route.d.mts +1 -1
  24. package/esm/client/navigation/current-route.mjs +1 -1
  25. package/esm/client/navigation/current-route.mjs.map +1 -1
  26. package/esm/client/navigation/fetch-page-data.mjs +1 -1
  27. package/esm/client/navigation/fetch-page-data.mjs.map +1 -1
  28. package/esm/client/navigation/navigation-root.mjs +1 -1
  29. package/esm/client/navigation/navigation-root.mjs.map +1 -1
  30. package/esm/client/navigation/prefetch.mjs +1 -1
  31. package/esm/client/navigation/prefetch.mjs.map +1 -1
  32. package/esm/client/navigation/refresh.d.mts +1 -1
  33. package/esm/client/navigation/refresh.mjs +1 -1
  34. package/esm/client/navigation/refresh.mjs.map +1 -1
  35. package/esm/client/navigation/scroll-to-fragment.mjs +1 -1
  36. package/esm/client/navigation/scroll-to-fragment.mjs.map +1 -1
  37. package/esm/client/navigation/verbs.d.mts +1 -1
  38. package/esm/client/navigation/verbs.mjs +1 -1
  39. package/esm/client/navigation/verbs.mjs.map +1 -1
  40. package/esm/client/runtime/manifest.d.mts +1 -1
  41. package/esm/client/runtime/manifest.mjs +1 -1
  42. package/esm/client/runtime/manifest.mjs.map +1 -1
  43. package/esm/client/runtime/matcher.d.mts +1 -1
  44. package/esm/client/runtime/matcher.mjs +1 -1
  45. package/esm/client/runtime/matcher.mjs.map +1 -1
  46. package/esm/client/runtime/types.d.mts +1 -1
  47. package/esm/components/default-app.mjs +1 -1
  48. package/esm/components/default-app.mjs.map +1 -1
  49. package/esm/components/document-context.d.mts +1 -1
  50. package/esm/components/document-context.mjs +1 -1
  51. package/esm/components/document-context.mjs.map +1 -1
  52. package/esm/components/head.d.mts +1 -1
  53. package/esm/components/head.mjs +1 -1
  54. package/esm/components/head.mjs.map +1 -1
  55. package/esm/components/link.d.mts +1 -1
  56. package/esm/components/link.mjs +1 -1
  57. package/esm/components/link.mjs.map +1 -1
  58. package/esm/components/scripts.d.mts +1 -1
  59. package/esm/components/scripts.mjs +1 -1
  60. package/esm/components/scripts.mjs.map +1 -1
  61. package/esm/context.d.mts +1 -1
  62. package/esm/hydration/index.mjs +1 -1
  63. package/esm/hydration/index.mjs.map +1 -1
  64. package/esm/hydration-payload.mjs +1 -1
  65. package/esm/hydration-payload.mjs.map +1 -1
  66. package/esm/index.d.mts +1 -1
  67. package/esm/loaders.d.mts +1 -1
  68. package/esm/metadata.d.mts +1 -1
  69. package/esm/metadata.mjs +1 -1
  70. package/esm/metadata.mjs.map +1 -1
  71. package/esm/props.d.mts +1 -1
  72. package/esm/route.d.mts +1 -1
  73. package/esm/routing/compose-route-path.d.mts +1 -1
  74. package/esm/routing/compose-route-path.mjs +1 -1
  75. package/esm/routing/compose-route-path.mjs.map +1 -1
  76. package/esm/routing/data-request.mjs +1 -1
  77. package/esm/routing/data-request.mjs.map +1 -1
  78. package/esm/routing/filesystem-route.mjs +1 -1
  79. package/esm/routing/filesystem-route.mjs.map +1 -1
  80. package/esm/routing/layout-policy.mjs +1 -1
  81. package/esm/routing/layout-policy.mjs.map +1 -1
  82. package/esm/routing/navigator.mjs +1 -1
  83. package/esm/routing/navigator.mjs.map +1 -1
  84. package/esm/routing/query-string.d.mts +1 -1
  85. package/esm/routing/query-string.mjs +1 -1
  86. package/esm/routing/query-string.mjs.map +1 -1
  87. package/esm/routing/route-identity.mjs +1 -1
  88. package/esm/routing/route-identity.mjs.map +1 -1
  89. package/esm/routing/route-table.d.mts +1 -1
  90. package/esm/routing/route-table.mjs +1 -1
  91. package/esm/routing/route-table.mjs.map +1 -1
  92. package/esm/routing/router-events.d.mts +1 -1
  93. package/esm/routing/router-events.mjs +1 -1
  94. package/esm/routing/router-events.mjs.map +1 -1
  95. package/esm/routing/url-fragment.mjs +1 -1
  96. package/esm/routing/url-fragment.mjs.map +1 -1
  97. package/esm/runtime/register-modules.d.mts +1 -1
  98. package/esm/runtime/register-modules.mjs +1 -1
  99. package/esm/runtime/register-modules.mjs.map +1 -1
  100. package/esm/server/build-hydration-payload.mjs +1 -1
  101. package/esm/server/build-hydration-payload.mjs.map +1 -1
  102. package/esm/server/client-asset-url-prefix.d.mts +1 -1
  103. package/esm/server/client-asset-url-prefix.mjs +1 -1
  104. package/esm/server/client-asset-url-prefix.mjs.map +1 -1
  105. package/esm/server/create-page-module-loader.d.mts +1 -1
  106. package/esm/server/create-page-module-loader.mjs +1 -1
  107. package/esm/server/create-page-module-loader.mjs.map +1 -1
  108. package/esm/server/create-page-route-handler.d.mts +1 -1
  109. package/esm/server/create-page-route-handler.mjs +1 -1
  110. package/esm/server/create-page-route-handler.mjs.map +1 -1
  111. package/esm/server/dev-server.mjs +1 -1
  112. package/esm/server/dev-server.mjs.map +1 -1
  113. package/esm/server/error-page.d.mts +1 -1
  114. package/esm/server/error-page.mjs +1 -1
  115. package/esm/server/error-page.mjs.map +1 -1
  116. package/esm/server/execute-page-request.d.mts +1 -1
  117. package/esm/server/execute-page-request.mjs +1 -1
  118. package/esm/server/execute-page-request.mjs.map +1 -1
  119. package/esm/server/execute-page-request.types.d.mts +1 -1
  120. package/esm/server/hydration-client-url.d.mts +1 -1
  121. package/esm/server/hydration-client-url.mjs +1 -1
  122. package/esm/server/hydration-client-url.mjs.map +1 -1
  123. package/esm/server/install-page-routes-from-manifest.d.mts +1 -1
  124. package/esm/server/install-page-routes-from-manifest.mjs +1 -1
  125. package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
  126. package/esm/server/install-page-routes.d.mts +1 -1
  127. package/esm/server/install-page-routes.mjs +1 -1
  128. package/esm/server/install-page-routes.mjs.map +1 -1
  129. package/esm/server/install-production-page-routes.mjs +1 -1
  130. package/esm/server/install-production-page-routes.mjs.map +1 -1
  131. package/esm/server/match-page-route.mjs +1 -1
  132. package/esm/server/match-page-route.mjs.map +1 -1
  133. package/esm/server/not-found-page.d.mts +1 -1
  134. package/esm/server/not-found-page.mjs +1 -1
  135. package/esm/server/not-found-page.mjs.map +1 -1
  136. package/esm/server/page-context.d.mts +1 -1
  137. package/esm/server/page-context.mjs +1 -1
  138. package/esm/server/page-context.mjs.map +1 -1
  139. package/esm/server/page-file-change.mjs +1 -1
  140. package/esm/server/page-file-change.mjs.map +1 -1
  141. package/esm/server/page-manifest.d.mts +1 -1
  142. package/esm/server/page-manifest.mjs +1 -1
  143. package/esm/server/page-manifest.mjs.map +1 -1
  144. package/esm/server/page-render-bundle.mjs +1 -1
  145. package/esm/server/page-render-bundle.mjs.map +1 -1
  146. package/esm/server/page-route-reload.mjs +1 -1
  147. package/esm/server/page-route-reload.mjs.map +1 -1
  148. package/esm/server/register-production-public-files.mjs +1 -1
  149. package/esm/server/register-production-public-files.mjs.map +1 -1
  150. package/esm/server/render-page.d.mts +1 -1
  151. package/esm/server/render-page.mjs +1 -1
  152. package/esm/server/render-page.mjs.map +1 -1
  153. package/esm/server/resolve-page-metadata.mjs +1 -1
  154. package/esm/server/resolve-page-metadata.mjs.map +1 -1
  155. package/esm/server/resolve-validation-data.mjs +1 -1
  156. package/esm/server/resolve-validation-data.mjs.map +1 -1
  157. package/esm/server/settle-page-response.d.mts +1 -1
  158. package/esm/server/settle-page-response.mjs +1 -1
  159. package/esm/server/settle-page-response.mjs.map +1 -1
  160. package/esm/server/stylesheet-urls.d.mts +1 -1
  161. package/esm/server/stylesheet-urls.mjs +1 -1
  162. package/esm/server/stylesheet-urls.mjs.map +1 -1
  163. package/esm/server/unregistered-pages.mjs +1 -1
  164. package/esm/server/unregistered-pages.mjs.map +1 -1
  165. package/esm/server/web-connector-factory.d.mts +1 -1
  166. package/esm/server/web-connector-factory.mjs +1 -1
  167. package/esm/server/web-connector-factory.mjs.map +1 -1
  168. package/esm/server/web-connector.d.mts +1 -1
  169. package/esm/server/web-connector.mjs +1 -1
  170. package/esm/server/web-connector.mjs.map +1 -1
  171. package/esm/shared.d.mts +1 -1
  172. package/esm/shared.mjs +1 -1
  173. package/esm/shared.mjs.map +1 -1
  174. package/esm/validation.d.mts +1 -1
  175. package/esm/vite/app-convention-aliases.mjs +1 -1
  176. package/esm/vite/app-convention-aliases.mjs.map +1 -1
  177. package/esm/vite/build-client.d.mts +1 -1
  178. package/esm/vite/build-client.mjs +1 -1
  179. package/esm/vite/build-client.mjs.map +1 -1
  180. package/esm/vite/gate-a-resolve.d.mts +1 -1
  181. package/esm/vite/gate-a-resolve.mjs +1 -1
  182. package/esm/vite/gate-a-resolve.mjs.map +1 -1
  183. package/esm/vite/gate-b-secrets.d.mts +1 -1
  184. package/esm/vite/gate-b-secrets.mjs +1 -1
  185. package/esm/vite/gate-b-secrets.mjs.map +1 -1
  186. package/esm/vite/gate-c-verify.d.mts +1 -1
  187. package/esm/vite/gate-c-verify.mjs +1 -1
  188. package/esm/vite/gate-c-verify.mjs.map +1 -1
  189. package/esm/vite/hydration-entries.d.mts +1 -1
  190. package/esm/vite/hydration-entries.mjs +1 -1
  191. package/esm/vite/hydration-entries.mjs.map +1 -1
  192. package/esm/vite/index.d.mts +1 -1
  193. package/esm/vite/index.mjs +1 -1
  194. package/esm/vite/index.mjs.map +1 -1
  195. package/esm/vite/page-registry-plugin.d.mts +1 -1
  196. package/esm/vite/page-registry-plugin.mjs +1 -1
  197. package/esm/vite/page-registry-plugin.mjs.map +1 -1
  198. package/esm/vite/projection.d.mts +1 -1
  199. package/esm/vite/projection.mjs +1 -1
  200. package/esm/vite/projection.mjs.map +1 -1
  201. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"execute-page-request.mjs","names":[],"sources":["C:/Work/Warlock.js/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 { enterAdditionalSharedScope, requireRunner } from \"./page-context\";\nimport { matchRoute } from \"./match-page-route\";\nimport { resolvePageMetadata } from \"./resolve-page-metadata\";\nimport { resolveValidationData } from \"./resolve-validation-data\";\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\nexport async function executePageRequest<TResult = PageDataBundle>(\n options: ExecutePageRequestOptions<TResult>,\n): Promise<TResult | Response | undefined> {\n const runner = requireRunner();\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 for (const level of LEVEL_ORDER) {\n for (const middleware of triple[level].middleware ?? []) {\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 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 bundle.error = buildErrorRecord(signalThrown, boundary, pathname);\n\n if (boundary.boundaryLevel === \"app\") {\n response.setStatusCode(500);\n forcedStatusCode = 500;\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\";\nimport { v } from \"@warlock.js/seal\";\nimport { enterSharedScope, sealShared } from \"../shared\";\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 {\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\nexport async function executePageRequest<TResult = PageDataBundle>(\n options: ExecutePageRequestOptions<TResult>,\n): Promise<TResult | Response | undefined> {\n const runner = requireRunner();\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 for (const level of LEVEL_ORDER) {\n for (const middleware of triple[level].middleware ?? []) {\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 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 bundle.error = buildErrorRecord(signalThrown, boundary, pathname);\n\n if (boundary.boundaryLevel === \"app\") {\n response.setStatusCode(500);\n forcedStatusCode = 500;\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":";;;;;;;;;;AA+CA,eAAsB,mBACpB,SACyC;CACzC,MAAM,SAAS,cAAc;CAC7B,MAAM,CAAC,UAAU,eAAe,QAAQ,IAAI,MAAM,GAAG;CACrD,MAAM,UAAU,WAAW,UAAU,QAAQ,MAAM;CAEnD,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;EAEA,KAAK,MAAM,SAAS,aAClB,KAAK,MAAM,cAAc,OAAO,OAAO,cAAc,CAAC,GAAG;GACvD,IAAI;GAEJ,IAAI;IACF,SAAS,MAAM,WAAW;KAAE;KAAS;IAAS,CAAC;GACjD,SAAS,QAAQ;IACf,OAAO,QAAQ,iBAAiB,QAAQ,kBAAkB,OAAO,MAAM,GAAG,QAAQ;IAClF,SAAS,cAAc,GAAG;IAC1B,OAAO,OAAO,MAAM;GACtB;GAEA,IAAI,WAAW,QAAW;IACxB,OAAO,eAAe;KACpB,OAAO;KACP;KACA,OAAO;KACP,YAAY,SAAS;IACvB;IACA,OAAO,OAAO,MAAM;GACtB;EACF;EAGF,MAAM,aAAa,OAAO,KAAK;EAE/B,IAAI,YAAY,QAAQ;GACtB,MAAM,OAAO,sBAAsB,WAAW,YAAY,OAAO;GACjE,MAAM,SAAS,MAAM,EAAE,SAAS,WAAW,QAAQ,IAAI;GAEvD,IAAI,OAAO,WAAW,OAAO,MAC3B,QAAQ,iBAAiB,OAAO,IAAI;GAGtC,IAAI,CAAC,OAAO,SAAS;IACnB,OAAO,eAAe;KAAE,OAAO;KAAc,QAAQ;KAAK,QAAQ,OAAO;IAAO;IAChF,OAAO,OAAO,MAAM;GACtB;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;GAC1B,MAAM,SAAS,OAAO,OAAO;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;GACnE,OAAO,QAAQ,iBAAiB,cAAc,UAAU,QAAQ;GAEhE,IAAI,SAAS,kBAAkB,OAAO;IACpC,SAAS,cAAc,GAAG;IAC1B,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"}
@@ -6,7 +6,7 @@ import { BufferedResponse } from "./settle-page-response.mjs";
6
6
  import { HttpContext, Request, Response } from "@warlock.js/core";
7
7
  import { BaseValidator } from "@warlock.js/seal";
8
8
 
9
- //#region web/src/server/execute-page-request.types.d.ts
9
+ //#region ../web/src/server/execute-page-request.types.d.ts
10
10
  type PipelineStore = SharedStore & {
11
11
  request: Request;
12
12
  response: Response;
@@ -1,4 +1,4 @@
1
- //#region web/src/server/hydration-client-url.d.ts
1
+ //#region ../web/src/server/hydration-client-url.d.ts
2
2
  interface ResolveHydrationClientUrlOptions {
3
3
  /** Absolute path to the client build output dir: `<outdir>/client`. */
4
4
  clientDir: string;
@@ -3,7 +3,7 @@ import { HYDRATION_CLIENT_ENTRY_NAME } from "../vite/hydration-entries.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import path from "node:path";
5
5
 
6
- //#region web/src/server/hydration-client-url.ts
6
+ //#region ../web/src/server/hydration-client-url.ts
7
7
  /**
8
8
  * The ONE runtime manifest read in production: one boot-time JSON
9
9
  * lookup of the `hydration` entry in `<clientDir>/.vite/manifest.json`,
@@ -1 +1 @@
1
- {"version":3,"file":"hydration-client-url.mjs","names":[],"sources":["C:/Work/Warlock.js/web/src/server/hydration-client-url.ts"],"sourcesContent":["/**\n * The ONE runtime manifest read in production: one boot-time JSON\n * lookup of the `hydration` entry in `<clientDir>/.vite/manifest.json`,\n * producing the string `install-page-routes.ts:139` already accepts as\n * `hydrationClientModuleUrl`.\n *\n * NEVER FALLS BACK. Each failure is its own named error so the boot log says\n * which half of the build→runtime handoff broke: a missing\n * file means the client build never ran, malformed JSON means the artifact is\n * corrupt, and a missing entry means the build/runtime entry-name contract\n * drifted. \"Serve without hydration\" is not among the outcomes.\n */\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { HYDRATION_CLIENT_ENTRY_NAME } from \"../vite/hydration-entries\";\nimport { CLIENT_ASSET_URL_PREFIX } from \"./client-asset-url-prefix\";\n\nexport interface ResolveHydrationClientUrlOptions {\n /** Absolute path to the client build output dir: `<outdir>/client`. */\n clientDir: string;\n}\n\n/** The manifest file is absent — the client build never ran, or ran elsewhere. */\nexport class WebClientManifestMissingError extends Error {\n public constructor(manifestPath: string, cause: unknown) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" is missing or unreadable. ` +\n \"This app was started in production without its `warlock build` client artifacts.\",\n { cause },\n );\n this.name = \"WebClientManifestMissingError\";\n }\n}\n\n/** The manifest file exists but is not parsable JSON, or is not a JSON object. */\nexport class WebClientManifestMalformedError extends Error {\n public constructor(manifestPath: string, cause: unknown) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" is not valid JSON object content.`,\n { cause },\n );\n this.name = \"WebClientManifestMalformedError\";\n }\n}\n\n/**\n * The manifest parses but carries no usable `hydration` entry.\n *\n * The entry name is a shared contract between the build (`build-client.ts`\n * rollup input) and this runtime read; drift fails loudly HERE. Nobody adds a\n * default.\n */\nexport class WebClientManifestEntryMissingError extends Error {\n public constructor(manifestPath: string, entryName: string) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" has no \"${entryName}\" entry with a \"file\" string. ` +\n \"The build and runtime hydration entry names have drifted.\",\n );\n this.name = \"WebClientManifestEntryMissingError\";\n }\n}\n\n/**\n * The manifest names an entry file that does NOT live under the asset\n * directory `CLIENT_ASSET_URL_PREFIX` is mounted from.\n *\n * Serving it would 404 — the static route only exposes that one directory —\n * so the artifact is rejected at boot instead of at first page view.\n */\nexport class WebClientAssetPrefixViolationError extends Error {\n public constructor(manifestPath: string, file: string) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" points its entry at \"${file}\", ` +\n `which is not under the \"${CLIENT_ASSET_URL_PREFIX}\" asset directory this framework serves. ` +\n \"This client artifact was not produced by this framework's client build configuration. \" +\n \"Re-run the client build instead of hand-editing the manifest or the build output.\",\n );\n this.name = \"WebClientAssetPrefixViolationError\";\n }\n}\n\ntype ManifestEntry = { file?: unknown; name?: unknown; isEntry?: unknown };\n\n/**\n * Find the hydration entry in a parsed Vite manifest.\n *\n * VITE KEYS BY SOURCE PATH, NOT BY ENTRY NAME. A rollup input of\n * `{ hydration: \"src/hydration/index.ts\" }` produces the record\n * `\"src/hydration/index.ts\": { name: \"hydration\", isEntry: true, file: \"assets/hydration-<hash>.js\" }`\n * — the name the build and this module share lives in the `name` FIELD, and\n * there is no `\"hydration\"` key at all. Indexing the manifest by\n * {@link HYDRATION_CLIENT_ENTRY_NAME} therefore missed every real manifest and\n * reported an entry-name drift that had not happened.\n *\n * Matching on `isEntry` as well as `name` keeps this unambiguous: a shared\n * chunk can carry a `name` too, and only entries are addressable as a module\n * URL.\n */\nfunction findHydrationEntry(manifest: Record<string, ManifestEntry | undefined>): ManifestEntry | undefined {\n for (const entry of Object.values(manifest)) {\n if (\n entry !== undefined &&\n typeof entry === \"object\" &&\n entry.isEntry === true &&\n entry.name === HYDRATION_CLIENT_ENTRY_NAME\n ) {\n return entry;\n }\n }\n\n return undefined;\n}\n\nexport function resolveHydrationClientUrl(options: ResolveHydrationClientUrlOptions): string {\n const manifestPath = path.join(options.clientDir, \".vite\", \"manifest.json\");\n\n let raw: string;\n\n try {\n raw = readFileSync(manifestPath, \"utf-8\");\n } catch (error) {\n throw new WebClientManifestMissingError(manifestPath, error);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw new WebClientManifestMalformedError(manifestPath, error);\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new WebClientManifestMalformedError(\n manifestPath,\n new TypeError(\"The manifest root is not a JSON object.\"),\n );\n }\n\n const entry = findHydrationEntry(parsed as Record<string, ManifestEntry | undefined>);\n\n if (entry === undefined || typeof entry !== \"object\" || typeof entry.file !== \"string\") {\n throw new WebClientManifestEntryMissingError(manifestPath, HYDRATION_CLIENT_ENTRY_NAME);\n }\n\n const url = `/${entry.file}`;\n\n // The prefix is DERIVED from CLIENT_ASSET_URL_PREFIX, never restated: this\n // file must not carry a second copy of the literal the constant owns.\n if (!url.startsWith(`${CLIENT_ASSET_URL_PREFIX}/`)) {\n throw new WebClientAssetPrefixViolationError(manifestPath, entry.file);\n }\n\n // Past this point the returned URL starts with CLIENT_ASSET_URL_PREFIX by\n // construction — which is exactly what the static-file route mounting\n // `<clientDir>/assets` at that same imported symbol relies on.\n return url;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuBA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,cAAsB,OAAgB;EACvD,MACE,sEAAsE,aAAa,iHAEnF,EAAE,MAAM,CACV;EACA,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,kCAAb,cAAqD,MAAM;CACzD,AAAO,YAAY,cAAsB,OAAgB;EACvD,MACE,sEAAsE,aAAa,sCACnF,EAAE,MAAM,CACV;EACA,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,IAAa,qCAAb,cAAwD,MAAM;CAC5D,AAAO,YAAY,cAAsB,WAAmB;EAC1D,MACE,sEAAsE,aAAa,YAAY,UAAU,wFAE3G;EACA,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,IAAa,qCAAb,cAAwD,MAAM;CAC5D,AAAO,YAAY,cAAsB,MAAc;EACrD,MACE,sEAAsE,aAAa,yBAAyB,KAAK,6BACpF,wBAAwB,iNAGvD;EACA,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;AAmBA,SAAS,mBAAmB,UAAgF;CAC1G,KAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,GACxC,IACE,UAAU,UACV,OAAO,UAAU,YACjB,MAAM,YAAY,QAClB,MAAM,sBAEN,OAAO;AAKb;AAEA,SAAgB,0BAA0B,SAAmD;CAC3F,MAAM,eAAe,KAAK,KAAK,QAAQ,WAAW,SAAS,eAAe;CAE1E,IAAI;CAEJ,IAAI;EACF,MAAM,aAAa,cAAc,OAAO;CAC1C,SAAS,OAAO;EACd,MAAM,IAAI,8BAA8B,cAAc,KAAK;CAC7D;CAEA,IAAI;CAEJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,SAAS,OAAO;EACd,MAAM,IAAI,gCAAgC,cAAc,KAAK;CAC/D;CAEA,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GACvE,MAAM,IAAI,gCACR,8BACA,IAAI,UAAU,yCAAyC,CACzD;CAGF,MAAM,QAAQ,mBAAmB,MAAmD;CAEpF,IAAI,UAAU,UAAa,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,UAC5E,MAAM,IAAI,mCAAmC,cAAc,2BAA2B;CAGxF,MAAM,MAAM,IAAI,MAAM;CAItB,IAAI,CAAC,IAAI,WAAW,aAA2B,EAAE,GAC/C,MAAM,IAAI,mCAAmC,cAAc,MAAM,IAAI;CAMvE,OAAO;AACT"}
1
+ {"version":3,"file":"hydration-client-url.mjs","names":[],"sources":["../../../../../../../web/src/server/hydration-client-url.ts"],"sourcesContent":["/**\n * The ONE runtime manifest read in production: one boot-time JSON\n * lookup of the `hydration` entry in `<clientDir>/.vite/manifest.json`,\n * producing the string `install-page-routes.ts:139` already accepts as\n * `hydrationClientModuleUrl`.\n *\n * NEVER FALLS BACK. Each failure is its own named error so the boot log says\n * which half of the build→runtime handoff broke: a missing\n * file means the client build never ran, malformed JSON means the artifact is\n * corrupt, and a missing entry means the build/runtime entry-name contract\n * drifted. \"Serve without hydration\" is not among the outcomes.\n */\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { HYDRATION_CLIENT_ENTRY_NAME } from \"../vite/hydration-entries\";\nimport { CLIENT_ASSET_URL_PREFIX } from \"./client-asset-url-prefix\";\n\nexport interface ResolveHydrationClientUrlOptions {\n /** Absolute path to the client build output dir: `<outdir>/client`. */\n clientDir: string;\n}\n\n/** The manifest file is absent — the client build never ran, or ran elsewhere. */\nexport class WebClientManifestMissingError extends Error {\n public constructor(manifestPath: string, cause: unknown) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" is missing or unreadable. ` +\n \"This app was started in production without its `warlock build` client artifacts.\",\n { cause },\n );\n this.name = \"WebClientManifestMissingError\";\n }\n}\n\n/** The manifest file exists but is not parsable JSON, or is not a JSON object. */\nexport class WebClientManifestMalformedError extends Error {\n public constructor(manifestPath: string, cause: unknown) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" is not valid JSON object content.`,\n { cause },\n );\n this.name = \"WebClientManifestMalformedError\";\n }\n}\n\n/**\n * The manifest parses but carries no usable `hydration` entry.\n *\n * The entry name is a shared contract between the build (`build-client.ts`\n * rollup input) and this runtime read; drift fails loudly HERE. Nobody adds a\n * default.\n */\nexport class WebClientManifestEntryMissingError extends Error {\n public constructor(manifestPath: string, entryName: string) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" has no \"${entryName}\" entry with a \"file\" string. ` +\n \"The build and runtime hydration entry names have drifted.\",\n );\n this.name = \"WebClientManifestEntryMissingError\";\n }\n}\n\n/**\n * The manifest names an entry file that does NOT live under the asset\n * directory `CLIENT_ASSET_URL_PREFIX` is mounted from.\n *\n * Serving it would 404 — the static route only exposes that one directory —\n * so the artifact is rejected at boot instead of at first page view.\n */\nexport class WebClientAssetPrefixViolationError extends Error {\n public constructor(manifestPath: string, file: string) {\n super(\n `Cannot resolve the hydration client URL: the Vite client manifest \"${manifestPath}\" points its entry at \"${file}\", ` +\n `which is not under the \"${CLIENT_ASSET_URL_PREFIX}\" asset directory this framework serves. ` +\n \"This client artifact was not produced by this framework's client build configuration. \" +\n \"Re-run the client build instead of hand-editing the manifest or the build output.\",\n );\n this.name = \"WebClientAssetPrefixViolationError\";\n }\n}\n\ntype ManifestEntry = { file?: unknown; name?: unknown; isEntry?: unknown };\n\n/**\n * Find the hydration entry in a parsed Vite manifest.\n *\n * VITE KEYS BY SOURCE PATH, NOT BY ENTRY NAME. A rollup input of\n * `{ hydration: \"src/hydration/index.ts\" }` produces the record\n * `\"src/hydration/index.ts\": { name: \"hydration\", isEntry: true, file: \"assets/hydration-<hash>.js\" }`\n * — the name the build and this module share lives in the `name` FIELD, and\n * there is no `\"hydration\"` key at all. Indexing the manifest by\n * {@link HYDRATION_CLIENT_ENTRY_NAME} therefore missed every real manifest and\n * reported an entry-name drift that had not happened.\n *\n * Matching on `isEntry` as well as `name` keeps this unambiguous: a shared\n * chunk can carry a `name` too, and only entries are addressable as a module\n * URL.\n */\nfunction findHydrationEntry(manifest: Record<string, ManifestEntry | undefined>): ManifestEntry | undefined {\n for (const entry of Object.values(manifest)) {\n if (\n entry !== undefined &&\n typeof entry === \"object\" &&\n entry.isEntry === true &&\n entry.name === HYDRATION_CLIENT_ENTRY_NAME\n ) {\n return entry;\n }\n }\n\n return undefined;\n}\n\nexport function resolveHydrationClientUrl(options: ResolveHydrationClientUrlOptions): string {\n const manifestPath = path.join(options.clientDir, \".vite\", \"manifest.json\");\n\n let raw: string;\n\n try {\n raw = readFileSync(manifestPath, \"utf-8\");\n } catch (error) {\n throw new WebClientManifestMissingError(manifestPath, error);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw new WebClientManifestMalformedError(manifestPath, error);\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new WebClientManifestMalformedError(\n manifestPath,\n new TypeError(\"The manifest root is not a JSON object.\"),\n );\n }\n\n const entry = findHydrationEntry(parsed as Record<string, ManifestEntry | undefined>);\n\n if (entry === undefined || typeof entry !== \"object\" || typeof entry.file !== \"string\") {\n throw new WebClientManifestEntryMissingError(manifestPath, HYDRATION_CLIENT_ENTRY_NAME);\n }\n\n const url = `/${entry.file}`;\n\n // The prefix is DERIVED from CLIENT_ASSET_URL_PREFIX, never restated: this\n // file must not carry a second copy of the literal the constant owns.\n if (!url.startsWith(`${CLIENT_ASSET_URL_PREFIX}/`)) {\n throw new WebClientAssetPrefixViolationError(manifestPath, entry.file);\n }\n\n // Past this point the returned URL starts with CLIENT_ASSET_URL_PREFIX by\n // construction — which is exactly what the static-file route mounting\n // `<clientDir>/assets` at that same imported symbol relies on.\n return url;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuBA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,cAAsB,OAAgB;EACvD,MACE,sEAAsE,aAAa,iHAEnF,EAAE,MAAM,CACV;EACA,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,kCAAb,cAAqD,MAAM;CACzD,AAAO,YAAY,cAAsB,OAAgB;EACvD,MACE,sEAAsE,aAAa,sCACnF,EAAE,MAAM,CACV;EACA,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,IAAa,qCAAb,cAAwD,MAAM;CAC5D,AAAO,YAAY,cAAsB,WAAmB;EAC1D,MACE,sEAAsE,aAAa,YAAY,UAAU,wFAE3G;EACA,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,IAAa,qCAAb,cAAwD,MAAM;CAC5D,AAAO,YAAY,cAAsB,MAAc;EACrD,MACE,sEAAsE,aAAa,yBAAyB,KAAK,6BACpF,wBAAwB,iNAGvD;EACA,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;AAmBA,SAAS,mBAAmB,UAAgF;CAC1G,KAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,GACxC,IACE,UAAU,UACV,OAAO,UAAU,YACjB,MAAM,YAAY,QAClB,MAAM,sBAEN,OAAO;AAKb;AAEA,SAAgB,0BAA0B,SAAmD;CAC3F,MAAM,eAAe,KAAK,KAAK,QAAQ,WAAW,SAAS,eAAe;CAE1E,IAAI;CAEJ,IAAI;EACF,MAAM,aAAa,cAAc,OAAO;CAC1C,SAAS,OAAO;EACd,MAAM,IAAI,8BAA8B,cAAc,KAAK;CAC7D;CAEA,IAAI;CAEJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,SAAS,OAAO;EACd,MAAM,IAAI,gCAAgC,cAAc,KAAK;CAC/D;CAEA,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GACvE,MAAM,IAAI,gCACR,8BACA,IAAI,UAAU,yCAAyC,CACzD;CAGF,MAAM,QAAQ,mBAAmB,MAAmD;CAEpF,IAAI,UAAU,UAAa,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,UAC5E,MAAM,IAAI,mCAAmC,cAAc,2BAA2B;CAGxF,MAAM,MAAM,IAAI,MAAM;CAItB,IAAI,CAAC,IAAI,WAAW,aAA2B,EAAE,GAC/C,MAAM,IAAI,mCAAmC,cAAc,MAAM,IAAI;CAMvE,OAAO;AACT"}
@@ -2,7 +2,7 @@ import { PageManifest } from "./page-manifest.mjs";
2
2
  import { PageRouteHandler, PageRouteHandlerOptions } from "./create-page-route-handler.mjs";
3
3
  import { Router } from "@warlock.js/core";
4
4
 
5
- //#region web/src/server/install-page-routes-from-manifest.d.ts
5
+ //#region ../web/src/server/install-page-routes-from-manifest.d.ts
6
6
  /**
7
7
  * How a handler is built for one page. Defaults to `createPageRouteHandler`;
8
8
  * taking it as an input keeps this module's own job — reading the manifest and
@@ -10,7 +10,7 @@ import { productionStylesheetUrls } from "./stylesheet-urls.mjs";
10
10
  import { DuplicateNotFoundPageError, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, createNotFoundRouteHandler, isNotFoundPageFile } from "./not-found-page.mjs";
11
11
  import { Response } from "@warlock.js/core";
12
12
 
13
- //#region web/src/server/install-page-routes-from-manifest.ts
13
+ //#region ../web/src/server/install-page-routes-from-manifest.ts
14
14
  /**
15
15
  * Page-route registration for a built application.
16
16
  *
@@ -1 +1 @@
1
- {"version":3,"file":"install-page-routes-from-manifest.mjs","names":[],"sources":["C:/Work/Warlock.js/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 ({@link layoutLevelOf}, {@link composeLayoutLevel}) — so the URL a page\r\n * answers on and the chain that guards it are decided by the page's own source\r\n * in both modes, and a build cannot quietly disagree with the dev server about\r\n * 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 { deriveFilesystemRouteName, deriveFilesystemRoutePath } from \"../routing/filesystem-route\";\r\nimport { NestedLayoutsNotSupportedError, selectPageLayout } from \"../routing/layout-policy\";\r\nimport { canonicalizeRouteExport, deriveFallbackRouteName } from \"../routing/route-identity\";\r\nimport { publishRouteTable } from \"../routing/route-table\";\r\nimport { Response, 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\";\nimport { isLoaderShortCircuit } from \"./settle-page-response\";\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 type RegisteredRouteShape,\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 };\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 (`install-page-routes.ts:145-150`).\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-server.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 `filesystemPageFileFor`\r\n * (`install-page-routes.ts:101-103`).\r\n */\r\nfunction webRelativeSourceFile(sourceFile: string): string {\r\n return sourceFile.split(\"/\").slice(2).join(\"/\");\r\n}\r\n\r\nfunction resolveRoute(\r\n routeExport: PageRouteExport | undefined,\r\n sourceFile: string,\r\n): { path: string; name: string } {\r\n if (routeExport === undefined) {\r\n const pageFile = webRelativeSourceFile(sourceFile);\r\n\r\n return {\r\n path: deriveFilesystemRoutePath({ pageFile }),\r\n name: deriveFilesystemRouteName(pageFile),\r\n };\r\n }\r\n\r\n const canonical = canonicalizeRouteExport(routeExport);\r\n\r\n return {\r\n path: canonical.path,\r\n name: canonical.name ?? deriveFallbackRouteName({ routePath: canonical.path, sourceFile }),\r\n };\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 `LayoutLevel.prefixesByDirectory`\r\n * (`install-page-routes.ts:214-222`) and the one\r\n * {@link deriveFilesystemRoutePath} uses to let a directory's own layout\r\n * rename the URL segment a bare directory name would otherwise 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 same resolution dev makes\r\n * (`install-page-routes.ts:138-164`), against loaded modules instead of Vite's.\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. Like the missing\r\n * app-root refusal below, that arm defends against stale or hand-edited build\r\n * artifacts: the build refuses to emit such a chain, but a manifest can reach a\r\n * running process 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 selection = selectPageLayout(\r\n page.layouts.map((layout) => ({\r\n layout: layout.sourceFile,\r\n renders: typeof (layout.module as LayoutModuleShape).default !== \"undefined\",\r\n })),\r\n );\r\n\r\n if (selection.type === \"rejected\") {\r\n throw new NestedLayoutsNotSupportedError(page.sourceFile, selection.layouts);\r\n }\r\n\r\n return {\r\n host:\r\n selection.type === \"selected\"\r\n ? page.layouts.find((layout) => layout.sourceFile === selection.layout)\r\n : page.layouts.at(-1),\r\n prefix: page.layouts.reduce(\r\n (composed, layout) =>\r\n composeRoutePath(composed, (layout.module as LayoutModuleShape).prefix ?? \"/\"),\r\n \"/\",\r\n ),\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: async (context: Parameters<NonNullable<LayoutModuleShape[\"loader\"]>>[0]) => {\r\n let hostData: unknown;\r\n\r\n for (let index = 0; index < page.layouts.length; index++) {\r\n const value = await (page.layouts[index].module as LayoutModuleShape).loader?.(context);\r\n\r\n if (value instanceof Response || isLoaderShortCircuit(value)) return value;\n if (index === hostIndex) hostData = value;\r\n }\r\n\r\n return hostData;\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 = 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 // 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 (`install-page-routes.ts:377-382`) and discovery applies at\r\n // build (`discover-pages.ts:925-935`), read here off the manifest's own\r\n // `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 `installPageRoutesFromManifest: composed route path \"${effectivePath}\" (layout ` +\r\n `prefix \"${layoutPrefix}\" + route.path \"${routePath}\") is declared by two pages — ` +\r\n `\"${existingFile}\" and \"${page.sourceFile}\". Every page's composed route path must ` +\r\n \"be unique.\",\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: () =>\r\n Promise.resolve(page.layouts.map((layout) => layout.module)),\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n stylesheetUrls,\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,\n statusForRenderedOk: 404,\n skipPageLoader: true,\n }),\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 ({@link layoutLevelOf}, {@link composeLayoutLevel}) — so the URL a page\r\n * answers on and the chain that guards it are decided by the page's own source\r\n * in both modes, and a build cannot quietly disagree with the dev server about\r\n * 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 { deriveFilesystemRouteName, deriveFilesystemRoutePath } from \"../routing/filesystem-route\";\r\nimport { NestedLayoutsNotSupportedError, selectPageLayout } from \"../routing/layout-policy\";\r\nimport { canonicalizeRouteExport, deriveFallbackRouteName } from \"../routing/route-identity\";\r\nimport { publishRouteTable } from \"../routing/route-table\";\r\nimport { Response, 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\";\nimport { isLoaderShortCircuit } from \"./settle-page-response\";\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 type RegisteredRouteShape,\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 };\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 (`install-page-routes.ts:145-150`).\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-server.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 `filesystemPageFileFor`\r\n * (`install-page-routes.ts:101-103`).\r\n */\r\nfunction webRelativeSourceFile(sourceFile: string): string {\r\n return sourceFile.split(\"/\").slice(2).join(\"/\");\r\n}\r\n\r\nfunction resolveRoute(\r\n routeExport: PageRouteExport | undefined,\r\n sourceFile: string,\r\n): { path: string; name: string } {\r\n if (routeExport === undefined) {\r\n const pageFile = webRelativeSourceFile(sourceFile);\r\n\r\n return {\r\n path: deriveFilesystemRoutePath({ pageFile }),\r\n name: deriveFilesystemRouteName(pageFile),\r\n };\r\n }\r\n\r\n const canonical = canonicalizeRouteExport(routeExport);\r\n\r\n return {\r\n path: canonical.path,\r\n name: canonical.name ?? deriveFallbackRouteName({ routePath: canonical.path, sourceFile }),\r\n };\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 `LayoutLevel.prefixesByDirectory`\r\n * (`install-page-routes.ts:214-222`) and the one\r\n * {@link deriveFilesystemRoutePath} uses to let a directory's own layout\r\n * rename the URL segment a bare directory name would otherwise 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 same resolution dev makes\r\n * (`install-page-routes.ts:138-164`), against loaded modules instead of Vite's.\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. Like the missing\r\n * app-root refusal below, that arm defends against stale or hand-edited build\r\n * artifacts: the build refuses to emit such a chain, but a manifest can reach a\r\n * running process 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 selection = selectPageLayout(\r\n page.layouts.map((layout) => ({\r\n layout: layout.sourceFile,\r\n renders: typeof (layout.module as LayoutModuleShape).default !== \"undefined\",\r\n })),\r\n );\r\n\r\n if (selection.type === \"rejected\") {\r\n throw new NestedLayoutsNotSupportedError(page.sourceFile, selection.layouts);\r\n }\r\n\r\n return {\r\n host:\r\n selection.type === \"selected\"\r\n ? page.layouts.find((layout) => layout.sourceFile === selection.layout)\r\n : page.layouts.at(-1),\r\n prefix: page.layouts.reduce(\r\n (composed, layout) =>\r\n composeRoutePath(composed, (layout.module as LayoutModuleShape).prefix ?? \"/\"),\r\n \"/\",\r\n ),\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: async (context: Parameters<NonNullable<LayoutModuleShape[\"loader\"]>>[0]) => {\r\n let hostData: unknown;\r\n\r\n for (let index = 0; index < page.layouts.length; index++) {\r\n const value = await (page.layouts[index].module as LayoutModuleShape).loader?.(context);\r\n\r\n if (value instanceof Response || isLoaderShortCircuit(value)) return value;\n if (index === hostIndex) hostData = value;\r\n }\r\n\r\n return hostData;\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 = 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 // 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 (`install-page-routes.ts:377-382`) and discovery applies at\r\n // build (`discover-pages.ts:925-935`), read here off the manifest's own\r\n // `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 `installPageRoutesFromManifest: composed route path \"${effectivePath}\" (layout ` +\r\n `prefix \"${layoutPrefix}\" + route.path \"${routePath}\") is declared by two pages — ` +\r\n `\"${existingFile}\" and \"${page.sourceFile}\". Every page's composed route path must ` +\r\n \"be unique.\",\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: () =>\r\n Promise.resolve(page.layouts.map((layout) => layout.module)),\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n stylesheetUrls,\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,\n statusForRenderedOk: 404,\n skipPageLoader: true,\n }),\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+HA,SAAS,sBAAsB,YAA4B;CACzD,OAAO,WAAW,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAChD;AAEA,SAAS,aACP,aACA,YACgC;CAChC,IAAI,gBAAgB,QAAW;EAC7B,MAAM,WAAW,sBAAsB,UAAU;EAEjD,OAAO;GACL,MAAM,0BAA0B,EAAE,SAAS,CAAC;GAC5C,MAAM,0BAA0B,QAAQ;EAC1C;CACF;CAEA,MAAM,YAAY,wBAAwB,WAAW;CAErD,OAAO;EACL,MAAM,UAAU;EAChB,MAAM,UAAU,QAAQ,wBAAwB;GAAE,WAAW,UAAU;GAAM;EAAW,CAAC;CAC3F;AACF;;;;;;;;AASA,SAAS,iBAAiB,MAAqD;CAC7E,OAAO,OAAO,YACZ,KAAK,QAAQ,SAAS,WAAW;EAC/B,MAAM,SAAU,OAAO,OAA6B;EAEpD,IAAI,WAAW,QAAW,OAAO,CAAC;EAElC,MAAM,WAAW,sBAAsB,OAAO,UAAU;EACxD,MAAM,aAAa,SAAS,YAAY,GAAG;EAG3C,OAAO,CAAC,CAFU,eAAe,KAAK,KAAK,SAAS,MAAM,GAAG,UAAU,GAEnD,MAAM,CAAC;CAC7B,CAAC,CACH;AACF;AA2CA,SAAS,cAAc,MAA0C;CAC/D,MAAM,YAAY,iBAChB,KAAK,QAAQ,KAAK,YAAY;EAC5B,QAAQ,OAAO;EACf,SAAS,OAAQ,OAAO,OAA6B,YAAY;CACnE,EAAE,CACJ;CAEA,IAAI,UAAU,SAAS,YACrB,MAAM,IAAI,+BAA+B,KAAK,YAAY,UAAU,OAAO;CAG7E,OAAO;EACL,MACE,UAAU,SAAS,aACf,KAAK,QAAQ,MAAM,WAAW,OAAO,eAAe,UAAU,MAAM,IACpE,KAAK,QAAQ,GAAG,EAAE;EACxB,QAAQ,KAAK,QAAQ,QAClB,UAAU,WACT,iBAAiB,UAAW,OAAO,OAA6B,UAAU,GAAG,GAC/E,GACF;CACF;AACF;;;;;;;;;;;;;;;AAgBA,SAAS,mBACP,MACA,MACyB;CACzB,MAAM,YAAY,KAAK,QAAQ,QAAQ,IAAI;CAE3C,OAAO;EACL,GAAG,KAAK;EACR,YAAY,KAAK,QAAQ,SAAS,WAAW,CAC3C,GAAK,OAAO,OAA6B,cAAc,CAAC,CAC1D,CAAC;EACD,QAAQ,OAAO,YAAqE;GAClF,IAAI;GAEJ,KAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,QAAQ,QAAQ,SAAS;IACxD,MAAM,QAAQ,MAAO,KAAK,QAAQ,OAAO,OAA6B,SAAS,OAAO;IAEtF,IAAI,iBAAiB,YAAY,qBAAqB,KAAK,GAAG,OAAO;IACrE,IAAI,UAAU,WAAW,WAAW;GACtC;GAEA,OAAO;EACT;CACF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,8BACd,SAC8B;CAC9B,MAAM,EACJ,QACA,UACA,0BACA,WACA,gBAAgB,2BACd;CAEJ,IAAI,SAAS,MAAM,WAAW,GAAG,OAAO,CAAC;CAEzC,MAAM,MAAM,SAAS;CAErB,IAAI,QAAQ,QACV,MAAM,IAAI,MACR,qEAAqE,SAAS,MAAM,OAAO,kMAI7F;CAOF,MAAM,aAAa,uBAAuB,QAAQ;CAGlD,MAAM,gBAAgB,SAAS,cAAc,SACzC,SACA,YAAY,SAAS,UAAW;CAMpC,MAAM,gBAAgB,SAAS,MAAM,QAAQ,SAAS,mBAAmB,KAAK,UAAU,CAAC;CACzF,MAAM,QAAQ,SAAS,MAAM,QAAQ,SAAS,CAAC,mBAAmB,KAAK,UAAU,CAAC;CAElF,IAAI,cAAc,SAAS,GACzB,MAAM,IAAI,2BAA2B,cAAc,KAAK,SAAS,KAAK,UAAU,CAAC;CAGnF,MAAM,eAAe,cAAc;CAEnC,IAAI,iBAAiB,UAAc,aAAa,OAA2B,UAAU,QACnF,MAAM,IAAI,+BAA+B,aAAa,UAAU;CAGlE,MAAM,YAA0C,CAAC;CACjD,MAAM,6BAAa,IAAI,IAAoB;CAE3C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,EAAE,MAAM,QAAQ,QAAQ,iBAAiB,cAAc,IAAI;EACjE,MAAM,cAAe,KAAK,OAA2B;EAErD,MAAM,EAAE,MAAM,WAAW,SAAS,aAAa,aAAa,KAAK,UAAU;EAO3E,MAAM,gBACJ,gBAAgB,SACZ,0BAA0B;GACxB,UAAU,sBAAsB,KAAK,UAAU;GAC/C,gBAAgB,iBAAiB,IAAI;EACvC,CAAC,IACD,iBAAiB,cAAc,SAAS;EAC9C,MAAM,eAAe,WAAW,IAAI,aAAa;EAEjD,IAAI,cACF,MAAM,IAAI,MACR,uDAAuD,cAAc,oBACxD,aAAa,kBAAkB,UAAU,iCAChD,aAAa,SAAS,KAAK,WAAW,oDAE9C;EAGF,WAAW,IAAI,eAAe,KAAK,UAAU;EAM7C,MAAM,iBACJ,KAAK,QAAQ,SAAS,KAAK,WAAW,SAClC,mBAAmB,MAAM,MAAM,IAC/B;EAUN,MAAM,iBACJ,cAAc,SACV,CAAC,IACD,yBAAyB,WAAW;GAClC,IAAI;GACJ,GAAG,KAAK,QAAQ,KAAK,eAAe,WAAW,UAAU;GACzD,KAAK;EACP,CAAC;EAEP,OAAO,IACL,eACA,cAAc;GACZ,MAAM;GACN;GACA,SAAS,IAAI;GACb,UAAU,KAAK;GACf,YAAY,QAAQ;GACpB,YACE,mBAAmB,SACf,cACC,aACC,aAAa,QAAQ,aACjB,QAAQ,QAAQ,cAAc,IAC9B,WAAW,QAAQ;GAC/B,+BACE,QAAQ,QAAQ,KAAK,QAAQ,KAAK,WAAW,OAAO,MAAM,CAAC;GAC7D;GACA;GACA;EACF,CAAC,GAID;GAAE;GAAM,QAAQ;EAAK,CACvB;EAEA,UAAU,KAAK;GACb,cAAc;GACd,MAAM;GACN;GACA,MAAM,KAAK;GACX,YAAY,QAAQ;EACtB,CAAC;CACH;CAQA,OAAO,SAEL,2BAA2B,EACzB,YACE,iBAAiB,SACb,SACA,cAAc;EACZ;EACA,MAAM;EACN,SAAS,IAAI;EACb,UAAU,aAAa;EAIvB,YAAY;EACZ;EACA;EACA;EAGA,gBACE,cAAc,SACV,CAAC,IACD,yBAAyB,WAAW,CAAC,IAAI,YAAY,aAAa,UAAU,CAAC;EACnF,YAAY,gBAAgB;EAC5B,qBAAqB;EACrB,gBAAgB;CAClB,CAAC,EACT,CAAC,GAGD;EAAE,MAAM;EAAsB,QAAQ;CAAK,CAC7C;CAQA,kBAAkB,WAAW,4CAA4C;CAEzE,OAAO;AACT"}
@@ -3,7 +3,7 @@ import { PipelineLoader, PipelineMiddleware } from "./execute-page-request.types
3
3
  import { Router } from "@warlock.js/core";
4
4
  import { ViteDevServer } from "vite";
5
5
 
6
- //#region web/src/server/install-page-routes.d.ts
6
+ //#region ../web/src/server/install-page-routes.d.ts
7
7
  type PageRouteExport = string | {
8
8
  path: string;
9
9
  name?: string;
@@ -12,7 +12,7 @@ import { ErrorPageDeclaresRouteError, discoverPageFiles, isErrorPageFile, layout
12
12
  import { Response } from "@warlock.js/core";
13
13
  import path from "node:path";
14
14
 
15
- //#region web/src/server/install-page-routes.ts
15
+ //#region ../web/src/server/install-page-routes.ts
16
16
  /**
17
17
  * Registers every page {@link discoverPageFiles} finds under `<appSrcRoot>`
18
18
  * into Warlock's router (`router.get`, `core/src/router/router.ts:359-361`)
@@ -1 +1 @@
1
- {"version":3,"file":"install-page-routes.mjs","names":[],"sources":["C:/Work/Warlock.js/web/src/server/install-page-routes.ts"],"sourcesContent":["/**\r\n * Registers every page {@link discoverPageFiles} finds under `<appSrcRoot>`\r\n * into Warlock's router (`router.get`, `core/src/router/router.ts:359-361`)\r\n * so `router.scanDevServer(fastify)` —\r\n * the sanctioned dev-server dispatch path (server matching is Warlock's\r\n * router; there is no second server matcher) — picks\r\n * it up. Replaces the two hand-rolled `fastify.get()` calls this file's\r\n * sibling, `dev-server.ts`, used to make directly.\r\n *\r\n * DELIBERATE EXCEPTION to \"web has no core dependency\", same\r\n * reasoning `dev-server.ts`'s own header comment records: this module is not\r\n * exported from either package barrel and is not part of `web/package.json`'s\r\n * dependency graph — dev/CLI bootstrap only.\r\n *\r\n * Scope note: a page's\r\n * `route.path` is now composed with the `prefix` export of EVERY `layout.tsx`\r\n * on its path — outermost first (`composeRoutePath` below) — before\r\n * registration and before the collision check, so `home.page.tsx`\r\n * (`path: \"/\"`, main layout `prefix: \"/\"`) resolves to `/` and\r\n * `products.page.tsx` (`path: \"/\"`, products layout `prefix: \"/products\"`)\r\n * resolves to `/products` — no collision. A page with no `layout.tsx` on its\r\n * path composes against the implicit root prefix `\"/\"` (e.g. `/contact-us`,\r\n * `/hydration-demo`, both unaffected by composition).\r\n *\r\n * WHICH PAGES EXIST is answered by {@link discoverPageFiles}\r\n * (`web/src/build/discover-pages.ts`) — the same walk production's build\r\n * shares — so this file owns no directory-walking of its own and serves the\r\n * global root (`<appSrcRoot>/web/**`) exactly as it serves a module's\r\n * (`<appSrcRoot>/app/<module>/web/**`). WHAT ROUTE A PAGE ANSWERS ON stays\r\n * this file's own job: each page and its nearest layout are still evaluated\r\n * through Vite (`vite.ssrLoadModule`), never read statically, because a dev\r\n * page module must be the one Vite serves, warm cache and all.\r\n */\r\nimport path from \"node:path\";\r\nimport type { ViteDevServer } from \"vite\";\r\nimport {\r\n discoverPageFiles,\r\n ErrorPageDeclaresRouteError,\r\n isErrorPageFile,\r\n layoutChainFor,\r\n toPosix,\r\n} from \"../build/discover-pages\";\r\nimport { NonLiteralRouteExportError, readRouteExports } from \"../build/read-route-exports\";\r\nimport { composeRoutePath } from \"../routing/compose-route-path\";\r\nimport {\r\n deriveFilesystemRouteName,\r\n deriveFilesystemRoutePath,\r\n} from \"../routing/filesystem-route\";\r\nimport { NestedLayoutsNotSupportedError, selectPageLayout } from \"../routing/layout-policy\";\r\nimport { canonicalizeRouteExport } from \"../routing/route-identity\";\r\nimport { publishRouteTable } from \"../routing/route-table\";\r\nimport { Response, type Router } from \"@warlock.js/core\";\r\nimport { createPageRouteHandler } from \"./create-page-route-handler\";\r\nimport type { ErrorPageModule } from \"./error-page\";\r\nimport type { PipelineLoader, PipelineMiddleware } from \"./execute-page-request\";\nimport { isLoaderShortCircuit } from \"./settle-page-response\";\nimport { devHandlerStylesheetUrls } 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 type RegisteredRouteShape,\r\n} from \"./not-found-page\";\r\n\r\n/** Re-exported so `web/src/server/index.ts`'s existing barrel export keeps resolving. */\r\nexport { composeRoutePath };\r\n\r\nexport type PageRouteExport = string | { path: string; name?: string };\r\n\r\nexport type PageModuleShape = {\r\n route?: PageRouteExport;\r\n};\r\n\r\nexport type InstalledPageRoute = {\r\n /** The canonical declared route path, before layout-prefix composition. */\r\n declaredPath: string;\r\n path: string;\r\n name: string;\r\n file: string;\r\n layoutFile: string | undefined;\r\n};\r\n\r\n/**\r\n * Ownership key for the framework's fallback 404 route. A NUL-prefixed value\r\n * cannot be a real filesystem path, so it cannot collide with an app page's\r\n * canonical source-file key.\r\n */\r\nexport const FRAMEWORK_DEFAULT_NOT_FOUND_SOURCE_FILE = \"\\0warlock:framework-default-404\";\r\n\r\n/**\r\n * The page's application-source-relative POSIX source path used as the router's\r\n * stable ownership key. `appSrcRoot`'s own basename preserves the existing\r\n * `src/web/...` source-file convention.\r\n */\r\nfunction canonicalSourceFileFor(pageFile: string, appSrcRoot: string): string {\r\n return `${path.basename(appSrcRoot)}/${toPosix(path.relative(appSrcRoot, pageFile))}`;\r\n}\r\n\r\nfunction filesystemPageFileFor(pageFile: string, appSrcRoot: string): string {\r\n return toPosix(path.relative(path.join(appSrcRoot, \"web\"), pageFile));\r\n}\r\n\r\n/**\r\n * Resolve the stable identity used to distinguish a route-export edit from an\r\n * ordinary component-body edit. The declared path is retained before layout\r\n * composition so `/settings` under `/admin` compares with the next declared\r\n * `/settings`, not with the effective `/admin/settings` route.\r\n */\r\nexport function resolvePageRouteIdentity(\r\n routeExport: PageRouteExport | undefined,\r\n pageFile: string,\r\n appSrcRoot: string,\r\n): Pick<InstalledPageRoute, \"declaredPath\" | \"name\"> {\r\n const filesystemPageFile = filesystemPageFileFor(pageFile, appSrcRoot);\r\n\r\n if (routeExport === undefined) {\r\n return {\r\n declaredPath: deriveFilesystemRoutePath({ pageFile: filesystemPageFile }),\r\n name: deriveFilesystemRouteName(filesystemPageFile),\r\n };\r\n }\r\n\r\n const route = canonicalizeRouteExport(routeExport);\r\n\r\n return {\r\n declaredPath: route.path,\r\n name: route.name ?? deriveFilesystemRouteName(filesystemPageFile),\r\n };\r\n}\r\n\r\nexport type LayoutModuleShape = {\r\n /** Universal registration hook; invoked on this real namespace, never a composed wrapper. */\r\n register?: () => unknown;\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`). In dev the\r\n * module is loaded, so this is a fact rather than a guess.\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/** How this module gets a layout module namespace — `vite.ssrLoadModule`, in practice. */\r\ntype LoadLayout = (layoutFile: string) => Promise<LayoutModuleShape>;\r\n\r\n/**\r\n * The page's layout LEVEL, resolved from its whole chain rather than from the\r\n * one layout nearest to it.\r\n *\r\n * The render pipeline has exactly one layout slot per page\r\n * (`execute-page-request.ts`'s `PageRouteEntry[\"triple\"]`), so the chain has to\r\n * be collapsed into one module before it reaches a handler. Two things collapse\r\n * 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 filename — a\r\n * `middleware`-only layout has no default export and is not a wrapper, and\r\n * passing a bare path to `selectPageLayout` would have it read as a rendering\r\n * one, which is the conservative default and the wrong answer here.\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\r\n * own directory knows nothing about is exactly the guard that must still run,\r\n * and a prefix nobody composed is a URL nobody wrote down.\r\n */\r\ntype LayoutLevel = {\r\n /** Every `layout.tsx` from the web root down to the page's directory, outermost first. */\r\n chain: string[];\r\n /**\r\n * The module id the handler's layout slot is registered under, or `undefined`\r\n * when the page has no layout at all: the layout that RENDERS, or — when none\r\n * does — the nearest one, which is the slot dev has always used and so the\r\n * choice that changes nothing but the middleware for a chain with no wrapper\r\n * in it.\r\n */\r\n layoutFile: string | undefined;\r\n /** Every layout's `prefix`, composed outermost first — `discoverPages`' own reduction. */\r\n prefix: string;\r\n /** Declared prefixes keyed by layout directory relative to this page's web root. */\r\n prefixesByDirectory: Readonly<Record<string, string>>;\r\n};\r\n\r\nasync function resolveLayoutLevel(\r\n pageFile: string,\r\n webRoot: string,\r\n loadLayout: LoadLayout,\r\n): Promise<LayoutLevel> {\r\n const chain = layoutChainFor(pageFile, webRoot);\r\n const modules = await Promise.all(chain.map(loadLayout));\r\n const selection = selectPageLayout(\r\n chain.map((layout, index) => ({\r\n layout,\r\n renders: typeof modules[index].default !== \"undefined\",\r\n })),\r\n );\r\n\r\n if (selection.type === \"rejected\") {\r\n throw new NestedLayoutsNotSupportedError(pageFile, selection.layouts);\r\n }\r\n\r\n return {\r\n chain,\r\n layoutFile: selection.type === \"selected\" ? selection.layout : chain.at(-1),\r\n prefix: modules.reduce(\r\n (composed, layoutModule) => composeRoutePath(composed, layoutModule.prefix ?? \"/\"),\r\n \"/\",\r\n ),\r\n prefixesByDirectory: Object.fromEntries(\r\n chain.flatMap((layoutFile, index) => {\r\n const prefix = modules[index].prefix;\r\n\r\n return prefix === undefined\r\n ? []\r\n : [[toPosix(path.relative(webRoot, path.dirname(layoutFile))), prefix]];\r\n }),\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * The layout slot's module for ONE request: the slot host's own namespace, with\r\n * the whole chain's middleware in place of its own — outermost first, which is\r\n * the order stage 3 runs the array in (`execute-page-request.ts:519-524`) and\r\n * the order an outer `optionalAuth` needs in order to have resolved an identity\r\n * before an inner `gate()` checks it.\r\n *\r\n * Loaded per call, not once at install time: a dev layout module must be the\r\n * one Vite is currently serving, edits and all.\r\n */\r\nasync function composeLayoutLevel(\r\n level: LayoutLevel & { layoutFile: string },\r\n loadLayout: LoadLayout,\r\n): Promise<LayoutModuleShape> {\r\n const modules = await Promise.all(level.chain.map(loadLayout));\r\n const hostIndex = level.chain.indexOf(level.layoutFile);\r\n const host = modules[hostIndex];\r\n\r\n return {\r\n ...host,\r\n middleware: modules.flatMap(layoutModule => [...(layoutModule.middleware ?? [])]),\r\n loader: async (context) => {\r\n let hostData: unknown;\r\n\r\n for (let index = 0; index < modules.length; index++) {\r\n const value = await modules[index].loader?.(context);\r\n\r\n if (value instanceof Response || isLoaderShortCircuit(value)) return value;\n if (index === hostIndex) hostData = value;\r\n }\r\n\r\n return hostData;\r\n },\r\n };\r\n}\r\n\r\nexport type InstallPageRoutesOptions = {\r\n router: Router;\r\n vite: ViteDevServer;\r\n /** v5/app/src — pages live under \"<appSrcRoot>/app/*\\/web/**\" and \"<appSrcRoot>/web/**\". */\r\n appSrcRoot: string;\r\n /** v5/app/src/web/root.tsx — the single global app-root file. */\r\n appFile: string;\r\n /**\r\n * The application root Vite's dev server serves from — `dev-server.ts`'s\r\n * `paths.appRoot`, i.e. `<appRoot>/src === appSrcRoot` by default. Every\r\n * handler's stylesheet URLs are expressed relative to THIS, because that is\r\n * the root Vite's dev server actually resolves `/…` URLs against\r\n * (`stylesheet-urls.ts`'s `devStylesheetUrls`) — not `appSrcRoot`, which is\r\n * one directory level in.\r\n *\r\n * OPTIONAL and defaulted to `path.dirname(appSrcRoot)`: the caller that\r\n * wires dev boot (`web-connector.ts`) does not pass this field today, and\r\n * that default is exactly the relationship it constructs `appSrcRoot` from\r\n * (`appSrcRoot = path.join(appRoot, \"src\")`) — correct for every actual\r\n * deployment, and overridable by a caller with a non-default layout.\r\n */\r\n appRoot?: string;\r\n /** Browser module loaded after the server-rendered application and payload. */\r\n hydrationClientModuleUrl?: string;\r\n /**\r\n * UNUSED. Retained on this type only because `web-connector.ts` still builds\r\n * an options object naming it (`devStylesheetUrls(paths.appRoot,\r\n * paths.appFile)`, computed once for the whole application). Each handler\r\n * now computes its OWN stylesheet chain — `[root, ...outer-to-inner matched\r\n * layouts, page]`, via `devHandlerStylesheetUrls` — inside the registration\r\n * loop below, because a single application-wide list cannot express \"this\r\n * page's own CSS\" without also carrying every other page's.\r\n */\r\n stylesheetUrls?: readonly string[];\r\n /** Same helper `dev-server.ts` exports — passed in, not imported, to avoid a dev-server.ts <-> this-file cycle. */\r\n};\r\n\r\n/**\r\n * Registers every discoverable page into `options.router`. Throws\r\n * IMMEDIATELY, naming both files, the moment two pages declare the same\r\n * `route.path` — a registration-time failure, not a runtime 404 one of them\r\n * silently loses.\r\n *\r\n * Pages with no `route` export derive their path and name from their location\r\n * below `src/web`, using the same pure filesystem-routing helper as the build.\r\n */\r\nexport async function installPageRoutes(\r\n options: InstallPageRoutesOptions,\r\n): Promise<InstalledPageRoute[]> {\r\n const {\r\n router,\r\n vite,\r\n appSrcRoot,\r\n appFile,\r\n hydrationClientModuleUrl,\r\n } = options;\r\n // See `InstallPageRoutesOptions.appRoot` for why this default, not\r\n // `appSrcRoot` itself, is the root every handler's CSS is resolved against.\r\n const stylesheetRoot = options.appRoot ?? path.dirname(appSrcRoot);\r\n const discovered = [...discoverPageFiles(appSrcRoot)].sort((left, right) =>\r\n left.pageFile < right.pageFile ? -1 : left.pageFile > right.pageFile ? 1 : 0,\r\n );\r\n\r\n // THE NOT-FOUND PAGE IS TAKEN OUT OF THE ORDINARY LOOP, not filtered inside\r\n // it. It has no `route` export to read, no path to compose and no collision\r\n // to check — every step below is about a page with a URL, and `404.page.tsx`\r\n // does not have one. Registering it here would put it at `/404`, which is not\r\n // a page anybody asked to be able to visit.\r\n const errorPageFiles = discovered.filter((page) => isErrorPageFile(page.pageFile));\r\n const notFoundPageFiles = discovered.filter((page) => isNotFoundPageFile(page.pageFile));\r\n const pageFiles = discovered.filter(\r\n (page) => !isNotFoundPageFile(page.pageFile) && !isErrorPageFile(page.pageFile),\r\n );\r\n\r\n if (errorPageFiles.length > 1) {\r\n throw new Error(`Two error pages were found: ${errorPageFiles.map((page) => page.pageFile).join(\", \")}.`);\r\n }\r\n\r\n const errorPageFile = errorPageFiles[0]?.pageFile;\r\n\r\n // Parse only: the error boundary must remain lazy until a request actually\r\n // fails, while a route export is still rejected at install time.\r\n if (errorPageFile !== undefined) {\r\n const declarations = readRouteExports(errorPageFile);\r\n if (!declarations.ok) throw new NonLiteralRouteExportError(declarations.rejection);\r\n if (declarations.route !== undefined) throw new ErrorPageDeclaresRouteError(errorPageFile);\r\n }\r\n const loadErrorPage = errorPageFile === undefined\r\n ? undefined\r\n : () => vite.ssrLoadModule(errorPageFile) as Promise<ErrorPageModule>;\r\n\r\n if (notFoundPageFiles.length > 1) {\r\n throw new DuplicateNotFoundPageError(notFoundPageFiles.map((page) => page.pageFile));\r\n }\r\n\r\n const installed: InstalledPageRoute[] = [];\r\n const fileByPath = new Map<string, string>();\r\n\r\n for (const { pageFile, webRoot } of pageFiles) {\r\n const pageModule = (await vite.ssrLoadModule(pageFile)) as PageModuleShape;\r\n\r\n const sourceFile = canonicalSourceFileFor(pageFile, appSrcRoot);\r\n\r\n // Route identity is explicit when declared and filesystem-derived otherwise.\r\n const { declaredPath: routePath, name } = resolvePageRouteIdentity(\r\n pageModule.route,\r\n pageFile,\r\n appSrcRoot,\r\n );\r\n\r\n const loadLayout: LoadLayout = layoutFile =>\r\n vite.ssrLoadModule(layoutFile) as Promise<LayoutModuleShape>;\r\n const layoutLevel = await resolveLayoutLevel(pageFile, webRoot, loadLayout);\r\n const { layoutFile, prefix: layoutPrefix } = layoutLevel;\r\n\r\n const effectivePath = pageModule.route === undefined\r\n ? deriveFilesystemRoutePath({\r\n pageFile: filesystemPageFileFor(pageFile, appSrcRoot),\r\n layoutPrefixes: layoutLevel.prefixesByDirectory,\r\n })\r\n : composeRoutePath(layoutPrefix, routePath);\r\n\r\n const existingFile = fileByPath.get(effectivePath);\r\n\r\n if (existingFile) {\r\n throw new Error(\r\n `installPageRoutes: composed route path \"${effectivePath}\" (layout ` +\r\n `prefix \"${layoutPrefix}\" + route.path \"${routePath}\") is declared by two ` +\r\n `pages (web/src/server/install-page-routes.ts) — \"${existingFile}\" and ` +\r\n `\"${pageFile}\". Every page's composed route path must be unique.`,\r\n );\r\n }\r\n\r\n fileByPath.set(effectivePath, pageFile);\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\r\n // (`layoutLevel.chain`), then the page — the same order the render\r\n // pipeline loads that chain in, so cascade order matches load order.\r\n // Computed once here, at registration, not per request: dev re-registers\r\n // on every restart, so a stale chain cannot outlive the source edit that\r\n // changed it.\r\n const stylesheetUrls = devHandlerStylesheetUrls(stylesheetRoot, [\r\n appFile,\r\n ...layoutLevel.chain,\r\n pageFile,\r\n ]);\r\n\r\n await router.withSourceFile(sourceFile, () =>\r\n router.get(\r\n effectivePath,\r\n // The handler itself is `createPageRouteHandler`\r\n // (`web/src/server/create-page-route-handler.ts`) — a named seam a\r\n // future `type: \"page\"` route can bind to, and testable without a Vite\r\n // server. Vite appears here only as the dev answer to \"how do I load a\r\n // module\"; the handler takes that as an input and knows nothing else\r\n // about it.\r\n createPageRouteHandler({\r\n path: effectivePath,\r\n name,\r\n appFile,\r\n pageFile,\r\n layoutFile,\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\r\n // Vite. A one-layout chain has nothing to compose, so it is left to\r\n // resolve as the exact module Vite hands back, untouched.\r\n loadModule:\r\n layoutLevel.chain.length > 1 && layoutFile !== undefined\r\n ? moduleId =>\r\n moduleId === layoutFile\r\n ? composeLayoutLevel({ ...layoutLevel, layoutFile }, loadLayout)\r\n : vite.ssrLoadModule(moduleId)\r\n : moduleId => vite.ssrLoadModule(moduleId),\r\n // Registration tracks real module namespaces, not the composed\r\n // layout wrapper above. Loading the raw chain per request also lets\r\n // Vite hand over a replacement namespace after an HMR update; the\r\n // helper's WeakSet then gives that new identity its one invocation.\r\n loadRegistrationLayouts: () => Promise.all(layoutLevel.chain.map(loadLayout)),\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n stylesheetUrls,\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\r\n installed.push({\r\n declaredPath: routePath,\r\n path: effectivePath,\r\n name,\r\n file: pageFile,\r\n layoutFile,\r\n });\r\n }\r\n\r\n /*\r\n THE CATCH-ALL, registered LAST and only when this application has a page\r\n surface at all. \"Configured with web, no pages yet\" is a legal state, and an\r\n application serving no pages has no page 404 to answer with — its unmatched\r\n URLs stay core's to answer, exactly as they are today.\r\n\r\n Registered even when the application ships no `404.page.tsx`: the framework\r\n default still answers 404, so an application that has not written one yet\r\n gets the right STATUS from the first request, and adding the file later\r\n changes the body and nothing else.\r\n */\r\n if (pageFiles.length > 0 || notFoundPageFiles.length > 0) {\r\n const notFoundPageFile = notFoundPageFiles[0]?.pageFile;\r\n\r\n // Read at INSTALL time, so a `route` export on the not-found page is\r\n // refused at boot with everything else — not on the first request that\r\n // misses, which is the one request nobody is watching.\r\n if (notFoundPageFile !== undefined) {\r\n const notFoundModule = (await vite.ssrLoadModule(notFoundPageFile)) as PageModuleShape;\r\n\r\n if (notFoundModule.route !== undefined) {\r\n throw new NotFoundPageDeclaresRouteError(notFoundPageFile);\r\n }\r\n }\r\n\r\n const registerNotFoundRoute = () =>\r\n router.get(\r\n NOT_FOUND_ROUTE_PATH,\r\n createNotFoundRouteHandler({\r\n renderPage:\r\n notFoundPageFile === undefined\r\n ? undefined\r\n : createPageRouteHandler({\r\n path: NOT_FOUND_ROUTE_PATH,\r\n name: NOT_FOUND_ROUTE_NAME,\r\n appFile,\r\n pageFile: notFoundPageFile,\r\n // NO LAYOUT, deliberately, and it is the same trade as \"no\r\n // loader on the 404 page\": a layout brings its whole chain's\r\n // middleware with it, and a guard that redirects or throws on\r\n // the not-found path turns a missing page into an incident. The\r\n // page renders inside the application root and nothing else.\r\n layoutFile: undefined,\r\n loadModule: (moduleId) => vite.ssrLoadModule(moduleId),\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: devHandlerStylesheetUrls(stylesheetRoot, [\r\n appFile,\r\n notFoundPageFile,\r\n ]),\r\n // The URL that missed IS this route's pattern for this request.\r\n matchPath: (requestPath) => requestPath,\n statusForRenderedOk: 404,\n skipPageLoader: true,\n }),\n }),\r\n // `isPage` for the same reason every other page route carries it: the\r\n // router's duplicate-name error reads the flag to say which claimant is\r\n // the page.\r\n { name: NOT_FOUND_ROUTE_NAME, isPage: true },\r\n );\r\n\r\n if (notFoundPageFile === undefined) {\r\n await router.withSourceFile(FRAMEWORK_DEFAULT_NOT_FOUND_SOURCE_FILE, registerNotFoundRoute);\r\n } else {\r\n await router.withSourceFile(\r\n canonicalSourceFileFor(notFoundPageFile, appSrcRoot),\r\n registerNotFoundRoute,\r\n );\r\n }\r\n }\r\n\r\n /*\r\n Published from the SAME loop that registered the routes, so `href()` and the\r\n router cannot disagree about where a name points. It happens here rather\r\n than in the caller because a caller that forgets leaves every `<Link>` on\r\n the server throwing at render — and dev republishes on every restart, which\r\n is why the table replaces wholesale instead of merging: a deleted page's\r\n name has to stop resolving.\r\n */\r\n publishRouteTable(installed, \"installPageRoutes (dev)\");\r\n\r\n return installed;\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"install-page-routes.mjs","names":[],"sources":["../../../../../../../web/src/server/install-page-routes.ts"],"sourcesContent":["/**\r\n * Registers every page {@link discoverPageFiles} finds under `<appSrcRoot>`\r\n * into Warlock's router (`router.get`, `core/src/router/router.ts:359-361`)\r\n * so `router.scanDevServer(fastify)` —\r\n * the sanctioned dev-server dispatch path (server matching is Warlock's\r\n * router; there is no second server matcher) — picks\r\n * it up. Replaces the two hand-rolled `fastify.get()` calls this file's\r\n * sibling, `dev-server.ts`, used to make directly.\r\n *\r\n * DELIBERATE EXCEPTION to \"web has no core dependency\", same\r\n * reasoning `dev-server.ts`'s own header comment records: this module is not\r\n * exported from either package barrel and is not part of `web/package.json`'s\r\n * dependency graph — dev/CLI bootstrap only.\r\n *\r\n * Scope note: a page's\r\n * `route.path` is now composed with the `prefix` export of EVERY `layout.tsx`\r\n * on its path — outermost first (`composeRoutePath` below) — before\r\n * registration and before the collision check, so `home.page.tsx`\r\n * (`path: \"/\"`, main layout `prefix: \"/\"`) resolves to `/` and\r\n * `products.page.tsx` (`path: \"/\"`, products layout `prefix: \"/products\"`)\r\n * resolves to `/products` — no collision. A page with no `layout.tsx` on its\r\n * path composes against the implicit root prefix `\"/\"` (e.g. `/contact-us`,\r\n * `/hydration-demo`, both unaffected by composition).\r\n *\r\n * WHICH PAGES EXIST is answered by {@link discoverPageFiles}\r\n * (`web/src/build/discover-pages.ts`) — the same walk production's build\r\n * shares — so this file owns no directory-walking of its own and serves the\r\n * global root (`<appSrcRoot>/web/**`) exactly as it serves a module's\r\n * (`<appSrcRoot>/app/<module>/web/**`). WHAT ROUTE A PAGE ANSWERS ON stays\r\n * this file's own job: each page and its nearest layout are still evaluated\r\n * through Vite (`vite.ssrLoadModule`), never read statically, because a dev\r\n * page module must be the one Vite serves, warm cache and all.\r\n */\r\nimport path from \"node:path\";\r\nimport type { ViteDevServer } from \"vite\";\r\nimport {\r\n discoverPageFiles,\r\n ErrorPageDeclaresRouteError,\r\n isErrorPageFile,\r\n layoutChainFor,\r\n toPosix,\r\n} from \"../build/discover-pages\";\r\nimport { NonLiteralRouteExportError, readRouteExports } from \"../build/read-route-exports\";\r\nimport { composeRoutePath } from \"../routing/compose-route-path\";\r\nimport {\r\n deriveFilesystemRouteName,\r\n deriveFilesystemRoutePath,\r\n} from \"../routing/filesystem-route\";\r\nimport { NestedLayoutsNotSupportedError, selectPageLayout } from \"../routing/layout-policy\";\r\nimport { canonicalizeRouteExport } from \"../routing/route-identity\";\r\nimport { publishRouteTable } from \"../routing/route-table\";\r\nimport { Response, type Router } from \"@warlock.js/core\";\r\nimport { createPageRouteHandler } from \"./create-page-route-handler\";\r\nimport type { ErrorPageModule } from \"./error-page\";\r\nimport type { PipelineLoader, PipelineMiddleware } from \"./execute-page-request\";\nimport { isLoaderShortCircuit } from \"./settle-page-response\";\nimport { devHandlerStylesheetUrls } 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 type RegisteredRouteShape,\r\n} from \"./not-found-page\";\r\n\r\n/** Re-exported so `web/src/server/index.ts`'s existing barrel export keeps resolving. */\r\nexport { composeRoutePath };\r\n\r\nexport type PageRouteExport = string | { path: string; name?: string };\r\n\r\nexport type PageModuleShape = {\r\n route?: PageRouteExport;\r\n};\r\n\r\nexport type InstalledPageRoute = {\r\n /** The canonical declared route path, before layout-prefix composition. */\r\n declaredPath: string;\r\n path: string;\r\n name: string;\r\n file: string;\r\n layoutFile: string | undefined;\r\n};\r\n\r\n/**\r\n * Ownership key for the framework's fallback 404 route. A NUL-prefixed value\r\n * cannot be a real filesystem path, so it cannot collide with an app page's\r\n * canonical source-file key.\r\n */\r\nexport const FRAMEWORK_DEFAULT_NOT_FOUND_SOURCE_FILE = \"\\0warlock:framework-default-404\";\r\n\r\n/**\r\n * The page's application-source-relative POSIX source path used as the router's\r\n * stable ownership key. `appSrcRoot`'s own basename preserves the existing\r\n * `src/web/...` source-file convention.\r\n */\r\nfunction canonicalSourceFileFor(pageFile: string, appSrcRoot: string): string {\r\n return `${path.basename(appSrcRoot)}/${toPosix(path.relative(appSrcRoot, pageFile))}`;\r\n}\r\n\r\nfunction filesystemPageFileFor(pageFile: string, appSrcRoot: string): string {\r\n return toPosix(path.relative(path.join(appSrcRoot, \"web\"), pageFile));\r\n}\r\n\r\n/**\r\n * Resolve the stable identity used to distinguish a route-export edit from an\r\n * ordinary component-body edit. The declared path is retained before layout\r\n * composition so `/settings` under `/admin` compares with the next declared\r\n * `/settings`, not with the effective `/admin/settings` route.\r\n */\r\nexport function resolvePageRouteIdentity(\r\n routeExport: PageRouteExport | undefined,\r\n pageFile: string,\r\n appSrcRoot: string,\r\n): Pick<InstalledPageRoute, \"declaredPath\" | \"name\"> {\r\n const filesystemPageFile = filesystemPageFileFor(pageFile, appSrcRoot);\r\n\r\n if (routeExport === undefined) {\r\n return {\r\n declaredPath: deriveFilesystemRoutePath({ pageFile: filesystemPageFile }),\r\n name: deriveFilesystemRouteName(filesystemPageFile),\r\n };\r\n }\r\n\r\n const route = canonicalizeRouteExport(routeExport);\r\n\r\n return {\r\n declaredPath: route.path,\r\n name: route.name ?? deriveFilesystemRouteName(filesystemPageFile),\r\n };\r\n}\r\n\r\nexport type LayoutModuleShape = {\r\n /** Universal registration hook; invoked on this real namespace, never a composed wrapper. */\r\n register?: () => unknown;\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`). In dev the\r\n * module is loaded, so this is a fact rather than a guess.\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/** How this module gets a layout module namespace — `vite.ssrLoadModule`, in practice. */\r\ntype LoadLayout = (layoutFile: string) => Promise<LayoutModuleShape>;\r\n\r\n/**\r\n * The page's layout LEVEL, resolved from its whole chain rather than from the\r\n * one layout nearest to it.\r\n *\r\n * The render pipeline has exactly one layout slot per page\r\n * (`execute-page-request.ts`'s `PageRouteEntry[\"triple\"]`), so the chain has to\r\n * be collapsed into one module before it reaches a handler. Two things collapse\r\n * 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 filename — a\r\n * `middleware`-only layout has no default export and is not a wrapper, and\r\n * passing a bare path to `selectPageLayout` would have it read as a rendering\r\n * one, which is the conservative default and the wrong answer here.\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\r\n * own directory knows nothing about is exactly the guard that must still run,\r\n * and a prefix nobody composed is a URL nobody wrote down.\r\n */\r\ntype LayoutLevel = {\r\n /** Every `layout.tsx` from the web root down to the page's directory, outermost first. */\r\n chain: string[];\r\n /**\r\n * The module id the handler's layout slot is registered under, or `undefined`\r\n * when the page has no layout at all: the layout that RENDERS, or — when none\r\n * does — the nearest one, which is the slot dev has always used and so the\r\n * choice that changes nothing but the middleware for a chain with no wrapper\r\n * in it.\r\n */\r\n layoutFile: string | undefined;\r\n /** Every layout's `prefix`, composed outermost first — `discoverPages`' own reduction. */\r\n prefix: string;\r\n /** Declared prefixes keyed by layout directory relative to this page's web root. */\r\n prefixesByDirectory: Readonly<Record<string, string>>;\r\n};\r\n\r\nasync function resolveLayoutLevel(\r\n pageFile: string,\r\n webRoot: string,\r\n loadLayout: LoadLayout,\r\n): Promise<LayoutLevel> {\r\n const chain = layoutChainFor(pageFile, webRoot);\r\n const modules = await Promise.all(chain.map(loadLayout));\r\n const selection = selectPageLayout(\r\n chain.map((layout, index) => ({\r\n layout,\r\n renders: typeof modules[index].default !== \"undefined\",\r\n })),\r\n );\r\n\r\n if (selection.type === \"rejected\") {\r\n throw new NestedLayoutsNotSupportedError(pageFile, selection.layouts);\r\n }\r\n\r\n return {\r\n chain,\r\n layoutFile: selection.type === \"selected\" ? selection.layout : chain.at(-1),\r\n prefix: modules.reduce(\r\n (composed, layoutModule) => composeRoutePath(composed, layoutModule.prefix ?? \"/\"),\r\n \"/\",\r\n ),\r\n prefixesByDirectory: Object.fromEntries(\r\n chain.flatMap((layoutFile, index) => {\r\n const prefix = modules[index].prefix;\r\n\r\n return prefix === undefined\r\n ? []\r\n : [[toPosix(path.relative(webRoot, path.dirname(layoutFile))), prefix]];\r\n }),\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * The layout slot's module for ONE request: the slot host's own namespace, with\r\n * the whole chain's middleware in place of its own — outermost first, which is\r\n * the order stage 3 runs the array in (`execute-page-request.ts:519-524`) and\r\n * the order an outer `optionalAuth` needs in order to have resolved an identity\r\n * before an inner `gate()` checks it.\r\n *\r\n * Loaded per call, not once at install time: a dev layout module must be the\r\n * one Vite is currently serving, edits and all.\r\n */\r\nasync function composeLayoutLevel(\r\n level: LayoutLevel & { layoutFile: string },\r\n loadLayout: LoadLayout,\r\n): Promise<LayoutModuleShape> {\r\n const modules = await Promise.all(level.chain.map(loadLayout));\r\n const hostIndex = level.chain.indexOf(level.layoutFile);\r\n const host = modules[hostIndex];\r\n\r\n return {\r\n ...host,\r\n middleware: modules.flatMap(layoutModule => [...(layoutModule.middleware ?? [])]),\r\n loader: async (context) => {\r\n let hostData: unknown;\r\n\r\n for (let index = 0; index < modules.length; index++) {\r\n const value = await modules[index].loader?.(context);\r\n\r\n if (value instanceof Response || isLoaderShortCircuit(value)) return value;\n if (index === hostIndex) hostData = value;\r\n }\r\n\r\n return hostData;\r\n },\r\n };\r\n}\r\n\r\nexport type InstallPageRoutesOptions = {\r\n router: Router;\r\n vite: ViteDevServer;\r\n /** v5/app/src — pages live under \"<appSrcRoot>/app/*\\/web/**\" and \"<appSrcRoot>/web/**\". */\r\n appSrcRoot: string;\r\n /** v5/app/src/web/root.tsx — the single global app-root file. */\r\n appFile: string;\r\n /**\r\n * The application root Vite's dev server serves from — `dev-server.ts`'s\r\n * `paths.appRoot`, i.e. `<appRoot>/src === appSrcRoot` by default. Every\r\n * handler's stylesheet URLs are expressed relative to THIS, because that is\r\n * the root Vite's dev server actually resolves `/…` URLs against\r\n * (`stylesheet-urls.ts`'s `devStylesheetUrls`) — not `appSrcRoot`, which is\r\n * one directory level in.\r\n *\r\n * OPTIONAL and defaulted to `path.dirname(appSrcRoot)`: the caller that\r\n * wires dev boot (`web-connector.ts`) does not pass this field today, and\r\n * that default is exactly the relationship it constructs `appSrcRoot` from\r\n * (`appSrcRoot = path.join(appRoot, \"src\")`) — correct for every actual\r\n * deployment, and overridable by a caller with a non-default layout.\r\n */\r\n appRoot?: string;\r\n /** Browser module loaded after the server-rendered application and payload. */\r\n hydrationClientModuleUrl?: string;\r\n /**\r\n * UNUSED. Retained on this type only because `web-connector.ts` still builds\r\n * an options object naming it (`devStylesheetUrls(paths.appRoot,\r\n * paths.appFile)`, computed once for the whole application). Each handler\r\n * now computes its OWN stylesheet chain — `[root, ...outer-to-inner matched\r\n * layouts, page]`, via `devHandlerStylesheetUrls` — inside the registration\r\n * loop below, because a single application-wide list cannot express \"this\r\n * page's own CSS\" without also carrying every other page's.\r\n */\r\n stylesheetUrls?: readonly string[];\r\n /** Same helper `dev-server.ts` exports — passed in, not imported, to avoid a dev-server.ts <-> this-file cycle. */\r\n};\r\n\r\n/**\r\n * Registers every discoverable page into `options.router`. Throws\r\n * IMMEDIATELY, naming both files, the moment two pages declare the same\r\n * `route.path` — a registration-time failure, not a runtime 404 one of them\r\n * silently loses.\r\n *\r\n * Pages with no `route` export derive their path and name from their location\r\n * below `src/web`, using the same pure filesystem-routing helper as the build.\r\n */\r\nexport async function installPageRoutes(\r\n options: InstallPageRoutesOptions,\r\n): Promise<InstalledPageRoute[]> {\r\n const {\r\n router,\r\n vite,\r\n appSrcRoot,\r\n appFile,\r\n hydrationClientModuleUrl,\r\n } = options;\r\n // See `InstallPageRoutesOptions.appRoot` for why this default, not\r\n // `appSrcRoot` itself, is the root every handler's CSS is resolved against.\r\n const stylesheetRoot = options.appRoot ?? path.dirname(appSrcRoot);\r\n const discovered = [...discoverPageFiles(appSrcRoot)].sort((left, right) =>\r\n left.pageFile < right.pageFile ? -1 : left.pageFile > right.pageFile ? 1 : 0,\r\n );\r\n\r\n // THE NOT-FOUND PAGE IS TAKEN OUT OF THE ORDINARY LOOP, not filtered inside\r\n // it. It has no `route` export to read, no path to compose and no collision\r\n // to check — every step below is about a page with a URL, and `404.page.tsx`\r\n // does not have one. Registering it here would put it at `/404`, which is not\r\n // a page anybody asked to be able to visit.\r\n const errorPageFiles = discovered.filter((page) => isErrorPageFile(page.pageFile));\r\n const notFoundPageFiles = discovered.filter((page) => isNotFoundPageFile(page.pageFile));\r\n const pageFiles = discovered.filter(\r\n (page) => !isNotFoundPageFile(page.pageFile) && !isErrorPageFile(page.pageFile),\r\n );\r\n\r\n if (errorPageFiles.length > 1) {\r\n throw new Error(`Two error pages were found: ${errorPageFiles.map((page) => page.pageFile).join(\", \")}.`);\r\n }\r\n\r\n const errorPageFile = errorPageFiles[0]?.pageFile;\r\n\r\n // Parse only: the error boundary must remain lazy until a request actually\r\n // fails, while a route export is still rejected at install time.\r\n if (errorPageFile !== undefined) {\r\n const declarations = readRouteExports(errorPageFile);\r\n if (!declarations.ok) throw new NonLiteralRouteExportError(declarations.rejection);\r\n if (declarations.route !== undefined) throw new ErrorPageDeclaresRouteError(errorPageFile);\r\n }\r\n const loadErrorPage = errorPageFile === undefined\r\n ? undefined\r\n : () => vite.ssrLoadModule(errorPageFile) as Promise<ErrorPageModule>;\r\n\r\n if (notFoundPageFiles.length > 1) {\r\n throw new DuplicateNotFoundPageError(notFoundPageFiles.map((page) => page.pageFile));\r\n }\r\n\r\n const installed: InstalledPageRoute[] = [];\r\n const fileByPath = new Map<string, string>();\r\n\r\n for (const { pageFile, webRoot } of pageFiles) {\r\n const pageModule = (await vite.ssrLoadModule(pageFile)) as PageModuleShape;\r\n\r\n const sourceFile = canonicalSourceFileFor(pageFile, appSrcRoot);\r\n\r\n // Route identity is explicit when declared and filesystem-derived otherwise.\r\n const { declaredPath: routePath, name } = resolvePageRouteIdentity(\r\n pageModule.route,\r\n pageFile,\r\n appSrcRoot,\r\n );\r\n\r\n const loadLayout: LoadLayout = layoutFile =>\r\n vite.ssrLoadModule(layoutFile) as Promise<LayoutModuleShape>;\r\n const layoutLevel = await resolveLayoutLevel(pageFile, webRoot, loadLayout);\r\n const { layoutFile, prefix: layoutPrefix } = layoutLevel;\r\n\r\n const effectivePath = pageModule.route === undefined\r\n ? deriveFilesystemRoutePath({\r\n pageFile: filesystemPageFileFor(pageFile, appSrcRoot),\r\n layoutPrefixes: layoutLevel.prefixesByDirectory,\r\n })\r\n : composeRoutePath(layoutPrefix, routePath);\r\n\r\n const existingFile = fileByPath.get(effectivePath);\r\n\r\n if (existingFile) {\r\n throw new Error(\r\n `installPageRoutes: composed route path \"${effectivePath}\" (layout ` +\r\n `prefix \"${layoutPrefix}\" + route.path \"${routePath}\") is declared by two ` +\r\n `pages (web/src/server/install-page-routes.ts) — \"${existingFile}\" and ` +\r\n `\"${pageFile}\". Every page's composed route path must be unique.`,\r\n );\r\n }\r\n\r\n fileByPath.set(effectivePath, pageFile);\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\r\n // (`layoutLevel.chain`), then the page — the same order the render\r\n // pipeline loads that chain in, so cascade order matches load order.\r\n // Computed once here, at registration, not per request: dev re-registers\r\n // on every restart, so a stale chain cannot outlive the source edit that\r\n // changed it.\r\n const stylesheetUrls = devHandlerStylesheetUrls(stylesheetRoot, [\r\n appFile,\r\n ...layoutLevel.chain,\r\n pageFile,\r\n ]);\r\n\r\n await router.withSourceFile(sourceFile, () =>\r\n router.get(\r\n effectivePath,\r\n // The handler itself is `createPageRouteHandler`\r\n // (`web/src/server/create-page-route-handler.ts`) — a named seam a\r\n // future `type: \"page\"` route can bind to, and testable without a Vite\r\n // server. Vite appears here only as the dev answer to \"how do I load a\r\n // module\"; the handler takes that as an input and knows nothing else\r\n // about it.\r\n createPageRouteHandler({\r\n path: effectivePath,\r\n name,\r\n appFile,\r\n pageFile,\r\n layoutFile,\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\r\n // Vite. A one-layout chain has nothing to compose, so it is left to\r\n // resolve as the exact module Vite hands back, untouched.\r\n loadModule:\r\n layoutLevel.chain.length > 1 && layoutFile !== undefined\r\n ? moduleId =>\r\n moduleId === layoutFile\r\n ? composeLayoutLevel({ ...layoutLevel, layoutFile }, loadLayout)\r\n : vite.ssrLoadModule(moduleId)\r\n : moduleId => vite.ssrLoadModule(moduleId),\r\n // Registration tracks real module namespaces, not the composed\r\n // layout wrapper above. Loading the raw chain per request also lets\r\n // Vite hand over a replacement namespace after an HMR update; the\r\n // helper's WeakSet then gives that new identity its one invocation.\r\n loadRegistrationLayouts: () => Promise.all(layoutLevel.chain.map(loadLayout)),\r\n hydrationClientModuleUrl,\r\n loadErrorPage,\r\n stylesheetUrls,\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\r\n installed.push({\r\n declaredPath: routePath,\r\n path: effectivePath,\r\n name,\r\n file: pageFile,\r\n layoutFile,\r\n });\r\n }\r\n\r\n /*\r\n THE CATCH-ALL, registered LAST and only when this application has a page\r\n surface at all. \"Configured with web, no pages yet\" is a legal state, and an\r\n application serving no pages has no page 404 to answer with — its unmatched\r\n URLs stay core's to answer, exactly as they are today.\r\n\r\n Registered even when the application ships no `404.page.tsx`: the framework\r\n default still answers 404, so an application that has not written one yet\r\n gets the right STATUS from the first request, and adding the file later\r\n changes the body and nothing else.\r\n */\r\n if (pageFiles.length > 0 || notFoundPageFiles.length > 0) {\r\n const notFoundPageFile = notFoundPageFiles[0]?.pageFile;\r\n\r\n // Read at INSTALL time, so a `route` export on the not-found page is\r\n // refused at boot with everything else — not on the first request that\r\n // misses, which is the one request nobody is watching.\r\n if (notFoundPageFile !== undefined) {\r\n const notFoundModule = (await vite.ssrLoadModule(notFoundPageFile)) as PageModuleShape;\r\n\r\n if (notFoundModule.route !== undefined) {\r\n throw new NotFoundPageDeclaresRouteError(notFoundPageFile);\r\n }\r\n }\r\n\r\n const registerNotFoundRoute = () =>\r\n router.get(\r\n NOT_FOUND_ROUTE_PATH,\r\n createNotFoundRouteHandler({\r\n renderPage:\r\n notFoundPageFile === undefined\r\n ? undefined\r\n : createPageRouteHandler({\r\n path: NOT_FOUND_ROUTE_PATH,\r\n name: NOT_FOUND_ROUTE_NAME,\r\n appFile,\r\n pageFile: notFoundPageFile,\r\n // NO LAYOUT, deliberately, and it is the same trade as \"no\r\n // loader on the 404 page\": a layout brings its whole chain's\r\n // middleware with it, and a guard that redirects or throws on\r\n // the not-found path turns a missing page into an incident. The\r\n // page renders inside the application root and nothing else.\r\n layoutFile: undefined,\r\n loadModule: (moduleId) => vite.ssrLoadModule(moduleId),\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: devHandlerStylesheetUrls(stylesheetRoot, [\r\n appFile,\r\n notFoundPageFile,\r\n ]),\r\n // The URL that missed IS this route's pattern for this request.\r\n matchPath: (requestPath) => requestPath,\n statusForRenderedOk: 404,\n skipPageLoader: true,\n }),\n }),\r\n // `isPage` for the same reason every other page route carries it: the\r\n // router's duplicate-name error reads the flag to say which claimant is\r\n // the page.\r\n { name: NOT_FOUND_ROUTE_NAME, isPage: true },\r\n );\r\n\r\n if (notFoundPageFile === undefined) {\r\n await router.withSourceFile(FRAMEWORK_DEFAULT_NOT_FOUND_SOURCE_FILE, registerNotFoundRoute);\r\n } else {\r\n await router.withSourceFile(\r\n canonicalSourceFileFor(notFoundPageFile, appSrcRoot),\r\n registerNotFoundRoute,\r\n );\r\n }\r\n }\r\n\r\n /*\r\n Published from the SAME loop that registered the routes, so `href()` and the\r\n router cannot disagree about where a name points. It happens here rather\r\n than in the caller because a caller that forgets leaves every `<Link>` on\r\n the server throwing at render — and dev republishes on every restart, which\r\n is why the table replaces wholesale instead of merging: a deleted page's\r\n name has to stop resolving.\r\n */\r\n publishRouteTable(installed, \"installPageRoutes (dev)\");\r\n\r\n return installed;\r\n}\r\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region web/src/server/install-production-page-routes.ts
1
+ //#region ../web/src/server/install-production-page-routes.ts
2
2
  /**
3
3
  * Register every page the manifest carries, and connect the pipeline the
4
4
  * handlers will run in.
@@ -1 +1 @@
1
- {"version":3,"file":"install-production-page-routes.mjs","names":[],"sources":["C:/Work/Warlock.js/web/src/server/install-production-page-routes.ts"],"sourcesContent":["/**\r\n * Standing up page serving for a BUILT application — the production half of\r\n * `WebConnector.boot()`, kept out of the connector so the mode branch there\r\n * stays one `if` and this path stays readable on its own.\r\n *\r\n * The development path answers \"which pages exist?\" by walking `app/` and\r\n * \"what is this module?\" by asking Vite to evaluate it. A production process\r\n * can ask neither question: there is no source tree beside the bundle and no\r\n * Vite. Both answers arrived at import time instead, in the generated pages\r\n * barrel's {@link PageManifest}, and everything below is what remains once\r\n * those two questions are already answered — a guard, a wiring step and a\r\n * registration step.\r\n *\r\n * NOTHING HERE REACHES FOR VITE, transitively included. `vite` is an optional\r\n * peer, so a production install does not carry it; an import that only ever\r\n * runs in development still fails at module load in production, which is why\r\n * the one import that has to happen at boot is a dynamic one and why it names\r\n * the pipeline barrel and nothing else.\r\n */\r\nimport type { Router } from \"@warlock.js/core\";\r\nimport type { SharedStoreResolver } from \"../shared\";\r\nimport type { PageContextRunner } from \"./execute-page-request\";\r\nimport type { InstalledManifestPageRoute } from \"./install-page-routes-from-manifest\";\r\nimport type { PageManifest } from \"./page-manifest\";\r\n\r\nexport type InstallProductionPageRoutesOptions = {\r\n router: Router;\r\n /** The table the generated production barrel provided at import time. */\r\n manifest: PageManifest;\r\n /**\r\n * Core's per-request context, handed over rather than imported: core is a\r\n * type-only peer of web, and two copies of core would mean two\r\n * AsyncLocalStorage stores and a page pipeline reading an empty one.\r\n */\r\n pageContext: PageContextRunner;\r\n /** Reads the current request's store out of {@link pageContext}. */\r\n sharedStore: SharedStoreResolver;\r\n /** Same helper `dev-server.ts` exports — passed in, never imported. */\r\n /**\r\n * Where the browser fetches the hydration entry from.\r\n *\r\n * A THUNK, not a value: the URL is read out of the client build's manifest,\r\n * and a build that discovered no pages has no hydration entry for that read\r\n * to find. Deferring it means a page-free production bundle boots without\r\n * demanding a file it had no reason to emit.\r\n */\r\n resolveHydrationClientModuleUrl: () => string;\r\n /**\r\n * Where the client build wrote its output. Forwarded to\r\n * `installPageRoutesFromManifest`, never read here: every registered\r\n * handler needs its OWN stylesheet chain\r\n * (`[root, ...outer-to-inner matched layouts, page]`), and the installer is\r\n * the one place that already walks the manifest's per-page layout chains to\r\n * build one.\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 * name — and no page that could need a stylesheet either.\r\n */\r\n clientDir?: string;\r\n};\r\n\r\n/**\r\n * Register every page the manifest carries, and connect the pipeline the\r\n * handlers will run in.\r\n *\r\n * An empty table registers nothing and is not an error: \"built with web, no\r\n * pages\" is a legal state of a built application, and it is also the one state\r\n * where there is no pipeline to connect and no hydration entry to point at, so\r\n * it returns before doing either.\r\n */\r\nexport async function installProductionPageRoutes(\r\n options: InstallProductionPageRoutesOptions,\r\n): Promise<InstalledManifestPageRoute[]> {\r\n const {\r\n router,\r\n manifest,\r\n pageContext,\r\n sharedStore,\r\n resolveHydrationClientModuleUrl,\r\n clientDir,\r\n } = options;\r\n\r\n if (manifest.pages.length === 0) return [];\r\n\r\n // Loaded at boot rather than statically, and through the BARREL: production\r\n // has one module graph, so the barrel's instance of the pipeline is the same\r\n // instance the manifest's page modules were linked against, and connecting\r\n // the request context on it connects it for every handler registered below.\r\n // Development cannot do this — its modules live in Vite's separate graph, and\r\n // it wires the same two seams on the copy Vite evaluated.\r\n const webServer = await import(\"./index\");\r\n\r\n webServer.connectSharedStore(sharedStore);\r\n webServer.connectPageContext(pageContext);\r\n\r\n return webServer.installPageRoutesFromManifest({\r\n router,\r\n manifest,\r\n hydrationClientModuleUrl: resolveHydrationClientModuleUrl(),\r\n clientDir,\r\n });\r\n}\r\n"],"mappings":";;;;;;;;;;AAuEA,eAAsB,4BACpB,SACuC;CACvC,MAAM,EACJ,QACA,UACA,aACA,aACA,iCACA,cACE;CAEJ,IAAI,SAAS,MAAM,WAAW,GAAG,OAAO,CAAC;CAQzC,MAAM,YAAY,MAAM,OAAO;CAE/B,UAAU,mBAAmB,WAAW;CACxC,UAAU,mBAAmB,WAAW;CAExC,OAAO,UAAU,8BAA8B;EAC7C;EACA;EACA,0BAA0B,gCAAgC;EAC1D;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"install-production-page-routes.mjs","names":[],"sources":["../../../../../../../web/src/server/install-production-page-routes.ts"],"sourcesContent":["/**\r\n * Standing up page serving for a BUILT application — the production half of\r\n * `WebConnector.boot()`, kept out of the connector so the mode branch there\r\n * stays one `if` and this path stays readable on its own.\r\n *\r\n * The development path answers \"which pages exist?\" by walking `app/` and\r\n * \"what is this module?\" by asking Vite to evaluate it. A production process\r\n * can ask neither question: there is no source tree beside the bundle and no\r\n * Vite. Both answers arrived at import time instead, in the generated pages\r\n * barrel's {@link PageManifest}, and everything below is what remains once\r\n * those two questions are already answered — a guard, a wiring step and a\r\n * registration step.\r\n *\r\n * NOTHING HERE REACHES FOR VITE, transitively included. `vite` is an optional\r\n * peer, so a production install does not carry it; an import that only ever\r\n * runs in development still fails at module load in production, which is why\r\n * the one import that has to happen at boot is a dynamic one and why it names\r\n * the pipeline barrel and nothing else.\r\n */\r\nimport type { Router } from \"@warlock.js/core\";\r\nimport type { SharedStoreResolver } from \"../shared\";\r\nimport type { PageContextRunner } from \"./execute-page-request\";\r\nimport type { InstalledManifestPageRoute } from \"./install-page-routes-from-manifest\";\r\nimport type { PageManifest } from \"./page-manifest\";\r\n\r\nexport type InstallProductionPageRoutesOptions = {\r\n router: Router;\r\n /** The table the generated production barrel provided at import time. */\r\n manifest: PageManifest;\r\n /**\r\n * Core's per-request context, handed over rather than imported: core is a\r\n * type-only peer of web, and two copies of core would mean two\r\n * AsyncLocalStorage stores and a page pipeline reading an empty one.\r\n */\r\n pageContext: PageContextRunner;\r\n /** Reads the current request's store out of {@link pageContext}. */\r\n sharedStore: SharedStoreResolver;\r\n /** Same helper `dev-server.ts` exports — passed in, never imported. */\r\n /**\r\n * Where the browser fetches the hydration entry from.\r\n *\r\n * A THUNK, not a value: the URL is read out of the client build's manifest,\r\n * and a build that discovered no pages has no hydration entry for that read\r\n * to find. Deferring it means a page-free production bundle boots without\r\n * demanding a file it had no reason to emit.\r\n */\r\n resolveHydrationClientModuleUrl: () => string;\r\n /**\r\n * Where the client build wrote its output. Forwarded to\r\n * `installPageRoutesFromManifest`, never read here: every registered\r\n * handler needs its OWN stylesheet chain\r\n * (`[root, ...outer-to-inner matched layouts, page]`), and the installer is\r\n * the one place that already walks the manifest's per-page layout chains to\r\n * build one.\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 * name — and no page that could need a stylesheet either.\r\n */\r\n clientDir?: string;\r\n};\r\n\r\n/**\r\n * Register every page the manifest carries, and connect the pipeline the\r\n * handlers will run in.\r\n *\r\n * An empty table registers nothing and is not an error: \"built with web, no\r\n * pages\" is a legal state of a built application, and it is also the one state\r\n * where there is no pipeline to connect and no hydration entry to point at, so\r\n * it returns before doing either.\r\n */\r\nexport async function installProductionPageRoutes(\r\n options: InstallProductionPageRoutesOptions,\r\n): Promise<InstalledManifestPageRoute[]> {\r\n const {\r\n router,\r\n manifest,\r\n pageContext,\r\n sharedStore,\r\n resolveHydrationClientModuleUrl,\r\n clientDir,\r\n } = options;\r\n\r\n if (manifest.pages.length === 0) return [];\r\n\r\n // Loaded at boot rather than statically, and through the BARREL: production\r\n // has one module graph, so the barrel's instance of the pipeline is the same\r\n // instance the manifest's page modules were linked against, and connecting\r\n // the request context on it connects it for every handler registered below.\r\n // Development cannot do this — its modules live in Vite's separate graph, and\r\n // it wires the same two seams on the copy Vite evaluated.\r\n const webServer = await import(\"./index\");\r\n\r\n webServer.connectSharedStore(sharedStore);\r\n webServer.connectPageContext(pageContext);\r\n\r\n return webServer.installPageRoutesFromManifest({\r\n router,\r\n manifest,\r\n hydrationClientModuleUrl: resolveHydrationClientModuleUrl(),\r\n clientDir,\r\n });\r\n}\r\n"],"mappings":";;;;;;;;;;AAuEA,eAAsB,4BACpB,SACuC;CACvC,MAAM,EACJ,QACA,UACA,aACA,aACA,iCACA,cACE;CAEJ,IAAI,SAAS,MAAM,WAAW,GAAG,OAAO,CAAC;CAQzC,MAAM,YAAY,MAAM,OAAO;CAE/B,UAAU,mBAAmB,WAAW;CACxC,UAAU,mBAAmB,WAAW;CAExC,OAAO,UAAU,8BAA8B;EAC7C;EACA;EACA,0BAA0B,gCAAgC;EAC1D;CACF,CAAC;AACH"}
@@ -1,4 +1,4 @@
1
- //#region web/src/server/match-page-route.ts
1
+ //#region ../web/src/server/match-page-route.ts
2
2
  /**
3
3
  * Stage 1 — turn a URL into a route entry plus its params.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"match-page-route.mjs","names":[],"sources":["C:/Work/Warlock.js/web/src/server/match-page-route.ts"],"sourcesContent":["import type { PageRouteEntry } from \"./execute-page-request.types\";\n\n/**\n * Stage 1 — turn a URL into a route entry plus its params.\n *\n * ⚠ **This is a SECOND matcher, and on the HTTP path it is redundant.** Core's\n * router has already matched by the time a page handler runs, and\n * `create-page-route-handler.ts` ignores the match it is handed. The one caller\n * that genuinely needs this is `renderPage(name, options)`, which synthesizes a\n * URL with no HTTP request behind it — and that path is not wired up\n * (`connectPageRoutes()` is never called).\n *\n * Removing it from the HTTP path is carded. Two things must be proven first:\n * that these params agree with core's, since `bundle.route.params` reaches the\n * hydration payload; and that dropping `bundle.route.query` — a public type\n * member — is announced rather than slipped in.\n */\n\nfunction splitSegments(path: string): string[] {\n return path.split(\"/\").filter(segment => segment.length > 0);\n}\n\nexport function matchPath(\n pattern: string,\n pathname: string,\n): Record<string, string> | undefined {\n const patternSegments = splitSegments(pattern);\n const pathSegments = splitSegments(pathname);\n\n if (patternSegments.length !== pathSegments.length) return undefined;\n\n const params: Record<string, string> = {};\n\n for (let index = 0; index < patternSegments.length; index++) {\n const patternSegment = patternSegments[index];\n const pathSegment = pathSegments[index];\n\n if (patternSegment.startsWith(\":\")) {\n params[patternSegment.slice(1)] = decodeURIComponent(pathSegment);\n continue;\n }\n\n if (patternSegment !== pathSegment) return undefined;\n }\n\n return params;\n}\n\nexport function matchRoute(\n pathname: string,\n routes: readonly PageRouteEntry[],\n): { entry: PageRouteEntry; params: Record<string, string> } | undefined {\n for (const entry of routes) {\n const params = matchPath(entry.path, pathname);\n\n if (params) return { entry, params };\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,SAAS,cAAc,MAAwB;CAC7C,OAAO,KAAK,MAAM,GAAG,EAAE,QAAO,YAAW,QAAQ,SAAS,CAAC;AAC7D;AAEA,SAAgB,UACd,SACA,UACoC;CACpC,MAAM,kBAAkB,cAAc,OAAO;CAC7C,MAAM,eAAe,cAAc,QAAQ;CAE3C,IAAI,gBAAgB,WAAW,aAAa,QAAQ,OAAO;CAE3D,MAAM,SAAiC,CAAC;CAExC,KAAK,IAAI,QAAQ,GAAG,QAAQ,gBAAgB,QAAQ,SAAS;EAC3D,MAAM,iBAAiB,gBAAgB;EACvC,MAAM,cAAc,aAAa;EAEjC,IAAI,eAAe,WAAW,GAAG,GAAG;GAClC,OAAO,eAAe,MAAM,CAAC,KAAK,mBAAmB,WAAW;GAChE;EACF;EAEA,IAAI,mBAAmB,aAAa,OAAO;CAC7C;CAEA,OAAO;AACT;AAEA,SAAgB,WACd,UACA,QACuE;CACvE,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,UAAU,MAAM,MAAM,QAAQ;EAE7C,IAAI,QAAQ,OAAO;GAAE;GAAO;EAAO;CACrC;AAGF"}
1
+ {"version":3,"file":"match-page-route.mjs","names":[],"sources":["../../../../../../../web/src/server/match-page-route.ts"],"sourcesContent":["import type { PageRouteEntry } from \"./execute-page-request.types\";\n\n/**\n * Stage 1 — turn a URL into a route entry plus its params.\n *\n * ⚠ **This is a SECOND matcher, and on the HTTP path it is redundant.** Core's\n * router has already matched by the time a page handler runs, and\n * `create-page-route-handler.ts` ignores the match it is handed. The one caller\n * that genuinely needs this is `renderPage(name, options)`, which synthesizes a\n * URL with no HTTP request behind it — and that path is not wired up\n * (`connectPageRoutes()` is never called).\n *\n * Removing it from the HTTP path is carded. Two things must be proven first:\n * that these params agree with core's, since `bundle.route.params` reaches the\n * hydration payload; and that dropping `bundle.route.query` — a public type\n * member — is announced rather than slipped in.\n */\n\nfunction splitSegments(path: string): string[] {\n return path.split(\"/\").filter(segment => segment.length > 0);\n}\n\nexport function matchPath(\n pattern: string,\n pathname: string,\n): Record<string, string> | undefined {\n const patternSegments = splitSegments(pattern);\n const pathSegments = splitSegments(pathname);\n\n if (patternSegments.length !== pathSegments.length) return undefined;\n\n const params: Record<string, string> = {};\n\n for (let index = 0; index < patternSegments.length; index++) {\n const patternSegment = patternSegments[index];\n const pathSegment = pathSegments[index];\n\n if (patternSegment.startsWith(\":\")) {\n params[patternSegment.slice(1)] = decodeURIComponent(pathSegment);\n continue;\n }\n\n if (patternSegment !== pathSegment) return undefined;\n }\n\n return params;\n}\n\nexport function matchRoute(\n pathname: string,\n routes: readonly PageRouteEntry[],\n): { entry: PageRouteEntry; params: Record<string, string> } | undefined {\n for (const entry of routes) {\n const params = matchPath(entry.path, pathname);\n\n if (params) return { entry, params };\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,SAAS,cAAc,MAAwB;CAC7C,OAAO,KAAK,MAAM,GAAG,EAAE,QAAO,YAAW,QAAQ,SAAS,CAAC;AAC7D;AAEA,SAAgB,UACd,SACA,UACoC;CACpC,MAAM,kBAAkB,cAAc,OAAO;CAC7C,MAAM,eAAe,cAAc,QAAQ;CAE3C,IAAI,gBAAgB,WAAW,aAAa,QAAQ,OAAO;CAE3D,MAAM,SAAiC,CAAC;CAExC,KAAK,IAAI,QAAQ,GAAG,QAAQ,gBAAgB,QAAQ,SAAS;EAC3D,MAAM,iBAAiB,gBAAgB;EACvC,MAAM,cAAc,aAAa;EAEjC,IAAI,eAAe,WAAW,GAAG,GAAG;GAClC,OAAO,eAAe,MAAM,CAAC,KAAK,mBAAmB,WAAW;GAChE;EACF;EAEA,IAAI,mBAAmB,aAAa,OAAO;CAC7C;CAEA,OAAO;AACT;AAEA,SAAgB,WACd,UACA,QACuE;CACvE,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,UAAU,MAAM,MAAM,QAAQ;EAE7C,IAAI,QAAQ,OAAO;GAAE;GAAO;EAAO;CACrC;AAGF"}
@@ -1,6 +1,6 @@
1
1
  import { PageRouteHandler } from "./create-page-route-handler.mjs";
2
2
 
3
- //#region web/src/server/not-found-page.d.ts
3
+ //#region ../web/src/server/not-found-page.d.ts
4
4
  /**
5
5
  * The one filename that makes a page THE not-found page.
6
6
  *
@@ -1,4 +1,4 @@
1
- //#region web/src/server/not-found-page.ts
1
+ //#region ../web/src/server/not-found-page.ts
2
2
  /**
3
3
  * The one filename that makes a page THE not-found page.
4
4
  *