@warlock.js/web 5.0.2 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/README.md +126 -12
  3. package/esm/build/contribution.d.mts +11 -4
  4. package/esm/build/contribution.mjs +46 -22
  5. package/esm/build/contribution.mjs.map +1 -1
  6. package/esm/build/discover-pages.mjs +318 -44
  7. package/esm/build/discover-pages.mjs.map +1 -1
  8. package/esm/build/generate-client-registry.mjs +47 -13
  9. package/esm/build/generate-client-registry.mjs.map +1 -1
  10. package/esm/build/generate-pages-barrel.mjs +55 -13
  11. package/esm/build/generate-pages-barrel.mjs.map +1 -1
  12. package/esm/build/page-default-export.mjs +48 -0
  13. package/esm/build/page-default-export.mjs.map +1 -0
  14. package/esm/build/page-routes-manifest.mjs +21 -0
  15. package/esm/build/page-routes-manifest.mjs.map +1 -0
  16. package/esm/build/public-files.mjs +45 -0
  17. package/esm/build/public-files.mjs.map +1 -0
  18. package/esm/build/read-route-exports.mjs +1 -1
  19. package/esm/build/read-route-exports.mjs.map +1 -1
  20. package/esm/client/build-hydrated-tree.mjs +40 -9
  21. package/esm/client/build-hydrated-tree.mjs.map +1 -1
  22. package/esm/client/hydrate-page.mjs +1 -1
  23. package/esm/client/hydrate-page.mjs.map +1 -1
  24. package/esm/client/navigation/current-route.d.mts +1 -1
  25. package/esm/client/navigation/current-route.mjs +1 -1
  26. package/esm/client/navigation/current-route.mjs.map +1 -1
  27. package/esm/client/navigation/fetch-page-data.mjs +1 -1
  28. package/esm/client/navigation/fetch-page-data.mjs.map +1 -1
  29. package/esm/client/navigation/navigation-root.mjs +44 -7
  30. package/esm/client/navigation/navigation-root.mjs.map +1 -1
  31. package/esm/client/navigation/prefetch.mjs +1 -1
  32. package/esm/client/navigation/prefetch.mjs.map +1 -1
  33. package/esm/client/navigation/refresh.d.mts +1 -1
  34. package/esm/client/navigation/refresh.mjs +1 -1
  35. package/esm/client/navigation/refresh.mjs.map +1 -1
  36. package/esm/client/navigation/scroll-to-fragment.mjs +26 -0
  37. package/esm/client/navigation/scroll-to-fragment.mjs.map +1 -0
  38. package/esm/client/navigation/verbs.d.mts +1 -1
  39. package/esm/client/navigation/verbs.mjs +1 -1
  40. package/esm/client/navigation/verbs.mjs.map +1 -1
  41. package/esm/client/runtime/index.d.mts +2 -1
  42. package/esm/client/runtime/index.mjs +2 -1
  43. package/esm/client/runtime/manifest.d.mts +1 -1
  44. package/esm/client/runtime/manifest.mjs +3 -2
  45. package/esm/client/runtime/manifest.mjs.map +1 -1
  46. package/esm/client/runtime/matcher.d.mts +13 -1
  47. package/esm/client/runtime/matcher.mjs +13 -1
  48. package/esm/client/runtime/matcher.mjs.map +1 -1
  49. package/esm/client/runtime/types.d.mts +15 -2
  50. package/esm/components/default-app.mjs +1 -1
  51. package/esm/components/default-app.mjs.map +1 -1
  52. package/esm/components/document-context.d.mts +40 -2
  53. package/esm/components/document-context.mjs +1 -1
  54. package/esm/components/document-context.mjs.map +1 -1
  55. package/esm/components/head.d.mts +1 -1
  56. package/esm/components/head.mjs +1 -1
  57. package/esm/components/head.mjs.map +1 -1
  58. package/esm/components/link.d.mts +1 -1
  59. package/esm/components/link.mjs +1 -1
  60. package/esm/components/link.mjs.map +1 -1
  61. package/esm/components/scripts.d.mts +1 -1
  62. package/esm/components/scripts.mjs +4 -2
  63. package/esm/components/scripts.mjs.map +1 -1
  64. package/esm/context.d.mts +6 -102
  65. package/esm/hydration/index.mjs +1 -1
  66. package/esm/hydration/index.mjs.map +1 -1
  67. package/esm/hydration-payload.d.mts +1 -1
  68. package/esm/hydration-payload.mjs +37 -8
  69. package/esm/hydration-payload.mjs.map +1 -1
  70. package/esm/index.d.mts +4 -3
  71. package/esm/loaders.d.mts +7 -36
  72. package/esm/metadata.d.mts +15 -1
  73. package/esm/metadata.mjs +45 -0
  74. package/esm/metadata.mjs.map +1 -0
  75. package/esm/props.d.mts +16 -8
  76. package/esm/route.d.mts +2 -17
  77. package/esm/routing/compose-route-path.d.mts +1 -1
  78. package/esm/routing/compose-route-path.mjs +1 -1
  79. package/esm/routing/compose-route-path.mjs.map +1 -1
  80. package/esm/routing/data-request.mjs +1 -1
  81. package/esm/routing/data-request.mjs.map +1 -1
  82. package/esm/routing/filesystem-route.mjs +45 -0
  83. package/esm/routing/filesystem-route.mjs.map +1 -0
  84. package/esm/routing/layout-policy.mjs +1 -1
  85. package/esm/routing/layout-policy.mjs.map +1 -1
  86. package/esm/routing/navigator.mjs +1 -1
  87. package/esm/routing/navigator.mjs.map +1 -1
  88. package/esm/routing/query-string.d.mts +1 -1
  89. package/esm/routing/query-string.mjs +1 -1
  90. package/esm/routing/query-string.mjs.map +1 -1
  91. package/esm/routing/route-identity.mjs +1 -1
  92. package/esm/routing/route-identity.mjs.map +1 -1
  93. package/esm/routing/route-table.d.mts +1 -1
  94. package/esm/routing/route-table.mjs +1 -1
  95. package/esm/routing/route-table.mjs.map +1 -1
  96. package/esm/routing/router-events.d.mts +1 -1
  97. package/esm/routing/router-events.mjs +1 -1
  98. package/esm/routing/router-events.mjs.map +1 -1
  99. package/esm/routing/url-fragment.mjs +120 -0
  100. package/esm/routing/url-fragment.mjs.map +1 -0
  101. package/esm/runtime/register-modules.d.mts +19 -0
  102. package/esm/runtime/register-modules.mjs +22 -0
  103. package/esm/runtime/register-modules.mjs.map +1 -0
  104. package/esm/server/build-hydration-payload.mjs +2 -1
  105. package/esm/server/build-hydration-payload.mjs.map +1 -1
  106. package/esm/server/client-asset-url-prefix.d.mts +1 -1
  107. package/esm/server/client-asset-url-prefix.mjs +1 -1
  108. package/esm/server/client-asset-url-prefix.mjs.map +1 -1
  109. package/esm/server/create-page-module-loader.d.mts +1 -1
  110. package/esm/server/create-page-module-loader.mjs +2 -1
  111. package/esm/server/create-page-module-loader.mjs.map +1 -1
  112. package/esm/server/create-page-route-handler.d.mts +58 -6
  113. package/esm/server/create-page-route-handler.mjs +127 -40
  114. package/esm/server/create-page-route-handler.mjs.map +1 -1
  115. package/esm/server/dev-server.mjs +2 -13
  116. package/esm/server/dev-server.mjs.map +1 -1
  117. package/esm/server/error-page.d.mts +14 -0
  118. package/esm/server/error-page.mjs +39 -0
  119. package/esm/server/error-page.mjs.map +1 -0
  120. package/esm/server/execute-page-request.d.mts +7 -4
  121. package/esm/server/execute-page-request.mjs +79 -80
  122. package/esm/server/execute-page-request.mjs.map +1 -1
  123. package/esm/server/execute-page-request.types.d.mts +33 -92
  124. package/esm/server/hydration-client-url.d.mts +1 -1
  125. package/esm/server/hydration-client-url.mjs +2 -2
  126. package/esm/server/hydration-client-url.mjs.map +1 -1
  127. package/esm/server/index.d.mts +6 -5
  128. package/esm/server/index.mjs +4 -4
  129. package/esm/server/install-page-routes-from-manifest.d.mts +17 -7
  130. package/esm/server/install-page-routes-from-manifest.mjs +91 -8
  131. package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
  132. package/esm/server/install-page-routes.d.mts +32 -13
  133. package/esm/server/install-page-routes.mjs +112 -32
  134. package/esm/server/install-page-routes.mjs.map +1 -1
  135. package/esm/server/install-production-page-routes.mjs +3 -31
  136. package/esm/server/install-production-page-routes.mjs.map +1 -1
  137. package/esm/server/match-page-route.mjs +2 -2
  138. package/esm/server/match-page-route.mjs.map +1 -1
  139. package/esm/server/not-found-page.d.mts +126 -0
  140. package/esm/server/not-found-page.mjs +157 -0
  141. package/esm/server/not-found-page.mjs.map +1 -0
  142. package/esm/server/page-context.d.mts +3 -9
  143. package/esm/server/page-context.mjs +3 -16
  144. package/esm/server/page-context.mjs.map +1 -1
  145. package/esm/server/page-file-change.mjs +77 -0
  146. package/esm/server/page-file-change.mjs.map +1 -0
  147. package/esm/server/page-manifest.d.mts +25 -10
  148. package/esm/server/page-manifest.mjs +1 -1
  149. package/esm/server/page-manifest.mjs.map +1 -1
  150. package/esm/server/page-render-bundle.mjs +38 -0
  151. package/esm/server/page-render-bundle.mjs.map +1 -0
  152. package/esm/server/page-route-reload.mjs +54 -0
  153. package/esm/server/page-route-reload.mjs.map +1 -0
  154. package/esm/server/register-production-public-files.mjs +40 -0
  155. package/esm/server/register-production-public-files.mjs.map +1 -0
  156. package/esm/server/render-page.d.mts +11 -14
  157. package/esm/server/render-page.mjs +154 -28
  158. package/esm/server/render-page.mjs.map +1 -1
  159. package/esm/server/resolve-page-metadata.mjs +2 -2
  160. package/esm/server/resolve-page-metadata.mjs.map +1 -1
  161. package/esm/server/resolve-validation-data.mjs +1 -1
  162. package/esm/server/resolve-validation-data.mjs.map +1 -1
  163. package/esm/server/settle-page-response.d.mts +58 -1
  164. package/esm/server/settle-page-response.mjs +111 -46
  165. package/esm/server/settle-page-response.mjs.map +1 -1
  166. package/esm/server/stylesheet-urls.d.mts +41 -30
  167. package/esm/server/stylesheet-urls.mjs +137 -52
  168. package/esm/server/stylesheet-urls.mjs.map +1 -1
  169. package/esm/server/unregistered-pages.mjs +42 -0
  170. package/esm/server/unregistered-pages.mjs.map +1 -0
  171. package/esm/server/web-connector-factory.d.mts +2 -3
  172. package/esm/server/web-connector-factory.mjs +6 -4
  173. package/esm/server/web-connector-factory.mjs.map +1 -1
  174. package/esm/server/web-connector.d.mts +1 -1
  175. package/esm/server/web-connector.mjs +233 -18
  176. package/esm/server/web-connector.mjs.map +1 -1
  177. package/esm/shared.d.mts +18 -3
  178. package/esm/shared.mjs +3 -7
  179. package/esm/shared.mjs.map +1 -1
  180. package/esm/validation.d.mts +1 -1
  181. package/esm/vite/app-convention-aliases.mjs +1 -1
  182. package/esm/vite/app-convention-aliases.mjs.map +1 -1
  183. package/esm/vite/build-client.d.mts +1 -1
  184. package/esm/vite/build-client.mjs +2 -2
  185. package/esm/vite/build-client.mjs.map +1 -1
  186. package/esm/vite/gate-a-resolve.d.mts +1 -1
  187. package/esm/vite/gate-a-resolve.mjs +67 -13
  188. package/esm/vite/gate-a-resolve.mjs.map +1 -1
  189. package/esm/vite/gate-b-secrets.d.mts +1 -1
  190. package/esm/vite/gate-b-secrets.mjs +35 -17
  191. package/esm/vite/gate-b-secrets.mjs.map +1 -1
  192. package/esm/vite/gate-c-verify.d.mts +1 -1
  193. package/esm/vite/gate-c-verify.mjs +6 -6
  194. package/esm/vite/gate-c-verify.mjs.map +1 -1
  195. package/esm/vite/hydration-entries.d.mts +1 -1
  196. package/esm/vite/hydration-entries.mjs +10 -6
  197. package/esm/vite/hydration-entries.mjs.map +1 -1
  198. package/esm/vite/index.d.mts +9 -6
  199. package/esm/vite/index.mjs +124 -11
  200. package/esm/vite/index.mjs.map +1 -1
  201. package/esm/vite/page-registry-plugin.d.mts +22 -3
  202. package/esm/vite/page-registry-plugin.mjs +247 -2
  203. package/esm/vite/page-registry-plugin.mjs.map +1 -1
  204. package/esm/vite/projection.d.mts +2 -2
  205. package/esm/vite/projection.mjs +38 -15
  206. package/esm/vite/projection.mjs.map +1 -1
  207. package/llms-full.txt +241 -49
  208. package/llms.txt +6 -2
  209. package/package.json +3 -3
  210. package/skills/add-web-to-an-app/SKILL.md +3 -3
  211. package/skills/create-a-page/SKILL.md +130 -7
  212. package/skills/load-page-data/SKILL.md +92 -12
  213. package/skills/navigate-on-the-client/SKILL.md +3 -1
  214. package/skills/serve-styles/SKILL.md +13 -12
  215. package/skills/use-layouts/SKILL.md +20 -8
  216. package/skills/write-the-root/SKILL.md +5 -1
  217. package/esm/server/buffered-response.d.mts +0 -58
  218. package/esm/server/buffered-response.mjs +0 -115
  219. package/esm/server/buffered-response.mjs.map +0 -1
@@ -1,13 +1,13 @@
1
- import { enterSharedScope, sealShared, shared } from "../shared.mjs";
2
- import { createBufferedResponse, isLoaderShortCircuit } from "./buffered-response.mjs";
3
- import { connectPageContext, connectPageSharedScope, enterAdditionalSharedScope, requireRunner } from "./page-context.mjs";
4
- import { matchRoute } from "./match-page-route.mjs";
1
+ import { enterSharedScope, sealShared } from "../shared.mjs";
5
2
  import { resolvePageMetadata } from "./resolve-page-metadata.mjs";
3
+ import { connectPageContext, enterAdditionalSharedScope, requireRunner } from "./page-context.mjs";
4
+ import { matchRoute } from "./match-page-route.mjs";
6
5
  import { resolveValidationData } from "./resolve-validation-data.mjs";
7
- import { LEVEL_ORDER, buildErrorRecord, commitBuffers, designateBoundary } from "./settle-page-response.mjs";
6
+ import { LEVEL_ORDER, buildErrorRecord, commitBuffers, createBufferedResponse, createLevelBuffer, designateBoundary, isLoaderShortCircuit } from "./settle-page-response.mjs";
7
+ import { Response } from "@warlock.js/core";
8
8
  import { v } from "@warlock.js/seal";
9
9
 
10
- //#region ../web/src/server/execute-page-request.ts
10
+ //#region web/src/server/execute-page-request.ts
11
11
  async function executePageRequest(options) {
12
12
  const runner = requireRunner();
13
13
  const [pathname, queryString] = options.url.split("?");
@@ -28,9 +28,9 @@ async function executePageRequest(options) {
28
28
  request,
29
29
  response
30
30
  };
31
- return await runner.run(store, async () => {
31
+ return runner.run(store, async () => {
32
32
  enterSharedScope(store);
33
- enterAdditionalSharedScope(store);
33
+ /* @__PURE__ */ enterAdditionalSharedScope(store);
34
34
  const finish = async (bundle) => options.finish ? await options.finish(bundle) : bundle;
35
35
  const bundle = { route: {
36
36
  name: matched.entry.name,
@@ -47,7 +47,7 @@ async function executePageRequest(options) {
47
47
  });
48
48
  } catch (thrown) {
49
49
  bundle.error = buildErrorRecord(thrown, designateBoundary(level, triple), pathname);
50
- bundle.commit = commitBuffers(response, [], 500);
50
+ response.setStatusCode(500);
51
51
  return finish(bundle);
52
52
  }
53
53
  if (output !== void 0) {
@@ -64,7 +64,7 @@ async function executePageRequest(options) {
64
64
  if (validation?.schema) {
65
65
  const data = resolveValidationData(validation.validating, request);
66
66
  const result = await v.validate(validation.schema, data);
67
- if (result.isValid && result.data) request.setValidatedData?.(result.data);
67
+ if (result.isValid && result.data) request.setValidatedData(result.data);
68
68
  if (!result.isValid) {
69
69
  bundle.shortCircuit = {
70
70
  stage: "validation",
@@ -76,86 +76,85 @@ async function executePageRequest(options) {
76
76
  }
77
77
  const sealedShared = await sealShared(store);
78
78
  bundle.shared = sealedShared;
79
- const buffers = LEVEL_ORDER.map((level) => {
80
- const { response: bufferedResponse, buffer } = createBufferedResponse();
81
- return {
82
- level,
83
- buffer,
84
- bufferedResponse
85
- };
86
- });
87
- const settled = await Promise.allSettled(buffers.map(({ level, bufferedResponse }) => {
88
- const loader = triple[level].loader;
89
- return (async () => loader ? await loader({
90
- request,
91
- response: bufferedResponse,
92
- shared
93
- }) : void 0)();
94
- }));
95
- let firstError;
96
- let firstSignal;
97
- function toSignal(value) {
98
- return isLoaderShortCircuit(value) ? value : void 0;
99
- }
100
- for (let index = 0; index < LEVEL_ORDER.length; index++) {
101
- const result = settled[index];
102
- if (result.status === "rejected") {
103
- firstError = {
104
- level: LEVEL_ORDER[index],
105
- index,
106
- error: result.reason
107
- };
108
- break;
109
- }
110
- const signal = toSignal(result.value);
111
- if (signal) {
112
- firstSignal = {
113
- level: LEVEL_ORDER[index],
114
- index,
115
- signal
116
- };
117
- break;
118
- }
119
- }
120
79
  const dataKeys = {
121
80
  app: "appData",
122
81
  layout: "layoutData",
123
82
  page: "pageData"
124
83
  };
125
- const dataCutoff = firstSignal ? firstSignal.index : LEVEL_ORDER.length;
84
+ const buffers = {
85
+ app: createLevelBuffer(),
86
+ layout: createLevelBuffer(),
87
+ page: createLevelBuffer()
88
+ };
89
+ let signalIndex = -1;
90
+ let signalKind;
91
+ let signalThrown;
92
+ let signalCircuit;
126
93
  for (let index = 0; index < LEVEL_ORDER.length; index++) {
127
- const result = settled[index];
128
- if (result.status !== "fulfilled") continue;
129
- if (firstError && index === firstError.index) continue;
130
- if (index > dataCutoff) continue;
131
- if (toSignal(result.value)) continue;
132
- bundle[dataKeys[LEVEL_ORDER[index]]] = result.value;
94
+ const level = LEVEL_ORDER[index];
95
+ const loader = triple[level].loader;
96
+ if (!loader) continue;
97
+ let value;
98
+ try {
99
+ value = await loader({
100
+ request,
101
+ response: createBufferedResponse(buffers[level]),
102
+ shared: sealedShared
103
+ });
104
+ } catch (thrown) {
105
+ signalIndex = index;
106
+ signalKind = "throw";
107
+ signalThrown = thrown;
108
+ break;
109
+ }
110
+ if (value instanceof Response) return value;
111
+ if (isLoaderShortCircuit(value)) {
112
+ signalIndex = index;
113
+ signalKind = "shortCircuit";
114
+ signalCircuit = value;
115
+ break;
116
+ }
117
+ bundle[dataKeys[level]] = value;
133
118
  }
134
- if (firstError) {
135
- const boundary = designateBoundary(firstError.level, triple);
136
- bundle.error = buildErrorRecord(firstError.error, boundary, pathname);
137
- bundle.commit = commitBuffers(response, buffers.slice(0, firstError.index), boundary.boundaryLevel === "app" ? 500 : void 0);
138
- } else if (firstSignal) {
119
+ let committedLevels;
120
+ /** Set only when a THROW escalated to the app boundary forces 500. */
121
+ let forcedStatusCode;
122
+ if (signalIndex === -1) committedLevels = [...LEVEL_ORDER];
123
+ else if (signalKind === "throw") {
124
+ committedLevels = LEVEL_ORDER.slice(0, signalIndex);
125
+ const boundary = designateBoundary(LEVEL_ORDER[signalIndex], triple);
126
+ bundle.error = buildErrorRecord(signalThrown, boundary, pathname);
127
+ if (boundary.boundaryLevel === "app") {
128
+ response.setStatusCode(500);
129
+ forcedStatusCode = 500;
130
+ }
131
+ } else {
132
+ committedLevels = LEVEL_ORDER.slice(0, signalIndex + 1);
133
+ const circuit = signalCircuit;
139
134
  bundle.shortCircuit = {
140
135
  stage: "loaders",
141
- level: firstSignal.level,
142
- kind: firstSignal.signal.kind,
143
- statusCode: firstSignal.signal.statusCode,
144
- url: firstSignal.signal.url,
145
- body: firstSignal.signal.body
136
+ level: LEVEL_ORDER[signalIndex],
137
+ kind: circuit.kind,
138
+ statusCode: circuit.statusCode,
139
+ url: circuit.url,
140
+ body: circuit.body
146
141
  };
147
- bundle.commit = commitBuffers(response, buffers.slice(0, firstSignal.index + 1));
148
- } else bundle.commit = commitBuffers(response, buffers);
149
- if (!bundle.shortCircuit) {
150
- const resolved = resolvePageMetadata({
151
- metadata: triple.page.metadata,
152
- data: bundle.pageData,
153
- error: bundle.error?.error,
154
- failed: Boolean(bundle.error),
155
- shared: sealedShared
156
- });
157
- bundle.metadata = resolved.metadata;
158
- if (resolved.thrown !== void 0) bundle.error = buildErrorRecord(resolved.thrown, designateBoundary("page", triple), bundle.route.path);
142
+ }
143
+ bundle.commit = commitBuffers(response, buffers, committedLevels);
144
+ if (forcedStatusCode !== void 0) bundle.commit.statusCode = forcedStatusCode;
145
+ if (bundle.shortCircuit) return finish(bundle);
146
+ const resolved = resolvePageMetadata({
147
+ metadata: triple.page.metadata,
148
+ data: bundle.pageData,
149
+ error: bundle.error?.error,
150
+ failed: Boolean(bundle.error),
151
+ shared: sealedShared
152
+ });
153
+ bundle.metadata = resolved.metadata;
154
+ if (resolved.thrown !== void 0) {
155
+ const boundary = designateBoundary("page", triple);
156
+ bundle.error = buildErrorRecord(resolved.thrown, boundary, bundle.route.path);
157
+ if (boundary.boundaryLevel === "app") response.setStatusCode(500);
159
158
  }
160
159
  return finish(bundle);
161
160
  });
@@ -1 +1 @@
1
- {"version":3,"file":"execute-page-request.mjs","names":[],"sources":["../../../../../../../web/src/server/execute-page-request.ts"],"sourcesContent":["import { v } from \"@warlock.js/seal\";\nimport { createBufferedResponse, isLoaderShortCircuit } from \"./buffered-response\";\nimport type { LoaderShortCircuitSignal } from \"./buffered-response\";\nimport { enterSharedScope, sealShared, shared } 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 designateBoundary,\n LEVEL_ORDER,\n} from \"./settle-page-response\";\nimport type {\n ExecutePageRequestOptions,\n PageDataBundle,\n PageLevelName,\n PageRouteMatch,\n PipelineResponse,\n PipelineStore,\n} from \"./execute-page-request.types\";\n\n/**\n * Pipeline stages 1–8 of a page request; stages 9–10 (render/emit) are the\n * render slice's, and this ends at the DATA BUNDLE.\n *\n * The founding sentence governs the shape — a page route is an ordinary Warlock\n * route whose handler renders React instead of returning JSON — so every stage\n * mirrors what core already does for an API request: core's own ALS store and\n * `run`, core's middleware short-circuit rule (`output !== undefined`), and\n * `validateAll`'s validation with its 422, recorded as a designation because\n * sending is the emit's job.\n *\n * The pieces live beside this file: `page-context.ts` (stage 2 wiring),\n * `match-page-route.ts` (stage 1), `resolve-validation-data.ts` (stage 4),\n * `settle-page-response.ts` (stage 7), `resolve-page-metadata.ts` (stage 8).\n */\n\nexport * from \"./execute-page-request.types\";\nexport { connectPageContext, connectPageSharedScope } from \"./page-context\";\nexport { buildErrorRecord, designateBoundary } from \"./settle-page-response\";\n\nexport async function executePageRequest<TResult = PageDataBundle>(\n options: ExecutePageRequestOptions<TResult>,\n): Promise<TResult | undefined> {\n const runner = requireRunner();\n\n // ── stage 1 · MATCH ─────────────────────────────────────────────────────\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\n const { request, response } = options.createHttp(match);\n\n // ── stage 2 · CONTEXT ───────────────────────────────────────────────────\n // Core's own store shape, built by core's own `buildStore` when the runner\n // carries one. `enterSharedScope` keys the shared target by this exact object.\n const store: PipelineStore = runner.buildStore\n ? runner.buildStore({ request, response })\n : { request, response };\n\n return await 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: PageDataBundle = {\n route: {\n name: matched.entry.name,\n path: matched.entry.path,\n params: match.params,\n query,\n },\n };\n\n // ── stage 3 · MIDDLEWARE ──────────────────────────────────────────────\n // Sequential, outermost first. Middleware gets the REAL request/response —\n // it runs alone, before any parallelism — and is the only writer of `shared`.\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 /*\n A middleware throw means NOTHING was served: no loaders ran, no\n buffers exist. The \"a nested boundary keeps the committed status\"\n rule assumes the page was substantially served, so it cannot apply\n here — the framework forces 500 whichever level the boundary\n designates to.\n */\n bundle.error = buildErrorRecord(thrown, designateBoundary(level, triple), pathname);\n bundle.commit = commitBuffers(response, [], 500);\n\n return finish(bundle);\n }\n\n if (output !== undefined) {\n bundle.shortCircuit = {\n stage: \"middleware\",\n level,\n value: output,\n statusCode: (response as PipelineResponse & { statusCode?: number }).statusCode,\n };\n\n return finish(bundle);\n }\n }\n }\n\n // ── stage 4 · VALIDATION ──────────────────────────────────────────────\n // Failure stops before the seal: a 422 designation in the bundle, since\n // sending is the emit slice's job.\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\n return finish(bundle);\n }\n }\n\n // ── stage 5 · SEAL shared ─────────────────────────────────────────────\n // From here `shared` is immutable and identical for every reader. The\n // bundle carries `sealShared`'s RETURN, never a proxy re-read.\n const sealedShared = await sealShared(store);\n\n bundle.shared = sealedShared;\n\n // ── stage 6 · LOADERS ─────────────────────────────────────────────────\n // Parallel, each with its OWN buffered response. Loaders never read each\n // other and cannot write `shared` — it is sealed, and a write throws.\n const buffers = LEVEL_ORDER.map(level => {\n const { response: bufferedResponse, buffer } = createBufferedResponse();\n\n return { level, buffer, bufferedResponse };\n });\n\n const settled = await Promise.allSettled(\n buffers.map(({ level, bufferedResponse }) => {\n const loader = triple[level].loader;\n\n return (async () =>\n loader ? await loader({ request, response: bufferedResponse, shared }) : undefined)();\n }),\n );\n\n // ── stage 7 · SETTLE / COMMIT ─────────────────────────────────────────\n // Walk root→leaf for the first abnormal outcome. A throw discards the\n // throwing layer's buffer and everything below it, and the framework owns\n // the status. A redirect/notFound commits the signalling layer's own buffer\n // and discards its siblings below.\n let firstError: { level: PageLevelName; index: number; error: unknown } | undefined;\n let firstSignal:\n | { level: PageLevelName; index: number; signal: LoaderShortCircuitSignal }\n | undefined;\n\n function toSignal(value: unknown) {\n return isLoaderShortCircuit(value) ? value : undefined;\n }\n\n for (let index = 0; index < LEVEL_ORDER.length; index++) {\n const result = settled[index];\n\n if (result.status === \"rejected\") {\n firstError = { level: LEVEL_ORDER[index], index, error: result.reason };\n break;\n }\n\n const signal = toSignal(result.value);\n\n if (signal) {\n firstSignal = { level: LEVEL_ORDER[index], index, signal };\n break;\n }\n }\n\n const dataKeys: Record<PageLevelName, \"appData\" | \"layoutData\" | \"pageData\"> = {\n app: \"appData\",\n layout: \"layoutData\",\n page: \"pageData\",\n };\n\n // A throw elsewhere never costs a healthy layer its data. Levels leafward\n // of a short-circuit go, because they will not render.\n const dataCutoff = firstSignal ? firstSignal.index : LEVEL_ORDER.length;\n\n for (let index = 0; index < LEVEL_ORDER.length; index++) {\n const result = settled[index];\n\n if (result.status !== \"fulfilled\") continue;\n if (firstError && index === firstError.index) continue;\n if (index > dataCutoff) continue;\n if (toSignal(result.value)) continue;\n\n bundle[dataKeys[LEVEL_ORDER[index]]] = result.value;\n }\n\n if (firstError) {\n const boundary = designateBoundary(firstError.level, triple);\n\n bundle.error = buildErrorRecord(firstError.error, boundary, pathname);\n\n // Commit strictly rootward of the throw. A NESTED boundary keeps the\n // committed status — the page was substantially served; only the ROOT\n // boundary forces 5xx.\n bundle.commit = commitBuffers(\n response,\n buffers.slice(0, firstError.index),\n boundary.boundaryLevel === \"app\" ? 500 : undefined,\n );\n } else if (firstSignal) {\n bundle.shortCircuit = {\n stage: \"loaders\",\n level: firstSignal.level,\n kind: firstSignal.signal.kind,\n statusCode: firstSignal.signal.statusCode,\n url: firstSignal.signal.url,\n body: firstSignal.signal.body,\n };\n bundle.commit = commitBuffers(response, buffers.slice(0, firstSignal.index + 1));\n } else {\n bundle.commit = commitBuffers(response, buffers);\n }\n\n // ── stage 8 · METADATA ────────────────────────────────────────────────\n // Skipped entirely after a loader short-circuit: a redirect/notFound answer\n // has no page render to describe.\n if (!bundle.shortCircuit) {\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 /*\n The page's own metadata function threw with no earlier error to mask.\n Record it exactly as a loader throw, so the boundary renders and the\n framework keeps the status — rather than letting it escape stage 8,\n where nothing is left to catch it and stage 7's buffers are already\n committed.\n */\n if (resolved.thrown !== undefined) {\n bundle.error = buildErrorRecord(\n resolved.thrown,\n designateBoundary(\"page\", triple),\n bundle.route.path,\n );\n }\n }\n\n return finish(bundle);\n });\n}\n"],"mappings":"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"}
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,67 +1,37 @@
1
1
  import { MetadataOutput, PageMetadata } from "../metadata.mjs";
2
+ import { SerializedErrorPageProps } from "../components/document-context.mjs";
2
3
  import { SharedStore } from "../shared.mjs";
3
4
  import { SharedContext } from "../index.mjs";
4
- import { WebRequest } from "../context.mjs";
5
- import { BufferedCookie, BufferedHeader, BufferedWebResponse } from "./buffered-response.mjs";
5
+ import { BufferedResponse } from "./settle-page-response.mjs";
6
+ import { HttpContext, Request, Response } from "@warlock.js/core";
6
7
  import { BaseValidator } from "@warlock.js/seal";
7
8
 
8
- //#region ../web/src/server/execute-page-request.types.d.ts
9
- /** At runtime this IS core's `RequestContextStore` (request-context.ts:10-13). */
9
+ //#region web/src/server/execute-page-request.types.d.ts
10
10
  type PipelineStore = SharedStore & {
11
- request: unknown;
12
- response: unknown;
11
+ request: Request;
12
+ response: Response;
13
13
  };
14
- /**
15
- * Core's `requestContext` satisfies this as-is — `run`/`getStore` are the
16
- * inherited `Context` delegates and `buildStore` is `RequestContext.buildStore`,
17
- * the same function core's http path feeds through `contextManager.buildStores`.
18
- */
19
14
  type PageContextRunner = {
20
15
  run<T>(store: PipelineStore, callback: () => Promise<T>): Promise<T>;
21
16
  getStore(): PipelineStore | undefined;
22
17
  buildStore?(payload?: Record<string, any>): PipelineStore;
23
18
  };
24
19
  type PageLevelName = "app" | "layout" | "page";
20
+ type PipelineMiddleware = (ctx: HttpContext) => unknown | Promise<unknown>;
25
21
  /**
26
- * The request members the pipeline touches, declared explicitly rather than
27
- * imported from core: `WebRequest` is the loader-facing minimal facade and does
28
- * not carry the validation sources.
29
- *
30
- * `query`/`params` are core's own parses (request.ts:1010,1026) and the ONLY
31
- * ones the pipeline reads. Stage 4 used to take them from a match object built
32
- * by re-parsing the URL — `resolve-validation-data.ts` records what that cost.
33
- */
34
- type PipelineRequest = WebRequest & {
35
- body?: Record<string, unknown>;
36
- headers?: Record<string, unknown>;
37
- query?: Record<string, unknown>;
38
- params?: Record<string, unknown>;
39
- setValidatedData?(data: Record<string, unknown>): void;
40
- };
41
- /**
42
- * Standalone rather than based on `WebResponse`: that facade's
43
- * `redirect`/`notFound` return the branded short-circuit signal, which core's
44
- * real `Response` never carries — basing this on it would reject the very
45
- * instances the seam exists to admit.
22
+ * A loader never sees the live `Response` stage 6 hands it a per-level
23
+ * `BufferedResponse` (`execute-page-request.ts`, `createBufferedResponse`),
24
+ * so its own type says that, rather than the core `Response` a middleware
25
+ * receives.
46
26
  */
47
- type PipelineResponse = {
48
- header(key: string, value: string): PipelineResponse;
49
- cookie(name: string, value: unknown, options?: Record<string, unknown>): PipelineResponse;
50
- setStatusCode(statusCode: number): PipelineResponse;
51
- parse(value: unknown): Promise<unknown>;
52
- };
53
- /** Pass-through is `undefined` — core's exact rule (`Request.executeMiddleware`). */
54
- type PipelineMiddleware = (ctx: {
55
- request: PipelineRequest;
56
- response: PipelineResponse;
57
- }) => unknown | Promise<unknown>;
58
- type PipelineLoader = (ctx: {
59
- request: PipelineRequest;
60
- response: BufferedWebResponse;
27
+ type PipelineLoaderContext = {
28
+ request: Request;
29
+ response: BufferedResponse;
61
30
  shared: SharedContext;
62
- }) => unknown | Promise<unknown>;
63
- /** The server half of a page/layout/App module. */
31
+ };
32
+ type PipelineLoader = (ctx: PipelineLoaderContext) => unknown | Promise<unknown>;
64
33
  type PageTripleModule = {
34
+ register?: () => unknown;
65
35
  route?: string | {
66
36
  readonly path: string;
67
37
  readonly name?: string;
@@ -72,7 +42,7 @@ type PageTripleModule = {
72
42
  validating?: readonly string[];
73
43
  };
74
44
  loader?: PipelineLoader;
75
- metadata?: PageMetadata<PipelineLoader>; /** Runs-twice half — carried through untouched; the render slice consumes them. */
45
+ metadata?: PageMetadata<PipelineLoader>;
76
46
  default?: unknown;
77
47
  ErrorBoundary?: unknown;
78
48
  };
@@ -91,54 +61,27 @@ type PageRouteMatch = {
91
61
  query: Record<string, string>;
92
62
  };
93
63
  type ExecutePageRequestOptions<TResult = PageDataBundle> = {
94
- /** Path + optional query string, e.g. `/products/42?tab=specs`. */url: string;
64
+ url: string;
95
65
  routes: readonly PageRouteEntry[];
96
- /**
97
- * Construct the Request/Response pair for this match, mirroring core's
98
- * `handleRoute` body (router.ts:924-932).
99
- */
100
- createHttp(match: PageRouteMatch): {
101
- request: PipelineRequest;
102
- response: PipelineResponse;
103
- };
104
- /**
105
- * The render seam — continues stages 9-10 inside the exact ALS store and
106
- * shared scope that middleware and loaders used.
107
- */
66
+ createHttp(match: PageRouteMatch): HttpContext;
108
67
  finish?(bundle: PageDataBundle): TResult | Promise<TResult>;
109
68
  };
110
69
  type PageBoundaryDesignation = {
111
- throwingLevel: PageLevelName; /** Nearest boundary at or rootward of the throw; `app` is the terminal fallback. */
70
+ throwingLevel: PageLevelName;
112
71
  boundaryLevel: PageLevelName;
113
72
  };
114
- type PageResponseCommit = {
115
- /** Final per-key state, root→leaf: a leafward write wins its key. */headers: BufferedHeader[];
116
- cookies: BufferedCookie[];
117
- statusCode?: number;
118
- committedLevels: PageLevelName[];
119
- };
120
73
  type PageShortCircuit = {
121
74
  stage: "middleware";
122
75
  level: PageLevelName;
123
- value: unknown; /** Captured at stage 3, so `finishRender` stays a pure function of (triple, bundle). */
76
+ value: unknown;
124
77
  statusCode?: number;
125
78
  } | {
126
79
  stage: "validation";
127
80
  status: number;
128
81
  errors: unknown;
129
- } | {
130
- stage: "loaders";
131
- level: PageLevelName;
132
- kind: "redirect" | "notFound";
133
- statusCode: number;
134
- url?: string;
135
- body?: unknown;
136
82
  };
137
- /**
138
- * What a throw becomes once it enters the bundle. `scrubbed` says whether
139
- * `error` is the raw thrown value or a production surrogate standing in for it.
140
- */
141
83
  type PageErrorRecord = {
84
+ /** Never serialized; preserves the actual thrown value for server error.page.tsx. */originalError: unknown;
142
85
  error: unknown;
143
86
  boundary: PageBoundaryDesignation;
144
87
  digest: string;
@@ -149,26 +92,24 @@ type PageDataBundle = {
149
92
  name: string;
150
93
  path: string;
151
94
  params: Record<string, string>;
152
- /**
153
- * ⚠ Re-parsed from the URL by `match-page-route.ts`, NOT core's parse, so it
154
- * is flat and last-wins: `?tags=a&tags=b&filter[status]=active` arrives as
155
- * `{ tags: "b", "filter[status]": "active" }`.
156
- *
157
- * Nothing in `web` reads it — stage 4 was the last consumer. It survives
158
- * only as part of this public type and goes away with the second matcher.
159
- * **Read `request.query`.**
160
- */
161
95
  query: Record<string, string>;
162
96
  };
163
97
  appData?: unknown;
164
98
  layoutData?: unknown;
165
- pageData?: unknown; /** `sealShared()`'s RETURN — the sealed target, never a proxy re-read. */
99
+ pageData?: unknown;
166
100
  shared?: Readonly<SharedContext>;
167
101
  metadata?: MetadataOutput;
168
- commit?: PageResponseCommit;
169
102
  shortCircuit?: PageShortCircuit;
170
103
  error?: PageErrorRecord;
104
+ /**
105
+ * Selected only for the framework-owned application error-page terminal —
106
+ * already the JSON-safe shape (`hydrationErrorPageProps` produces this, not
107
+ * the raw `ErrorPageProps` an authored `error.page.tsx` renders from during
108
+ * SSR), because this field's only consumer is the hydration payload
109
+ * (`build-hydration-payload.ts`), never a component prop.
110
+ */
111
+ errorPage?: SerializedErrorPageProps;
171
112
  };
172
113
  //#endregion
173
- export { ExecutePageRequestOptions, PageBoundaryDesignation, PageContextRunner, PageDataBundle, PageErrorRecord, PageLevelName, PageResponseCommit, PageRouteEntry, PageRouteMatch, PageShortCircuit, PageTripleModule, PipelineLoader, PipelineMiddleware, PipelineRequest, PipelineResponse, PipelineStore };
114
+ export { ExecutePageRequestOptions, PageBoundaryDesignation, PageContextRunner, PageDataBundle, PageErrorRecord, PageLevelName, PageRouteEntry, PageRouteMatch, PageShortCircuit, PageTripleModule, PipelineLoader, PipelineLoaderContext, PipelineMiddleware, PipelineStore };
174
115
  //# sourceMappingURL=execute-page-request.types.d.mts.map
@@ -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;
@@ -1,9 +1,9 @@
1
1
  import { CLIENT_ASSET_URL_PREFIX } from "./client-asset-url-prefix.mjs";
2
2
  import { HYDRATION_CLIENT_ENTRY_NAME } from "../vite/hydration-entries.mjs";
3
- import path from "node:path";
4
3
  import { readFileSync } from "node:fs";
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":["../../../../../../../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":["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,13 +1,14 @@
1
1
  import { composeRoutePath } from "../routing/compose-route-path.mjs";
2
- import { connectSharedStore } from "../shared.mjs";
3
2
  import { PAYLOAD_SCRIPT_ID, escapePayload } from "../components/document-context.mjs";
4
- import { BufferedCookie, BufferedHeader, BufferedWebResponse, LOADER_SHORT_CIRCUIT, LoaderShortCircuitSignal, ResponseBuffer, createBufferedResponse, isLoaderShortCircuit } from "./buffered-response.mjs";
5
- import { ExecutePageRequestOptions, PageBoundaryDesignation, PageContextRunner, PageDataBundle, PageLevelName, PageResponseCommit, PageRouteEntry, PageRouteMatch, PageShortCircuit, PageTripleModule, PipelineLoader, PipelineMiddleware, PipelineStore } from "./execute-page-request.types.mjs";
6
- import { connectPageContext, connectPageSharedScope } from "./page-context.mjs";
3
+ import { connectSharedStore } from "../shared.mjs";
4
+ import { BufferedCookie, BufferedHeader, LoaderShortCircuitKind, PageResponseCommit } from "./settle-page-response.mjs";
5
+ import { ExecutePageRequestOptions, PageBoundaryDesignation, PageContextRunner, PageDataBundle, PageLevelName, PageRouteEntry, PageRouteMatch, PageShortCircuit, PageTripleModule, PipelineLoader, PipelineMiddleware, PipelineStore } from "./execute-page-request.types.mjs";
6
+ import { connectPageContext } from "./page-context.mjs";
7
7
  import { executePageRequest } from "./execute-page-request.mjs";
8
8
  import { InstallPageRoutesOptions, InstalledPageRoute, LayoutModuleShape, PageModuleShape, PageRouteExport, installPageRoutes } from "./install-page-routes.mjs";
9
9
  import { PageRoutesRegistry, RenderPageOptions, RenderPageRequestOptions, RenderedPage, connectPageRoutes, renderPage, renderPageRequest } from "./render-page.mjs";
10
10
  import { PageModuleNotInManifestError, createPageModuleLoader } from "./create-page-module-loader.mjs";
11
11
  import { InstallPageRoutesFromManifestOptions, InstalledManifestPageRoute, PageRouteHandlerFactory, installPageRoutesFromManifest } from "./install-page-routes-from-manifest.mjs";
12
+ import { DuplicateNotFoundPageError, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, NotFoundRouteHandlerOptions, RegisteredRouteShape, UnmatchedRequestKind, acceptsHtmlExplicitly, classifyUnmatchedRequest, createNotFoundRouteHandler, frameworkDefaultNotFoundDocument, isNotFoundPageFile } from "./not-found-page.mjs";
12
13
  import { VITE_DIRECT_CSS_QUERY, devStylesheetUrls, productionStylesheetUrls } from "./stylesheet-urls.mjs";
13
- export { type BufferedCookie, type BufferedHeader, type BufferedWebResponse, type ExecutePageRequestOptions, type InstallPageRoutesFromManifestOptions, type InstallPageRoutesOptions, type InstalledManifestPageRoute, type InstalledPageRoute, LOADER_SHORT_CIRCUIT, type LayoutModuleShape, type LoaderShortCircuitSignal, PAYLOAD_SCRIPT_ID, type PageBoundaryDesignation, type PageContextRunner, type PageDataBundle, type PageLevelName, PageModuleNotInManifestError, type PageModuleShape, type PageResponseCommit, type PageRouteEntry, type PageRouteExport, type PageRouteHandlerFactory, type PageRouteMatch, type PageRoutesRegistry, type PageShortCircuit, type PageTripleModule, type PipelineLoader, type PipelineMiddleware, type PipelineStore, type RenderPageOptions, type RenderPageRequestOptions, type RenderedPage, type ResponseBuffer, VITE_DIRECT_CSS_QUERY, composeRoutePath, connectPageContext, connectPageRoutes, connectPageSharedScope, connectSharedStore, createBufferedResponse, createPageModuleLoader, devStylesheetUrls, escapePayload, executePageRequest, installPageRoutes, installPageRoutesFromManifest, isLoaderShortCircuit, productionStylesheetUrls, renderPage, renderPageRequest };
14
+ export { type BufferedCookie, type BufferedHeader, DuplicateNotFoundPageError, type ExecutePageRequestOptions, type InstallPageRoutesFromManifestOptions, type InstallPageRoutesOptions, type InstalledManifestPageRoute, type InstalledPageRoute, type LayoutModuleShape, type LoaderShortCircuitKind, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, type NotFoundRouteHandlerOptions, PAYLOAD_SCRIPT_ID, type PageBoundaryDesignation, type PageContextRunner, type PageDataBundle, type PageLevelName, PageModuleNotInManifestError, type PageModuleShape, type PageResponseCommit, type PageRouteEntry, type PageRouteExport, type PageRouteHandlerFactory, type PageRouteMatch, type PageRoutesRegistry, type PageShortCircuit, type PageTripleModule, type PipelineLoader, type PipelineMiddleware, type PipelineStore, type RegisteredRouteShape, type RenderPageOptions, type RenderPageRequestOptions, type RenderedPage, type UnmatchedRequestKind, VITE_DIRECT_CSS_QUERY, acceptsHtmlExplicitly, classifyUnmatchedRequest, composeRoutePath, connectPageContext, connectPageRoutes, connectSharedStore, createNotFoundRouteHandler, createPageModuleLoader, devStylesheetUrls, escapePayload, executePageRequest, frameworkDefaultNotFoundDocument, installPageRoutes, installPageRoutesFromManifest, isNotFoundPageFile, productionStylesheetUrls, renderPage, renderPageRequest };
@@ -1,13 +1,13 @@
1
1
  import { connectSharedStore } from "../shared.mjs";
2
2
  import { PAYLOAD_SCRIPT_ID, escapePayload } from "../components/document-context.mjs";
3
- import { LOADER_SHORT_CIRCUIT, createBufferedResponse, isLoaderShortCircuit } from "./buffered-response.mjs";
4
- import { connectPageContext, connectPageSharedScope } from "./page-context.mjs";
3
+ import { connectPageContext } from "./page-context.mjs";
5
4
  import { executePageRequest } from "./execute-page-request.mjs";
6
5
  import { connectPageRoutes, renderPage, renderPageRequest } from "./render-page.mjs";
7
6
  import { PageModuleNotInManifestError, createPageModuleLoader } from "./create-page-module-loader.mjs";
8
7
  import { composeRoutePath } from "../routing/compose-route-path.mjs";
8
+ import { VITE_DIRECT_CSS_QUERY, devStylesheetUrls, productionStylesheetUrls } from "./stylesheet-urls.mjs";
9
+ import { DuplicateNotFoundPageError, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, acceptsHtmlExplicitly, classifyUnmatchedRequest, createNotFoundRouteHandler, frameworkDefaultNotFoundDocument, isNotFoundPageFile } from "./not-found-page.mjs";
9
10
  import { installPageRoutesFromManifest } from "./install-page-routes-from-manifest.mjs";
10
11
  import { installPageRoutes } from "./install-page-routes.mjs";
11
- import { VITE_DIRECT_CSS_QUERY, devStylesheetUrls, productionStylesheetUrls } from "./stylesheet-urls.mjs";
12
12
 
13
- export { LOADER_SHORT_CIRCUIT, PAYLOAD_SCRIPT_ID, PageModuleNotInManifestError, VITE_DIRECT_CSS_QUERY, composeRoutePath, connectPageContext, connectPageRoutes, connectPageSharedScope, connectSharedStore, createBufferedResponse, createPageModuleLoader, devStylesheetUrls, escapePayload, executePageRequest, installPageRoutes, installPageRoutesFromManifest, isLoaderShortCircuit, productionStylesheetUrls, renderPage, renderPageRequest };
13
+ export { DuplicateNotFoundPageError, NOT_FOUND_PAGE_FILENAME, NOT_FOUND_ROUTE_NAME, NOT_FOUND_ROUTE_PATH, NotFoundPageDeclaresRouteError, PAYLOAD_SCRIPT_ID, PageModuleNotInManifestError, VITE_DIRECT_CSS_QUERY, acceptsHtmlExplicitly, classifyUnmatchedRequest, composeRoutePath, connectPageContext, connectPageRoutes, connectSharedStore, createNotFoundRouteHandler, createPageModuleLoader, devStylesheetUrls, escapePayload, executePageRequest, frameworkDefaultNotFoundDocument, installPageRoutes, installPageRoutesFromManifest, isNotFoundPageFile, productionStylesheetUrls, renderPage, renderPageRequest };
@@ -1,9 +1,8 @@
1
1
  import { PageManifest } from "./page-manifest.mjs";
2
- import { BufferedCookie } from "./buffered-response.mjs";
3
2
  import { PageRouteHandler, PageRouteHandlerOptions } from "./create-page-route-handler.mjs";
4
- import { Response, Router } from "@warlock.js/core";
3
+ import { Router } from "@warlock.js/core";
5
4
 
6
- //#region ../web/src/server/install-page-routes-from-manifest.d.ts
5
+ //#region web/src/server/install-page-routes-from-manifest.d.ts
7
6
  /**
8
7
  * How a handler is built for one page. Defaults to `createPageRouteHandler`;
9
8
  * taking it as an input keeps this module's own job — reading the manifest and
@@ -11,7 +10,8 @@ import { Response, Router } from "@warlock.js/core";
11
10
  */
12
11
  type PageRouteHandlerFactory = (options: PageRouteHandlerOptions) => PageRouteHandler;
13
12
  type InstalledManifestPageRoute = {
14
- /** The composed path the route was registered on. */path: string; /** The resolved route name; shared namespace with API routes. */
13
+ /** The canonical declared route path, before layout-prefix composition. */declaredPath: string; /** The composed path the route was registered on. */
14
+ path: string; /** The resolved route name; shared namespace with API routes. */
15
15
  name: string; /** The page's manifest `sourceFile`. */
16
16
  file: string; /** The layout's manifest `sourceFile`, when the page has one. */
17
17
  layoutFile: string | undefined;
@@ -19,9 +19,19 @@ type InstalledManifestPageRoute = {
19
19
  type InstallPageRoutesFromManifestOptions = {
20
20
  router: Router; /** The table the generated production barrel provided at import time. */
21
21
  manifest: PageManifest; /** Browser module loaded after the server-rendered application and payload. */
22
- hydrationClientModuleUrl?: string; /** Stylesheet URLs emitted into every page's `<head>`. */
23
- stylesheetUrls?: readonly string[]; /** Same helper `dev-server.ts` exports — passed in, never imported. */
24
- applyBufferedCookie: (response: Response, cookie: BufferedCookie) => void;
22
+ hydrationClientModuleUrl?: string;
23
+ /**
24
+ * Where the client build wrote its output — `productionStylesheetUrls`'s own
25
+ * `clientDir` argument, forwarded here rather than pre-read into a flat list:
26
+ * each registered handler needs its OWN chain
27
+ * (`[root, ...outer-to-inner matched layouts, page]`, matched by the
28
+ * manifest's own `sourceFile` ids), not one list shared by every page.
29
+ *
30
+ * OPTIONAL for the same reason `PageManifest.clientDir` is: a build that
31
+ * discovered zero pages emits no client bundle, so there is no directory to
32
+ * read stylesheets from — and no page that could need one either.
33
+ */
34
+ clientDir?: string; /** Same helper `dev-server.ts` exports — passed in, never imported. */
25
35
  createHandler?: PageRouteHandlerFactory;
26
36
  };
27
37
  /**