vinext 1.3.1 → 1.4.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 (234) hide show
  1. package/README.md +5 -2
  2. package/THIRD_PARTY_LICENSES.md +47 -0
  3. package/dist/_virtual/_rolldown/runtime.js +3 -2
  4. package/dist/build/prerender.js +1 -1
  5. package/dist/build/report.d.ts +0 -14
  6. package/dist/build/report.js +1 -51
  7. package/dist/check.js +1 -1
  8. package/dist/cli.js +3 -0
  9. package/dist/client/dev-error-overlay.js +1 -1
  10. package/dist/client/navigation-runtime.d.ts +13 -1
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/next-config.d.ts +19 -0
  13. package/dist/config/next-config.js +64 -1
  14. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-codec.js +327 -0
  15. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-data.js +123 -0
  16. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/index.js +29 -0
  17. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/internal.js +134 -0
  18. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-codec.js +50 -0
  19. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data-generated.js +455 -0
  20. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data.js +147 -0
  21. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/big5-added.json.js +169 -0
  22. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp936.json.js +2584 -0
  23. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp949.json.js +2220 -0
  24. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp950.json.js +227 -0
  25. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/eucjp.json.js +425 -0
  26. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gb18030-ranges.json.js +427 -0
  27. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gbk-added.json.js +229 -0
  28. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/shiftjis.json.js +292 -0
  29. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf16.js +106 -0
  30. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf7.js +182 -0
  31. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/bom-handling.js +42 -0
  32. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/extend-node.js +147 -0
  33. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/index.js +94 -0
  34. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/streams.js +95 -0
  35. package/dist/deps/.pnpm/safer-buffer@2.1.2/deps/safer-buffer/safer.js +45 -0
  36. package/dist/entries/app-rsc-entry.js +17 -48
  37. package/dist/entries/app-rsc-manifest.js +3 -4
  38. package/dist/entries/app-ssr-entry.js +1 -0
  39. package/dist/entries/pages-server-entry.js +33 -8
  40. package/dist/index.js +83 -70
  41. package/dist/init-cloudflare.d.ts +1 -3
  42. package/dist/init-cloudflare.js +10 -48
  43. package/dist/init-platform.d.ts +2 -0
  44. package/dist/init-platform.js +4 -0
  45. package/dist/plugins/extensionless-dynamic-import.js +6 -2
  46. package/dist/plugins/next-intl-catalog.d.ts +12 -0
  47. package/dist/plugins/next-intl-catalog.js +85 -0
  48. package/dist/plugins/pages-node-externals.d.ts +1 -0
  49. package/dist/plugins/pages-node-externals.js +1 -1
  50. package/dist/plugins/use-cache-callable.d.ts +1 -1
  51. package/dist/plugins/use-cache-callable.js +136 -94
  52. package/dist/routing/app-route-graph.d.ts +6 -0
  53. package/dist/routing/app-route-graph.js +9 -1
  54. package/dist/server/api-handler.js +19 -4
  55. package/dist/server/app-bfcache-identity.js +1 -1
  56. package/dist/server/app-browser-entry.js +79 -63
  57. package/dist/server/app-browser-history-controller.d.ts +5 -4
  58. package/dist/server/app-browser-history-controller.js +93 -15
  59. package/dist/server/app-browser-navigation-controller.d.ts +2 -2
  60. package/dist/server/app-browser-navigation-controller.js +1 -2
  61. package/dist/server/app-browser-rsc-redirect.js +4 -4
  62. package/dist/server/app-browser-server-action-client.d.ts +2 -0
  63. package/dist/server/app-browser-server-action-client.js +4 -3
  64. package/dist/server/app-browser-state.d.ts +2 -4
  65. package/dist/server/app-browser-state.js +9 -4
  66. package/dist/server/app-browser-supplemental-refresh.js +16 -1
  67. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  68. package/dist/server/app-browser-visible-commit.js +2 -12
  69. package/dist/server/app-elements-wire.d.ts +6 -38
  70. package/dist/server/app-elements-wire.js +4 -41
  71. package/dist/server/app-elements.d.ts +2 -2
  72. package/dist/server/app-elements.js +2 -2
  73. package/dist/server/app-history-state.d.ts +2 -2
  74. package/dist/server/app-history-state.js +5 -2
  75. package/dist/server/app-mounted-layouts.d.ts +21 -0
  76. package/dist/server/app-mounted-layouts.js +67 -0
  77. package/dist/server/app-optimistic-routing.d.ts +19 -1
  78. package/dist/server/app-optimistic-routing.js +42 -2
  79. package/dist/server/app-page-boundary-render.js +7 -6
  80. package/dist/server/app-page-boundary.js +2 -2
  81. package/dist/server/app-page-cache-finalizer.d.ts +12 -1
  82. package/dist/server/app-page-cache-finalizer.js +70 -29
  83. package/dist/server/app-page-cache.d.ts +13 -1
  84. package/dist/server/app-page-cache.js +31 -9
  85. package/dist/server/app-page-dispatch.d.ts +5 -10
  86. package/dist/server/app-page-dispatch.js +84 -154
  87. package/dist/server/app-page-element-builder.d.ts +2 -2
  88. package/dist/server/app-page-element-builder.js +10 -9
  89. package/dist/server/app-page-execution.d.ts +17 -52
  90. package/dist/server/app-page-execution.js +99 -95
  91. package/dist/server/app-page-head.d.ts +13 -0
  92. package/dist/server/app-page-head.js +44 -16
  93. package/dist/server/app-page-http-access-fallback-metadata.d.ts +1 -0
  94. package/dist/server/app-page-http-access-fallback-metadata.js +2 -2
  95. package/dist/server/app-page-layout-gate.d.ts +35 -0
  96. package/dist/server/app-page-layout-gate.js +50 -0
  97. package/dist/server/app-page-params.d.ts +0 -1
  98. package/dist/server/app-page-params.js +1 -14
  99. package/dist/server/app-page-probe.d.ts +3 -21
  100. package/dist/server/app-page-probe.js +4 -37
  101. package/dist/server/app-page-render.d.ts +2 -12
  102. package/dist/server/app-page-render.js +89 -220
  103. package/dist/server/app-page-request.d.ts +1 -3
  104. package/dist/server/app-page-request.js +1 -1
  105. package/dist/server/app-page-response.js +3 -3
  106. package/dist/server/app-page-route-wiring.d.ts +13 -6
  107. package/dist/server/app-page-route-wiring.js +67 -61
  108. package/dist/server/app-page-rsc-render-status.d.ts +27 -1
  109. package/dist/server/app-page-rsc-render-status.js +121 -13
  110. package/dist/server/app-page-stream.d.ts +2 -0
  111. package/dist/server/app-page-stream.js +2 -2
  112. package/dist/server/app-pages-bridge.d.ts +4 -0
  113. package/dist/server/app-pages-bridge.js +7 -3
  114. package/dist/server/app-render-dependency.d.ts +11 -4
  115. package/dist/server/app-render-dependency.js +86 -38
  116. package/dist/server/app-request-stage-dispatch.js +1 -1
  117. package/dist/server/app-route-handler-cache.js +2 -2
  118. package/dist/server/app-route-handler-dispatch.js +14 -9
  119. package/dist/server/app-route-handler-execution.js +8 -3
  120. package/dist/server/app-route-handler-policy.d.ts +1 -1
  121. package/dist/server/app-route-handler-policy.js +3 -3
  122. package/dist/server/app-route-handler-response.js +1 -1
  123. package/dist/server/app-route-handler-runtime.js +17 -4
  124. package/dist/server/app-rsc-cache-busting.d.ts +2 -2
  125. package/dist/server/app-rsc-cache-busting.js +4 -4
  126. package/dist/server/app-rsc-error-handler.d.ts +2 -1
  127. package/dist/server/app-rsc-error-handler.js +9 -1
  128. package/dist/server/app-rsc-errors.d.ts +8 -0
  129. package/dist/server/app-rsc-errors.js +8 -1
  130. package/dist/server/app-rsc-handler.d.ts +5 -5
  131. package/dist/server/app-rsc-handler.js +30 -24
  132. package/dist/server/app-rsc-request-normalization.d.ts +0 -4
  133. package/dist/server/app-rsc-request-normalization.js +1 -4
  134. package/dist/server/app-rsc-response-finalizer.js +2 -2
  135. package/dist/server/app-rsc-response-stage.js +5 -3
  136. package/dist/server/app-server-action-execution.d.ts +3 -1
  137. package/dist/server/app-server-action-execution.js +7 -4
  138. package/dist/server/app-ssr-entry.d.ts +2 -0
  139. package/dist/server/app-ssr-entry.js +9 -8
  140. package/dist/server/app-ssr-stream.js +1 -1
  141. package/dist/server/app-visited-response-cache.d.ts +36 -1
  142. package/dist/server/app-visited-response-cache.js +46 -5
  143. package/dist/server/app-worker-stages.d.ts +25 -3
  144. package/dist/server/app-worker-stages.js +45 -6
  145. package/dist/server/artifact-compatibility.d.ts +0 -1
  146. package/dist/server/artifact-compatibility.js +1 -10
  147. package/dist/server/cache-proof.d.ts +1 -115
  148. package/dist/server/cache-proof.js +1 -544
  149. package/dist/server/cacheability-request.js +13 -8
  150. package/dist/server/dev-server.js +4 -3
  151. package/dist/server/headers.d.ts +6 -3
  152. package/dist/server/headers.js +9 -5
  153. package/dist/server/isr-cache.d.ts +8 -2
  154. package/dist/server/isr-cache.js +26 -1
  155. package/dist/server/metadata-route-response.js +32 -15
  156. package/dist/server/multi-stage.d.ts +52 -11
  157. package/dist/server/multi-stage.js +13 -1
  158. package/dist/server/navigation-planner.js +1 -1
  159. package/dist/server/pages-api-route.js +28 -19
  160. package/dist/server/pages-media-type.d.ts +21 -1
  161. package/dist/server/pages-media-type.js +69 -5
  162. package/dist/server/pages-node-compat.js +52 -27
  163. package/dist/server/pages-page-handler.js +11 -20
  164. package/dist/server/pages-request-pipeline.d.ts +8 -0
  165. package/dist/server/pages-request-pipeline.js +2 -2
  166. package/dist/server/pages-request-stage-entry.js +14 -12
  167. package/dist/server/pages-response-stage-entry.js +9 -3
  168. package/dist/server/prod-server.js +5 -1
  169. package/dist/server/request-pipeline.d.ts +6 -0
  170. package/dist/server/request-pipeline.js +21 -1
  171. package/dist/server/shared-cache-identity.d.ts +23 -0
  172. package/dist/server/shared-cache-identity.js +36 -0
  173. package/dist/server/worker-stages.d.ts +16 -3
  174. package/dist/server/worker-stages.js +23 -4
  175. package/dist/shims/cache-handler.js +2 -2
  176. package/dist/shims/cache-request-state.d.ts +17 -20
  177. package/dist/shims/cache-request-state.js +16 -21
  178. package/dist/shims/cache-runtime.js +27 -11
  179. package/dist/shims/cache.d.ts +8 -7
  180. package/dist/shims/cache.js +17 -19
  181. package/dist/shims/cacheability-classification.d.ts +2 -0
  182. package/dist/shims/cdn-cache.d.ts +22 -5
  183. package/dist/shims/client-page-root.js +2 -2
  184. package/dist/shims/dynamic.js +9 -6
  185. package/dist/shims/error-boundary.js +2 -2
  186. package/dist/shims/fetch-cache-state.d.ts +5 -1
  187. package/dist/shims/fetch-cache-state.js +1 -3
  188. package/dist/shims/fetch-cache.d.ts +2 -1
  189. package/dist/shims/fetch-cache.js +34 -24
  190. package/dist/shims/form.js +2 -2
  191. package/dist/shims/headers.d.ts +8 -23
  192. package/dist/shims/headers.js +17 -67
  193. package/dist/shims/internal/navigation-untracked.js +1 -1
  194. package/dist/shims/link.js +3 -1
  195. package/dist/shims/metadata.d.ts +23 -21
  196. package/dist/shims/metadata.js +62 -24
  197. package/dist/shims/navigation-server.js +1 -1
  198. package/dist/shims/navigation.d.ts +74 -7
  199. package/dist/shims/navigation.js +183 -29
  200. package/dist/shims/navigation.react-server.js +2 -2
  201. package/dist/shims/request-state-types.d.ts +2 -2
  202. package/dist/shims/slot.d.ts +12 -0
  203. package/dist/shims/slot.js +37 -9
  204. package/dist/shims/unified-request-context.d.ts +1 -1
  205. package/dist/shims/unified-request-context.js +2 -3
  206. package/dist/utils/cache-control-metadata.d.ts +5 -0
  207. package/dist/utils/cache-control-metadata.js +9 -1
  208. package/dist/utils/project.d.ts +2 -2
  209. package/dist/utils/project.js +3 -16
  210. package/dist/utils/regex.d.ts +5 -0
  211. package/dist/utils/regex.js +11 -1
  212. package/dist/utils/text-stream.d.ts +10 -5
  213. package/dist/utils/text-stream.js +6 -6
  214. package/package.json +2 -1
  215. package/dist/build/layout-classification-types.d.ts +0 -60
  216. package/dist/build/layout-classification-types.js +0 -1
  217. package/dist/build/layout-classification.d.ts +0 -62
  218. package/dist/build/layout-classification.js +0 -98
  219. package/dist/build/route-classification-injector.d.ts +0 -33
  220. package/dist/build/route-classification-injector.js +0 -59
  221. package/dist/build/route-classification-manifest.d.ts +0 -54
  222. package/dist/build/route-classification-manifest.js +0 -143
  223. package/dist/server/app-browser-client-reuse-manifest.d.ts +0 -11
  224. package/dist/server/app-browser-client-reuse-manifest.js +0 -101
  225. package/dist/server/app-layout-param-observation.d.ts +0 -42
  226. package/dist/server/app-layout-param-observation.js +0 -168
  227. package/dist/server/client-reuse-manifest.d.ts +0 -103
  228. package/dist/server/client-reuse-manifest.js +0 -209
  229. package/dist/server/skip-cache-proof.d.ts +0 -60
  230. package/dist/server/skip-cache-proof.js +0 -168
  231. package/dist/server/static-layout-client-reuse-proof.d.ts +0 -14
  232. package/dist/server/static-layout-client-reuse-proof.js +0 -33
  233. package/dist/utils/sorted-array.d.ts +0 -8
  234. package/dist/utils/sorted-array.js +0 -20
@@ -1,10 +1,8 @@
1
1
  import { VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER } from "./headers.js";
2
- import { isPromiseLike } from "../utils/promise.js";
3
- import { runWithConnectionProbe } from "../shims/headers.js";
4
- import { isHTTPAccessFallbackError, isThrownNavigationError } from "../shims/navigation-errors.js";
5
- import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createRscRedirectLocation } from "./app-rsc-cache-busting.js";
6
- import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
7
2
  import { copyLinkHeaderProvenance, getFrameworkLinkHeader, markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
3
+ import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
4
+ import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createRscRedirectLocation } from "./app-rsc-cache-busting.js";
5
+ import { isHTTPAccessFallbackError, isThrownNavigationError } from "../shims/navigation-errors.js";
8
6
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
9
7
  import { parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
10
8
  import { renderSsrErrorMetaTags } from "./app-ssr-error-meta.js";
@@ -61,6 +59,12 @@ const APP_PAGE_SLOT_ERROR_NOT_FOUND_BOUNDARY = Symbol.for("vinext.appPage.slotEr
61
59
  * which no slot tag marks either.
62
60
  */
63
61
  const APP_PAGE_PAGE_ERROR = Symbol.for("vinext.appPage.pageError");
62
+ /**
63
+ * Marker we tag onto an error a route layout's own component threw, holding
64
+ * that layout's index. Its boundaries wrap only its `children`, so the
65
+ * boundary above the layout catches it, as for a slot the layout renders.
66
+ */
67
+ const APP_PAGE_LAYOUT_ERROR_LAYOUT_INDEX = Symbol.for("vinext.appPage.layoutErrorLayoutIndex");
64
68
  function tagAppPageHttpAccessFallbackError(error, tags) {
65
69
  if (!isHTTPAccessFallbackError(error)) return error;
66
70
  const source = error;
@@ -85,9 +89,17 @@ function tagAppPageSlotError(error, layoutIndex, notFoundBoundary) {
85
89
  function tagAppPagePageError(error) {
86
90
  return tagAppPageHttpAccessFallbackError(error, { [APP_PAGE_PAGE_ERROR]: { value: true } });
87
91
  }
92
+ function tagAppPageLayoutError(error, layoutIndex) {
93
+ return tagAppPageHttpAccessFallbackError(error, { [APP_PAGE_LAYOUT_ERROR_LAYOUT_INDEX]: { value: layoutIndex } });
94
+ }
88
95
  function isAppPagePageError(error) {
89
96
  return !!error && typeof error === "object" && Reflect.get(error, APP_PAGE_PAGE_ERROR) === true;
90
97
  }
98
+ function getAppPageLayoutErrorLayoutIndex(error) {
99
+ if (!error || typeof error !== "object") return null;
100
+ const layoutIndex = Reflect.get(error, APP_PAGE_LAYOUT_ERROR_LAYOUT_INDEX);
101
+ return typeof layoutIndex === "number" ? layoutIndex : null;
102
+ }
91
103
  function getAppPageSlotErrorLayoutIndex(error) {
92
104
  if (!error || typeof error !== "object") return null;
93
105
  const layoutIndex = Reflect.get(error, APP_PAGE_SLOT_ERROR_LAYOUT_INDEX);
@@ -149,8 +161,10 @@ function resolveAppPageSpecialError(error) {
149
161
  * runs only in the RSC render. A notFound() / forbidden() / unauthorized()
150
162
  * a parallel slot's page threw is caught above the layout rendering it,
151
163
  * unless the slot's own not-found catches its notFound() and every other copy
152
- * is the page's or another slot's. A copy neither threw, such as a template's,
153
- * keeps the fallback above that layout.
164
+ * is the page's or another slot's. One a route layout threw is caught above
165
+ * that layout, and the outermost such layout wins. A copy nothing tagged,
166
+ * such as a template's or one from a component a layout or the page renders,
167
+ * marks the error unattributed.
154
168
  */
155
169
  function resolveAppPageShellSpecialError(error, renderedSpecialErrors) {
156
170
  const specialError = resolveAppPageSpecialError(error);
@@ -163,14 +177,32 @@ function resolveAppPageShellSpecialError(error, renderedSpecialErrors) {
163
177
  if (specialError.kind !== "http-access-fallback") return specialError;
164
178
  const skipsSlotCaughtNotFound = specialError.statusCode === 404 && renderedSpecialErrors.every((rendered) => isAppPagePageError(rendered) || getAppPageSlotErrorLayoutIndex(rendered) !== null);
165
179
  let slotLayoutIndex = null;
180
+ let layoutIndex = null;
181
+ let unattributed = false;
166
182
  for (const rendered of renderedSpecialErrors) {
183
+ const throwingLayoutIndex = getAppPageLayoutErrorLayoutIndex(rendered);
184
+ if (throwingLayoutIndex !== null) {
185
+ if (layoutIndex === null || throwingLayoutIndex < layoutIndex) layoutIndex = throwingLayoutIndex;
186
+ continue;
187
+ }
167
188
  if (skipsSlotCaughtNotFound && getAppPageSlotErrorNotFoundBoundary(rendered) !== null) continue;
168
- const layoutIndex = getAppPageSlotErrorLayoutIndex(rendered);
169
- if (layoutIndex !== null && (slotLayoutIndex === null || layoutIndex < slotLayoutIndex)) slotLayoutIndex = layoutIndex;
189
+ const slotOwnerIndex = getAppPageSlotErrorLayoutIndex(rendered);
190
+ if (slotOwnerIndex !== null) {
191
+ if (slotLayoutIndex === null || slotOwnerIndex < slotLayoutIndex) slotLayoutIndex = slotOwnerIndex;
192
+ continue;
193
+ }
194
+ if (!isAppPagePageError(rendered) && resolveAppPageSpecialError(rendered)?.fromMetadata !== true) unattributed = true;
195
+ }
196
+ if (layoutIndex !== null && slotLayoutIndex !== null) {
197
+ if (layoutIndex <= slotLayoutIndex) slotLayoutIndex = null;
198
+ else layoutIndex = null;
170
199
  }
171
- return slotLayoutIndex === null ? specialError : {
200
+ if (slotLayoutIndex === null && layoutIndex === null && !unattributed) return specialError;
201
+ return {
172
202
  ...specialError,
173
- slotLayoutIndex
203
+ ...slotLayoutIndex !== null ? { slotLayoutIndex } : {},
204
+ ...layoutIndex !== null ? { layoutIndex } : {},
205
+ ...unattributed ? { unattributed: true } : {}
174
206
  };
175
207
  }
176
208
  /**
@@ -194,6 +226,7 @@ function resolveAppPageCaughtNotFoundBoundaries(error, renderedSpecialErrors) {
194
226
  let caughtInSlot = false;
195
227
  for (const rendered of renderedSpecialErrors) {
196
228
  if (resolveAppPageSpecialError(rendered)?.fromMetadata === true) return null;
229
+ if (getAppPageLayoutErrorLayoutIndex(rendered) !== null) return null;
197
230
  const boundary = getAppPageSlotErrorNotFoundBoundary(rendered);
198
231
  if (boundary !== null) {
199
232
  caughtInSlot = true;
@@ -230,21 +263,62 @@ function resolveAppPageSpecialErrorStoredHeaders(specialError, basePath) {
230
263
  if (specialError.kind !== "redirect") return void 0;
231
264
  return { location: applyAppPageRedirectBasePath(specialError.location, basePath) };
232
265
  }
266
+ const REDIRECT_BASES = [{
267
+ host: "a.redirect-base.vinext.invalid",
268
+ protocol: "https:"
269
+ }, {
270
+ host: "b.redirect-base.vinext.invalid",
271
+ protocol: "http:"
272
+ }];
273
+ function resolveAgainstPlaceholder(location, requestUrl, placeholder) {
274
+ const base = new URL(requestUrl);
275
+ base.protocol = placeholder.protocol;
276
+ base.host = placeholder.host;
277
+ base.port = "";
278
+ const resolved = new URL(location, base);
279
+ return {
280
+ relative: resolved.origin === base.origin,
281
+ resolved
282
+ };
283
+ }
284
+ /**
285
+ * A URL reference as the URL parser reads it, with every byte a header value
286
+ * cannot carry percent-encoded. The parser trims C0 controls and spaces and
287
+ * drops tabs and newlines; percent-decoding reverses the rest, including in a
288
+ * host, so the client resolves it as it would the raw target.
289
+ */
290
+ function toHeaderSafeUrlReference(location) {
291
+ return location.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "").replace(/[\t\n\r]/g, "").replace(/[^\u0021-\u007e]+/g, (chars) => Array.from(new TextEncoder().encode(chars), (byte) => `%${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
292
+ }
233
293
  /**
234
- * Returns a path-relative form (`/foo?bar`) of an absolute URL when it shares
235
- * the request's origin; otherwise returns the URL verbatim. Used so the digest
236
- * we embed in the flight payload matches Next.js's convention — the digest
237
- * stores the path the developer passed to `redirect("/about")`, not a
238
- * fully-qualified URL like `https://example.com/about`.
294
+ * The redirect target embedded in the flight digest and its streamed
295
+ * side-channel header. An absolute target stays in the digest as `redirect()`
296
+ * received it, as in Next.js's digest, so every host that shares the cache
297
+ * entry is sent to the same place; the header carries the same reference made
298
+ * header-safe, so a protocol-relative or scheme-relative target still resolves
299
+ * against the page each client fetched. A relative target resolves against the
300
+ * request's path to a path-only form, which the client resolves against its
301
+ * own origin. Resolving against two placeholder origins tells these apart the
302
+ * way the URL parser does, never depends on the request's origin, and cannot
303
+ * mistake an absolute target naming one placeholder for a relative one.
239
304
  */
240
- function sameOriginPathOrAbsolute(location, requestUrl) {
305
+ function resolveRedirectTargets(location, requestUrl) {
241
306
  try {
242
- const resolved = new URL(location, requestUrl);
243
- const requestOrigin = new URL(requestUrl).origin;
244
- if (resolved.origin !== requestOrigin) return resolved.toString();
245
- return `${resolved.pathname}${resolved.search}${resolved.hash}`;
307
+ const [first, second] = REDIRECT_BASES.map((placeholder) => resolveAgainstPlaceholder(location, requestUrl, placeholder));
308
+ if (!first.relative || !second.relative) return {
309
+ digestUrl: location,
310
+ headerUrl: toHeaderSafeUrlReference(location)
311
+ };
312
+ const { pathname, search, hash } = first.resolved;
313
+ return {
314
+ digestUrl: `${pathname}${search}${hash}`,
315
+ headerUrl: `${pathname}${search}${hash}`
316
+ };
246
317
  } catch {
247
- return location;
318
+ return {
319
+ digestUrl: location,
320
+ headerUrl: toHeaderSafeUrlReference(location)
321
+ };
248
322
  }
249
323
  }
250
324
  function buildMetadataRedirectHtmlResponse(options) {
@@ -263,7 +337,7 @@ async function buildAppPageSpecialErrorResponse(options) {
263
337
  if (options.specialError.kind === "redirect") {
264
338
  options.clearRequestContext();
265
339
  const prefixedLocation = applyAppPageRedirectBasePath(options.specialError.location, options.basePath);
266
- const digestUrl = sameOriginPathOrAbsolute(prefixedLocation, options.request.url);
340
+ const { digestUrl, headerUrl } = resolveRedirectTargets(prefixedLocation, options.request.url);
267
341
  const digest = formatNextRedirectDigest({
268
342
  type: options.specialError.type ?? "replace",
269
343
  url: digestUrl,
@@ -282,7 +356,7 @@ async function buildAppPageSpecialErrorResponse(options) {
282
356
  mergeMiddlewareResponseHeaders(headers, options.middlewareContext?.headers ?? null);
283
357
  applyRscCompatibilityIdHeader(headers);
284
358
  applyRscDeploymentIdHeader(headers);
285
- headers.set(VINEXT_RSC_REDIRECT_HEADER, digestUrl);
359
+ headers.set(VINEXT_RSC_REDIRECT_HEADER, headerUrl);
286
360
  headers.set(VINEXT_RSC_REDIRECT_TYPE_HEADER, options.specialError.type ?? "replace");
287
361
  const pendingCookies = options.getAndClearPendingCookies?.() ?? [];
288
362
  for (const cookie of pendingCookies) headers.append("Set-Cookie", cookie);
@@ -320,76 +394,6 @@ async function buildAppPageSpecialErrorResponse(options) {
320
394
  const responseStatus = options.specialError.fromMetadata === true && options.serveStreamingMetadata !== false ? 200 : options.specialError.statusCode;
321
395
  return mergeAppPageSpecialErrorHeaders(new Response(getAppPageStatusText(options.specialError.statusCode), { status: responseStatus }), options.middlewareContext);
322
396
  }
323
- /** See `LayoutFlags` type docblock in app-elements.ts for lifecycle. */
324
- async function probeAppPageLayouts(options) {
325
- const layoutFlags = {};
326
- const cls = options.classification ?? null;
327
- return {
328
- response: await options.runWithSuppressedHookWarning(async () => {
329
- for (let layoutIndex = options.layoutCount - 1; layoutIndex >= 0; layoutIndex--) {
330
- const buildTimeResult = cls?.buildTimeClassifications?.get(layoutIndex);
331
- if (cls && buildTimeResult) {
332
- const layoutId = cls.getLayoutId(layoutIndex);
333
- layoutFlags[layoutId] = buildTimeResult === "static" ? "s" : "d";
334
- const errorResponse = await probeLayoutForErrors(options, layoutIndex);
335
- if (errorResponse) return errorResponse;
336
- const observationDynamic = cls.isLayoutObservationDynamic?.(layoutId) === true;
337
- layoutFlags[layoutId] = buildTimeResult === "dynamic" || observationDynamic ? "d" : "s";
338
- if (cls.debugClassification) {
339
- if (observationDynamic && buildTimeResult === "static") cls.debugClassification(layoutId, {
340
- layer: "runtime-probe",
341
- outcome: "dynamic"
342
- });
343
- else cls.debugClassification(layoutId, cls.buildTimeReasons?.get(layoutIndex) ?? { layer: "no-classifier" });
344
- }
345
- continue;
346
- }
347
- if (cls) {
348
- const layoutId = cls.getLayoutId(layoutIndex);
349
- try {
350
- const { dynamicDetected } = await cls.runWithIsolatedDynamicScope(async () => {
351
- const outcome = await runWithConnectionProbe(() => options.probeLayoutAt(layoutIndex));
352
- return outcome.completed ? outcome.result : null;
353
- });
354
- const observationDynamic = cls.isLayoutObservationDynamic?.(layoutId) === true;
355
- const layoutDynamic = dynamicDetected || observationDynamic;
356
- layoutFlags[layoutId] = layoutDynamic ? "d" : "s";
357
- if (cls.debugClassification) cls.debugClassification(layoutId, {
358
- layer: "runtime-probe",
359
- outcome: layoutDynamic ? "dynamic" : "static"
360
- });
361
- } catch (error) {
362
- layoutFlags[layoutId] = "d";
363
- if (cls.debugClassification) cls.debugClassification(layoutId, {
364
- layer: "runtime-probe",
365
- outcome: "dynamic",
366
- error: error instanceof Error ? error.message : String(error)
367
- });
368
- const errorResponse = await options.onLayoutError(error, layoutIndex);
369
- if (errorResponse) return errorResponse;
370
- }
371
- continue;
372
- }
373
- const errorResponse = await probeLayoutForErrors(options, layoutIndex);
374
- if (errorResponse) return errorResponse;
375
- }
376
- return null;
377
- }),
378
- layoutFlags
379
- };
380
- }
381
- async function probeLayoutForErrors(options, layoutIndex) {
382
- const outcome = await runWithConnectionProbe(async () => {
383
- try {
384
- const layoutResult = options.probeLayoutAt(layoutIndex);
385
- if (isPromiseLike(layoutResult)) await layoutResult;
386
- } catch (error) {
387
- return options.onLayoutError(error, layoutIndex);
388
- }
389
- return null;
390
- });
391
- return outcome.completed ? outcome.result : null;
392
- }
393
397
  async function readAppPageBinaryStream(stream) {
394
398
  const reader = stream.getReader();
395
399
  const chunks = [];
@@ -434,4 +438,4 @@ function buildAppPageFontLinkHeader(preloads) {
434
438
  return preloads.map((preload) => `<${preload.href}>; rel=preload; as=font; type=${preload.type}; crossorigin`).join(", ");
435
439
  }
436
440
  //#endregion
437
- export { APP_PAGE_NOT_FOUND_BOUNDARY_ID, bufferAppPageBinaryStream, buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse, probeAppPageLayouts, readAppPageBinaryStream, resolveAppPageCaughtNotFoundBoundaries, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, tagAppPageMetadataError, tagAppPagePageError, tagAppPageSlotError, teeAppPageRscStreamForCapture };
441
+ export { APP_PAGE_NOT_FOUND_BOUNDARY_ID, bufferAppPageBinaryStream, buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse, readAppPageBinaryStream, resolveAppPageCaughtNotFoundBoundaries, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, tagAppPageLayoutError, tagAppPageMetadataError, tagAppPagePageError, tagAppPageSlotError, teeAppPageRscStreamForCapture };
@@ -21,6 +21,12 @@ export type AppPageHeadParallelRoute<TModule extends AppPageHeadModule = AppPage
21
21
  layoutParams?: readonly AppPageParams[] | null;
22
22
  layoutModule?: TModule | null;
23
23
  layoutModules?: readonly (TModule | null | undefined)[] | null;
24
+ /**
25
+ * Whether each layout is in its page's own loader-tree segment, from the
26
+ * filesystem: route groups are segments, and an intercept's
27
+ * `layoutTreePositions` don't count its page's. Unmarked layouts are above it.
28
+ */
29
+ layoutsBesidePage?: readonly boolean[] | null;
24
30
  layoutTreePositions?: readonly number[] | null;
25
31
  moduleRoutePrefixSegments?: readonly string[] | null;
26
32
  moduleRouteSegments?: readonly string[] | null;
@@ -59,6 +65,11 @@ type AppPageHeadSlot<TModule extends AppPageHeadModule = AppPageHeadModule> = {
59
65
  /** Directory depth of the folder holding the `@slot` directory. */
60
66
  ownerTreePosition?: number | null;
61
67
  page?: TModule | null;
68
+ /**
69
+ * The active page's tree position below the slot root, route groups
70
+ * included, in the coordinates of `configLayoutTreePositions`.
71
+ */
72
+ pageTreePosition?: number | null;
62
73
  routeSegments?: readonly string[] | null;
63
74
  };
64
75
  type ResolveActiveParallelRouteHeadInputsOptions<TModule extends AppPageHeadModule = AppPageHeadModule> = {
@@ -179,6 +190,8 @@ type AppPageSearchParamsCollection = {
179
190
  hasSearchParams: boolean;
180
191
  pageSearchParams: AppPageSearchParams;
181
192
  };
193
+ /** Whether each intercepting layout is in its intercepting page's own directory. */
194
+ export declare function resolveInterceptLayoutsBesidePage(layoutSegments: readonly (readonly string[])[] | null | undefined, branchSegments: readonly string[] | null | undefined): boolean[];
182
195
  export declare function resolveActiveParallelRouteHeadInputs<TModule extends AppPageHeadModule>(options: ResolveActiveParallelRouteHeadInputsOptions<TModule>): ActiveParallelRouteHeadInput<TModule>[];
183
196
  /**
184
197
  * Whether a slot intercept keeps the source route's head. Next.js's client
@@ -3,8 +3,8 @@ import { runWithFetchDedupe } from "../shims/fetch-cache.js";
3
3
  import { searchParamsToRecord } from "../utils/query.js";
4
4
  import { mergeMetadataEntries, mergeParentMetadataEntries, mergeViewport, postProcessMetadata, resolveModuleMetadata as resolveModuleMetadata$1, resolveModuleViewport } from "../shims/metadata.js";
5
5
  import { tagAppPageMetadataError } from "./app-page-execution.js";
6
- import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
7
6
  import { createAppMetadataModuleRoute, traceGenerateMetadata } from "./app-metadata-tracing.js";
7
+ import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
8
8
  //#region src/server/app-page-head.ts
9
9
  /**
10
10
  * Wrapped {@link _resolveModuleMetadata} that tags any thrown error with the
@@ -25,6 +25,10 @@ async function resolveModuleMetadata(moduleRoute, ...args) {
25
25
  throw tagAppPageMetadataError(error);
26
26
  }
27
27
  }
28
+ /** Whether each intercepting layout is in its intercepting page's own directory. */
29
+ function resolveInterceptLayoutsBesidePage(layoutSegments, branchSegments) {
30
+ return (layoutSegments ?? []).map((segments) => segments.length === branchSegments?.length);
31
+ }
28
32
  function resolveActiveParallelRouteHeadInputs(options) {
29
33
  const slotEntries = Object.entries(options.slots ?? {});
30
34
  return slotEntries.map(([slotKey, slot]) => {
@@ -56,6 +60,7 @@ function resolveActiveParallelRouteHeadInputs(options) {
56
60
  ...resolveParallelLayoutParams(options.interceptBranchSegments ?? segments, segments.length, options.interceptParams ?? options.params)
57
61
  };
58
62
  })],
63
+ layoutsBesidePage: [...slot.layout ? [false] : [], ...resolveInterceptLayoutsBesidePage(options.interceptLayoutSegments, options.interceptBranchSegments)],
59
64
  layoutTreePositions: [...slot.layout ? [0] : [], ...interceptLayouts.filter(isPresent).map(() => options.routeSegments.length)],
60
65
  pageModule: options.interceptPage,
61
66
  params: options.interceptParams ?? options.params,
@@ -72,6 +77,8 @@ function resolveActiveParallelRouteHeadInputs(options) {
72
77
  }
73
78
  const slotRouteSegments = slot.routeSegments ?? options.routeSegments;
74
79
  const moduleRouteSegments = slot.routeSegments ?? [];
80
+ const layoutTreePositions = [...slot.layout ? [0] : [], ...slot.configLayoutTreePositions ?? []];
81
+ const pageTreePosition = slot.pageTreePosition;
75
82
  return {
76
83
  head: {
77
84
  layoutModules: [slot.layout, ...slot.configLayouts ?? []].filter(isPresent),
@@ -79,7 +86,8 @@ function resolveActiveParallelRouteHeadInputs(options) {
79
86
  ...ownerParams,
80
87
  ...resolveParallelLayoutParams(slot.routeSegments ?? options.routeSegments, treePosition, options.slotParams?.[slotKey] ?? options.params)
81
88
  }))],
82
- layoutTreePositions: [...slot.layout ? [0] : [], ...slot.configLayoutTreePositions ?? []],
89
+ ...pageTreePosition == null ? {} : { layoutsBesidePage: layoutTreePositions.map((treePosition) => treePosition >= pageTreePosition) },
90
+ layoutTreePositions,
83
91
  pageModule: slot.page,
84
92
  params: slotParams,
85
93
  ...ownerRouteSegments.length > 0 ? { moduleRoutePrefixSegments: ownerRouteSegments } : {},
@@ -219,14 +227,20 @@ function createLayoutInputs(layoutModules, layoutTreePositions) {
219
227
  }
220
228
  return layoutInputs;
221
229
  }
222
- /** The walk's accumulated metadata, with its resolved title as the shown string. */
230
+ /** The walk's accumulated metadata, with its resolved titles as the shown strings. */
223
231
  function toShownMetadata(accumulated) {
224
- const title = accumulated.title;
225
- if (title === void 0 || typeof title === "string") return accumulated;
226
- return {
227
- ...accumulated,
228
- title: title.absolute || void 0
232
+ const shown = (title) => typeof title === "object" && title ? title.absolute || void 0 : title;
233
+ const result = { ...accumulated };
234
+ if (typeof accumulated.title === "object") result.title = shown(accumulated.title);
235
+ if (accumulated.openGraph) result.openGraph = {
236
+ ...accumulated.openGraph,
237
+ title: shown(accumulated.openGraph.title)
238
+ };
239
+ if (accumulated.twitter) result.twitter = {
240
+ ...accumulated.twitter,
241
+ title: shown(accumulated.twitter.title)
229
242
  };
243
+ return result;
230
244
  }
231
245
  function getParallelRouteModules(parallelRoute) {
232
246
  return [...parallelRoute.layoutModules ?? [], parallelRoute.layoutModule].filter(isPresent);
@@ -236,6 +250,7 @@ function parallelRouteHasDynamicMetadata(parallelRoute) {
236
250
  }
237
251
  function createRouteHeadBranch(layoutInputs, routeSegments, params, pageModule) {
238
252
  const segments = layoutInputs.map((layoutInput) => ({
253
+ besidePage: layoutInput.treePosition >= routeSegments.length,
239
254
  module: layoutInput.module,
240
255
  moduleRoute: createAppMetadataModuleRoute(routeSegments.slice(0, layoutInput.treePosition), "layout"),
241
256
  page: false,
@@ -260,6 +275,7 @@ function createParallelRouteHeadBranch(parallelRoute, fallbackParams, fallbackRo
260
275
  const layoutTreePositions = parallelRoute.layoutTreePositions ?? [];
261
276
  const layoutParams = parallelRoute.layoutParams ?? [];
262
277
  const segments = getParallelRouteModules(parallelRoute).map((module, index) => ({
278
+ besidePage: parallelRoute.layoutsBesidePage?.[index] ?? false,
263
279
  module,
264
280
  moduleRoute: createAppMetadataModuleRoute([...moduleRoutePrefixSegments, ...moduleRouteSegments.slice(0, layoutTreePositions[index] ?? 0)], "layout"),
265
281
  page: false,
@@ -296,14 +312,14 @@ function createHiddenHeadBranch(hidden, slot, fallbackParams, fallbackRouteSegme
296
312
  * With `deferParentErrors`, `error` never waits on `parent`: what can only be
297
313
  * known after it goes to `deferredError` instead.
298
314
  */
299
- function startAppPageHeadItem(kind, segment, parent, deferParentErrors = false) {
315
+ function startAppPageHeadItem(kind, segment, parent, deferParentErrors = false, stashed = parent) {
300
316
  const observedParent = observeParent(parent);
301
317
  const parentRead = new Promise((resolve) => {
302
318
  observedParent.onRead = () => resolve(null);
303
319
  });
304
320
  const result = kind.resolve(segment, observedParent);
305
321
  result.catch(() => null);
306
- const resolved = parent.then((resolvedParent) => result.then((value) => value ? kind.merge(resolvedParent, value) : resolvedParent));
322
+ const resolved = parent.then((resolvedParent) => stashed.then((resolvedStashed) => result.then((value) => value ? kind.merge(resolvedParent, value, resolvedStashed) : resolvedParent)));
307
323
  resolved.catch(() => null);
308
324
  const mergeError = result.then((value) => value ? parent.then(() => resolved.then(() => null), () => null) : null, () => null);
309
325
  const ownError = result.then(() => null, (error) => {
@@ -362,8 +378,12 @@ function walkAppPageHead(kind, route, slots, hiddenBranches, outletIndex) {
362
378
  let resolved = Promise.resolve(kind.initial);
363
379
  let slotBranchParent = null;
364
380
  let afterOutlet = false;
365
- const start = (segment, parent) => {
366
- const item = startAppPageHeadItem(kind, segment, parent, !slotBranchParent && afterOutlet);
381
+ const lastBranch = slots.at(-1) ?? route;
382
+ const lastPage = lastBranch.segments.at(-1)?.page ? lastBranch.segments.at(-1) : void 0;
383
+ const lastPageLayout = lastPage && lastBranch.segments.at(-2);
384
+ let beforeLastPageLayout;
385
+ const start = (segment, parent, stashed) => {
386
+ const item = startAppPageHeadItem(kind, segment, parent, !slotBranchParent && afterOutlet, stashed);
367
387
  if (item.deferredError) deferredErrors.push(item.deferredError);
368
388
  if (slotBranchParent) {
369
389
  const error = slotBranchParent.then(() => item.error, () => null);
@@ -379,7 +399,8 @@ function walkAppPageHead(kind, route, slots, hiddenBranches, outletIndex) {
379
399
  return item;
380
400
  };
381
401
  const startShown = (segment) => {
382
- const item = start(segment, resolved);
402
+ if (segment === lastPageLayout && segment.besidePage) beforeLastPageLayout = resolved;
403
+ const item = start(segment, resolved, segment === lastPage ? beforeLastPageLayout : void 0);
383
404
  resolved = item.resolved;
384
405
  return item.result;
385
406
  };
@@ -489,9 +510,16 @@ function prepareAppPageHeadInner(options) {
489
510
  const segmentSearchParamsObserver = (segment) => segment.page ? options.searchParamsObserver : void 0;
490
511
  const metadataWalk = walkAppPageHead({
491
512
  initial: {},
492
- merge: (resolved, metadata) => {
513
+ merge: (resolved, metadata, stashed) => {
493
514
  try {
494
- return mergeParentMetadataEntries([{ metadata: resolved }, { metadata }]);
515
+ return mergeParentMetadataEntries(resolved === stashed ? [{ metadata: resolved }, { metadata }] : [
516
+ { metadata: stashed },
517
+ {
518
+ isPage: true,
519
+ metadata: resolved
520
+ },
521
+ { metadata }
522
+ ]);
495
523
  } catch (error) {
496
524
  throw tagAppPageMetadataError(error);
497
525
  }
@@ -540,4 +568,4 @@ function prepareAppPageHeadInner(options) {
540
568
  };
541
569
  }
542
570
  //#endregion
543
- export { collectAppPageSearchParams, hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveAppPageHead, resolveOrderedAppPageMetadata, slotInterceptKeepsSourceHead };
571
+ export { collectAppPageSearchParams, hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveAppPageHead, resolveInterceptLayoutsBesidePage, resolveOrderedAppPageMetadata, slotInterceptKeepsSourceHead };
@@ -33,6 +33,7 @@ type RouteNotFoundHeadPlanRoute<TModule extends AppPageHeadModule> = {
33
33
  notFounds?: readonly (TModule | null | undefined)[] | null;
34
34
  notFoundTreePosition?: number | null;
35
35
  routeSegments?: readonly string[] | null;
36
+ segmentNotFoundTreePositions?: readonly number[] | null;
36
37
  };
37
38
  /**
38
39
  * The not-found head a route's own head falls back to. Like Next.js's
@@ -1,8 +1,8 @@
1
1
  import { isStaticGenerationAccessError } from "../shims/internal/headers-state.js";
2
2
  import { mergeViewport, resolveModuleViewport } from "../shims/metadata.js";
3
3
  import { resolveAppPageSpecialError } from "./app-page-execution.js";
4
- import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
5
4
  import { createAppMetadataModuleRoute } from "./app-metadata-tracing.js";
5
+ import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
6
6
  import { resolveOrderedAppPageMetadata } from "./app-page-head.js";
7
7
  import { resolveAppPageParentHttpAccessBoundary } from "./app-page-boundary.js";
8
8
  //#region src/server/app-page-http-access-fallback-metadata.ts
@@ -124,7 +124,7 @@ function createRouteNotFoundHeadPlanOptions(options) {
124
124
  if (!boundaryModule) {
125
125
  const parentBoundary = resolveAppPageParentHttpAccessBoundary({
126
126
  layoutIndex: layoutModules.length,
127
- rootNotFoundModule: options.rootNotFoundModule,
127
+ rootNotFoundModule: route.segmentNotFoundTreePositions ? null : options.rootNotFoundModule,
128
128
  routeNotFoundModules: route.notFounds,
129
129
  statusCode: 404
130
130
  });
@@ -0,0 +1,35 @@
1
+ //#region src/server/app-page-layout-gate.d.ts
2
+ /**
3
+ * Holds an RSC response until the layouts above the route's first loading
4
+ * boundary have rendered, so that a layout's redirect(), notFound(),
5
+ * forbidden() or unauthorized() still answers the request: a redirect through
6
+ * the `X-Vinext-Rsc-Redirect` side channel, and the others with their
7
+ * boundary's status. The layouts render once, in the render itself. A
8
+ * signal from a component a layout renders streams in the payload, as in
9
+ * Next.js.
10
+ */
11
+ export type AppPageLayoutGate = {
12
+ /** Holds the response until the layout with `layoutId` has rendered. */
13
+ hold(layoutId: string, layoutIndex: number): void;
14
+ /** Reports how the layout with `layoutId` rendered. */
15
+ settle(layoutId: string, failure?: {
16
+ error: unknown;
17
+ }): void;
18
+ /**
19
+ * The outermost held layout that threw, once every held layout `isRendered`
20
+ * includes has rendered, or null. An error elsewhere in the render doesn't
21
+ * stop the wait, as Flight goes on to render the other entries, but the
22
+ * render ending does: `ended` settles when its stream closes or errors.
23
+ */
24
+ wait(isRendered: (layoutId: string) => boolean, ended: Promise<unknown>): Promise<AppPageLayoutGateFailure | null>;
25
+ };
26
+ type AppPageLayoutGateFailure = {
27
+ error: unknown;
28
+ layoutIndex: number;
29
+ };
30
+ export declare function createAppPageLayoutGate(): AppPageLayoutGate;
31
+ export declare function registerAppPageLayoutGate(elements: object, gate: AppPageLayoutGate): void;
32
+ /** The gate of the layouts in `element`, an App Router elements record. */
33
+ export declare function getAppPageLayoutGate(element: unknown): AppPageLayoutGate | null;
34
+ //#endregion
35
+ export {};
@@ -0,0 +1,50 @@
1
+ //#region src/server/app-page-layout-gate.ts
2
+ function createAppPageLayoutGate() {
3
+ const held = /* @__PURE__ */ new Map();
4
+ const settled = /* @__PURE__ */ new Set();
5
+ let failure = null;
6
+ let ended = false;
7
+ let check = null;
8
+ return {
9
+ hold(layoutId, layoutIndex) {
10
+ held.set(layoutId, layoutIndex);
11
+ },
12
+ settle(layoutId, layoutFailure) {
13
+ const layoutIndex = held.get(layoutId);
14
+ if (layoutIndex === void 0 || settled.has(layoutId)) return;
15
+ settled.add(layoutId);
16
+ if (layoutFailure) {
17
+ if (failure === null || layoutIndex < failure.layoutIndex) failure = {
18
+ error: layoutFailure.error,
19
+ layoutIndex
20
+ };
21
+ }
22
+ check?.();
23
+ },
24
+ wait(isRendered, renderEnded) {
25
+ return new Promise((resolve) => {
26
+ check = () => {
27
+ for (const layoutId of held.keys()) if (!ended && !settled.has(layoutId) && isRendered(layoutId)) return;
28
+ check = null;
29
+ resolve(failure);
30
+ };
31
+ const end = () => {
32
+ ended = true;
33
+ check?.();
34
+ };
35
+ renderEnded.then(end, end);
36
+ check();
37
+ });
38
+ }
39
+ };
40
+ }
41
+ const appPageLayoutGates = /* @__PURE__ */ new WeakMap();
42
+ function registerAppPageLayoutGate(elements, gate) {
43
+ appPageLayoutGates.set(elements, gate);
44
+ }
45
+ /** The gate of the layouts in `element`, an App Router elements record. */
46
+ function getAppPageLayoutGate(element) {
47
+ return typeof element === "object" && element !== null ? appPageLayoutGates.get(element) ?? null : null;
48
+ }
49
+ //#endregion
50
+ export { createAppPageLayoutGate, getAppPageLayoutGate, registerAppPageLayoutGate };
@@ -1,7 +1,6 @@
1
1
  import { AppPageParams } from "./app-page-boundary.js";
2
2
  //#region src/server/app-page-params.d.ts
3
3
  export declare function getAppPageSegmentParamName(segment: string): string | null;
4
- export declare function resolveAppPageSegmentParamScopeKeys(routeSegments: readonly string[] | null | undefined, treePosition: number): readonly string[];
5
4
  export declare function resolveAppPageSegmentParams(routeSegments: readonly string[] | null | undefined, treePosition: number, matchedParams: AppPageParams): AppPageParams;
6
5
  export declare function resolveAppPageBranchParams(branchSegments: readonly string[], treePosition: number, matchedParams: AppPageParams, scopedSegments?: readonly string[]): AppPageParams;
7
6
  //#endregion
@@ -8,19 +8,6 @@ function getAppPageSegmentParamName(segment) {
8
8
  function isEmptyOptionalCatchAll(segment, paramValue) {
9
9
  return segment.startsWith("[[...") && Array.isArray(paramValue) && paramValue.length === 0;
10
10
  }
11
- function resolveAppPageSegmentParamScopeKeys(routeSegments, treePosition) {
12
- const paramNames = [];
13
- const seen = /* @__PURE__ */ new Set();
14
- const segments = routeSegments ?? [];
15
- const end = Math.min(Math.max(treePosition, 0), segments.length);
16
- for (let index = 0; index < end; index++) {
17
- const paramName = getAppPageSegmentParamName(segments[index]);
18
- if (!paramName || seen.has(paramName)) continue;
19
- seen.add(paramName);
20
- paramNames.push(paramName);
21
- }
22
- return paramNames;
23
- }
24
11
  function resolveAppPageSegmentParams(routeSegments, treePosition, matchedParams) {
25
12
  const segmentParams = {};
26
13
  const segments = routeSegments ?? [];
@@ -43,4 +30,4 @@ function resolveAppPageBranchParams(branchSegments, treePosition, matchedParams,
43
30
  return scopedParams;
44
31
  }
45
32
  //#endregion
46
- export { getAppPageSegmentParamName, resolveAppPageBranchParams, resolveAppPageSegmentParamScopeKeys, resolveAppPageSegmentParams };
33
+ export { getAppPageSegmentParamName, resolveAppPageBranchParams, resolveAppPageSegmentParams };
@@ -1,31 +1,13 @@
1
- import { LayoutFlags } from "./app-elements-wire.js";
2
- import { AppPageSpecialError, LayoutClassificationOptions } from "./app-page-execution.js";
3
1
  //#region src/server/app-page-probe.d.ts
4
2
  type ProbeReactServerSubtreeOptions = Readonly<{
5
3
  maxDepth?: number;
6
4
  maxNodes?: number;
7
5
  }>;
8
6
  /**
9
- * Invokes server-component children returned by a layout probe so per-layout
10
- * skip eligibility observes data dependencies created below the layout's
11
- * immediate function body. The real RSC render remains authoritative; probe
12
- * failures only make static-layout skip fall back to render-and-send.
7
+ * Invokes the server components a layout renders, so the error-path layout
8
+ * probe sees an HTTP access fallback thrown below the layout's own function
9
+ * body. The real RSC render remains authoritative.
13
10
  */
14
11
  export declare function probeReactServerSubtree(node: unknown, options?: ProbeReactServerSubtreeOptions): Promise<void>;
15
- type ProbeAppPageBeforeRenderResult = {
16
- response: Response | null;
17
- layoutFlags: LayoutFlags;
18
- };
19
- type ProbeAppPageBeforeRenderOptions = {
20
- skipProbes?: boolean;
21
- layoutCount: number;
22
- probeLayoutAt: (layoutIndex: number) => unknown;
23
- renderLayoutSpecialError: (specialError: AppPageSpecialError, layoutIndex: number) => Promise<Response>;
24
- resolveSpecialError: (error: unknown) => AppPageSpecialError | null;
25
- runWithSuppressedHookWarning<T>(probe: () => Promise<T>): Promise<T>;
26
- /** When provided, enables per-layout static/dynamic classification. */
27
- classification?: LayoutClassificationOptions | null;
28
- };
29
- export declare function probeAppPageBeforeRender(options: ProbeAppPageBeforeRenderOptions): Promise<ProbeAppPageBeforeRenderResult>;
30
12
  //#endregion
31
13
  export {};