vinext 0.0.52 → 0.0.54

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 (330) hide show
  1. package/README.md +1 -1
  2. package/dist/build/clean-output.d.ts +14 -0
  3. package/dist/build/clean-output.js +36 -0
  4. package/dist/build/clean-output.js.map +1 -0
  5. package/dist/build/inline-css.d.ts +7 -0
  6. package/dist/build/inline-css.js +50 -0
  7. package/dist/build/inline-css.js.map +1 -0
  8. package/dist/build/prerender.d.ts +6 -2
  9. package/dist/build/prerender.js +51 -12
  10. package/dist/build/prerender.js.map +1 -1
  11. package/dist/build/run-prerender.js +10 -1
  12. package/dist/build/run-prerender.js.map +1 -1
  13. package/dist/build/static-export.d.ts +5 -0
  14. package/dist/build/static-export.js +8 -3
  15. package/dist/build/static-export.js.map +1 -1
  16. package/dist/check.js +4 -0
  17. package/dist/check.js.map +1 -1
  18. package/dist/cli.js +19 -4
  19. package/dist/cli.js.map +1 -1
  20. package/dist/client/instrumentation-client-inject.d.ts +34 -0
  21. package/dist/client/instrumentation-client-inject.js +57 -0
  22. package/dist/client/instrumentation-client-inject.js.map +1 -0
  23. package/dist/client/navigation-runtime.d.ts +16 -2
  24. package/dist/client/navigation-runtime.js +16 -1
  25. package/dist/client/navigation-runtime.js.map +1 -1
  26. package/dist/client/vinext-next-data.d.ts +2 -1
  27. package/dist/client/vinext-next-data.js.map +1 -1
  28. package/dist/client/window-next.d.ts +17 -2
  29. package/dist/client/window-next.js.map +1 -1
  30. package/dist/cloudflare/tpr.js +1 -1
  31. package/dist/cloudflare/tpr.js.map +1 -1
  32. package/dist/config/config-matchers.js +2 -1
  33. package/dist/config/config-matchers.js.map +1 -1
  34. package/dist/config/next-config.d.ts +95 -4
  35. package/dist/config/next-config.js +173 -14
  36. package/dist/config/next-config.js.map +1 -1
  37. package/dist/deploy.js +42 -7
  38. package/dist/deploy.js.map +1 -1
  39. package/dist/entries/app-browser-entry.d.ts +11 -1
  40. package/dist/entries/app-browser-entry.js +16 -6
  41. package/dist/entries/app-browser-entry.js.map +1 -1
  42. package/dist/entries/app-rsc-entry.d.ts +12 -3
  43. package/dist/entries/app-rsc-entry.js +41 -8
  44. package/dist/entries/app-rsc-entry.js.map +1 -1
  45. package/dist/entries/app-rsc-manifest.d.ts +21 -1
  46. package/dist/entries/app-rsc-manifest.js +6 -4
  47. package/dist/entries/app-rsc-manifest.js.map +1 -1
  48. package/dist/entries/pages-client-entry.d.ts +4 -1
  49. package/dist/entries/pages-client-entry.js +40 -3
  50. package/dist/entries/pages-client-entry.js.map +1 -1
  51. package/dist/entries/pages-server-entry.js +292 -34
  52. package/dist/entries/pages-server-entry.js.map +1 -1
  53. package/dist/entries/runtime-entry-module.d.ts +1 -10
  54. package/dist/entries/runtime-entry-module.js +2 -12
  55. package/dist/entries/runtime-entry-module.js.map +1 -1
  56. package/dist/index.js +91 -10
  57. package/dist/index.js.map +1 -1
  58. package/dist/plugins/fonts.js +25 -2
  59. package/dist/plugins/fonts.js.map +1 -1
  60. package/dist/plugins/remove-console.d.ts +16 -0
  61. package/dist/plugins/remove-console.js +176 -0
  62. package/dist/plugins/remove-console.js.map +1 -0
  63. package/dist/routing/app-route-graph.d.ts +24 -1
  64. package/dist/routing/app-route-graph.js +52 -4
  65. package/dist/routing/app-route-graph.js.map +1 -1
  66. package/dist/routing/app-router.d.ts +2 -2
  67. package/dist/routing/app-router.js +2 -2
  68. package/dist/routing/app-router.js.map +1 -1
  69. package/dist/routing/file-matcher.d.ts +21 -1
  70. package/dist/routing/file-matcher.js +39 -1
  71. package/dist/routing/file-matcher.js.map +1 -1
  72. package/dist/routing/pages-router.d.ts +1 -1
  73. package/dist/routing/pages-router.js +10 -3
  74. package/dist/routing/pages-router.js.map +1 -1
  75. package/dist/routing/route-trie.js +13 -18
  76. package/dist/routing/route-trie.js.map +1 -1
  77. package/dist/routing/utils.d.ts +11 -1
  78. package/dist/routing/utils.js +15 -1
  79. package/dist/routing/utils.js.map +1 -1
  80. package/dist/server/api-handler.js +19 -10
  81. package/dist/server/api-handler.js.map +1 -1
  82. package/dist/server/app-browser-action-result.d.ts +16 -1
  83. package/dist/server/app-browser-action-result.js +15 -1
  84. package/dist/server/app-browser-action-result.js.map +1 -1
  85. package/dist/server/app-browser-entry.js +47 -28
  86. package/dist/server/app-browser-entry.js.map +1 -1
  87. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  88. package/dist/server/app-browser-navigation-controller.js +4 -0
  89. package/dist/server/app-browser-navigation-controller.js.map +1 -1
  90. package/dist/server/app-elements-wire.d.ts +13 -4
  91. package/dist/server/app-elements-wire.js +10 -1
  92. package/dist/server/app-elements-wire.js.map +1 -1
  93. package/dist/server/app-elements.d.ts +2 -2
  94. package/dist/server/app-elements.js +2 -2
  95. package/dist/server/app-elements.js.map +1 -1
  96. package/dist/server/app-fallback-renderer.d.ts +27 -8
  97. package/dist/server/app-fallback-renderer.js +19 -8
  98. package/dist/server/app-fallback-renderer.js.map +1 -1
  99. package/dist/server/app-history-state.js +6 -2
  100. package/dist/server/app-history-state.js.map +1 -1
  101. package/dist/server/app-inline-css-client.d.ts +7 -0
  102. package/dist/server/app-inline-css-client.js +37 -0
  103. package/dist/server/app-inline-css-client.js.map +1 -0
  104. package/dist/server/app-interception-context-header.d.ts +33 -0
  105. package/dist/server/app-interception-context-header.js +44 -0
  106. package/dist/server/app-interception-context-header.js.map +1 -0
  107. package/dist/server/app-mounted-slots-header.d.ts +19 -0
  108. package/dist/server/app-mounted-slots-header.js +40 -1
  109. package/dist/server/app-mounted-slots-header.js.map +1 -1
  110. package/dist/server/app-optimistic-routing.js +26 -18
  111. package/dist/server/app-optimistic-routing.js.map +1 -1
  112. package/dist/server/app-page-boundary-render.d.ts +1 -0
  113. package/dist/server/app-page-boundary-render.js +2 -0
  114. package/dist/server/app-page-boundary-render.js.map +1 -1
  115. package/dist/server/app-page-boundary.d.ts +22 -1
  116. package/dist/server/app-page-boundary.js +30 -3
  117. package/dist/server/app-page-boundary.js.map +1 -1
  118. package/dist/server/app-page-cache.d.ts +9 -3
  119. package/dist/server/app-page-cache.js +14 -8
  120. package/dist/server/app-page-cache.js.map +1 -1
  121. package/dist/server/app-page-dispatch.d.ts +13 -1
  122. package/dist/server/app-page-dispatch.js +136 -82
  123. package/dist/server/app-page-dispatch.js.map +1 -1
  124. package/dist/server/app-page-element-builder.d.ts +2 -1
  125. package/dist/server/app-page-element-builder.js +17 -30
  126. package/dist/server/app-page-element-builder.js.map +1 -1
  127. package/dist/server/app-page-execution.d.ts +1 -0
  128. package/dist/server/app-page-execution.js +2 -0
  129. package/dist/server/app-page-execution.js.map +1 -1
  130. package/dist/server/app-page-head.d.ts +1 -0
  131. package/dist/server/app-page-head.js +8 -0
  132. package/dist/server/app-page-head.js.map +1 -1
  133. package/dist/server/app-page-render-identity.d.ts +22 -0
  134. package/dist/server/app-page-render-identity.js +42 -0
  135. package/dist/server/app-page-render-identity.js.map +1 -0
  136. package/dist/server/app-page-render-observation.js +1 -1
  137. package/dist/server/app-page-render.d.ts +9 -1
  138. package/dist/server/app-page-render.js +8 -2
  139. package/dist/server/app-page-render.js.map +1 -1
  140. package/dist/server/app-page-request.d.ts +6 -3
  141. package/dist/server/app-page-request.js +5 -2
  142. package/dist/server/app-page-request.js.map +1 -1
  143. package/dist/server/app-page-response.d.ts +11 -1
  144. package/dist/server/app-page-response.js +16 -4
  145. package/dist/server/app-page-response.js.map +1 -1
  146. package/dist/server/app-page-route-wiring.d.ts +16 -0
  147. package/dist/server/app-page-route-wiring.js +25 -10
  148. package/dist/server/app-page-route-wiring.js.map +1 -1
  149. package/dist/server/app-page-stream.d.ts +12 -0
  150. package/dist/server/app-page-stream.js +3 -0
  151. package/dist/server/app-page-stream.js.map +1 -1
  152. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  153. package/dist/server/app-route-handler-dispatch.js +3 -0
  154. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  155. package/dist/server/app-route-handler-execution.d.ts +1 -0
  156. package/dist/server/app-route-handler-execution.js +1 -0
  157. package/dist/server/app-route-handler-execution.js.map +1 -1
  158. package/dist/server/app-route-handler-response.js +38 -6
  159. package/dist/server/app-route-handler-response.js.map +1 -1
  160. package/dist/server/app-rsc-handler.d.ts +16 -3
  161. package/dist/server/app-rsc-handler.js +60 -11
  162. package/dist/server/app-rsc-handler.js.map +1 -1
  163. package/dist/server/app-rsc-request-normalization.d.ts +2 -1
  164. package/dist/server/app-rsc-request-normalization.js +6 -4
  165. package/dist/server/app-rsc-request-normalization.js.map +1 -1
  166. package/dist/server/app-segment-config.d.ts +4 -1
  167. package/dist/server/app-segment-config.js +6 -1
  168. package/dist/server/app-segment-config.js.map +1 -1
  169. package/dist/server/app-server-action-execution.d.ts +22 -3
  170. package/dist/server/app-server-action-execution.js +46 -7
  171. package/dist/server/app-server-action-execution.js.map +1 -1
  172. package/dist/server/app-ssr-entry.d.ts +6 -0
  173. package/dist/server/app-ssr-entry.js +57 -6
  174. package/dist/server/app-ssr-entry.js.map +1 -1
  175. package/dist/server/app-ssr-error-meta.js +3 -3
  176. package/dist/server/app-ssr-error-meta.js.map +1 -1
  177. package/dist/server/app-ssr-stream.d.ts +25 -1
  178. package/dist/server/app-ssr-stream.js +237 -19
  179. package/dist/server/app-ssr-stream.js.map +1 -1
  180. package/dist/server/app-static-generation.d.ts +1 -0
  181. package/dist/server/app-static-generation.js +2 -1
  182. package/dist/server/app-static-generation.js.map +1 -1
  183. package/dist/server/client-trace-metadata.d.ts +31 -0
  184. package/dist/server/client-trace-metadata.js +83 -0
  185. package/dist/server/client-trace-metadata.js.map +1 -0
  186. package/dist/server/cookie-utils.d.ts +13 -0
  187. package/dist/server/cookie-utils.js +20 -0
  188. package/dist/server/cookie-utils.js.map +1 -0
  189. package/dist/server/default-not-found-module.d.ts +20 -0
  190. package/dist/server/default-not-found-module.js +20 -0
  191. package/dist/server/default-not-found-module.js.map +1 -0
  192. package/dist/server/dev-server.d.ts +8 -1
  193. package/dist/server/dev-server.js +56 -11
  194. package/dist/server/dev-server.js.map +1 -1
  195. package/dist/server/headers.d.ts +5 -1
  196. package/dist/server/headers.js +5 -1
  197. package/dist/server/headers.js.map +1 -1
  198. package/dist/server/html.d.ts +2 -1
  199. package/dist/server/html.js +6 -1
  200. package/dist/server/html.js.map +1 -1
  201. package/dist/server/image-optimization.d.ts +13 -4
  202. package/dist/server/image-optimization.js +15 -4
  203. package/dist/server/image-optimization.js.map +1 -1
  204. package/dist/server/isr-cache.d.ts +7 -5
  205. package/dist/server/isr-cache.js +17 -6
  206. package/dist/server/isr-cache.js.map +1 -1
  207. package/dist/server/middleware-runtime.js +1 -2
  208. package/dist/server/middleware-runtime.js.map +1 -1
  209. package/dist/server/middleware.js +1 -1
  210. package/dist/server/middleware.js.map +1 -1
  211. package/dist/server/pages-api-route.d.ts +18 -0
  212. package/dist/server/pages-api-route.js +3 -1
  213. package/dist/server/pages-api-route.js.map +1 -1
  214. package/dist/server/pages-body-parser-config.d.ts +60 -0
  215. package/dist/server/pages-body-parser-config.js +79 -0
  216. package/dist/server/pages-body-parser-config.js.map +1 -0
  217. package/dist/server/pages-data-route.js +1 -0
  218. package/dist/server/pages-data-route.js.map +1 -1
  219. package/dist/server/pages-default-404.d.ts +31 -0
  220. package/dist/server/pages-default-404.js +40 -0
  221. package/dist/server/pages-default-404.js.map +1 -0
  222. package/dist/server/pages-document-initial-props.d.ts +7 -0
  223. package/dist/server/pages-document-initial-props.js +14 -0
  224. package/dist/server/pages-document-initial-props.js.map +1 -0
  225. package/dist/server/pages-node-compat.d.ts +10 -0
  226. package/dist/server/pages-node-compat.js +12 -1
  227. package/dist/server/pages-node-compat.js.map +1 -1
  228. package/dist/server/pages-page-data.d.ts +40 -0
  229. package/dist/server/pages-page-data.js +19 -14
  230. package/dist/server/pages-page-data.js.map +1 -1
  231. package/dist/server/pages-page-method.d.ts +48 -0
  232. package/dist/server/pages-page-method.js +19 -0
  233. package/dist/server/pages-page-method.js.map +1 -0
  234. package/dist/server/pages-page-response.d.ts +8 -0
  235. package/dist/server/pages-page-response.js +21 -11
  236. package/dist/server/pages-page-response.js.map +1 -1
  237. package/dist/server/pages-serializable-props.d.ts +25 -0
  238. package/dist/server/pages-serializable-props.js +69 -0
  239. package/dist/server/pages-serializable-props.js.map +1 -0
  240. package/dist/server/prerender-route-params.d.ts +14 -0
  241. package/dist/server/prerender-route-params.js +94 -0
  242. package/dist/server/prerender-route-params.js.map +1 -0
  243. package/dist/server/prod-server.d.ts +3 -23
  244. package/dist/server/prod-server.js +43 -57
  245. package/dist/server/prod-server.js.map +1 -1
  246. package/dist/server/proxy-trust.d.ts +41 -0
  247. package/dist/server/proxy-trust.js +70 -0
  248. package/dist/server/proxy-trust.js.map +1 -0
  249. package/dist/server/request-pipeline.d.ts +3 -3
  250. package/dist/server/request-pipeline.js +5 -4
  251. package/dist/server/request-pipeline.js.map +1 -1
  252. package/dist/server/seed-cache.js +12 -6
  253. package/dist/server/seed-cache.js.map +1 -1
  254. package/dist/server/server-action-not-found.js +3 -2
  255. package/dist/server/server-action-not-found.js.map +1 -1
  256. package/dist/server/static-file-cache.js +2 -1
  257. package/dist/server/static-file-cache.js.map +1 -1
  258. package/dist/server/streaming-metadata.d.ts +5 -0
  259. package/dist/server/streaming-metadata.js +10 -0
  260. package/dist/server/streaming-metadata.js.map +1 -0
  261. package/dist/shims/app-router-scroll-state.d.ts +14 -0
  262. package/dist/shims/app-router-scroll-state.js +51 -0
  263. package/dist/shims/app-router-scroll-state.js.map +1 -0
  264. package/dist/shims/app-router-scroll.d.ts +28 -0
  265. package/dist/shims/app-router-scroll.js +115 -0
  266. package/dist/shims/app-router-scroll.js.map +1 -0
  267. package/dist/shims/before-interactive-context.d.ts +30 -0
  268. package/dist/shims/before-interactive-context.js +10 -0
  269. package/dist/shims/before-interactive-context.js.map +1 -0
  270. package/dist/shims/cache-runtime.d.ts +1 -1
  271. package/dist/shims/cache-runtime.js +14 -1
  272. package/dist/shims/cache-runtime.js.map +1 -1
  273. package/dist/shims/cache.d.ts +6 -0
  274. package/dist/shims/cache.js +7 -0
  275. package/dist/shims/cache.js.map +1 -1
  276. package/dist/shims/default-not-found.d.ts +12 -0
  277. package/dist/shims/default-not-found.js +61 -0
  278. package/dist/shims/default-not-found.js.map +1 -0
  279. package/dist/shims/error.js +3 -0
  280. package/dist/shims/error.js.map +1 -1
  281. package/dist/shims/font-local.d.ts +5 -0
  282. package/dist/shims/font-local.js +6 -2
  283. package/dist/shims/font-local.js.map +1 -1
  284. package/dist/shims/head.js +4 -4
  285. package/dist/shims/head.js.map +1 -1
  286. package/dist/shims/headers.d.ts +13 -2
  287. package/dist/shims/headers.js +73 -22
  288. package/dist/shims/headers.js.map +1 -1
  289. package/dist/shims/image.d.ts +1 -1
  290. package/dist/shims/image.js +4 -4
  291. package/dist/shims/image.js.map +1 -1
  292. package/dist/shims/internal/app-route-detection.d.ts +37 -0
  293. package/dist/shims/internal/app-route-detection.js +69 -0
  294. package/dist/shims/internal/app-route-detection.js.map +1 -0
  295. package/dist/shims/internal/pages-data-target.d.ts +58 -0
  296. package/dist/shims/internal/pages-data-target.js +91 -0
  297. package/dist/shims/internal/pages-data-target.js.map +1 -0
  298. package/dist/shims/internal/pages-data-url.d.ts +42 -0
  299. package/dist/shims/internal/pages-data-url.js +73 -0
  300. package/dist/shims/internal/pages-data-url.js.map +1 -0
  301. package/dist/shims/link.d.ts +18 -2
  302. package/dist/shims/link.js +129 -15
  303. package/dist/shims/link.js.map +1 -1
  304. package/dist/shims/metadata.d.ts +9 -7
  305. package/dist/shims/metadata.js +70 -7
  306. package/dist/shims/metadata.js.map +1 -1
  307. package/dist/shims/navigation.d.ts +1 -2
  308. package/dist/shims/navigation.js +94 -20
  309. package/dist/shims/navigation.js.map +1 -1
  310. package/dist/shims/router.d.ts +5 -0
  311. package/dist/shims/router.js +389 -80
  312. package/dist/shims/router.js.map +1 -1
  313. package/dist/shims/script.d.ts +11 -1
  314. package/dist/shims/script.js +158 -15
  315. package/dist/shims/script.js.map +1 -1
  316. package/dist/shims/server.js +1 -0
  317. package/dist/shims/server.js.map +1 -1
  318. package/dist/shims/url-utils.d.ts +2 -1
  319. package/dist/shims/url-utils.js +15 -4
  320. package/dist/shims/url-utils.js.map +1 -1
  321. package/dist/utils/html-limited-bots.d.ts +5 -0
  322. package/dist/utils/html-limited-bots.js +15 -0
  323. package/dist/utils/html-limited-bots.js.map +1 -0
  324. package/dist/utils/path.d.ts +13 -0
  325. package/dist/utils/path.js +16 -0
  326. package/dist/utils/path.js.map +1 -0
  327. package/dist/utils/query.d.ts +6 -0
  328. package/dist/utils/query.js +10 -1
  329. package/dist/utils/query.js.map +1 -1
  330. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"app-fallback-renderer.js","names":[],"sources":["../../src/server/app-fallback-renderer.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\nimport {\n renderAppPageErrorBoundary,\n renderAppPageHttpAccessFallback,\n type AppPageBoundaryRoute,\n} from \"./app-page-boundary-render.js\";\nimport { DEFAULT_GLOBAL_ERROR_MODULE } from \"./default-global-error-module.js\";\nimport type { AppPageFontPreload } from \"./app-page-execution.js\";\nimport type { AppPageMiddlewareContext } from \"./app-page-response.js\";\nimport type { AppPageSsrHandler } from \"./app-page-stream.js\";\nimport type { MetadataFileRoute } from \"./metadata-routes.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype AppPageComponent = import(\"react\").ComponentType<any>;\ntype AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\ntype AppPageBoundaryOnError = (\n error: unknown,\n requestInfo: unknown,\n errorContext: unknown,\n) => unknown;\n\ntype AppFallbackRendererRootBoundaries<TModule extends AppPageModule = AppPageModule> = {\n rootForbiddenModule?: TModule | null;\n rootLayouts: readonly (TModule | null | undefined)[];\n rootNotFoundModule?: TModule | null;\n rootUnauthorizedModule?: TModule | null;\n};\n\ntype AppFallbackRendererFontProviders = {\n buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;\n getFontLinks: () => string[];\n getFontPreloads: () => AppPageFontPreload[];\n getFontStyles: () => string[];\n};\n\ntype AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> = {\n clearRequestContext: () => void;\n createRscOnErrorHandler: (\n request: Request,\n pathname: string,\n routePath: string,\n ) => AppPageBoundaryOnError;\n fontProviders: AppFallbackRendererFontProviders;\n getNavigationContext: () => unknown;\n globalErrorModule?: TModule | null;\n /**\n * Optional `app/global-not-found.tsx` module. When provided, route-miss 404s\n * render this module as a standalone document (skipping the root layout)\n * because it ships its own `<html>` and `<body>`. Page-triggered `notFound()`\n * calls continue to use the regular `not-found.tsx` boundary inside layouts.\n * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx\n */\n globalNotFoundModule?: TModule | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n metadataRoutes: MetadataFileRoute[];\n /** Configured next.config `basePath`, threaded into file-based metadata href emission. */\n basePath?: string;\n resolveChildSegments: (\n routeSegments: readonly string[],\n treePosition: number,\n params: AppPageParams,\n ) => string[];\n rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;\n rscRenderer: (\n element: ReactNode | AppElements,\n options: { onError: AppPageBoundaryOnError },\n ) => ReadableStream<Uint8Array>;\n sanitizer: (error: Error) => Error;\n ssrLoader: () => Promise<AppPageSsrHandler>;\n};\n\ntype AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {\n renderErrorBoundary: (\n route: AppPageBoundaryRoute<TModule> | null,\n error: unknown,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderHttpAccessFallback: (\n route: AppPageBoundaryRoute<TModule> | null,\n statusCode: number,\n isRscRequest: boolean,\n request: Request,\n opts: {\n boundaryComponent?: AppPageComponent | null;\n layouts?: readonly (TModule | null | undefined)[] | null;\n matchedParams?: AppPageParams;\n },\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderNotFound: (\n route: AppPageBoundaryRoute<TModule> | null,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n};\n\nconst EMPTY_MW_CTX: AppPageMiddlewareContext = { headers: null, status: null };\n\nexport function createAppFallbackRenderer<TModule extends AppPageModule>(\n options: AppFallbackRendererOptions<TModule>,\n): AppFallbackRenderer<TModule> {\n const {\n basePath = \"\",\n clearRequestContext,\n createRscOnErrorHandler: buildRscOnErrorHandler,\n fontProviders,\n getNavigationContext,\n globalErrorModule,\n globalNotFoundModule,\n makeThenableParams,\n metadataRoutes,\n resolveChildSegments,\n rootBoundaries,\n rscRenderer,\n sanitizer,\n ssrLoader,\n } = options;\n\n const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } =\n rootBoundaries;\n\n // When the app does not define `app/global-error.tsx`, fall back to vinext's\n // built-in default global error component so that uncaught render errors\n // produce the same UI Next.js ships out of the box (matching markup, inline\n // styles, theme CSS, and the \"ERROR <digest>\" footer for server errors).\n // See packages/vinext/src/shims/default-global-error.tsx and\n // packages/vinext/src/server/default-global-error-module.ts.\n const effectiveGlobalErrorModule: TModule | null =\n globalErrorModule ?? (DEFAULT_GLOBAL_ERROR_MODULE as unknown as TModule);\n\n return {\n renderHttpAccessFallback(\n route,\n statusCode,\n isRscRequest,\n request,\n opts,\n scriptNonce,\n middlewareContext,\n ) {\n // global-not-found.tsx replaces the root layout for route-miss 404s.\n // Only applies when:\n // - The user defined app/global-not-found.tsx\n // - The 404 originates from a route miss (no matched route)\n // - The caller did not already pick a specific boundary component\n // Page-triggered notFound() calls (route is non-null) keep using the\n // regular not-found.tsx boundary inside the route's layouts.\n // See https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx#L495-L520\n const useGlobalNotFound =\n statusCode === 404 && !!globalNotFoundModule && !route && !opts?.boundaryComponent;\n\n if (useGlobalNotFound) {\n const globalNotFoundComponent = globalNotFoundModule?.default ?? null;\n if (globalNotFoundComponent) {\n return renderAppPageHttpAccessFallback({\n boundaryComponent: globalNotFoundComponent,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isRscRequest,\n layoutModules: [],\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule: null,\n rootLayouts: [],\n rootNotFoundModule: null,\n rootUnauthorizedModule: null,\n route: null,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n skipLayoutWrapping: true,\n statusCode,\n });\n }\n }\n\n return renderAppPageHttpAccessFallback({\n basePath,\n boundaryComponent: opts?.boundaryComponent ?? null,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isRscRequest,\n layoutModules: opts?.layouts ?? null,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule,\n rootLayouts,\n rootNotFoundModule,\n rootUnauthorizedModule,\n route,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n statusCode,\n });\n },\n\n renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext) {\n return this.renderHttpAccessFallback(\n route,\n 404,\n isRscRequest,\n request,\n { matchedParams },\n scriptNonce,\n middlewareContext,\n );\n },\n\n renderErrorBoundary(\n route,\n error,\n isRscRequest,\n request,\n matchedParams,\n scriptNonce,\n middlewareContext,\n ) {\n return renderAppPageErrorBoundary({\n basePath,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n error,\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isRscRequest,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootLayouts,\n route,\n renderToReadableStream: rscRenderer,\n sanitizeErrorForClient: sanitizer,\n scriptNonce,\n });\n },\n };\n}\n"],"mappings":";;;AA4GA,MAAM,eAAyC;CAAE,SAAS;CAAM,QAAQ;CAAM;AAE9E,SAAgB,0BACd,SAC8B;CAC9B,MAAM,EACJ,WAAW,IACX,qBACA,yBAAyB,wBACzB,eACA,sBACA,mBACA,sBACA,oBACA,gBACA,sBACA,gBACA,aACA,WACA,cACE;CAEJ,MAAM,EAAE,qBAAqB,aAAa,oBAAoB,2BAC5D;CAQF,MAAM,6BACJ,qBAAsB;CAExB,OAAO;EACL,yBACE,OACA,YACA,cACA,SACA,MACA,aACA,mBACA;GAYA,IAFE,eAAe,OAAO,CAAC,CAAC,wBAAwB,CAAC,SAAS,CAAC,MAAM,mBAE5C;IACrB,MAAM,0BAA0B,sBAAsB,WAAW;IACjE,IAAI,yBACF,OAAO,gCAAgC;KACrC,mBAAmB;KACnB,qBAAqB,cAAc;KACnC;KACA,wBAAwB,UAAU,WAAW;MAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;KAE7D,cAAc,cAAc;KAC5B,iBAAiB,cAAc;KAC/B,eAAe,cAAc;KAC7B;KACA,mBAAmB;KACnB;KACA,eAAe,EAAE;KACjB,gBAAgB;KAChB;KACA,eAAe,MAAM,iBAAiB,EAAE;KACxC,mBAAmB,qBAAqB;KACxC;KACA,YAAY,QAAQ;KACpB;KACA,qBAAqB;KACrB,aAAa,EAAE;KACf,oBAAoB;KACpB,wBAAwB;KACxB,OAAO;KACP,wBAAwB;KACxB;KACA,oBAAoB;KACpB;KACD,CAAC;;GAIN,OAAO,gCAAgC;IACrC;IACA,mBAAmB,MAAM,qBAAqB;IAC9C,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA,mBAAmB;IACnB;IACA,eAAe,MAAM,WAAW;IAChC,gBAAgB;IAChB;IACA,eAAe,MAAM,iBAAiB,OAAO,UAAU,EAAE;IACzD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA;IACA;IACA;IACA,wBAAwB;IACxB;IACA;IACD,CAAC;;EAGJ,eAAe,OAAO,cAAc,SAAS,eAAe,aAAa,mBAAmB;GAC1F,OAAO,KAAK,yBACV,OACA,KACA,cACA,SACA,EAAE,eAAe,EACjB,aACA,kBACD;;EAGH,oBACE,OACA,OACA,cACA,SACA,eACA,aACA,mBACA;GACA,OAAO,2BAA2B;IAChC;IACA,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D;IACA,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA,mBAAmB;IACnB;IACA,gBAAgB;IAChB;IACA,eAAe,iBAAiB,OAAO,UAAU,EAAE;IACnD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA,wBAAwB;IACxB,wBAAwB;IACxB;IACD,CAAC;;EAEL"}
1
+ {"version":3,"file":"app-fallback-renderer.js","names":[],"sources":["../../src/server/app-fallback-renderer.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\nimport {\n renderAppPageErrorBoundary,\n renderAppPageHttpAccessFallback,\n type AppPageBoundaryRoute,\n} from \"./app-page-boundary-render.js\";\nimport { DEFAULT_GLOBAL_ERROR_MODULE } from \"./default-global-error-module.js\";\nimport { DEFAULT_NOT_FOUND_MODULE } from \"./default-not-found-module.js\";\nimport type { AppPageFontPreload } from \"./app-page-execution.js\";\nimport type { AppPageMiddlewareContext } from \"./app-page-response.js\";\nimport type { AppPageSsrHandler } from \"./app-page-stream.js\";\nimport type { MetadataFileRoute } from \"./metadata-routes.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype AppPageComponent = import(\"react\").ComponentType<any>;\ntype AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\ntype AppPageBoundaryOnError = (\n error: unknown,\n requestInfo: unknown,\n errorContext: unknown,\n) => unknown;\n\ntype AppFallbackRendererRootBoundaries<TModule extends AppPageModule = AppPageModule> = {\n rootForbiddenModule?: TModule | null;\n rootLayouts: readonly (TModule | null | undefined)[];\n rootNotFoundModule?: TModule | null;\n rootUnauthorizedModule?: TModule | null;\n};\n\ntype AppFallbackRendererFontProviders = {\n buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;\n getFontLinks: () => string[];\n getFontPreloads: () => AppPageFontPreload[];\n getFontStyles: () => string[];\n};\n\ntype AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> = {\n clearRequestContext: () => void;\n createRscOnErrorHandler: (\n request: Request,\n pathname: string,\n routePath: string,\n ) => AppPageBoundaryOnError;\n fontProviders: AppFallbackRendererFontProviders;\n getNavigationContext: () => unknown;\n globalErrorModule?: TModule | null;\n /**\n * Loader for the user's `app/global-not-found.tsx` module. When provided,\n * route-miss 404s render this module as a standalone document (skipping the\n * root layout) because it ships its own `<html>` and `<body>`. Page-triggered\n * `notFound()` calls continue to use the regular `not-found.tsx` boundary\n * inside layouts.\n *\n * Passed as a deferred loader (rather than the resolved module) so the\n * generated RSC entry can use `() => import(...)` for chunk isolation.\n * Without that isolation, the bundler co-locates global-not-found's CSS\n * with the root layout's CSS in a single chunk and the CSS minifier\n * (lightningcss) drops overlapping declarations as dead code — breaking\n * the cascade for route-miss 404s where only global-not-found is rendered.\n *\n * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx\n * @see Next.js test: test/e2e/app-dir/initial-css-order/initial-css-order.test.ts\n */\n loadGlobalNotFoundModule?: (() => Promise<TModule | null | undefined>) | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n metadataRoutes: MetadataFileRoute[];\n /** Configured next.config `basePath`, threaded into file-based metadata href emission. */\n basePath?: string;\n resolveChildSegments: (\n routeSegments: readonly string[],\n treePosition: number,\n params: AppPageParams,\n ) => string[];\n rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;\n rscRenderer: (\n element: ReactNode | AppElements,\n options: { onError: AppPageBoundaryOnError },\n ) => ReadableStream<Uint8Array>;\n sanitizer: (error: Error) => Error;\n ssrLoader: () => Promise<AppPageSsrHandler>;\n};\n\ntype AppFallbackRendererCallContext = {\n /**\n * Whether the matched (or invoking) route opts into Next.js' edge runtime via\n * `export const runtime = \"edge\"`. Propagated so boundary/error/not-found\n * responses carry `x-edge-runtime: 1` for edge routes, matching the page\n * render path. Defaults to `false` when no route is matched.\n */\n isEdgeRuntime?: boolean;\n};\n\ntype AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {\n renderErrorBoundary: (\n route: AppPageBoundaryRoute<TModule> | null,\n error: unknown,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n callContext?: AppFallbackRendererCallContext,\n ) => Promise<Response | null>;\n renderHttpAccessFallback: (\n route: AppPageBoundaryRoute<TModule> | null,\n statusCode: number,\n isRscRequest: boolean,\n request: Request,\n opts: {\n boundaryComponent?: AppPageComponent | null;\n layouts?: readonly (TModule | null | undefined)[] | null;\n matchedParams?: AppPageParams;\n },\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n callContext?: AppFallbackRendererCallContext,\n ) => Promise<Response | null>;\n renderNotFound: (\n route: AppPageBoundaryRoute<TModule> | null,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n callContext?: AppFallbackRendererCallContext,\n ) => Promise<Response | null>;\n};\n\nconst EMPTY_MW_CTX: AppPageMiddlewareContext = { headers: null, status: null };\n\nexport function createAppFallbackRenderer<TModule extends AppPageModule>(\n options: AppFallbackRendererOptions<TModule>,\n): AppFallbackRenderer<TModule> {\n const {\n basePath = \"\",\n clearRequestContext,\n createRscOnErrorHandler: buildRscOnErrorHandler,\n fontProviders,\n getNavigationContext,\n globalErrorModule,\n loadGlobalNotFoundModule,\n makeThenableParams,\n metadataRoutes,\n resolveChildSegments,\n rootBoundaries,\n rscRenderer,\n sanitizer,\n ssrLoader,\n } = options;\n\n const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } =\n rootBoundaries;\n\n // When the app does not define `app/global-error.tsx`, fall back to vinext's\n // built-in default global error component so that uncaught render errors\n // produce the same UI Next.js ships out of the box (matching markup, inline\n // styles, theme CSS, and the \"ERROR <digest>\" footer for server errors).\n // See packages/vinext/src/shims/default-global-error.tsx and\n // packages/vinext/src/server/default-global-error-module.ts.\n const effectiveGlobalErrorModule: TModule | null =\n globalErrorModule ?? (DEFAULT_GLOBAL_ERROR_MODULE as unknown as TModule);\n\n // When the app does not define `app/not-found.tsx` (and has not opted into\n // `app/global-not-found.tsx`), fall back to vinext's built-in default\n // not-found component so route-miss 404s render the canonical Next.js\n // markup (status + \"This page could not be found.\" message). Matches the\n // default not-found UI shipped with Next.js's app loader.\n // See packages/vinext/src/shims/default-not-found.tsx and\n // packages/vinext/src/server/default-not-found-module.ts.\n const effectiveRootNotFoundModule: TModule | null =\n rootNotFoundModule ?? (DEFAULT_NOT_FOUND_MODULE as unknown as TModule);\n\n // Cache the result of `loadGlobalNotFoundModule()` so subsequent route-miss\n // 404s in the same worker hit a warm import instead of re-resolving the\n // dynamic chunk. The loader itself is invoked at most once per worker;\n // failures are surfaced on every call so they don't get swallowed.\n let globalNotFoundModulePromise: Promise<TModule | null | undefined> | null = null;\n function resolveGlobalNotFoundModule(): Promise<TModule | null | undefined> | null {\n if (!loadGlobalNotFoundModule) return null;\n if (globalNotFoundModulePromise === null) {\n globalNotFoundModulePromise = Promise.resolve().then(loadGlobalNotFoundModule);\n }\n return globalNotFoundModulePromise;\n }\n\n return {\n async renderHttpAccessFallback(\n route,\n statusCode,\n isRscRequest,\n request,\n opts,\n scriptNonce,\n middlewareContext,\n callContext,\n ) {\n // global-not-found.tsx replaces the root layout for route-miss 404s.\n // Only applies when:\n // - The user defined app/global-not-found.tsx\n // - The 404 originates from a route miss (no matched route)\n // - The caller did not already pick a specific boundary component\n // Page-triggered notFound() calls (route is non-null) keep using the\n // regular not-found.tsx boundary inside the route's layouts.\n // See https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx#L495-L520\n const useGlobalNotFound =\n statusCode === 404 && !!loadGlobalNotFoundModule && !route && !opts?.boundaryComponent;\n\n if (useGlobalNotFound) {\n const globalNotFoundModule = await resolveGlobalNotFoundModule();\n const globalNotFoundComponent = globalNotFoundModule?.default ?? null;\n if (globalNotFoundComponent) {\n return renderAppPageHttpAccessFallback({\n boundaryComponent: globalNotFoundComponent,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isEdgeRuntime: callContext?.isEdgeRuntime,\n isRscRequest,\n layoutModules: [],\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule: null,\n rootLayouts: [],\n rootNotFoundModule: null,\n rootUnauthorizedModule: null,\n route: null,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n skipLayoutWrapping: true,\n statusCode,\n });\n }\n }\n\n return renderAppPageHttpAccessFallback({\n basePath,\n boundaryComponent: opts?.boundaryComponent ?? null,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isEdgeRuntime: callContext?.isEdgeRuntime,\n isRscRequest,\n layoutModules: opts?.layouts ?? null,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule,\n rootLayouts,\n rootNotFoundModule: effectiveRootNotFoundModule,\n rootUnauthorizedModule,\n route,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n statusCode,\n });\n },\n\n renderNotFound(\n route,\n isRscRequest,\n request,\n matchedParams,\n scriptNonce,\n middlewareContext,\n callContext,\n ) {\n return this.renderHttpAccessFallback(\n route,\n 404,\n isRscRequest,\n request,\n { matchedParams },\n scriptNonce,\n middlewareContext,\n callContext,\n );\n },\n\n renderErrorBoundary(\n route,\n error,\n isRscRequest,\n request,\n matchedParams,\n scriptNonce,\n middlewareContext,\n callContext,\n ) {\n return renderAppPageErrorBoundary({\n basePath,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n error,\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isEdgeRuntime: callContext?.isEdgeRuntime,\n isRscRequest,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootLayouts,\n route,\n renderToReadableStream: rscRenderer,\n sanitizeErrorForClient: sanitizer,\n scriptNonce,\n });\n },\n };\n}\n"],"mappings":";;;;AAoIA,MAAM,eAAyC;CAAE,SAAS;CAAM,QAAQ;CAAM;AAE9E,SAAgB,0BACd,SAC8B;CAC9B,MAAM,EACJ,WAAW,IACX,qBACA,yBAAyB,wBACzB,eACA,sBACA,mBACA,0BACA,oBACA,gBACA,sBACA,gBACA,aACA,WACA,cACE;CAEJ,MAAM,EAAE,qBAAqB,aAAa,oBAAoB,2BAC5D;CAQF,MAAM,6BACJ,qBAAsB;CASxB,MAAM,8BACJ,sBAAuB;CAMzB,IAAI,8BAA0E;CAC9E,SAAS,8BAA0E;EACjF,IAAI,CAAC,0BAA0B,OAAO;EACtC,IAAI,gCAAgC,MAClC,8BAA8B,QAAQ,SAAS,CAAC,KAAK,yBAAyB;EAEhF,OAAO;;CAGT,OAAO;EACL,MAAM,yBACJ,OACA,YACA,cACA,SACA,MACA,aACA,mBACA,aACA;GAYA,IAFE,eAAe,OAAO,CAAC,CAAC,4BAA4B,CAAC,SAAS,CAAC,MAAM,mBAEhD;IAErB,MAAM,2BAA0B,MADG,6BAA6B,GACV,WAAW;IACjE,IAAI,yBACF,OAAO,gCAAgC;KACrC,mBAAmB;KACnB,qBAAqB,cAAc;KACnC;KACA,wBAAwB,UAAU,WAAW;MAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;KAE7D,cAAc,cAAc;KAC5B,iBAAiB,cAAc;KAC/B,eAAe,cAAc;KAC7B;KACA,mBAAmB;KACnB,eAAe,aAAa;KAC5B;KACA,eAAe,EAAE;KACjB,gBAAgB;KAChB;KACA,eAAe,MAAM,iBAAiB,EAAE;KACxC,mBAAmB,qBAAqB;KACxC;KACA,YAAY,QAAQ;KACpB;KACA,qBAAqB;KACrB,aAAa,EAAE;KACf,oBAAoB;KACpB,wBAAwB;KACxB,OAAO;KACP,wBAAwB;KACxB;KACA,oBAAoB;KACpB;KACD,CAAC;;GAIN,OAAO,gCAAgC;IACrC;IACA,mBAAmB,MAAM,qBAAqB;IAC9C,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA,mBAAmB;IACnB,eAAe,aAAa;IAC5B;IACA,eAAe,MAAM,WAAW;IAChC,gBAAgB;IAChB;IACA,eAAe,MAAM,iBAAiB,OAAO,UAAU,EAAE;IACzD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA,oBAAoB;IACpB;IACA;IACA,wBAAwB;IACxB;IACA;IACD,CAAC;;EAGJ,eACE,OACA,cACA,SACA,eACA,aACA,mBACA,aACA;GACA,OAAO,KAAK,yBACV,OACA,KACA,cACA,SACA,EAAE,eAAe,EACjB,aACA,mBACA,YACD;;EAGH,oBACE,OACA,OACA,cACA,SACA,eACA,aACA,mBACA,aACA;GACA,OAAO,2BAA2B;IAChC;IACA,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D;IACA,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA,mBAAmB;IACnB,eAAe,aAAa;IAC5B;IACA,gBAAgB;IAChB;IACA,eAAe,iBAAiB,OAAO,UAAU,EAAE;IACnD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA,wBAAwB;IACxB,wBAAwB;IACxB;IACD,CAAC;;EAEL"}
@@ -20,8 +20,12 @@ function createHistoryStateWithNavigationMetadata(state, metadata) {
20
20
  }
21
21
  function createExternalHistoryStatePreservingMetadata(callerState, currentHistoryState) {
22
22
  const previousNextUrl = readHistoryStatePreviousNextUrl(currentHistoryState);
23
- if (previousNextUrl === null) return callerState;
24
- return createHistoryStateWithPreviousNextUrl(callerState, previousNextUrl);
23
+ const traversalIndex = readHistoryStateTraversalIndex(currentHistoryState);
24
+ if (previousNextUrl === null && traversalIndex === null) return callerState;
25
+ return createHistoryStateWithNavigationMetadata(callerState, {
26
+ previousNextUrl,
27
+ traversalIndex
28
+ });
25
29
  }
26
30
  function readHistoryStatePreviousNextUrl(state) {
27
31
  const value = cloneHistoryState(state)[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];
@@ -1 +1 @@
1
- {"version":3,"file":"app-history-state.js","names":[],"sources":["../../src/server/app-history-state.ts"],"sourcesContent":["import type { TraverseDirection } from \"./navigation-planner.js\";\n\nconst VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY = \"__vinext_previousNextUrl\";\nconst VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY = \"__vinext_historyIndex\";\n\ntype HistoryStateRecord = {\n [key: string]: unknown;\n};\n\nexport type HistoryTraversalIntent = {\n direction: TraverseDirection;\n historyState: unknown;\n targetHistoryIndex: number | null;\n};\n\nfunction cloneHistoryState(state: unknown): HistoryStateRecord {\n if (!state || typeof state !== \"object\") {\n return {};\n }\n\n const nextState: HistoryStateRecord = {};\n for (const [key, value] of Object.entries(state)) {\n nextState[key] = value;\n }\n return nextState;\n}\n\nexport function createHistoryStateWithPreviousNextUrl(\n state: unknown,\n previousNextUrl: string | null,\n): HistoryStateRecord | null {\n return createHistoryStateWithNavigationMetadata(state, { previousNextUrl });\n}\n\nexport function createHistoryStateWithNavigationMetadata(\n state: unknown,\n metadata: {\n previousNextUrl: string | null;\n traversalIndex?: number | null;\n },\n): HistoryStateRecord | null {\n const nextState = cloneHistoryState(state);\n\n if (metadata.previousNextUrl === null) {\n delete nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];\n } else {\n nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY] = metadata.previousNextUrl;\n }\n\n if (metadata.traversalIndex !== undefined) {\n if (isValidHistoryTraversalIndex(metadata.traversalIndex)) {\n nextState[VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY] = metadata.traversalIndex;\n } else {\n delete nextState[VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY];\n }\n }\n\n return Object.keys(nextState).length > 0 ? nextState : null;\n}\n\nexport function createExternalHistoryStatePreservingMetadata(\n callerState: unknown,\n currentHistoryState: unknown,\n): unknown {\n const previousNextUrl = readHistoryStatePreviousNextUrl(currentHistoryState);\n if (previousNextUrl === null) {\n return callerState;\n }\n\n return createHistoryStateWithPreviousNextUrl(callerState, previousNextUrl);\n}\n\nexport function readHistoryStatePreviousNextUrl(state: unknown): string | null {\n const value = cloneHistoryState(state)[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction isValidHistoryTraversalIndex(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nexport function readHistoryStateTraversalIndex(state: unknown): number | null {\n const value = cloneHistoryState(state)[VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY];\n return isValidHistoryTraversalIndex(value) ? value : null;\n}\n\nexport function resolveHistoryTraversalIntent(options: {\n currentHistoryIndex: number | null;\n historyState: unknown;\n}): HistoryTraversalIntent {\n const targetHistoryIndex = readHistoryStateTraversalIndex(options.historyState);\n let direction: TraverseDirection = \"unknown\";\n\n if (options.currentHistoryIndex !== null && targetHistoryIndex !== null) {\n if (targetHistoryIndex < options.currentHistoryIndex) {\n direction = \"back\";\n } else if (targetHistoryIndex > options.currentHistoryIndex) {\n direction = \"forward\";\n }\n }\n\n return {\n direction,\n historyState: options.historyState,\n targetHistoryIndex,\n };\n}\n"],"mappings":";AAEA,MAAM,6CAA6C;AACnD,MAAM,yCAAyC;AAY/C,SAAS,kBAAkB,OAAoC;CAC7D,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO,EAAE;CAGX,MAAM,YAAgC,EAAE;CACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAC9C,UAAU,OAAO;CAEnB,OAAO;;AAGT,SAAgB,sCACd,OACA,iBAC2B;CAC3B,OAAO,yCAAyC,OAAO,EAAE,iBAAiB,CAAC;;AAG7E,SAAgB,yCACd,OACA,UAI2B;CAC3B,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,SAAS,oBAAoB,MAC/B,OAAO,UAAU;MAEjB,UAAU,8CAA8C,SAAS;CAGnE,IAAI,SAAS,mBAAmB,KAAA,GAC9B,IAAI,6BAA6B,SAAS,eAAe,EACvD,UAAU,0CAA0C,SAAS;MAE7D,OAAO,UAAU;CAIrB,OAAO,OAAO,KAAK,UAAU,CAAC,SAAS,IAAI,YAAY;;AAGzD,SAAgB,6CACd,aACA,qBACS;CACT,MAAM,kBAAkB,gCAAgC,oBAAoB;CAC5E,IAAI,oBAAoB,MACtB,OAAO;CAGT,OAAO,sCAAsC,aAAa,gBAAgB;;AAG5E,SAAgB,gCAAgC,OAA+B;CAC7E,MAAM,QAAQ,kBAAkB,MAAM,CAAC;CACvC,OAAO,OAAO,UAAU,WAAW,QAAQ;;AAG7C,SAAS,6BAA6B,OAAiC;CACrE,OAAO,OAAO,UAAU,YAAY,OAAO,cAAc,MAAM,IAAI,SAAS;;AAG9E,SAAgB,+BAA+B,OAA+B;CAC5E,MAAM,QAAQ,kBAAkB,MAAM,CAAC;CACvC,OAAO,6BAA6B,MAAM,GAAG,QAAQ;;AAGvD,SAAgB,8BAA8B,SAGnB;CACzB,MAAM,qBAAqB,+BAA+B,QAAQ,aAAa;CAC/E,IAAI,YAA+B;CAEnC,IAAI,QAAQ,wBAAwB,QAAQ,uBAAuB;MAC7D,qBAAqB,QAAQ,qBAC/B,YAAY;OACP,IAAI,qBAAqB,QAAQ,qBACtC,YAAY;;CAIhB,OAAO;EACL;EACA,cAAc,QAAQ;EACtB;EACD"}
1
+ {"version":3,"file":"app-history-state.js","names":[],"sources":["../../src/server/app-history-state.ts"],"sourcesContent":["import type { TraverseDirection } from \"./navigation-planner.js\";\n\nconst VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY = \"__vinext_previousNextUrl\";\nconst VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY = \"__vinext_historyIndex\";\n\ntype HistoryStateRecord = {\n [key: string]: unknown;\n};\n\nexport type HistoryTraversalIntent = {\n direction: TraverseDirection;\n historyState: unknown;\n targetHistoryIndex: number | null;\n};\n\nfunction cloneHistoryState(state: unknown): HistoryStateRecord {\n if (!state || typeof state !== \"object\") {\n return {};\n }\n\n const nextState: HistoryStateRecord = {};\n for (const [key, value] of Object.entries(state)) {\n nextState[key] = value;\n }\n return nextState;\n}\n\nexport function createHistoryStateWithPreviousNextUrl(\n state: unknown,\n previousNextUrl: string | null,\n): HistoryStateRecord | null {\n return createHistoryStateWithNavigationMetadata(state, { previousNextUrl });\n}\n\nexport function createHistoryStateWithNavigationMetadata(\n state: unknown,\n metadata: {\n previousNextUrl: string | null;\n traversalIndex?: number | null;\n },\n): HistoryStateRecord | null {\n const nextState = cloneHistoryState(state);\n\n if (metadata.previousNextUrl === null) {\n delete nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];\n } else {\n nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY] = metadata.previousNextUrl;\n }\n\n if (metadata.traversalIndex !== undefined) {\n if (isValidHistoryTraversalIndex(metadata.traversalIndex)) {\n nextState[VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY] = metadata.traversalIndex;\n } else {\n delete nextState[VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY];\n }\n }\n\n return Object.keys(nextState).length > 0 ? nextState : null;\n}\n\nexport function createExternalHistoryStatePreservingMetadata(\n callerState: unknown,\n currentHistoryState: unknown,\n): unknown {\n const previousNextUrl = readHistoryStatePreviousNextUrl(currentHistoryState);\n const traversalIndex = readHistoryStateTraversalIndex(currentHistoryState);\n\n if (previousNextUrl === null && traversalIndex === null) {\n return callerState;\n }\n\n return createHistoryStateWithNavigationMetadata(callerState, {\n previousNextUrl,\n traversalIndex,\n });\n}\n\nexport function readHistoryStatePreviousNextUrl(state: unknown): string | null {\n const value = cloneHistoryState(state)[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction isValidHistoryTraversalIndex(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nexport function readHistoryStateTraversalIndex(state: unknown): number | null {\n const value = cloneHistoryState(state)[VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY];\n return isValidHistoryTraversalIndex(value) ? value : null;\n}\n\nexport function resolveHistoryTraversalIntent(options: {\n currentHistoryIndex: number | null;\n historyState: unknown;\n}): HistoryTraversalIntent {\n const targetHistoryIndex = readHistoryStateTraversalIndex(options.historyState);\n let direction: TraverseDirection = \"unknown\";\n\n if (options.currentHistoryIndex !== null && targetHistoryIndex !== null) {\n if (targetHistoryIndex < options.currentHistoryIndex) {\n direction = \"back\";\n } else if (targetHistoryIndex > options.currentHistoryIndex) {\n direction = \"forward\";\n }\n }\n\n return {\n direction,\n historyState: options.historyState,\n targetHistoryIndex,\n };\n}\n"],"mappings":";AAEA,MAAM,6CAA6C;AACnD,MAAM,yCAAyC;AAY/C,SAAS,kBAAkB,OAAoC;CAC7D,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO,EAAE;CAGX,MAAM,YAAgC,EAAE;CACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAC9C,UAAU,OAAO;CAEnB,OAAO;;AAGT,SAAgB,sCACd,OACA,iBAC2B;CAC3B,OAAO,yCAAyC,OAAO,EAAE,iBAAiB,CAAC;;AAG7E,SAAgB,yCACd,OACA,UAI2B;CAC3B,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,SAAS,oBAAoB,MAC/B,OAAO,UAAU;MAEjB,UAAU,8CAA8C,SAAS;CAGnE,IAAI,SAAS,mBAAmB,KAAA,GAC9B,IAAI,6BAA6B,SAAS,eAAe,EACvD,UAAU,0CAA0C,SAAS;MAE7D,OAAO,UAAU;CAIrB,OAAO,OAAO,KAAK,UAAU,CAAC,SAAS,IAAI,YAAY;;AAGzD,SAAgB,6CACd,aACA,qBACS;CACT,MAAM,kBAAkB,gCAAgC,oBAAoB;CAC5E,MAAM,iBAAiB,+BAA+B,oBAAoB;CAE1E,IAAI,oBAAoB,QAAQ,mBAAmB,MACjD,OAAO;CAGT,OAAO,yCAAyC,aAAa;EAC3D;EACA;EACD,CAAC;;AAGJ,SAAgB,gCAAgC,OAA+B;CAC7E,MAAM,QAAQ,kBAAkB,MAAM,CAAC;CACvC,OAAO,OAAO,UAAU,WAAW,QAAQ;;AAG7C,SAAS,6BAA6B,OAAiC;CACrE,OAAO,OAAO,UAAU,YAAY,OAAO,cAAc,MAAM,IAAI,SAAS;;AAG9E,SAAgB,+BAA+B,OAA+B;CAC5E,MAAM,QAAQ,kBAAkB,MAAM,CAAC;CACvC,OAAO,6BAA6B,MAAM,GAAG,QAAQ;;AAGvD,SAAgB,8BAA8B,SAGnB;CACzB,MAAM,qBAAqB,+BAA+B,QAAQ,aAAa;CAC/E,IAAI,YAA+B;CAEnC,IAAI,QAAQ,wBAAwB,QAAQ,uBAAuB;MAC7D,qBAAqB,QAAQ,qBAC/B,YAAY;OACP,IAAI,qBAAqB,QAAQ,qBACtC,YAAY;;CAIhB,OAAO;EACL;EACA,cAAc,QAAQ;EACtB;EACD"}
@@ -0,0 +1,7 @@
1
+ //#region src/server/app-inline-css-client.d.ts
2
+ type InlineCssStylesheetLinkElement = Pick<HTMLLinkElement, "getAttribute" | "hasAttribute">;
3
+ declare function isInlineCssStylesheetLinkElement(link: InlineCssStylesheetLinkElement): boolean;
4
+ declare function removeStylesheetLinksCoveredByInlineCss(): void;
5
+ //#endregion
6
+ export { isInlineCssStylesheetLinkElement, removeStylesheetLinksCoveredByInlineCss };
7
+ //# sourceMappingURL=app-inline-css-client.d.ts.map
@@ -0,0 +1,37 @@
1
+ import { htmlTokenListContains } from "./html.js";
2
+ //#region src/server/app-inline-css-client.ts
3
+ function inlineStyleCoversStylesheetHref(styleHref, linkHref) {
4
+ for (const candidate of styleHref.split(/\s+/)) {
5
+ if (candidate === linkHref) return true;
6
+ try {
7
+ const candidateUrl = new URL(candidate, window.location.href);
8
+ const linkUrl = new URL(linkHref, window.location.href);
9
+ if (candidateUrl.href === linkUrl.href) return true;
10
+ } catch {}
11
+ }
12
+ return false;
13
+ }
14
+ function isInlineCssStylesheetLinkElement(link) {
15
+ return htmlTokenListContains(link.getAttribute("rel"), "stylesheet") && link.hasAttribute("href") && (link.hasAttribute("data-precedence") || link.hasAttribute("precedence"));
16
+ }
17
+ function removeStylesheetLinksCoveredByInlineCss() {
18
+ const inlineStyles = document.head.querySelectorAll("style[data-vinext-inline-css][data-href]");
19
+ if (inlineStyles.length === 0) return;
20
+ const links = document.head.querySelectorAll("link[rel][href]");
21
+ for (const link of links) {
22
+ if (!isInlineCssStylesheetLinkElement(link)) continue;
23
+ const href = link.getAttribute("href");
24
+ if (!href) continue;
25
+ for (const style of inlineStyles) {
26
+ const styleHref = style.getAttribute("data-href");
27
+ if (styleHref && inlineStyleCoversStylesheetHref(styleHref, href)) {
28
+ link.remove();
29
+ break;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ //#endregion
35
+ export { isInlineCssStylesheetLinkElement, removeStylesheetLinksCoveredByInlineCss };
36
+
37
+ //# sourceMappingURL=app-inline-css-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-inline-css-client.js","names":[],"sources":["../../src/server/app-inline-css-client.ts"],"sourcesContent":["import { htmlTokenListContains } from \"./html.js\";\n\ntype InlineCssStylesheetLinkElement = Pick<HTMLLinkElement, \"getAttribute\" | \"hasAttribute\">;\n\nfunction inlineStyleCoversStylesheetHref(styleHref: string, linkHref: string): boolean {\n for (const candidate of styleHref.split(/\\s+/)) {\n if (candidate === linkHref) return true;\n try {\n const candidateUrl = new URL(candidate, window.location.href);\n const linkUrl = new URL(linkHref, window.location.href);\n if (candidateUrl.href === linkUrl.href) return true;\n } catch {\n // If either value is not parseable, exact string comparison above is the\n // only safe comparison.\n }\n }\n\n return false;\n}\n\nexport function isInlineCssStylesheetLinkElement(link: InlineCssStylesheetLinkElement): boolean {\n return (\n htmlTokenListContains(link.getAttribute(\"rel\"), \"stylesheet\") &&\n link.hasAttribute(\"href\") &&\n (link.hasAttribute(\"data-precedence\") || link.hasAttribute(\"precedence\"))\n );\n}\n\nexport function removeStylesheetLinksCoveredByInlineCss(): void {\n const inlineStyles = document.head.querySelectorAll<HTMLStyleElement>(\n \"style[data-vinext-inline-css][data-href]\",\n );\n if (inlineStyles.length === 0) return;\n\n const links = document.head.querySelectorAll<HTMLLinkElement>(\"link[rel][href]\");\n for (const link of links) {\n if (!isInlineCssStylesheetLinkElement(link)) continue;\n\n const href = link.getAttribute(\"href\");\n if (!href) continue;\n\n for (const style of inlineStyles) {\n const styleHref = style.getAttribute(\"data-href\");\n if (styleHref && inlineStyleCoversStylesheetHref(styleHref, href)) {\n link.remove();\n break;\n }\n }\n }\n}\n"],"mappings":";;AAIA,SAAS,gCAAgC,WAAmB,UAA2B;CACrF,KAAK,MAAM,aAAa,UAAU,MAAM,MAAM,EAAE;EAC9C,IAAI,cAAc,UAAU,OAAO;EACnC,IAAI;GACF,MAAM,eAAe,IAAI,IAAI,WAAW,OAAO,SAAS,KAAK;GAC7D,MAAM,UAAU,IAAI,IAAI,UAAU,OAAO,SAAS,KAAK;GACvD,IAAI,aAAa,SAAS,QAAQ,MAAM,OAAO;UACzC;;CAMV,OAAO;;AAGT,SAAgB,iCAAiC,MAA+C;CAC9F,OACE,sBAAsB,KAAK,aAAa,MAAM,EAAE,aAAa,IAC7D,KAAK,aAAa,OAAO,KACxB,KAAK,aAAa,kBAAkB,IAAI,KAAK,aAAa,aAAa;;AAI5E,SAAgB,0CAAgD;CAC9D,MAAM,eAAe,SAAS,KAAK,iBACjC,2CACD;CACD,IAAI,aAAa,WAAW,GAAG;CAE/B,MAAM,QAAQ,SAAS,KAAK,iBAAkC,kBAAkB;CAChF,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,CAAC,iCAAiC,KAAK,EAAE;EAE7C,MAAM,OAAO,KAAK,aAAa,OAAO;EACtC,IAAI,CAAC,MAAM;EAEX,KAAK,MAAM,SAAS,cAAc;GAChC,MAAM,YAAY,MAAM,aAAa,YAAY;GACjD,IAAI,aAAa,gCAAgC,WAAW,KAAK,EAAE;IACjE,KAAK,QAAQ;IACb"}
@@ -0,0 +1,33 @@
1
+ //#region src/server/app-interception-context-header.d.ts
2
+ /**
3
+ * Normalize the `x-vinext-interception-context` header from inbound requests.
4
+ *
5
+ * The browser sends the current pathname (e.g. `/feed`) as interception context
6
+ * so the server can decide whether to render an intercepted parallel route.
7
+ * The legitimate value is always a same-origin URL pathname produced by the
8
+ * vinext browser entry — never an arbitrary string.
9
+ *
10
+ * Security: this value flows into cache-key construction (via
11
+ * `getOptimisticRouteTemplateKey`, `getOptimisticPrefetchSourceKey`, and
12
+ * outbound RSC payload cache keys). Without bounds, an attacker who controls
13
+ * this header can fabricate unbounded distinct values to fragment the cache
14
+ * or drive per-write KV billing. See `SECURITY-AUDIT-2026-05.md` finding
15
+ * F-PROD-1.
16
+ *
17
+ * Bounds applied:
18
+ * - Null bytes are stripped (header-injection defense).
19
+ * - The value must start with `/` (a pathname).
20
+ * - Whitespace is rejected (real pathnames do not contain raw whitespace;
21
+ * legitimate spaces would be percent-encoded).
22
+ * - Length capped at MAX_INTERCEPTION_CONTEXT_LENGTH bytes. Values that
23
+ * exceed the cap are treated as absent so the request is still served,
24
+ * just without interception.
25
+ *
26
+ * Anything that fails validation returns null, matching the prior behavior of
27
+ * an absent header. This is intentionally more permissive than rejecting the
28
+ * whole request — interception is a progressive enhancement.
29
+ */
30
+ declare function normalizeInterceptionContextHeader(raw: string | null | undefined): string | null;
31
+ //#endregion
32
+ export { normalizeInterceptionContextHeader };
33
+ //# sourceMappingURL=app-interception-context-header.d.ts.map
@@ -0,0 +1,44 @@
1
+ //#region src/server/app-interception-context-header.ts
2
+ /**
3
+ * Normalize the `x-vinext-interception-context` header from inbound requests.
4
+ *
5
+ * The browser sends the current pathname (e.g. `/feed`) as interception context
6
+ * so the server can decide whether to render an intercepted parallel route.
7
+ * The legitimate value is always a same-origin URL pathname produced by the
8
+ * vinext browser entry — never an arbitrary string.
9
+ *
10
+ * Security: this value flows into cache-key construction (via
11
+ * `getOptimisticRouteTemplateKey`, `getOptimisticPrefetchSourceKey`, and
12
+ * outbound RSC payload cache keys). Without bounds, an attacker who controls
13
+ * this header can fabricate unbounded distinct values to fragment the cache
14
+ * or drive per-write KV billing. See `SECURITY-AUDIT-2026-05.md` finding
15
+ * F-PROD-1.
16
+ *
17
+ * Bounds applied:
18
+ * - Null bytes are stripped (header-injection defense).
19
+ * - The value must start with `/` (a pathname).
20
+ * - Whitespace is rejected (real pathnames do not contain raw whitespace;
21
+ * legitimate spaces would be percent-encoded).
22
+ * - Length capped at MAX_INTERCEPTION_CONTEXT_LENGTH bytes. Values that
23
+ * exceed the cap are treated as absent so the request is still served,
24
+ * just without interception.
25
+ *
26
+ * Anything that fails validation returns null, matching the prior behavior of
27
+ * an absent header. This is intentionally more permissive than rejecting the
28
+ * whole request — interception is a progressive enhancement.
29
+ */
30
+ /** Hard cap on the byte length of the interception-context header value. */
31
+ const MAX_INTERCEPTION_CONTEXT_LENGTH = 1024;
32
+ function normalizeInterceptionContextHeader(raw) {
33
+ if (!raw) return null;
34
+ const stripped = raw.replaceAll("\0", "");
35
+ if (stripped.length === 0) return null;
36
+ if (stripped.length > MAX_INTERCEPTION_CONTEXT_LENGTH) return null;
37
+ if (!stripped.startsWith("/")) return null;
38
+ if (/\s/.test(stripped)) return null;
39
+ return stripped;
40
+ }
41
+ //#endregion
42
+ export { normalizeInterceptionContextHeader };
43
+
44
+ //# sourceMappingURL=app-interception-context-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-interception-context-header.js","names":[],"sources":["../../src/server/app-interception-context-header.ts"],"sourcesContent":["/**\n * Normalize the `x-vinext-interception-context` header from inbound requests.\n *\n * The browser sends the current pathname (e.g. `/feed`) as interception context\n * so the server can decide whether to render an intercepted parallel route.\n * The legitimate value is always a same-origin URL pathname produced by the\n * vinext browser entry — never an arbitrary string.\n *\n * Security: this value flows into cache-key construction (via\n * `getOptimisticRouteTemplateKey`, `getOptimisticPrefetchSourceKey`, and\n * outbound RSC payload cache keys). Without bounds, an attacker who controls\n * this header can fabricate unbounded distinct values to fragment the cache\n * or drive per-write KV billing. See `SECURITY-AUDIT-2026-05.md` finding\n * F-PROD-1.\n *\n * Bounds applied:\n * - Null bytes are stripped (header-injection defense).\n * - The value must start with `/` (a pathname).\n * - Whitespace is rejected (real pathnames do not contain raw whitespace;\n * legitimate spaces would be percent-encoded).\n * - Length capped at MAX_INTERCEPTION_CONTEXT_LENGTH bytes. Values that\n * exceed the cap are treated as absent so the request is still served,\n * just without interception.\n *\n * Anything that fails validation returns null, matching the prior behavior of\n * an absent header. This is intentionally more permissive than rejecting the\n * whole request — interception is a progressive enhancement.\n */\n\n/** Hard cap on the byte length of the interception-context header value. */\nconst MAX_INTERCEPTION_CONTEXT_LENGTH = 1024;\n\nexport function normalizeInterceptionContextHeader(raw: string | null | undefined): string | null {\n if (!raw) return null;\n // Strip null bytes first so length bounds can't be evaded by padding with \\0.\n const stripped = raw.replaceAll(\"\\0\", \"\");\n if (stripped.length === 0) return null;\n if (stripped.length > MAX_INTERCEPTION_CONTEXT_LENGTH) return null;\n // Must look like a same-origin pathname. Anything else (a full URL, a token,\n // junk bytes) is not a legitimate value the browser would emit.\n if (!stripped.startsWith(\"/\")) return null;\n // Raw whitespace is not legitimate inside a pathname.\n if (/\\s/.test(stripped)) return null;\n return stripped;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAM,kCAAkC;AAExC,SAAgB,mCAAmC,KAA+C;CAChG,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,WAAW,IAAI,WAAW,MAAM,GAAG;CACzC,IAAI,SAAS,WAAW,GAAG,OAAO;CAClC,IAAI,SAAS,SAAS,iCAAiC,OAAO;CAG9D,IAAI,CAAC,SAAS,WAAW,IAAI,EAAE,OAAO;CAEtC,IAAI,KAAK,KAAK,SAAS,EAAE,OAAO;CAChC,OAAO"}
@@ -6,6 +6,25 @@
6
6
  * rendered, which changes across navigations. This normalizes to a canonical form
7
7
  * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.
8
8
  *
9
+ * Security: the value flows into the ISR RSC cache key (`appIsrRscKey`). Without
10
+ * bounds, an attacker who controls this header can fabricate unbounded distinct
11
+ * values to fan out KV writes (per-write billing) or fragment the cache. See
12
+ * `SECURITY-AUDIT-2026-05.md` finding F-PROD-1. The legitimate wire format is a
13
+ * whitespace-separated list of `slot:<name>:<treePath>` tokens (see
14
+ * `createAppPayloadSlotId` in `app-elements-wire.ts`); anything else is rejected.
15
+ *
16
+ * Bounds applied:
17
+ * - Total raw header value capped at MAX_RAW_HEADER_LENGTH bytes (returns null
18
+ * if exceeded so the request is treated as if the header were absent).
19
+ * - Each token capped at MAX_TOKEN_LENGTH bytes.
20
+ * - Token count capped at MAX_SLOT_TOKENS (extras are dropped after sort + dedup).
21
+ * - Each token must match the legitimate slot-id shape, as defined by the
22
+ * AppElements wire codec (`AppElementsWire.isSlotId`). Wire-format details
23
+ * are intentionally kept inside the codec so this module does not duplicate
24
+ * them. Malformed tokens are dropped silently rather than rejecting the
25
+ * whole request — this matches the prior forgiving behavior for browsers
26
+ * that send legitimate but stale formats during rolling deploys.
27
+ *
9
28
  * Consumed by:
10
29
  * - app-rsc-request-normalization (request lifecycle, reads incoming header)
11
30
  * - app-elements (outgoing x-vinext-mounted-slots construction)
@@ -1,3 +1,4 @@
1
+ import { AppElementsWire } from "./app-elements-wire.js";
1
2
  //#region src/server/app-mounted-slots-header.ts
2
3
  /**
3
4
  * Normalize the `x-vinext-mounted-slots` header for request handling and cache keying.
@@ -6,14 +7,52 @@
6
7
  * rendered, which changes across navigations. This normalizes to a canonical form
7
8
  * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.
8
9
  *
10
+ * Security: the value flows into the ISR RSC cache key (`appIsrRscKey`). Without
11
+ * bounds, an attacker who controls this header can fabricate unbounded distinct
12
+ * values to fan out KV writes (per-write billing) or fragment the cache. See
13
+ * `SECURITY-AUDIT-2026-05.md` finding F-PROD-1. The legitimate wire format is a
14
+ * whitespace-separated list of `slot:<name>:<treePath>` tokens (see
15
+ * `createAppPayloadSlotId` in `app-elements-wire.ts`); anything else is rejected.
16
+ *
17
+ * Bounds applied:
18
+ * - Total raw header value capped at MAX_RAW_HEADER_LENGTH bytes (returns null
19
+ * if exceeded so the request is treated as if the header were absent).
20
+ * - Each token capped at MAX_TOKEN_LENGTH bytes.
21
+ * - Token count capped at MAX_SLOT_TOKENS (extras are dropped after sort + dedup).
22
+ * - Each token must match the legitimate slot-id shape, as defined by the
23
+ * AppElements wire codec (`AppElementsWire.isSlotId`). Wire-format details
24
+ * are intentionally kept inside the codec so this module does not duplicate
25
+ * them. Malformed tokens are dropped silently rather than rejecting the
26
+ * whole request — this matches the prior forgiving behavior for browsers
27
+ * that send legitimate but stale formats during rolling deploys.
28
+ *
9
29
  * Consumed by:
10
30
  * - app-rsc-request-normalization (request lifecycle, reads incoming header)
11
31
  * - app-elements (outgoing x-vinext-mounted-slots construction)
12
32
  * - isr-cache (RSC cache key generation)
13
33
  */
34
+ /** Hard cap on the raw header value byte length. Real values are <1 KB. */
35
+ const MAX_RAW_HEADER_LENGTH = 4096;
36
+ /** Hard cap on a single slot token byte length. */
37
+ const MAX_TOKEN_LENGTH = 256;
38
+ /** Hard cap on the number of slot tokens kept after normalization. */
39
+ const MAX_SLOT_TOKENS = 16;
40
+ /**
41
+ * Validate a single mounted-slot token. Shape validation is delegated to the
42
+ * AppElements wire codec so the wire format definition lives in exactly one
43
+ * place. This module only enforces the additional security cap on token byte
44
+ * length to bound cache-key cardinality.
45
+ */
46
+ function isValidSlotToken(token) {
47
+ if (token.length === 0 || token.length > MAX_TOKEN_LENGTH) return false;
48
+ return AppElementsWire.isSlotId(token);
49
+ }
14
50
  function normalizeMountedSlotsHeader(raw) {
15
51
  if (!raw) return null;
16
- return Array.from(new Set(raw.split(/\s+/).filter(Boolean))).sort().join(" ") || null;
52
+ if (raw.length > MAX_RAW_HEADER_LENGTH) return null;
53
+ const validTokens = raw.split(/\s+/).filter((token) => token && isValidSlotToken(token));
54
+ if (validTokens.length === 0) return null;
55
+ return Array.from(new Set(validTokens)).sort().slice(0, MAX_SLOT_TOKENS).join(" ") || null;
17
56
  }
18
57
  //#endregion
19
58
  export { normalizeMountedSlotsHeader };
@@ -1 +1 @@
1
- {"version":3,"file":"app-mounted-slots-header.js","names":[],"sources":["../../src/server/app-mounted-slots-header.ts"],"sourcesContent":["/**\n * Normalize the `x-vinext-mounted-slots` header for request handling and cache keying.\n *\n * The browser sends mounted slot ids as a space-separated list in the order slots were\n * rendered, which changes across navigations. This normalizes to a canonical form\n * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.\n *\n * Consumed by:\n * - app-rsc-request-normalization (request lifecycle, reads incoming header)\n * - app-elements (outgoing x-vinext-mounted-slots construction)\n * - isr-cache (RSC cache key generation)\n */\nexport function normalizeMountedSlotsHeader(raw: string | null | undefined): string | null {\n if (!raw) return null;\n const normalized = Array.from(new Set(raw.split(/\\s+/).filter(Boolean)))\n .sort()\n .join(\" \");\n return normalized || null;\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,4BAA4B,KAA+C;CACzF,IAAI,CAAC,KAAK,OAAO;CAIjB,OAHmB,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC,CACrE,MAAM,CACN,KAAK,IACS,IAAI"}
1
+ {"version":3,"file":"app-mounted-slots-header.js","names":[],"sources":["../../src/server/app-mounted-slots-header.ts"],"sourcesContent":["/**\n * Normalize the `x-vinext-mounted-slots` header for request handling and cache keying.\n *\n * The browser sends mounted slot ids as a space-separated list in the order slots were\n * rendered, which changes across navigations. This normalizes to a canonical form\n * (sorted, deduplicated) so equivalent slot sets map to the same RSC cache entry.\n *\n * Security: the value flows into the ISR RSC cache key (`appIsrRscKey`). Without\n * bounds, an attacker who controls this header can fabricate unbounded distinct\n * values to fan out KV writes (per-write billing) or fragment the cache. See\n * `SECURITY-AUDIT-2026-05.md` finding F-PROD-1. The legitimate wire format is a\n * whitespace-separated list of `slot:<name>:<treePath>` tokens (see\n * `createAppPayloadSlotId` in `app-elements-wire.ts`); anything else is rejected.\n *\n * Bounds applied:\n * - Total raw header value capped at MAX_RAW_HEADER_LENGTH bytes (returns null\n * if exceeded so the request is treated as if the header were absent).\n * - Each token capped at MAX_TOKEN_LENGTH bytes.\n * - Token count capped at MAX_SLOT_TOKENS (extras are dropped after sort + dedup).\n * - Each token must match the legitimate slot-id shape, as defined by the\n * AppElements wire codec (`AppElementsWire.isSlotId`). Wire-format details\n * are intentionally kept inside the codec so this module does not duplicate\n * them. Malformed tokens are dropped silently rather than rejecting the\n * whole request — this matches the prior forgiving behavior for browsers\n * that send legitimate but stale formats during rolling deploys.\n *\n * Consumed by:\n * - app-rsc-request-normalization (request lifecycle, reads incoming header)\n * - app-elements (outgoing x-vinext-mounted-slots construction)\n * - isr-cache (RSC cache key generation)\n */\n\nimport { AppElementsWire } from \"./app-elements-wire.js\";\n\n/** Hard cap on the raw header value byte length. Real values are <1 KB. */\nconst MAX_RAW_HEADER_LENGTH = 4096;\n/** Hard cap on a single slot token byte length. */\nconst MAX_TOKEN_LENGTH = 256;\n/** Hard cap on the number of slot tokens kept after normalization. */\nconst MAX_SLOT_TOKENS = 16;\n\n/**\n * Validate a single mounted-slot token. Shape validation is delegated to the\n * AppElements wire codec so the wire format definition lives in exactly one\n * place. This module only enforces the additional security cap on token byte\n * length to bound cache-key cardinality.\n */\nfunction isValidSlotToken(token: string): boolean {\n if (token.length === 0 || token.length > MAX_TOKEN_LENGTH) return false;\n return AppElementsWire.isSlotId(token);\n}\n\nexport function normalizeMountedSlotsHeader(raw: string | null | undefined): string | null {\n if (!raw) return null;\n if (raw.length > MAX_RAW_HEADER_LENGTH) return null;\n const validTokens = raw.split(/\\s+/).filter((token) => token && isValidSlotToken(token));\n if (validTokens.length === 0) return null;\n const normalized = Array.from(new Set(validTokens)).sort().slice(0, MAX_SLOT_TOKENS).join(\" \");\n return normalized || null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAM,wBAAwB;;AAE9B,MAAM,mBAAmB;;AAEzB,MAAM,kBAAkB;;;;;;;AAQxB,SAAS,iBAAiB,OAAwB;CAChD,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,kBAAkB,OAAO;CAClE,OAAO,gBAAgB,SAAS,MAAM;;AAGxC,SAAgB,4BAA4B,KAA+C;CACzF,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,IAAI,SAAS,uBAAuB,OAAO;CAC/C,MAAM,cAAc,IAAI,MAAM,MAAM,CAAC,QAAQ,UAAU,SAAS,iBAAiB,MAAM,CAAC;CACxF,IAAI,YAAY,WAAW,GAAG,OAAO;CAErC,OADmB,MAAM,KAAK,IAAI,IAAI,YAAY,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAC,KAAK,IACzE,IAAI"}
@@ -1,4 +1,4 @@
1
- import { decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
1
+ import { buildParams, decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
2
2
  import { stripBasePath } from "../utils/base-path.js";
3
3
  import { isUnknownRecord } from "../utils/record.js";
4
4
  import { AppElementsWire } from "./app-elements-wire.js";
@@ -77,35 +77,43 @@ function getRouteTrie(routeManifest) {
77
77
  routeTrieCache.set(routeManifest, trie);
78
78
  return trie;
79
79
  }
80
- function matchNode(node, urlParts, index) {
80
+ function matchNode(node, urlParts, index, entries) {
81
81
  if (index === urlParts.length) {
82
82
  if (node.route !== null) return {
83
83
  route: node.route,
84
- params: Object.create(null)
84
+ params: buildParams(entries)
85
85
  };
86
86
  if (node.optionalCatchAllChild !== null) return {
87
87
  route: node.optionalCatchAllChild.route,
88
- params: Object.create(null)
88
+ params: buildParams(entries)
89
89
  };
90
90
  return null;
91
91
  }
92
92
  const segment = urlParts[index];
93
93
  const staticChild = node.staticChildren.get(segment);
94
- if (staticChild !== void 0) return matchNode(staticChild, urlParts, index + 1);
94
+ if (staticChild !== void 0) return matchNode(staticChild, urlParts, index + 1, entries);
95
95
  if (node.dynamicChild !== null) {
96
- const match = matchNode(node.dynamicChild.node, urlParts, index + 1);
97
- if (match === null) return null;
98
- match.params[node.dynamicChild.paramName] = segment;
99
- return match;
96
+ entries.push([node.dynamicChild.paramName, segment]);
97
+ const match = matchNode(node.dynamicChild.node, urlParts, index + 1, entries);
98
+ if (match !== null) return match;
99
+ entries.pop();
100
+ }
101
+ if (node.catchAllChild !== null) {
102
+ const params = buildParams(entries);
103
+ params[node.catchAllChild.paramName] = urlParts.slice(index);
104
+ return {
105
+ route: node.catchAllChild.route,
106
+ params
107
+ };
108
+ }
109
+ if (node.optionalCatchAllChild !== null) {
110
+ const params = buildParams(entries);
111
+ params[node.optionalCatchAllChild.paramName] = urlParts.slice(index);
112
+ return {
113
+ route: node.optionalCatchAllChild.route,
114
+ params
115
+ };
100
116
  }
101
- if (node.catchAllChild !== null) return {
102
- route: node.catchAllChild.route,
103
- params: { [node.catchAllChild.paramName]: urlParts.slice(index) }
104
- };
105
- if (node.optionalCatchAllChild !== null) return {
106
- route: node.optionalCatchAllChild.route,
107
- params: { [node.optionalCatchAllChild.paramName]: urlParts.slice(index) }
108
- };
109
117
  return null;
110
118
  }
111
119
  function hrefToRouteParts(href, basePath) {
@@ -122,7 +130,7 @@ function hrefToRouteParts(href, basePath) {
122
130
  function matchOptimisticRouteManifestRoute(options) {
123
131
  const urlParts = hrefToRouteParts(options.href, options.basePath);
124
132
  if (urlParts === null) return null;
125
- const match = matchNode(getRouteTrie(options.routeManifest), urlParts, 0);
133
+ const match = matchNode(getRouteTrie(options.routeManifest), urlParts, 0, []);
126
134
  if (match === null) return null;
127
135
  decodeMatchedParams(match.params);
128
136
  return match;
@@ -1 +1 @@
1
- {"version":3,"file":"app-optimistic-routing.js","names":[],"sources":["../../src/server/app-optimistic-routing.ts"],"sourcesContent":["import { createElement, isValidElement, Suspense } from \"react\";\nimport { isUnknownRecord } from \"../utils/record.js\";\nimport { stripBasePath } from \"../utils/base-path.js\";\nimport { decodeMatchedParams, splitPathnameForRouteMatch } from \"../routing/utils.js\";\nimport type { RouteManifest, RouteManifestRoute } from \"../routing/app-route-graph.js\";\nimport { stripRscCacheBustingSearchParam, stripRscSuffix } from \"./app-rsc-cache-busting.js\";\nimport {\n AppElementsWire,\n APP_PREFETCH_LOADING_SHELL_MARKER_KEY,\n type AppElementValue,\n type AppElements,\n} from \"./app-elements.js\";\n\ntype OptimisticRouteTrieNode = {\n catchAllChild: { paramName: string; route: RouteManifestRoute } | null;\n dynamicChild: { node: OptimisticRouteTrieNode; paramName: string } | null;\n optionalCatchAllChild: { paramName: string; route: RouteManifestRoute } | null;\n route: RouteManifestRoute | null;\n staticChildren: Map<string, OptimisticRouteTrieNode>;\n};\n\ntype OptimisticRouteMatch = {\n params: Record<string, string | string[]>;\n route: RouteManifestRoute;\n};\n\nexport type OptimisticRouteTemplate = {\n elements: AppElements;\n mountedSlotsHeader: string | null;\n pageElementIds: readonly string[];\n routeId: string;\n};\n\ntype OptimisticNavigationPayload = {\n elements: AppElements;\n params: Record<string, string | string[]>;\n template: OptimisticRouteTemplate;\n};\n\nconst routeTrieCache = new WeakMap<RouteManifest, OptimisticRouteTrieNode>();\n// Shared never-settling thenable used to suspend optimistic page segments until\n// the real RSC payload replaces them.\nconst OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER = new Promise<never>(() => {});\n\nexport function getOptimisticRouteTemplateKey(options: {\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeId: string;\n}): string {\n return `${options.routeId}\\0${options.interceptionContext ?? \"\"}\\0${options.mountedSlotsHeader ?? \"\"}`;\n}\n\nexport function getOptimisticPrefetchSourceKey(options: {\n cacheKey: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n}): string {\n return `${options.cacheKey}\\0${options.interceptionContext ?? \"\"}\\0${options.mountedSlotsHeader ?? \"\"}`;\n}\n\nfunction createNode(): OptimisticRouteTrieNode {\n return {\n catchAllChild: null,\n dynamicChild: null,\n optionalCatchAllChild: null,\n route: null,\n staticChildren: new Map(),\n };\n}\n\nfunction buildRouteTrie(routeManifest: RouteManifest): OptimisticRouteTrieNode {\n const root = createNode();\n\n for (const route of routeManifest.segmentGraph.routes.values()) {\n let node = root;\n const parts = route.patternParts;\n\n if (parts.length === 0) {\n node.route ??= route;\n continue;\n }\n\n for (const [index, part] of parts.entries()) {\n const isTerminal = index === parts.length - 1;\n if (part.startsWith(\":\") && part.endsWith(\"+\")) {\n if (isTerminal && node.catchAllChild === null) {\n node.catchAllChild = { paramName: part.slice(1, -1), route };\n }\n break;\n }\n\n if (part.startsWith(\":\") && part.endsWith(\"*\")) {\n if (isTerminal && node.optionalCatchAllChild === null) {\n node.optionalCatchAllChild = { paramName: part.slice(1, -1), route };\n }\n break;\n }\n\n if (part.startsWith(\":\")) {\n const paramName = part.slice(1);\n if (node.dynamicChild === null) {\n node.dynamicChild = { node: createNode(), paramName };\n } else if (node.dynamicChild.paramName !== paramName && import.meta.env.DEV) {\n console.warn(\n `[vinext] Optimistic route trie found conflicting dynamic segments at the same level: :${node.dynamicChild.paramName} vs ${part}`,\n );\n }\n node = node.dynamicChild.node;\n if (isTerminal) node.route ??= route;\n continue;\n }\n\n let staticChild = node.staticChildren.get(part);\n if (staticChild === undefined) {\n staticChild = createNode();\n node.staticChildren.set(part, staticChild);\n }\n node = staticChild;\n if (isTerminal) node.route ??= route;\n }\n }\n\n return root;\n}\n\nfunction getRouteTrie(routeManifest: RouteManifest): OptimisticRouteTrieNode {\n const existing = routeTrieCache.get(routeManifest);\n if (existing) return existing;\n\n const trie = buildRouteTrie(routeManifest);\n routeTrieCache.set(routeManifest, trie);\n return trie;\n}\n\nfunction matchNode(\n node: OptimisticRouteTrieNode,\n urlParts: readonly string[],\n index: number,\n): OptimisticRouteMatch | null {\n if (index === urlParts.length) {\n if (node.route !== null) {\n return { route: node.route, params: Object.create(null) };\n }\n if (node.optionalCatchAllChild !== null) {\n return {\n route: node.optionalCatchAllChild.route,\n params: Object.create(null),\n };\n }\n return null;\n }\n\n const segment = urlParts[index];\n const staticChild = node.staticChildren.get(segment);\n if (staticChild !== undefined) {\n // Static children are authoritative for optimistic routing. If a known\n // static subtree does not contain the remaining URL, do not fall through to\n // a catch-all sibling and render the wrong loading boundary.\n return matchNode(staticChild, urlParts, index + 1);\n }\n\n if (node.dynamicChild !== null) {\n const match = matchNode(node.dynamicChild.node, urlParts, index + 1);\n if (match === null) return null;\n match.params[node.dynamicChild.paramName] = segment;\n return match;\n }\n\n if (node.catchAllChild !== null) {\n return {\n route: node.catchAllChild.route,\n params: {\n [node.catchAllChild.paramName]: urlParts.slice(index),\n },\n };\n }\n\n if (node.optionalCatchAllChild !== null) {\n return {\n route: node.optionalCatchAllChild.route,\n params: {\n [node.optionalCatchAllChild.paramName]: urlParts.slice(index),\n },\n };\n }\n\n return null;\n}\n\nfunction hrefToRouteParts(href: string, basePath: string): string[] | null {\n let url: URL;\n try {\n url = new URL(href, \"https://vinext.local\");\n } catch {\n return null;\n }\n\n stripRscCacheBustingSearchParam(url);\n const withoutRscSuffix = stripRscSuffix(url.pathname);\n const appPathname = stripBasePath(withoutRscSuffix, basePath);\n return splitPathnameForRouteMatch(appPathname === \"\" ? \"/\" : appPathname);\n}\n\nexport function matchOptimisticRouteManifestRoute(options: {\n basePath: string;\n href: string;\n routeManifest: RouteManifest;\n}): OptimisticRouteMatch | null {\n const urlParts = hrefToRouteParts(options.href, options.basePath);\n if (urlParts === null) return null;\n\n const match = matchNode(getRouteTrie(options.routeManifest), urlParts, 0);\n if (match === null) return null;\n\n decodeMatchedParams(match.params);\n return match;\n}\n\nfunction elementHasSuspenseFallback(value: unknown, depth = 0): boolean {\n if (depth > 100) return false;\n if (Array.isArray(value)) {\n return value.some((entry) => elementHasSuspenseFallback(entry, depth + 1));\n }\n if (!isValidElement(value)) return false;\n\n const props = Reflect.get(value, \"props\");\n if (value.type === Suspense && isUnknownRecord(props)) {\n const fallback = Reflect.get(props, \"fallback\");\n if (fallback !== null && fallback !== undefined) return true;\n }\n\n if (!isUnknownRecord(props)) return false;\n return elementHasSuspenseFallback(Reflect.get(props, \"children\"), depth + 1);\n}\n\nfunction getPageElementIds(elements: AppElements): string[] {\n return Object.keys(elements)\n .filter((key) => AppElementsWire.parseElementKey(key)?.kind === \"page\")\n .sort();\n}\n\nfunction OptimisticRouteSegment(): null {\n throw OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER;\n}\n\nexport function createOptimisticRouteTemplate(options: {\n allowLoadingShell?: boolean;\n basePath: string;\n elements: AppElements;\n href: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeManifest: RouteManifest;\n}): OptimisticRouteTemplate | null {\n const match = matchOptimisticRouteManifestRoute({\n basePath: options.basePath,\n href: options.href,\n routeManifest: options.routeManifest,\n });\n if (match === null || !match.route.isDynamic) return null;\n if (options.interceptionContext !== null) return null;\n\n const metadata = AppElementsWire.readMetadata(options.elements);\n if (metadata.interception !== null || metadata.interceptionContext !== null) return null;\n\n const routeElement = options.elements[metadata.routeId];\n // Full-prefetch learning is intentionally heuristic: legacy full prefetches\n // are accepted only when the serialized route subtree still contains a\n // Suspense fallback. Authoritative loading-shell prefetches use the marker\n // check below instead.\n if (!options.allowLoadingShell && !elementHasSuspenseFallback(routeElement)) return null;\n if (\n options.allowLoadingShell &&\n options.elements[APP_PREFETCH_LOADING_SHELL_MARKER_KEY] !== \"LoadingBoundary\"\n ) {\n return null;\n }\n // Shell prefetches must include the eagerly-rendered loading component. A\n // null route element means the server had no route loading boundary.\n if (options.allowLoadingShell && (routeElement === undefined || routeElement === null))\n return null;\n\n const pageElementIds = getPageElementIds(options.elements);\n if (pageElementIds.length === 0) return null;\n\n return {\n elements: options.elements,\n mountedSlotsHeader: options.mountedSlotsHeader,\n pageElementIds,\n routeId: match.route.id,\n };\n}\n\nexport function createOptimisticRouteElements(template: OptimisticRouteTemplate): AppElements {\n const elements: Record<string, AppElementValue> = { ...template.elements };\n for (const pageElementId of template.pageElementIds) {\n elements[pageElementId] = createElement(OptimisticRouteSegment);\n }\n return elements;\n}\n\nexport function resolveOptimisticNavigationPayload(options: {\n basePath: string;\n href: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeManifest: RouteManifest;\n templates: ReadonlyMap<string, OptimisticRouteTemplate>;\n}): OptimisticNavigationPayload | null {\n if (options.interceptionContext !== null) return null;\n\n const match = matchOptimisticRouteManifestRoute({\n basePath: options.basePath,\n href: options.href,\n routeManifest: options.routeManifest,\n });\n if (match === null || !match.route.isDynamic) return null;\n\n const template = options.templates.get(\n getOptimisticRouteTemplateKey({\n interceptionContext: options.interceptionContext,\n mountedSlotsHeader: options.mountedSlotsHeader,\n routeId: match.route.id,\n }),\n );\n if (template === undefined) return null;\n if (template.mountedSlotsHeader !== options.mountedSlotsHeader) return null;\n\n return {\n elements: createOptimisticRouteElements(template),\n params: match.params,\n template,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-optimistic-routing.js","names":[],"sources":["../../src/server/app-optimistic-routing.ts"],"sourcesContent":["import { createElement, isValidElement, Suspense } from \"react\";\nimport { isUnknownRecord } from \"../utils/record.js\";\nimport { stripBasePath } from \"../utils/base-path.js\";\nimport { buildParams, decodeMatchedParams, splitPathnameForRouteMatch } from \"../routing/utils.js\";\nimport type { RouteManifest, RouteManifestRoute } from \"../routing/app-route-graph.js\";\nimport { stripRscCacheBustingSearchParam, stripRscSuffix } from \"./app-rsc-cache-busting.js\";\nimport {\n AppElementsWire,\n APP_PREFETCH_LOADING_SHELL_MARKER_KEY,\n type AppElementValue,\n type AppElements,\n} from \"./app-elements.js\";\n\ntype OptimisticRouteTrieNode = {\n catchAllChild: { paramName: string; route: RouteManifestRoute } | null;\n dynamicChild: { node: OptimisticRouteTrieNode; paramName: string } | null;\n optionalCatchAllChild: { paramName: string; route: RouteManifestRoute } | null;\n route: RouteManifestRoute | null;\n staticChildren: Map<string, OptimisticRouteTrieNode>;\n};\n\ntype OptimisticRouteMatch = {\n params: Record<string, string | string[]>;\n route: RouteManifestRoute;\n};\n\nexport type OptimisticRouteTemplate = {\n elements: AppElements;\n mountedSlotsHeader: string | null;\n pageElementIds: readonly string[];\n routeId: string;\n};\n\ntype OptimisticNavigationPayload = {\n elements: AppElements;\n params: Record<string, string | string[]>;\n template: OptimisticRouteTemplate;\n};\n\nconst routeTrieCache = new WeakMap<RouteManifest, OptimisticRouteTrieNode>();\n// Shared never-settling thenable used to suspend optimistic page segments until\n// the real RSC payload replaces them.\nconst OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER = new Promise<never>(() => {});\n\nexport function getOptimisticRouteTemplateKey(options: {\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeId: string;\n}): string {\n return `${options.routeId}\\0${options.interceptionContext ?? \"\"}\\0${options.mountedSlotsHeader ?? \"\"}`;\n}\n\nexport function getOptimisticPrefetchSourceKey(options: {\n cacheKey: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n}): string {\n return `${options.cacheKey}\\0${options.interceptionContext ?? \"\"}\\0${options.mountedSlotsHeader ?? \"\"}`;\n}\n\nfunction createNode(): OptimisticRouteTrieNode {\n return {\n catchAllChild: null,\n dynamicChild: null,\n optionalCatchAllChild: null,\n route: null,\n staticChildren: new Map(),\n };\n}\n\nfunction buildRouteTrie(routeManifest: RouteManifest): OptimisticRouteTrieNode {\n const root = createNode();\n\n for (const route of routeManifest.segmentGraph.routes.values()) {\n let node = root;\n const parts = route.patternParts;\n\n if (parts.length === 0) {\n node.route ??= route;\n continue;\n }\n\n for (const [index, part] of parts.entries()) {\n const isTerminal = index === parts.length - 1;\n if (part.startsWith(\":\") && part.endsWith(\"+\")) {\n if (isTerminal && node.catchAllChild === null) {\n node.catchAllChild = { paramName: part.slice(1, -1), route };\n }\n break;\n }\n\n if (part.startsWith(\":\") && part.endsWith(\"*\")) {\n if (isTerminal && node.optionalCatchAllChild === null) {\n node.optionalCatchAllChild = { paramName: part.slice(1, -1), route };\n }\n break;\n }\n\n if (part.startsWith(\":\")) {\n const paramName = part.slice(1);\n if (node.dynamicChild === null) {\n node.dynamicChild = { node: createNode(), paramName };\n } else if (node.dynamicChild.paramName !== paramName && import.meta.env.DEV) {\n console.warn(\n `[vinext] Optimistic route trie found conflicting dynamic segments at the same level: :${node.dynamicChild.paramName} vs ${part}`,\n );\n }\n node = node.dynamicChild.node;\n if (isTerminal) node.route ??= route;\n continue;\n }\n\n let staticChild = node.staticChildren.get(part);\n if (staticChild === undefined) {\n staticChild = createNode();\n node.staticChildren.set(part, staticChild);\n }\n node = staticChild;\n if (isTerminal) node.route ??= route;\n }\n }\n\n return root;\n}\n\nfunction getRouteTrie(routeManifest: RouteManifest): OptimisticRouteTrieNode {\n const existing = routeTrieCache.get(routeManifest);\n if (existing) return existing;\n\n const trie = buildRouteTrie(routeManifest);\n routeTrieCache.set(routeManifest, trie);\n return trie;\n}\n\nfunction matchNode(\n node: OptimisticRouteTrieNode,\n urlParts: readonly string[],\n index: number,\n entries: Array<[string, string | string[]]>,\n): OptimisticRouteMatch | null {\n if (index === urlParts.length) {\n if (node.route !== null) {\n return { route: node.route, params: buildParams(entries) };\n }\n if (node.optionalCatchAllChild !== null) {\n return {\n route: node.optionalCatchAllChild.route,\n params: buildParams(entries),\n };\n }\n return null;\n }\n\n const segment = urlParts[index];\n const staticChild = node.staticChildren.get(segment);\n if (staticChild !== undefined) {\n // Static children are authoritative for optimistic routing. If a known\n // static subtree does not contain the remaining URL, do not fall through to\n // a catch-all sibling and render the wrong loading boundary.\n return matchNode(staticChild, urlParts, index + 1, entries);\n }\n\n if (node.dynamicChild !== null) {\n entries.push([node.dynamicChild.paramName, segment]);\n const match = matchNode(node.dynamicChild.node, urlParts, index + 1, entries);\n if (match !== null) return match;\n entries.pop();\n }\n\n if (node.catchAllChild !== null) {\n const params = buildParams(entries);\n params[node.catchAllChild.paramName] = urlParts.slice(index);\n return { route: node.catchAllChild.route, params };\n }\n\n // At this point index < urlParts.length, so remaining always has ≥1 segment.\n if (node.optionalCatchAllChild !== null) {\n const params = buildParams(entries);\n params[node.optionalCatchAllChild.paramName] = urlParts.slice(index);\n return { route: node.optionalCatchAllChild.route, params };\n }\n\n return null;\n}\n\nfunction hrefToRouteParts(href: string, basePath: string): string[] | null {\n let url: URL;\n try {\n url = new URL(href, \"https://vinext.local\");\n } catch {\n return null;\n }\n\n stripRscCacheBustingSearchParam(url);\n const withoutRscSuffix = stripRscSuffix(url.pathname);\n const appPathname = stripBasePath(withoutRscSuffix, basePath);\n return splitPathnameForRouteMatch(appPathname === \"\" ? \"/\" : appPathname);\n}\n\nexport function matchOptimisticRouteManifestRoute(options: {\n basePath: string;\n href: string;\n routeManifest: RouteManifest;\n}): OptimisticRouteMatch | null {\n const urlParts = hrefToRouteParts(options.href, options.basePath);\n if (urlParts === null) return null;\n\n const match = matchNode(getRouteTrie(options.routeManifest), urlParts, 0, []);\n if (match === null) return null;\n\n decodeMatchedParams(match.params);\n return match;\n}\n\nfunction elementHasSuspenseFallback(value: unknown, depth = 0): boolean {\n if (depth > 100) return false;\n if (Array.isArray(value)) {\n return value.some((entry) => elementHasSuspenseFallback(entry, depth + 1));\n }\n if (!isValidElement(value)) return false;\n\n const props = Reflect.get(value, \"props\");\n if (value.type === Suspense && isUnknownRecord(props)) {\n const fallback = Reflect.get(props, \"fallback\");\n if (fallback !== null && fallback !== undefined) return true;\n }\n\n if (!isUnknownRecord(props)) return false;\n return elementHasSuspenseFallback(Reflect.get(props, \"children\"), depth + 1);\n}\n\nfunction getPageElementIds(elements: AppElements): string[] {\n return Object.keys(elements)\n .filter((key) => AppElementsWire.parseElementKey(key)?.kind === \"page\")\n .sort();\n}\n\nfunction OptimisticRouteSegment(): null {\n throw OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER;\n}\n\nexport function createOptimisticRouteTemplate(options: {\n allowLoadingShell?: boolean;\n basePath: string;\n elements: AppElements;\n href: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeManifest: RouteManifest;\n}): OptimisticRouteTemplate | null {\n const match = matchOptimisticRouteManifestRoute({\n basePath: options.basePath,\n href: options.href,\n routeManifest: options.routeManifest,\n });\n if (match === null || !match.route.isDynamic) return null;\n if (options.interceptionContext !== null) return null;\n\n const metadata = AppElementsWire.readMetadata(options.elements);\n if (metadata.interception !== null || metadata.interceptionContext !== null) return null;\n\n const routeElement = options.elements[metadata.routeId];\n // Full-prefetch learning is intentionally heuristic: legacy full prefetches\n // are accepted only when the serialized route subtree still contains a\n // Suspense fallback. Authoritative loading-shell prefetches use the marker\n // check below instead.\n if (!options.allowLoadingShell && !elementHasSuspenseFallback(routeElement)) return null;\n if (\n options.allowLoadingShell &&\n options.elements[APP_PREFETCH_LOADING_SHELL_MARKER_KEY] !== \"LoadingBoundary\"\n ) {\n return null;\n }\n // Shell prefetches must include the eagerly-rendered loading component. A\n // null route element means the server had no route loading boundary.\n if (options.allowLoadingShell && (routeElement === undefined || routeElement === null))\n return null;\n\n const pageElementIds = getPageElementIds(options.elements);\n if (pageElementIds.length === 0) return null;\n\n return {\n elements: options.elements,\n mountedSlotsHeader: options.mountedSlotsHeader,\n pageElementIds,\n routeId: match.route.id,\n };\n}\n\nexport function createOptimisticRouteElements(template: OptimisticRouteTemplate): AppElements {\n const elements: Record<string, AppElementValue> = { ...template.elements };\n for (const pageElementId of template.pageElementIds) {\n elements[pageElementId] = createElement(OptimisticRouteSegment);\n }\n return elements;\n}\n\nexport function resolveOptimisticNavigationPayload(options: {\n basePath: string;\n href: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeManifest: RouteManifest;\n templates: ReadonlyMap<string, OptimisticRouteTemplate>;\n}): OptimisticNavigationPayload | null {\n if (options.interceptionContext !== null) return null;\n\n const match = matchOptimisticRouteManifestRoute({\n basePath: options.basePath,\n href: options.href,\n routeManifest: options.routeManifest,\n });\n if (match === null || !match.route.isDynamic) return null;\n\n const template = options.templates.get(\n getOptimisticRouteTemplateKey({\n interceptionContext: options.interceptionContext,\n mountedSlotsHeader: options.mountedSlotsHeader,\n routeId: match.route.id,\n }),\n );\n if (template === undefined) return null;\n if (template.mountedSlotsHeader !== options.mountedSlotsHeader) return null;\n\n return {\n elements: createOptimisticRouteElements(template),\n params: match.params,\n template,\n };\n}\n"],"mappings":"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"}
@@ -34,6 +34,7 @@ type AppPageBoundaryRenderCommonOptions<TModule extends AppPageModule = AppPageM
34
34
  getFontStyles: () => string[];
35
35
  getNavigationContext: () => unknown;
36
36
  globalErrorModule?: TModule | null;
37
+ isEdgeRuntime?: boolean;
37
38
  isRscRequest: boolean;
38
39
  loadSsrHandler: () => Promise<AppPageSsrHandler>;
39
40
  makeThenableParams: (params: AppPageParams) => unknown;
@@ -93,6 +93,7 @@ async function renderAppPageBoundaryElementResponse(options) {
93
93
  clearRequestContext: options.clearRequestContext,
94
94
  fontData,
95
95
  fontLinkHeader: options.buildFontLinkHeader(fontData.preloads),
96
+ isEdgeRuntime: options.isEdgeRuntime,
96
97
  middlewareHeaders: options.middlewareContext.headers,
97
98
  navigationContext: options.getNavigationContext(),
98
99
  rscStream,
@@ -110,6 +111,7 @@ async function renderAppPageBoundaryElementResponse(options) {
110
111
  pathname,
111
112
  route: options.route
112
113
  }),
114
+ isEdgeRuntime: options.isEdgeRuntime,
113
115
  isRscRequest: options.isRscRequest,
114
116
  middlewareHeaders: options.middlewareContext.headers,
115
117
  renderToReadableStream: options.renderToReadableStream,