vinext 0.0.51 → 0.0.53

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 (423) 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/precompress.d.ts +7 -7
  6. package/dist/build/precompress.js +18 -17
  7. package/dist/build/precompress.js.map +1 -1
  8. package/dist/build/prerender.d.ts +9 -16
  9. package/dist/build/prerender.js +88 -50
  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-args.d.ts +1 -0
  19. package/dist/cli-args.js +5 -0
  20. package/dist/cli-args.js.map +1 -1
  21. package/dist/cli.js +58 -4
  22. package/dist/cli.js.map +1 -1
  23. package/dist/client/instrumentation-client-inject.d.ts +34 -0
  24. package/dist/client/instrumentation-client-inject.js +57 -0
  25. package/dist/client/instrumentation-client-inject.js.map +1 -0
  26. package/dist/client/navigation-runtime.d.ts +60 -0
  27. package/dist/client/navigation-runtime.js +171 -0
  28. package/dist/client/navigation-runtime.js.map +1 -0
  29. package/dist/client/pages-router-link-navigation.d.ts +26 -0
  30. package/dist/client/pages-router-link-navigation.js +14 -0
  31. package/dist/client/pages-router-link-navigation.js.map +1 -0
  32. package/dist/client/vinext-next-data.d.ts +14 -3
  33. package/dist/client/vinext-next-data.js +50 -1
  34. package/dist/client/vinext-next-data.js.map +1 -0
  35. package/dist/client/window-next.d.ts +10 -2
  36. package/dist/client/window-next.js.map +1 -1
  37. package/dist/cloudflare/kv-cache-handler.js +2 -1
  38. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  39. package/dist/cloudflare/tpr.js +1 -1
  40. package/dist/cloudflare/tpr.js.map +1 -1
  41. package/dist/config/config-matchers.d.ts +63 -16
  42. package/dist/config/config-matchers.js +145 -9
  43. package/dist/config/config-matchers.js.map +1 -1
  44. package/dist/config/next-config.d.ts +32 -5
  45. package/dist/config/next-config.js +55 -15
  46. package/dist/config/next-config.js.map +1 -1
  47. package/dist/deploy.js +130 -46
  48. package/dist/deploy.js.map +1 -1
  49. package/dist/entries/app-browser-entry.js +9 -3
  50. package/dist/entries/app-browser-entry.js.map +1 -1
  51. package/dist/entries/app-rsc-entry.d.ts +4 -2
  52. package/dist/entries/app-rsc-entry.js +76 -16
  53. package/dist/entries/app-rsc-entry.js.map +1 -1
  54. package/dist/entries/app-rsc-manifest.d.ts +1 -0
  55. package/dist/entries/app-rsc-manifest.js +53 -6
  56. package/dist/entries/app-rsc-manifest.js.map +1 -1
  57. package/dist/entries/app-ssr-entry.d.ts +3 -3
  58. package/dist/entries/app-ssr-entry.js +4 -4
  59. package/dist/entries/app-ssr-entry.js.map +1 -1
  60. package/dist/entries/pages-client-entry.js +40 -3
  61. package/dist/entries/pages-client-entry.js.map +1 -1
  62. package/dist/entries/pages-server-entry.js +261 -31
  63. package/dist/entries/pages-server-entry.js.map +1 -1
  64. package/dist/entries/runtime-entry-module.d.ts +2 -1
  65. package/dist/entries/runtime-entry-module.js +9 -3
  66. package/dist/entries/runtime-entry-module.js.map +1 -1
  67. package/dist/index.js +161 -46
  68. package/dist/index.js.map +1 -1
  69. package/dist/plugins/css-data-url.d.ts +7 -0
  70. package/dist/plugins/css-data-url.js +81 -0
  71. package/dist/plugins/css-data-url.js.map +1 -0
  72. package/dist/plugins/fonts.js +30 -5
  73. package/dist/plugins/fonts.js.map +1 -1
  74. package/dist/plugins/middleware-server-only.d.ts +54 -0
  75. package/dist/plugins/middleware-server-only.js +91 -0
  76. package/dist/plugins/middleware-server-only.js.map +1 -0
  77. package/dist/plugins/optimize-imports.js +4 -4
  78. package/dist/plugins/optimize-imports.js.map +1 -1
  79. package/dist/plugins/strip-server-exports.js +5 -8
  80. package/dist/plugins/strip-server-exports.js.map +1 -1
  81. package/dist/routing/app-route-graph.d.ts +20 -1
  82. package/dist/routing/app-route-graph.js +58 -6
  83. package/dist/routing/app-route-graph.js.map +1 -1
  84. package/dist/routing/app-router.d.ts +2 -2
  85. package/dist/routing/app-router.js +2 -2
  86. package/dist/routing/app-router.js.map +1 -1
  87. package/dist/routing/route-trie.js +13 -18
  88. package/dist/routing/route-trie.js.map +1 -1
  89. package/dist/routing/utils.d.ts +12 -1
  90. package/dist/routing/utils.js +18 -1
  91. package/dist/routing/utils.js.map +1 -1
  92. package/dist/server/api-handler.js +153 -42
  93. package/dist/server/api-handler.js.map +1 -1
  94. package/dist/server/app-browser-action-result.d.ts +16 -1
  95. package/dist/server/app-browser-action-result.js +15 -1
  96. package/dist/server/app-browser-action-result.js.map +1 -1
  97. package/dist/server/app-browser-entry.js +309 -155
  98. package/dist/server/app-browser-entry.js.map +1 -1
  99. package/dist/server/app-browser-interception-context.d.ts +24 -0
  100. package/dist/server/app-browser-interception-context.js +32 -0
  101. package/dist/server/app-browser-interception-context.js.map +1 -0
  102. package/dist/server/app-browser-navigation-controller.d.ts +3 -1
  103. package/dist/server/app-browser-navigation-controller.js +5 -1
  104. package/dist/server/app-browser-navigation-controller.js.map +1 -1
  105. package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
  106. package/dist/server/app-browser-rsc-redirect.js +2 -2
  107. package/dist/server/app-browser-rsc-redirect.js.map +1 -1
  108. package/dist/server/app-browser-state.d.ts +18 -1
  109. package/dist/server/app-browser-state.js +19 -1
  110. package/dist/server/app-browser-state.js.map +1 -1
  111. package/dist/server/app-browser-stream.d.ts +5 -14
  112. package/dist/server/app-browser-stream.js +13 -7
  113. package/dist/server/app-browser-stream.js.map +1 -1
  114. package/dist/server/app-browser-visible-commit.d.ts +2 -1
  115. package/dist/server/app-browser-visible-commit.js +1 -0
  116. package/dist/server/app-browser-visible-commit.js.map +1 -1
  117. package/dist/server/app-elements-wire.d.ts +10 -5
  118. package/dist/server/app-elements-wire.js +84 -2
  119. package/dist/server/app-elements-wire.js.map +1 -1
  120. package/dist/server/app-elements.d.ts +3 -2
  121. package/dist/server/app-elements.js +3 -2
  122. package/dist/server/app-elements.js.map +1 -1
  123. package/dist/server/app-fallback-renderer.d.ts +12 -3
  124. package/dist/server/app-fallback-renderer.js +15 -8
  125. package/dist/server/app-fallback-renderer.js.map +1 -1
  126. package/dist/server/app-history-state.js +6 -2
  127. package/dist/server/app-history-state.js.map +1 -1
  128. package/dist/server/app-interception-context-header.d.ts +33 -0
  129. package/dist/server/app-interception-context-header.js +44 -0
  130. package/dist/server/app-interception-context-header.js.map +1 -0
  131. package/dist/server/app-middleware.d.ts +13 -0
  132. package/dist/server/app-middleware.js +3 -1
  133. package/dist/server/app-middleware.js.map +1 -1
  134. package/dist/server/app-mounted-slots-header.d.ts +19 -0
  135. package/dist/server/app-mounted-slots-header.js +40 -1
  136. package/dist/server/app-mounted-slots-header.js.map +1 -1
  137. package/dist/server/app-optimistic-routing.d.ts +54 -0
  138. package/dist/server/app-optimistic-routing.js +208 -0
  139. package/dist/server/app-optimistic-routing.js.map +1 -0
  140. package/dist/server/app-page-boundary-render.d.ts +1 -0
  141. package/dist/server/app-page-boundary-render.js +2 -0
  142. package/dist/server/app-page-boundary-render.js.map +1 -1
  143. package/dist/server/app-page-boundary.d.ts +1 -0
  144. package/dist/server/app-page-boundary.js +2 -0
  145. package/dist/server/app-page-boundary.js.map +1 -1
  146. package/dist/server/app-page-cache.d.ts +15 -1
  147. package/dist/server/app-page-cache.js +68 -7
  148. package/dist/server/app-page-cache.js.map +1 -1
  149. package/dist/server/app-page-dispatch.d.ts +5 -0
  150. package/dist/server/app-page-dispatch.js +39 -5
  151. package/dist/server/app-page-dispatch.js.map +1 -1
  152. package/dist/server/app-page-element-builder.d.ts +2 -1
  153. package/dist/server/app-page-element-builder.js +7 -3
  154. package/dist/server/app-page-element-builder.js.map +1 -1
  155. package/dist/server/app-page-execution.d.ts +29 -1
  156. package/dist/server/app-page-execution.js +91 -4
  157. package/dist/server/app-page-execution.js.map +1 -1
  158. package/dist/server/app-page-head.d.ts +1 -0
  159. package/dist/server/app-page-head.js +29 -2
  160. package/dist/server/app-page-head.js.map +1 -1
  161. package/dist/server/app-page-probe.js +1 -1
  162. package/dist/server/app-page-render-observation.js +1 -1
  163. package/dist/server/app-page-render.d.ts +3 -0
  164. package/dist/server/app-page-render.js +7 -3
  165. package/dist/server/app-page-render.js.map +1 -1
  166. package/dist/server/app-page-response.d.ts +11 -1
  167. package/dist/server/app-page-response.js +18 -5
  168. package/dist/server/app-page-response.js.map +1 -1
  169. package/dist/server/app-page-route-wiring.d.ts +1 -0
  170. package/dist/server/app-page-route-wiring.js +35 -15
  171. package/dist/server/app-page-route-wiring.js.map +1 -1
  172. package/dist/server/app-page-stream.d.ts +4 -0
  173. package/dist/server/app-page-stream.js +3 -0
  174. package/dist/server/app-page-stream.js.map +1 -1
  175. package/dist/server/app-prerender-static-params.d.ts +2 -1
  176. package/dist/server/app-prerender-static-params.js +44 -8
  177. package/dist/server/app-prerender-static-params.js.map +1 -1
  178. package/dist/server/app-route-handler-cache.d.ts +2 -2
  179. package/dist/server/app-route-handler-cache.js +3 -2
  180. package/dist/server/app-route-handler-cache.js.map +1 -1
  181. package/dist/server/app-route-handler-dispatch.d.ts +7 -1
  182. package/dist/server/app-route-handler-dispatch.js +4 -1
  183. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  184. package/dist/server/app-route-handler-execution.d.ts +18 -2
  185. package/dist/server/app-route-handler-execution.js +1 -0
  186. package/dist/server/app-route-handler-execution.js.map +1 -1
  187. package/dist/server/app-route-handler-response.js +6 -5
  188. package/dist/server/app-route-handler-response.js.map +1 -1
  189. package/dist/server/app-router-entry.js +6 -2
  190. package/dist/server/app-router-entry.js.map +1 -1
  191. package/dist/server/app-rsc-handler.d.ts +11 -1
  192. package/dist/server/app-rsc-handler.js +48 -21
  193. package/dist/server/app-rsc-handler.js.map +1 -1
  194. package/dist/server/app-rsc-render-mode.d.ts +4 -3
  195. package/dist/server/app-rsc-render-mode.js +7 -1
  196. package/dist/server/app-rsc-render-mode.js.map +1 -1
  197. package/dist/server/app-rsc-request-normalization.d.ts +4 -1
  198. package/dist/server/app-rsc-request-normalization.js +6 -2
  199. package/dist/server/app-rsc-request-normalization.js.map +1 -1
  200. package/dist/server/app-rsc-response-finalizer.d.ts +8 -1
  201. package/dist/server/app-rsc-response-finalizer.js +10 -3
  202. package/dist/server/app-rsc-response-finalizer.js.map +1 -1
  203. package/dist/server/app-rsc-route-matching.js +2 -2
  204. package/dist/server/app-rsc-route-matching.js.map +1 -1
  205. package/dist/server/app-segment-config.d.ts +4 -1
  206. package/dist/server/app-segment-config.js +6 -1
  207. package/dist/server/app-segment-config.js.map +1 -1
  208. package/dist/server/app-server-action-execution.d.ts +1 -0
  209. package/dist/server/app-server-action-execution.js +5 -1
  210. package/dist/server/app-server-action-execution.js.map +1 -1
  211. package/dist/server/app-ssr-entry.d.ts +2 -0
  212. package/dist/server/app-ssr-entry.js +92 -55
  213. package/dist/server/app-ssr-entry.js.map +1 -1
  214. package/dist/server/app-ssr-stream.d.ts +30 -2
  215. package/dist/server/app-ssr-stream.js +95 -8
  216. package/dist/server/app-ssr-stream.js.map +1 -1
  217. package/dist/server/app-static-generation.d.ts +1 -0
  218. package/dist/server/app-static-generation.js +2 -1
  219. package/dist/server/app-static-generation.js.map +1 -1
  220. package/dist/server/artifact-compatibility.d.ts +1 -1
  221. package/dist/server/artifact-compatibility.js.map +1 -1
  222. package/dist/server/cache-headers.d.ts +7 -0
  223. package/dist/server/cache-headers.js +19 -0
  224. package/dist/server/cache-headers.js.map +1 -0
  225. package/dist/server/cache-proof.d.ts +49 -3
  226. package/dist/server/cache-proof.js +78 -22
  227. package/dist/server/cache-proof.js.map +1 -1
  228. package/dist/server/client-reuse-manifest.d.ts +99 -0
  229. package/dist/server/client-reuse-manifest.js +212 -0
  230. package/dist/server/client-reuse-manifest.js.map +1 -0
  231. package/dist/server/default-global-error-module.d.ts +20 -0
  232. package/dist/server/default-global-error-module.js +20 -0
  233. package/dist/server/default-global-error-module.js.map +1 -0
  234. package/dist/server/default-not-found-module.d.ts +20 -0
  235. package/dist/server/default-not-found-module.js +20 -0
  236. package/dist/server/default-not-found-module.js.map +1 -0
  237. package/dist/server/dev-server.d.ts +10 -2
  238. package/dist/server/dev-server.js +99 -36
  239. package/dist/server/dev-server.js.map +1 -1
  240. package/dist/server/edge-api-runtime.d.ts +5 -0
  241. package/dist/server/edge-api-runtime.js +8 -0
  242. package/dist/server/edge-api-runtime.js.map +1 -0
  243. package/dist/server/headers.d.ts +22 -1
  244. package/dist/server/headers.js +22 -1
  245. package/dist/server/headers.js.map +1 -1
  246. package/dist/server/http-error-responses.d.ts +16 -1
  247. package/dist/server/http-error-responses.js +21 -1
  248. package/dist/server/http-error-responses.js.map +1 -1
  249. package/dist/server/image-optimization.d.ts +13 -4
  250. package/dist/server/image-optimization.js +15 -4
  251. package/dist/server/image-optimization.js.map +1 -1
  252. package/dist/server/isr-cache.d.ts +6 -2
  253. package/dist/server/isr-cache.js +20 -4
  254. package/dist/server/isr-cache.js.map +1 -1
  255. package/dist/server/middleware-runtime.d.ts +15 -0
  256. package/dist/server/middleware-runtime.js +59 -7
  257. package/dist/server/middleware-runtime.js.map +1 -1
  258. package/dist/server/middleware.d.ts +1 -1
  259. package/dist/server/middleware.js +5 -3
  260. package/dist/server/middleware.js.map +1 -1
  261. package/dist/server/navigation-planner.d.ts +9 -3
  262. package/dist/server/navigation-planner.js +98 -25
  263. package/dist/server/navigation-planner.js.map +1 -1
  264. package/dist/server/navigation-trace.d.ts +2 -1
  265. package/dist/server/navigation-trace.js +1 -0
  266. package/dist/server/navigation-trace.js.map +1 -1
  267. package/dist/server/pages-api-route.d.ts +45 -1
  268. package/dist/server/pages-api-route.js +27 -4
  269. package/dist/server/pages-api-route.js.map +1 -1
  270. package/dist/server/pages-body-parser-config.d.ts +60 -0
  271. package/dist/server/pages-body-parser-config.js +79 -0
  272. package/dist/server/pages-body-parser-config.js.map +1 -0
  273. package/dist/server/pages-data-route.d.ts +77 -0
  274. package/dist/server/pages-data-route.js +98 -0
  275. package/dist/server/pages-data-route.js.map +1 -0
  276. package/dist/server/pages-default-404.d.ts +31 -0
  277. package/dist/server/pages-default-404.js +40 -0
  278. package/dist/server/pages-default-404.js.map +1 -0
  279. package/dist/server/pages-i18n.d.ts +51 -1
  280. package/dist/server/pages-i18n.js +61 -1
  281. package/dist/server/pages-i18n.js.map +1 -1
  282. package/dist/server/pages-node-compat.d.ts +10 -0
  283. package/dist/server/pages-node-compat.js +12 -1
  284. package/dist/server/pages-node-compat.js.map +1 -1
  285. package/dist/server/pages-page-data.d.ts +69 -2
  286. package/dist/server/pages-page-data.js +47 -31
  287. package/dist/server/pages-page-data.js.map +1 -1
  288. package/dist/server/pages-page-response.d.ts +13 -1
  289. package/dist/server/pages-page-response.js +16 -11
  290. package/dist/server/pages-page-response.js.map +1 -1
  291. package/dist/server/prerender-route-params.d.ts +14 -0
  292. package/dist/server/prerender-route-params.js +94 -0
  293. package/dist/server/prerender-route-params.js.map +1 -0
  294. package/dist/server/prod-server.d.ts +15 -37
  295. package/dist/server/prod-server.js +143 -107
  296. package/dist/server/prod-server.js.map +1 -1
  297. package/dist/server/proxy-trust.d.ts +41 -0
  298. package/dist/server/proxy-trust.js +70 -0
  299. package/dist/server/proxy-trust.js.map +1 -0
  300. package/dist/server/request-pipeline.d.ts +13 -4
  301. package/dist/server/request-pipeline.js +32 -14
  302. package/dist/server/request-pipeline.js.map +1 -1
  303. package/dist/server/seed-cache.d.ts +12 -31
  304. package/dist/server/seed-cache.js +30 -37
  305. package/dist/server/seed-cache.js.map +1 -1
  306. package/dist/server/server-action-not-found.js +8 -3
  307. package/dist/server/server-action-not-found.js.map +1 -1
  308. package/dist/server/skip-cache-proof.d.ts +41 -0
  309. package/dist/server/skip-cache-proof.js +101 -0
  310. package/dist/server/skip-cache-proof.js.map +1 -0
  311. package/dist/server/static-file-cache.d.ts +1 -1
  312. package/dist/server/static-file-cache.js +8 -7
  313. package/dist/server/static-file-cache.js.map +1 -1
  314. package/dist/server/streaming-metadata.d.ts +5 -0
  315. package/dist/server/streaming-metadata.js +10 -0
  316. package/dist/server/streaming-metadata.js.map +1 -0
  317. package/dist/shims/app-router-scroll-state.d.ts +12 -0
  318. package/dist/shims/app-router-scroll-state.js +38 -0
  319. package/dist/shims/app-router-scroll-state.js.map +1 -0
  320. package/dist/shims/app-router-scroll.d.ts +14 -0
  321. package/dist/shims/app-router-scroll.js +100 -0
  322. package/dist/shims/app-router-scroll.js.map +1 -0
  323. package/dist/shims/before-interactive-context.d.ts +30 -0
  324. package/dist/shims/before-interactive-context.js +10 -0
  325. package/dist/shims/before-interactive-context.js.map +1 -0
  326. package/dist/shims/cache-runtime.d.ts +1 -1
  327. package/dist/shims/cache-runtime.js +14 -1
  328. package/dist/shims/cache-runtime.js.map +1 -1
  329. package/dist/shims/client-locale.d.ts +15 -0
  330. package/dist/shims/client-locale.js +13 -0
  331. package/dist/shims/client-locale.js.map +1 -0
  332. package/dist/shims/default-global-error.d.ts +32 -0
  333. package/dist/shims/default-global-error.js +181 -0
  334. package/dist/shims/default-global-error.js.map +1 -0
  335. package/dist/shims/default-not-found.d.ts +12 -0
  336. package/dist/shims/default-not-found.js +61 -0
  337. package/dist/shims/default-not-found.js.map +1 -0
  338. package/dist/shims/document.d.ts +59 -3
  339. package/dist/shims/document.js +36 -5
  340. package/dist/shims/document.js.map +1 -1
  341. package/dist/shims/error-boundary.d.ts +2 -2
  342. package/dist/shims/font-local.d.ts +5 -0
  343. package/dist/shims/font-local.js +6 -2
  344. package/dist/shims/font-local.js.map +1 -1
  345. package/dist/shims/form.js +13 -6
  346. package/dist/shims/form.js.map +1 -1
  347. package/dist/shims/head.js +4 -4
  348. package/dist/shims/head.js.map +1 -1
  349. package/dist/shims/headers.d.ts +6 -2
  350. package/dist/shims/headers.js +64 -21
  351. package/dist/shims/headers.js.map +1 -1
  352. package/dist/shims/image.d.ts +1 -1
  353. package/dist/shims/image.js +4 -4
  354. package/dist/shims/image.js.map +1 -1
  355. package/dist/shims/internal/pages-data-target.d.ts +58 -0
  356. package/dist/shims/internal/pages-data-target.js +91 -0
  357. package/dist/shims/internal/pages-data-target.js.map +1 -0
  358. package/dist/shims/internal/pages-data-url.d.ts +42 -0
  359. package/dist/shims/internal/pages-data-url.js +73 -0
  360. package/dist/shims/internal/pages-data-url.js.map +1 -0
  361. package/dist/shims/link.d.ts +21 -3
  362. package/dist/shims/link.js +189 -30
  363. package/dist/shims/link.js.map +1 -1
  364. package/dist/shims/metadata.d.ts +2 -1
  365. package/dist/shims/metadata.js +65 -6
  366. package/dist/shims/metadata.js.map +1 -1
  367. package/dist/shims/navigation.d.ts +8 -2
  368. package/dist/shims/navigation.js +67 -23
  369. package/dist/shims/navigation.js.map +1 -1
  370. package/dist/shims/og.d.ts +18 -2
  371. package/dist/shims/og.js +49 -1
  372. package/dist/shims/og.js.map +1 -0
  373. package/dist/shims/request-state-types.d.ts +1 -1
  374. package/dist/shims/root-params.d.ts +3 -1
  375. package/dist/shims/root-params.js +11 -3
  376. package/dist/shims/root-params.js.map +1 -1
  377. package/dist/shims/router-state.d.ts +1 -0
  378. package/dist/shims/router-state.js.map +1 -1
  379. package/dist/shims/router.d.ts +12 -5
  380. package/dist/shims/router.js +535 -86
  381. package/dist/shims/router.js.map +1 -1
  382. package/dist/shims/script.js +86 -12
  383. package/dist/shims/script.js.map +1 -1
  384. package/dist/shims/server.d.ts +21 -4
  385. package/dist/shims/server.js +30 -9
  386. package/dist/shims/server.js.map +1 -1
  387. package/dist/shims/slot.js +5 -1
  388. package/dist/shims/slot.js.map +1 -1
  389. package/dist/shims/unified-request-context.d.ts +1 -1
  390. package/dist/shims/url-safety.d.ts +23 -1
  391. package/dist/shims/url-safety.js +29 -2
  392. package/dist/shims/url-safety.js.map +1 -1
  393. package/dist/shims/url-utils.d.ts +2 -1
  394. package/dist/shims/url-utils.js +15 -4
  395. package/dist/shims/url-utils.js.map +1 -1
  396. package/dist/typegen.d.ts +10 -0
  397. package/dist/typegen.js +242 -0
  398. package/dist/typegen.js.map +1 -0
  399. package/dist/utils/asset-prefix.d.ts +33 -5
  400. package/dist/utils/asset-prefix.js +39 -6
  401. package/dist/utils/asset-prefix.js.map +1 -1
  402. package/dist/utils/cache-control-metadata.d.ts +2 -1
  403. package/dist/utils/cache-control-metadata.js +1 -3
  404. package/dist/utils/cache-control-metadata.js.map +1 -1
  405. package/dist/utils/domain-locale.d.ts +2 -1
  406. package/dist/utils/domain-locale.js +9 -1
  407. package/dist/utils/domain-locale.js.map +1 -1
  408. package/dist/utils/html-limited-bots.d.ts +5 -0
  409. package/dist/utils/html-limited-bots.js +15 -0
  410. package/dist/utils/html-limited-bots.js.map +1 -0
  411. package/dist/utils/lazy-chunks.d.ts +1 -1
  412. package/dist/utils/lazy-chunks.js +1 -1
  413. package/dist/utils/lazy-chunks.js.map +1 -1
  414. package/dist/utils/prerender-output-paths.d.ts +15 -0
  415. package/dist/utils/prerender-output-paths.js +24 -0
  416. package/dist/utils/prerender-output-paths.js.map +1 -0
  417. package/dist/utils/query.d.ts +23 -1
  418. package/dist/utils/query.js +46 -2
  419. package/dist/utils/query.js.map +1 -1
  420. package/dist/utils/record.d.ts +5 -0
  421. package/dist/utils/record.js +8 -0
  422. package/dist/utils/record.js.map +1 -0
  423. package/package.json +11 -3
@@ -1 +1 @@
1
- {"version":3,"file":"seed-cache.js","names":[],"sources":["../../src/server/seed-cache.ts"],"sourcesContent":["/**\n * Seed the memory cache from pre-rendered build output.\n *\n * Reads `vinext-prerender.json` and the corresponding HTML/RSC files from\n * `dist/server/prerendered-routes/`, then populates the active CacheHandler\n * so pre-rendered pages are served as cache HITs on the very first request\n * instead of triggering a full re-render.\n *\n * This is only useful for the MemoryCacheHandler (the default for Node.js\n * production). Persistent backends like KV already retain entries across\n * deploys and can be pre-populated via TPR or similar mechanisms.\n *\n * Consistency model:\n * - The manifest is authoritative for which routes were pre-rendered and their\n * revalidation config. The HTML/RSC files on disk are the source of truth\n * for content. Both are produced by the same build and are immutable after\n * the build completes.\n * - Cache keys include the buildId, so entries from a previous build are never\n * matched by a new server process (new build = new buildId = new keys).\n * - Seeded entries are indistinguishable from entries created by the ISR\n * render path: same cache value shape, same revalidate duration tracking,\n * same cache key construction. The serving path does not know or care\n * whether an entry was seeded or rendered.\n *\n * Concurrency model:\n * - This function runs at startup before the HTTP server begins accepting\n * requests, so there are no concurrent readers during seeding. All I/O is\n * synchronous (readFileSync) which is appropriate for a startup-only path\n * that runs once before the event loop serves traffic.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getCacheHandler, type CachedAppPageValue } from \"vinext/shims/cache\";\nimport { isrCacheKey, setRevalidateDuration } from \"./isr-cache.js\";\nimport { getOutputPath, getRscOutputPath } from \"../build/prerender.js\";\n\n// ─── Manifest types ───────────────────────────────────────────────────────────\n\ntype PrerenderManifest = {\n buildId: string;\n trailingSlash?: boolean;\n routes: PrerenderManifestRoute[];\n};\n\ntype PrerenderManifestRoute = {\n route: string;\n status: string;\n revalidate?: number | false;\n expire?: number;\n path?: string;\n router?: \"app\" | \"pages\";\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Read pre-rendered routes from disk and seed the active CacheHandler.\n *\n * Call this during production server startup, before any requests are served.\n * If the manifest doesn't exist (no prerender phase was run), this is a no-op.\n *\n * @param serverDir - Path to `dist/server/` (where vinext-prerender.json lives)\n * @returns The number of routes seeded (0 if no manifest or no renderable routes).\n */\nexport async function seedMemoryCacheFromPrerender(serverDir: string): Promise<number> {\n const manifestPath = path.join(serverDir, \"vinext-prerender.json\");\n if (!fs.existsSync(manifestPath)) return 0;\n\n let manifest: PrerenderManifest;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf-8\"));\n } catch (err) {\n console.warn(\"[vinext] Failed to parse vinext-prerender.json, skipping cache seeding:\", err);\n return 0;\n }\n\n const { buildId, routes } = manifest;\n if (!buildId || !Array.isArray(routes)) return 0;\n\n const trailingSlash = manifest.trailingSlash ?? false;\n const prerenderDir = path.join(serverDir, \"prerendered-routes\");\n const handler = getCacheHandler();\n let seeded = 0;\n\n for (const route of routes) {\n if (route.status !== \"rendered\") continue;\n if (route.router !== \"app\") continue;\n\n const pathname = route.path ?? route.route;\n const baseKey = isrCacheKey(\"app\", pathname, buildId);\n const revalidateSeconds = typeof route.revalidate === \"number\" ? route.revalidate : undefined;\n const expireSeconds = typeof route.expire === \"number\" ? route.expire : undefined;\n\n if (\n await seedHtml(\n handler,\n prerenderDir,\n baseKey,\n pathname,\n trailingSlash,\n revalidateSeconds,\n expireSeconds,\n )\n ) {\n await seedRsc(handler, prerenderDir, baseKey, pathname, revalidateSeconds, expireSeconds);\n seeded++;\n }\n }\n\n return seeded;\n}\n\n// ─── Internals ────────────────────────────────────────────────────────────────\n\n/**\n * Build the CacheHandler context object from a revalidate value.\n * `revalidate: undefined` (static routes) → empty context → no expiry.\n */\nfunction revalidateCtx(\n revalidateSeconds: number | undefined,\n expireSeconds: number | undefined,\n): Record<string, unknown> {\n if (revalidateSeconds === undefined) return {};\n return expireSeconds === undefined\n ? { cacheControl: { revalidate: revalidateSeconds }, revalidate: revalidateSeconds }\n : {\n cacheControl: { revalidate: revalidateSeconds, expire: expireSeconds },\n revalidate: revalidateSeconds,\n };\n}\n\n/**\n * Seed the HTML cache entry for a single route.\n * Returns true if the file existed and was seeded.\n */\nasync function seedHtml(\n handler: ReturnType<typeof getCacheHandler>,\n prerenderDir: string,\n baseKey: string,\n pathname: string,\n trailingSlash: boolean,\n revalidateSeconds: number | undefined,\n expireSeconds: number | undefined,\n): Promise<boolean> {\n const relPath = getOutputPath(pathname, trailingSlash);\n const fullPath = path.join(prerenderDir, relPath);\n if (!fs.existsSync(fullPath)) return false;\n\n const htmlValue: CachedAppPageValue = {\n kind: \"APP_PAGE\",\n html: fs.readFileSync(fullPath, \"utf-8\"),\n rscData: undefined,\n headers: undefined,\n postponed: undefined,\n status: undefined,\n };\n\n const key = baseKey + \":html\";\n await handler.set(key, htmlValue, revalidateCtx(revalidateSeconds, expireSeconds));\n\n if (revalidateSeconds !== undefined) {\n setRevalidateDuration(key, revalidateSeconds);\n }\n\n return true;\n}\n\n/**\n * Seed the RSC cache entry for a single route.\n * No-op if the .rsc file doesn't exist on disk.\n */\nasync function seedRsc(\n handler: ReturnType<typeof getCacheHandler>,\n prerenderDir: string,\n baseKey: string,\n pathname: string,\n revalidateSeconds: number | undefined,\n expireSeconds: number | undefined,\n): Promise<void> {\n const relPath = getRscOutputPath(pathname);\n const fullPath = path.join(prerenderDir, relPath);\n if (!fs.existsSync(fullPath)) return;\n\n const rscBuffer = fs.readFileSync(fullPath);\n const rscValue: CachedAppPageValue = {\n kind: \"APP_PAGE\",\n html: \"\",\n rscData: rscBuffer.buffer.slice(\n rscBuffer.byteOffset,\n rscBuffer.byteOffset + rscBuffer.byteLength,\n ),\n headers: undefined,\n postponed: undefined,\n status: undefined,\n };\n\n const key = baseKey + \":rsc\";\n await handler.set(key, rscValue, revalidateCtx(revalidateSeconds, expireSeconds));\n\n if (revalidateSeconds !== undefined) {\n setRevalidateDuration(key, revalidateSeconds);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,eAAsB,6BAA6B,WAAoC;CACrF,MAAM,eAAe,KAAK,KAAK,WAAW,wBAAwB;CAClE,IAAI,CAAC,GAAG,WAAW,aAAa,EAAE,OAAO;CAEzC,IAAI;CACJ,IAAI;EACF,WAAW,KAAK,MAAM,GAAG,aAAa,cAAc,QAAQ,CAAC;UACtD,KAAK;EACZ,QAAQ,KAAK,2EAA2E,IAAI;EAC5F,OAAO;;CAGT,MAAM,EAAE,SAAS,WAAW;CAC5B,IAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,OAAO,EAAE,OAAO;CAE/C,MAAM,gBAAgB,SAAS,iBAAiB;CAChD,MAAM,eAAe,KAAK,KAAK,WAAW,qBAAqB;CAC/D,MAAM,UAAU,iBAAiB;CACjC,IAAI,SAAS;CAEb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,WAAW,YAAY;EACjC,IAAI,MAAM,WAAW,OAAO;EAE5B,MAAM,WAAW,MAAM,QAAQ,MAAM;EACrC,MAAM,UAAU,YAAY,OAAO,UAAU,QAAQ;EACrD,MAAM,oBAAoB,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa,KAAA;EACpF,MAAM,gBAAgB,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA;EAExE,IACE,MAAM,SACJ,SACA,cACA,SACA,UACA,eACA,mBACA,cACD,EACD;GACA,MAAM,QAAQ,SAAS,cAAc,SAAS,UAAU,mBAAmB,cAAc;GACzF;;;CAIJ,OAAO;;;;;;AAST,SAAS,cACP,mBACA,eACyB;CACzB,IAAI,sBAAsB,KAAA,GAAW,OAAO,EAAE;CAC9C,OAAO,kBAAkB,KAAA,IACrB;EAAE,cAAc,EAAE,YAAY,mBAAmB;EAAE,YAAY;EAAmB,GAClF;EACE,cAAc;GAAE,YAAY;GAAmB,QAAQ;GAAe;EACtE,YAAY;EACb;;;;;;AAOP,eAAe,SACb,SACA,cACA,SACA,UACA,eACA,mBACA,eACkB;CAClB,MAAM,UAAU,cAAc,UAAU,cAAc;CACtD,MAAM,WAAW,KAAK,KAAK,cAAc,QAAQ;CACjD,IAAI,CAAC,GAAG,WAAW,SAAS,EAAE,OAAO;CAErC,MAAM,YAAgC;EACpC,MAAM;EACN,MAAM,GAAG,aAAa,UAAU,QAAQ;EACxC,SAAS,KAAA;EACT,SAAS,KAAA;EACT,WAAW,KAAA;EACX,QAAQ,KAAA;EACT;CAED,MAAM,MAAM,UAAU;CACtB,MAAM,QAAQ,IAAI,KAAK,WAAW,cAAc,mBAAmB,cAAc,CAAC;CAElF,IAAI,sBAAsB,KAAA,GACxB,sBAAsB,KAAK,kBAAkB;CAG/C,OAAO;;;;;;AAOT,eAAe,QACb,SACA,cACA,SACA,UACA,mBACA,eACe;CACf,MAAM,UAAU,iBAAiB,SAAS;CAC1C,MAAM,WAAW,KAAK,KAAK,cAAc,QAAQ;CACjD,IAAI,CAAC,GAAG,WAAW,SAAS,EAAE;CAE9B,MAAM,YAAY,GAAG,aAAa,SAAS;CAC3C,MAAM,WAA+B;EACnC,MAAM;EACN,MAAM;EACN,SAAS,UAAU,OAAO,MACxB,UAAU,YACV,UAAU,aAAa,UAAU,WAClC;EACD,SAAS,KAAA;EACT,WAAW,KAAA;EACX,QAAQ,KAAA;EACT;CAED,MAAM,MAAM,UAAU;CACtB,MAAM,QAAQ,IAAI,KAAK,UAAU,cAAc,mBAAmB,cAAc,CAAC;CAEjF,IAAI,sBAAsB,KAAA,GACxB,sBAAsB,KAAK,kBAAkB"}
1
+ {"version":3,"file":"seed-cache.js","names":[],"sources":["../../src/server/seed-cache.ts"],"sourcesContent":["/**\n * Seed the memory cache from pre-rendered build output.\n *\n * Reads `vinext-prerender.json` and the corresponding HTML/RSC files from\n * `dist/server/prerendered-routes/`, then populates the active CacheHandler\n * so pre-rendered pages are served as cache HITs on the very first request\n * instead of triggering a full re-render.\n *\n * This is only useful for the MemoryCacheHandler (the default for Node.js\n * production). Persistent backends like KV already retain entries across\n * deploys and can be pre-populated via TPR or similar mechanisms.\n *\n * Consistency model:\n * - The manifest is authoritative for which routes were pre-rendered and their\n * revalidation config. The HTML/RSC files on disk are the source of truth\n * for content. Both are produced by the same build and are immutable after\n * the build completes.\n * - Cache keys include the buildId, so entries from a previous build are never\n * matched by a new server process (new build = new buildId = new keys).\n * - Seeded entries are indistinguishable from entries created by the ISR\n * render path: same cache value shape, same revalidate duration tracking,\n * same cache key construction. The serving path does not know or care\n * whether an entry was seeded or rendered.\n *\n * Concurrency model:\n * - This function runs at startup before the HTTP server begins accepting\n * requests, so there are no concurrent readers during seeding. All I/O is\n * synchronous (readFileSync) which is appropriate for a startup-only path\n * that runs once before the event loop serves traffic.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { CachedAppPageValue } from \"vinext/shims/cache\";\nimport { isrCacheKey, isrSetPrerenderedAppPage } from \"./isr-cache.js\";\nimport { getOutputPath, getRscOutputPath } from \"../utils/prerender-output-paths.js\";\nimport { normalizePathnameForRouteMatch } from \"../routing/utils.js\";\nimport { normalizePath } from \"./normalize-path.js\";\n\n// ─── Manifest types ───────────────────────────────────────────────────────────\n\ntype PrerenderManifest = {\n buildId: string;\n trailingSlash?: boolean;\n routes: PrerenderManifestRoute[];\n};\n\ntype PrerenderManifestRoute = {\n route: string;\n status: string;\n revalidate?: number | false;\n expire?: number;\n path?: string;\n router?: \"app\" | \"pages\";\n};\n\ntype PrerenderCacheSeedMetadata = {\n expireSeconds?: number;\n revalidateSeconds?: number;\n};\n\ntype PrerenderCacheSeedOptions = {\n buildAppPageHtmlKey?: (pathname: string) => string;\n buildAppPageRscKey?: (pathname: string) => string;\n writeAppPageEntry?: (\n key: string,\n data: CachedAppPageValue,\n metadata: PrerenderCacheSeedMetadata,\n ) => Promise<void>;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Read pre-rendered routes from disk and seed the active CacheHandler.\n *\n * Call this during production server startup, before any requests are served.\n * If the manifest doesn't exist (no prerender phase was run), this is a no-op.\n *\n * @param serverDir - Path to `dist/server/` (where vinext-prerender.json lives)\n * @returns The number of routes seeded (0 if no manifest or no renderable routes).\n */\nexport async function seedMemoryCacheFromPrerender(\n serverDir: string,\n options?: PrerenderCacheSeedOptions,\n): Promise<number> {\n const manifestPath = path.join(serverDir, \"vinext-prerender.json\");\n if (!fs.existsSync(manifestPath)) return 0;\n\n let manifest: PrerenderManifest;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf-8\"));\n } catch (err) {\n console.warn(\"[vinext] Failed to parse vinext-prerender.json, skipping cache seeding:\", err);\n return 0;\n }\n\n const { buildId, routes } = manifest;\n if (!buildId || !Array.isArray(routes)) return 0;\n\n const trailingSlash = manifest.trailingSlash ?? false;\n const prerenderDir = path.join(serverDir, \"prerendered-routes\");\n const writeAppPageEntry = options?.writeAppPageEntry ?? createDefaultAppPageEntryWriter();\n let seeded = 0;\n\n for (const route of routes) {\n if (route.status !== \"rendered\") continue;\n if (route.router !== \"app\") continue;\n\n const artifactPathname = route.path ?? route.route;\n const cachePathname = normalizePrerenderCachePathname(artifactPathname);\n // Fallback keys support older generated entries that do not export their\n // runtime key builders. Current App Router entries inject buildAppPage*Key\n // so seeded keys match process.env.__VINEXT_BUILD_ID exactly.\n const baseKey = isrCacheKey(\"app\", cachePathname, buildId);\n const htmlKey = options?.buildAppPageHtmlKey?.(cachePathname) ?? baseKey + \":html\";\n const rscKey = options?.buildAppPageRscKey?.(cachePathname) ?? baseKey + \":rsc\";\n const revalidateSeconds = typeof route.revalidate === \"number\" ? route.revalidate : undefined;\n const expireSeconds = typeof route.expire === \"number\" ? route.expire : undefined;\n\n if (\n await seedHtml(\n writeAppPageEntry,\n prerenderDir,\n htmlKey,\n artifactPathname,\n trailingSlash,\n revalidateSeconds,\n expireSeconds,\n )\n ) {\n await seedRsc(\n writeAppPageEntry,\n prerenderDir,\n rscKey,\n artifactPathname,\n revalidateSeconds,\n expireSeconds,\n );\n seeded++;\n }\n }\n\n return seeded;\n}\n\n// ─── Internals ────────────────────────────────────────────────────────────────\n\nfunction normalizePrerenderCachePathname(pathname: string): string {\n return normalizePath(normalizePathnameForRouteMatch(pathname));\n}\n\nfunction createDefaultAppPageEntryWriter(): NonNullable<\n PrerenderCacheSeedOptions[\"writeAppPageEntry\"]\n> {\n return (key, data, metadata) => isrSetPrerenderedAppPage(key, data, metadata);\n}\n\n/**\n * Seed the HTML cache entry for a single route.\n * Returns true if the file existed and was seeded.\n */\nasync function seedHtml(\n writeAppPageEntry: NonNullable<PrerenderCacheSeedOptions[\"writeAppPageEntry\"]>,\n prerenderDir: string,\n key: string,\n pathname: string,\n trailingSlash: boolean,\n revalidateSeconds: number | undefined,\n expireSeconds: number | undefined,\n): Promise<boolean> {\n const relPath = getOutputPath(pathname, trailingSlash);\n const fullPath = path.join(prerenderDir, relPath);\n if (!fs.existsSync(fullPath)) return false;\n\n const htmlValue: CachedAppPageValue = {\n kind: \"APP_PAGE\",\n html: fs.readFileSync(fullPath, \"utf-8\"),\n rscData: undefined,\n headers: undefined,\n postponed: undefined,\n status: undefined,\n };\n\n await writeAppPageEntry(key, htmlValue, { expireSeconds, revalidateSeconds });\n\n return true;\n}\n\n/**\n * Seed the RSC cache entry for a single route.\n * No-op if the .rsc file doesn't exist on disk.\n */\nasync function seedRsc(\n writeAppPageEntry: NonNullable<PrerenderCacheSeedOptions[\"writeAppPageEntry\"]>,\n prerenderDir: string,\n key: string,\n pathname: string,\n revalidateSeconds: number | undefined,\n expireSeconds: number | undefined,\n): Promise<void> {\n const relPath = getRscOutputPath(pathname);\n const fullPath = path.join(prerenderDir, relPath);\n if (!fs.existsSync(fullPath)) return;\n\n const rscBuffer = fs.readFileSync(fullPath);\n const rscValue: CachedAppPageValue = {\n kind: \"APP_PAGE\",\n html: \"\",\n rscData: rscBuffer.buffer.slice(\n rscBuffer.byteOffset,\n rscBuffer.byteOffset + rscBuffer.byteLength,\n ),\n headers: undefined,\n postponed: undefined,\n status: undefined,\n };\n\n await writeAppPageEntry(key, rscValue, { expireSeconds, revalidateSeconds });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFA,eAAsB,6BACpB,WACA,SACiB;CACjB,MAAM,eAAe,KAAK,KAAK,WAAW,wBAAwB;CAClE,IAAI,CAAC,GAAG,WAAW,aAAa,EAAE,OAAO;CAEzC,IAAI;CACJ,IAAI;EACF,WAAW,KAAK,MAAM,GAAG,aAAa,cAAc,QAAQ,CAAC;UACtD,KAAK;EACZ,QAAQ,KAAK,2EAA2E,IAAI;EAC5F,OAAO;;CAGT,MAAM,EAAE,SAAS,WAAW;CAC5B,IAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,OAAO,EAAE,OAAO;CAE/C,MAAM,gBAAgB,SAAS,iBAAiB;CAChD,MAAM,eAAe,KAAK,KAAK,WAAW,qBAAqB;CAC/D,MAAM,oBAAoB,SAAS,qBAAqB,iCAAiC;CACzF,IAAI,SAAS;CAEb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,MAAM,WAAW,YAAY;EACjC,IAAI,MAAM,WAAW,OAAO;EAE5B,MAAM,mBAAmB,MAAM,QAAQ,MAAM;EAC7C,MAAM,gBAAgB,gCAAgC,iBAAiB;EAIvE,MAAM,UAAU,YAAY,OAAO,eAAe,QAAQ;EAC1D,MAAM,UAAU,SAAS,sBAAsB,cAAc,IAAI,UAAU;EAC3E,MAAM,SAAS,SAAS,qBAAqB,cAAc,IAAI,UAAU;EACzE,MAAM,oBAAoB,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa,KAAA;EACpF,MAAM,gBAAgB,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA;EAExE,IACE,MAAM,SACJ,mBACA,cACA,SACA,kBACA,eACA,mBACA,cACD,EACD;GACA,MAAM,QACJ,mBACA,cACA,QACA,kBACA,mBACA,cACD;GACD;;;CAIJ,OAAO;;AAKT,SAAS,gCAAgC,UAA0B;CACjE,OAAO,cAAc,+BAA+B,SAAS,CAAC;;AAGhE,SAAS,kCAEP;CACA,QAAQ,KAAK,MAAM,aAAa,yBAAyB,KAAK,MAAM,SAAS;;;;;;AAO/E,eAAe,SACb,mBACA,cACA,KACA,UACA,eACA,mBACA,eACkB;CAClB,MAAM,UAAU,cAAc,UAAU,cAAc;CACtD,MAAM,WAAW,KAAK,KAAK,cAAc,QAAQ;CACjD,IAAI,CAAC,GAAG,WAAW,SAAS,EAAE,OAAO;CAWrC,MAAM,kBAAkB,KAAK;EAR3B,MAAM;EACN,MAAM,GAAG,aAAa,UAAU,QAAQ;EACxC,SAAS,KAAA;EACT,SAAS,KAAA;EACT,WAAW,KAAA;EACX,QAAQ,KAAA;EAG4B,EAAE;EAAE;EAAe;EAAmB,CAAC;CAE7E,OAAO;;;;;;AAOT,eAAe,QACb,mBACA,cACA,KACA,UACA,mBACA,eACe;CACf,MAAM,UAAU,iBAAiB,SAAS;CAC1C,MAAM,WAAW,KAAK,KAAK,cAAc,QAAQ;CACjD,IAAI,CAAC,GAAG,WAAW,SAAS,EAAE;CAE9B,MAAM,YAAY,GAAG,aAAa,SAAS;CAa3C,MAAM,kBAAkB,KAAK;EAX3B,MAAM;EACN,MAAM;EACN,SAAS,UAAU,OAAO,MACxB,UAAU,YACV,UAAU,aAAa,UAAU,WAClC;EACD,SAAS,KAAA;EACT,WAAW,KAAA;EACX,QAAQ,KAAA;EAG2B,EAAE;EAAE;EAAe;EAAmB,CAAC"}
@@ -19,9 +19,14 @@ function getUnknownMessage(error) {
19
19
  function isServerActionNotFoundError(error, actionId) {
20
20
  const message = getUnknownMessage(error);
21
21
  if (!message) return false;
22
- if (!actionId) return message.startsWith("Failed to find Server Action");
23
- if (message.startsWith(getServerActionNotFoundPrefix(actionId))) return true;
24
- return Boolean(actionId && message.includes(`[vite-rsc] invalid server reference '${actionId}'`));
22
+ if (actionId && message.startsWith(getServerActionNotFoundPrefix(actionId))) return true;
23
+ if (!actionId && message.startsWith("Failed to find Server Action")) return true;
24
+ if (actionId) {
25
+ if (message.includes(`[vite-rsc] invalid server reference '${actionId}'`)) return true;
26
+ if (message.includes(`server reference not found '${actionId}'`)) return true;
27
+ return false;
28
+ }
29
+ return /\[vite-rsc] invalid server reference '/.test(message) || /server reference not found '/.test(message);
25
30
  }
26
31
  function createServerActionNotFoundResponse() {
27
32
  return new Response(SERVER_ACTION_NOT_FOUND_BODY, {
@@ -1 +1 @@
1
- {"version":3,"file":"server-action-not-found.js","names":["SERVER_ACTION_NOT_FOUND_HEADER"],"sources":["../../src/server/server-action-not-found.ts"],"sourcesContent":["import { NEXTJS_ACTION_NOT_FOUND_HEADER as SERVER_ACTION_NOT_FOUND_HEADER } from \"./headers.js\";\nimport { UnrecognizedActionError } from \"vinext/shims/unrecognized-action-error\";\n\nconst SERVER_ACTION_NOT_FOUND_DOCS =\n \"https://nextjs.org/docs/messages/failed-to-find-server-action\";\nconst SERVER_ACTION_NOT_FOUND_BODY = \"Server action not found.\";\n\nfunction getServerActionNotFoundPrefix(actionId: string | null): string {\n return `Failed to find Server Action${actionId ? ` \"${actionId}\"` : \"\"}.`;\n}\n\nexport function getServerActionNotFoundMessage(actionId: string | null): string {\n return `${getServerActionNotFoundPrefix(\n actionId,\n )} This request might be from an older or newer deployment.\\nRead more: ${SERVER_ACTION_NOT_FOUND_DOCS}`;\n}\n\nfunction getServerActionNotFoundClientMessage(actionId: string): string {\n return `Server Action \"${actionId}\" was not found on the server. \\nRead more: ${SERVER_ACTION_NOT_FOUND_DOCS}`;\n}\n\nfunction getUnknownMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return typeof error === \"string\" ? error : \"\";\n}\n\nexport function isServerActionNotFoundError(error: unknown, actionId: string | null): boolean {\n const message = getUnknownMessage(error);\n if (!message) {\n return false;\n }\n\n if (!actionId) {\n return message.startsWith(\"Failed to find Server Action\");\n }\n\n if (message.startsWith(getServerActionNotFoundPrefix(actionId))) {\n return true;\n }\n\n return Boolean(actionId && message.includes(`[vite-rsc] invalid server reference '${actionId}'`));\n}\n\nexport function createServerActionNotFoundResponse(): Response {\n return new Response(SERVER_ACTION_NOT_FOUND_BODY, {\n status: 404,\n headers: {\n [SERVER_ACTION_NOT_FOUND_HEADER]: \"1\",\n \"content-type\": \"text/plain\",\n },\n });\n}\n\nfunction isServerActionNotFoundResponse(response: Pick<Response, \"headers\">): boolean {\n return response.headers.get(SERVER_ACTION_NOT_FOUND_HEADER) === \"1\";\n}\n\n/**\n * Throw an `UnrecognizedActionError` when the server reported the requested\n * server action id as unknown (the `x-nextjs-action-not-found` response\n * header); otherwise return so the caller can keep processing the response.\n *\n * The client-side counterpart of `createServerActionNotFoundResponse`. The\n * typed error lets client `catch` blocks call the public\n * `unstable_isUnrecognizedActionError` predicate to detect client/server\n * deployment skew and recover (typically by reloading the page).\n *\n * Mirrors Next.js, whose server-action reducer throws `UnrecognizedActionError`\n * on this same response header:\n * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts\n */\nexport function throwOnServerActionNotFound(\n response: Pick<Response, \"headers\">,\n actionId: string,\n): void {\n if (isServerActionNotFoundResponse(response)) {\n throw new UnrecognizedActionError(getServerActionNotFoundClientMessage(actionId));\n }\n}\n"],"mappings":";;;AAGA,MAAM,+BACJ;AACF,MAAM,+BAA+B;AAErC,SAAS,8BAA8B,UAAiC;CACtE,OAAO,+BAA+B,WAAW,KAAK,SAAS,KAAK,GAAG;;AAGzE,SAAgB,+BAA+B,UAAiC;CAC9E,OAAO,GAAG,8BACR,SACD,CAAC,wEAAwE;;AAG5E,SAAS,qCAAqC,UAA0B;CACtE,OAAO,kBAAkB,SAAS,8CAA8C;;AAGlF,SAAS,kBAAkB,OAAwB;CACjD,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,OAAO,OAAO,UAAU,WAAW,QAAQ;;AAG7C,SAAgB,4BAA4B,OAAgB,UAAkC;CAC5F,MAAM,UAAU,kBAAkB,MAAM;CACxC,IAAI,CAAC,SACH,OAAO;CAGT,IAAI,CAAC,UACH,OAAO,QAAQ,WAAW,+BAA+B;CAG3D,IAAI,QAAQ,WAAW,8BAA8B,SAAS,CAAC,EAC7D,OAAO;CAGT,OAAO,QAAQ,YAAY,QAAQ,SAAS,wCAAwC,SAAS,GAAG,CAAC;;AAGnG,SAAgB,qCAA+C;CAC7D,OAAO,IAAI,SAAS,8BAA8B;EAChD,QAAQ;EACR,SAAS;IACNA,iCAAiC;GAClC,gBAAgB;GACjB;EACF,CAAC;;AAGJ,SAAS,+BAA+B,UAA8C;CACpF,OAAO,SAAS,QAAQ,IAAIA,+BAA+B,KAAK;;;;;;;;;;;;;;;;AAiBlE,SAAgB,4BACd,UACA,UACM;CACN,IAAI,+BAA+B,SAAS,EAC1C,MAAM,IAAI,wBAAwB,qCAAqC,SAAS,CAAC"}
1
+ {"version":3,"file":"server-action-not-found.js","names":["SERVER_ACTION_NOT_FOUND_HEADER"],"sources":["../../src/server/server-action-not-found.ts"],"sourcesContent":["import { NEXTJS_ACTION_NOT_FOUND_HEADER as SERVER_ACTION_NOT_FOUND_HEADER } from \"./headers.js\";\nimport { UnrecognizedActionError } from \"vinext/shims/unrecognized-action-error\";\n\nconst SERVER_ACTION_NOT_FOUND_DOCS =\n \"https://nextjs.org/docs/messages/failed-to-find-server-action\";\nconst SERVER_ACTION_NOT_FOUND_BODY = \"Server action not found.\";\n\nfunction getServerActionNotFoundPrefix(actionId: string | null): string {\n return `Failed to find Server Action${actionId ? ` \"${actionId}\"` : \"\"}.`;\n}\n\nexport function getServerActionNotFoundMessage(actionId: string | null): string {\n return `${getServerActionNotFoundPrefix(\n actionId,\n )} This request might be from an older or newer deployment.\\nRead more: ${SERVER_ACTION_NOT_FOUND_DOCS}`;\n}\n\nfunction getServerActionNotFoundClientMessage(actionId: string): string {\n return `Server Action \"${actionId}\" was not found on the server. \\nRead more: ${SERVER_ACTION_NOT_FOUND_DOCS}`;\n}\n\nfunction getUnknownMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return typeof error === \"string\" ? error : \"\";\n}\n\nexport function isServerActionNotFoundError(error: unknown, actionId: string | null): boolean {\n const message = getUnknownMessage(error);\n if (!message) {\n return false;\n }\n\n if (actionId && message.startsWith(getServerActionNotFoundPrefix(actionId))) {\n return true;\n }\n\n if (!actionId && message.startsWith(\"Failed to find Server Action\")) {\n return true;\n }\n\n // `@vitejs/plugin-rsc` raises two different \"no such server reference\"\n // errors depending on the build mode. Both mean the same thing — the\n // referenced server action id isn't in the runtime manifest — and must\n // surface as Next.js' 404 + action-not-found header rather than a generic\n // 500. The progressive (no-JS) path also hits this in `decodeAction(body)`\n // before it has any actionId in hand, so match these patterns whether or\n // not the caller has resolved an action id from request headers.\n //\n // - dev: `[vite-rsc] invalid server reference '<id>'` (from the reference\n // validation virtual module loaded ahead of dynamic import)\n // - prod: `server reference not found '<id>'` (from the built\n // `virtual:vite-rsc/server-references` lookup, including the case\n // where the build has no server actions at all)\n //\n // See: @vitejs/plugin-rsc dist/rsc.js (`server reference not found`) and\n // dist/plugin-*.js (`[vite-rsc] invalid <type> reference`).\n if (actionId) {\n if (message.includes(`[vite-rsc] invalid server reference '${actionId}'`)) {\n return true;\n }\n if (message.includes(`server reference not found '${actionId}'`)) {\n return true;\n }\n return false;\n }\n\n return (\n /\\[vite-rsc] invalid server reference '/.test(message) ||\n /server reference not found '/.test(message)\n );\n}\n\nexport function createServerActionNotFoundResponse(): Response {\n return new Response(SERVER_ACTION_NOT_FOUND_BODY, {\n status: 404,\n headers: {\n [SERVER_ACTION_NOT_FOUND_HEADER]: \"1\",\n \"content-type\": \"text/plain\",\n },\n });\n}\n\nfunction isServerActionNotFoundResponse(response: Pick<Response, \"headers\">): boolean {\n return response.headers.get(SERVER_ACTION_NOT_FOUND_HEADER) === \"1\";\n}\n\n/**\n * Throw an `UnrecognizedActionError` when the server reported the requested\n * server action id as unknown (the `x-nextjs-action-not-found` response\n * header); otherwise return so the caller can keep processing the response.\n *\n * The client-side counterpart of `createServerActionNotFoundResponse`. The\n * typed error lets client `catch` blocks call the public\n * `unstable_isUnrecognizedActionError` predicate to detect client/server\n * deployment skew and recover (typically by reloading the page).\n *\n * Mirrors Next.js, whose server-action reducer throws `UnrecognizedActionError`\n * on this same response header:\n * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts\n */\nexport function throwOnServerActionNotFound(\n response: Pick<Response, \"headers\">,\n actionId: string,\n): void {\n if (isServerActionNotFoundResponse(response)) {\n throw new UnrecognizedActionError(getServerActionNotFoundClientMessage(actionId));\n }\n}\n"],"mappings":";;;AAGA,MAAM,+BACJ;AACF,MAAM,+BAA+B;AAErC,SAAS,8BAA8B,UAAiC;CACtE,OAAO,+BAA+B,WAAW,KAAK,SAAS,KAAK,GAAG;;AAGzE,SAAgB,+BAA+B,UAAiC;CAC9E,OAAO,GAAG,8BACR,SACD,CAAC,wEAAwE;;AAG5E,SAAS,qCAAqC,UAA0B;CACtE,OAAO,kBAAkB,SAAS,8CAA8C;;AAGlF,SAAS,kBAAkB,OAAwB;CACjD,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,OAAO,OAAO,UAAU,WAAW,QAAQ;;AAG7C,SAAgB,4BAA4B,OAAgB,UAAkC;CAC5F,MAAM,UAAU,kBAAkB,MAAM;CACxC,IAAI,CAAC,SACH,OAAO;CAGT,IAAI,YAAY,QAAQ,WAAW,8BAA8B,SAAS,CAAC,EACzE,OAAO;CAGT,IAAI,CAAC,YAAY,QAAQ,WAAW,+BAA+B,EACjE,OAAO;CAmBT,IAAI,UAAU;EACZ,IAAI,QAAQ,SAAS,wCAAwC,SAAS,GAAG,EACvE,OAAO;EAET,IAAI,QAAQ,SAAS,+BAA+B,SAAS,GAAG,EAC9D,OAAO;EAET,OAAO;;CAGT,OACE,yCAAyC,KAAK,QAAQ,IACtD,+BAA+B,KAAK,QAAQ;;AAIhD,SAAgB,qCAA+C;CAC7D,OAAO,IAAI,SAAS,8BAA8B;EAChD,QAAQ;EACR,SAAS;IACNA,iCAAiC;GAClC,gBAAgB;GACjB;EACF,CAAC;;AAGJ,SAAS,+BAA+B,UAA8C;CACpF,OAAO,SAAS,QAAQ,IAAIA,+BAA+B,KAAK;;;;;;;;;;;;;;;;AAiBlE,SAAgB,4BACd,UACA,UACM;CACN,IAAI,+BAA+B,SAAS,EAC1C,MAAM,IAAI,wBAAwB,qCAAqC,SAAS,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { ArtifactCompatibilityEnvelope, ArtifactCompatibilityEvaluationOptions } from "./artifact-compatibility.js";
2
+ import { StaticLayoutArtifactReuseDecision } from "./cache-proof.js";
3
+ import { ClientReuseManifestEntry, ClientReuseManifestEntryRejection, ClientReuseManifestSkipDisposition, ClientReuseManifestTraceFields } from "./client-reuse-manifest.js";
4
+
5
+ //#region src/server/skip-cache-proof.d.ts
6
+ type SkipCacheInvalidationProof = Readonly<{
7
+ kind: "invalidated";
8
+ invalidationEpoch: string | null;
9
+ }> | Readonly<{
10
+ kind: "unknown";
11
+ }> | Readonly<{
12
+ kind: "valid";
13
+ }>;
14
+ type SkipCacheArtifactProof = Readonly<{
15
+ compatibility: ArtifactCompatibilityEnvelope;
16
+ invalidation: SkipCacheInvalidationProof;
17
+ payloadHash: string | null;
18
+ }>;
19
+ type SkipCacheCrossCheckAcceptanceCode = "SKIP_CACHE_CROSS_CHECK_PASSED";
20
+ type SkipCacheCrossCheckVerified = Readonly<{
21
+ code: SkipCacheCrossCheckAcceptanceCode;
22
+ entryId: string;
23
+ fields: ClientReuseManifestTraceFields;
24
+ kind: "verified";
25
+ skipDisposition: ClientReuseManifestSkipDisposition;
26
+ }>;
27
+ type SkipCacheCrossCheckRejected = Readonly<{
28
+ kind: "rejected";
29
+ rejection: ClientReuseManifestEntryRejection;
30
+ skipDisposition: ClientReuseManifestSkipDisposition;
31
+ }>;
32
+ type SkipCacheCrossCheckResult = SkipCacheCrossCheckRejected | SkipCacheCrossCheckVerified;
33
+ type CrossCheckClientReuseManifestEntryWithCacheInput = Readonly<{
34
+ artifact: SkipCacheArtifactProof;
35
+ cacheDecision: StaticLayoutArtifactReuseDecision | null;
36
+ entry: ClientReuseManifestEntry;
37
+ }> & ArtifactCompatibilityEvaluationOptions;
38
+ declare function crossCheckClientReuseManifestEntryWithCache(input: CrossCheckClientReuseManifestEntryWithCacheInput): SkipCacheCrossCheckResult;
39
+ //#endregion
40
+ export { SkipCacheInvalidationProof, crossCheckClientReuseManifestEntryWithCache };
41
+ //# sourceMappingURL=skip-cache-proof.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { evaluateArtifactCompatibility } from "./artifact-compatibility.js";
2
+ import { createClientReusePayloadHash } from "./client-reuse-manifest.js";
3
+ //#region src/server/skip-cache-proof.ts
4
+ const ARTIFACT_COMPATIBILITY_PROOF_FIELDS = [
5
+ "schemaVersion",
6
+ "graphVersion",
7
+ "deploymentVersion",
8
+ "appElementsSchemaVersion",
9
+ "rscPayloadSchemaVersion",
10
+ "rootBoundaryId",
11
+ "renderEpoch"
12
+ ];
13
+ function createDisabledSkipDisposition() {
14
+ return {
15
+ code: "SKIP_MODEL_DISABLED",
16
+ enabled: false,
17
+ mode: "renderAndSend"
18
+ };
19
+ }
20
+ function rejectSkipCacheCrossCheck(entry, code, fields = {}) {
21
+ return {
22
+ kind: "rejected",
23
+ rejection: {
24
+ code,
25
+ entryId: entry.id,
26
+ fields
27
+ },
28
+ skipDisposition: createDisabledSkipDisposition()
29
+ };
30
+ }
31
+ function collectArtifactCompatibilityProofMismatches(artifactCompatibility, proofCompatibility) {
32
+ const mismatchedFields = [];
33
+ for (const field of ARTIFACT_COMPATIBILITY_PROOF_FIELDS) if (artifactCompatibility[field] !== proofCompatibility[field]) mismatchedFields.push(field);
34
+ return mismatchedFields;
35
+ }
36
+ function assertNever(value) {
37
+ throw new Error(`Unhandled skip/cache proof state: ${String(value)}`);
38
+ }
39
+ function crossCheckInvalidationProof(entry, invalidation) {
40
+ switch (invalidation.kind) {
41
+ case "valid": return null;
42
+ case "unknown": return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_INVALIDATION_UNKNOWN");
43
+ case "invalidated": return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_INVALIDATED", { invalidationEpoch: invalidation.invalidationEpoch });
44
+ default: return assertNever(invalidation);
45
+ }
46
+ }
47
+ function crossCheckClientReuseManifestEntryWithCache(input) {
48
+ const { cacheDecision, entry } = input;
49
+ if (cacheDecision === null) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PROOF_MISSING");
50
+ if (cacheDecision.kind === "fallback") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PROOF_REJECTED", {
51
+ cacheProofCode: cacheDecision.fallback.code,
52
+ cacheProofMode: cacheDecision.fallback.mode,
53
+ cacheProofScope: cacheDecision.fallback.scope
54
+ });
55
+ const { proof } = cacheDecision;
56
+ if (entry.kind !== "layout" || proof.reuseClass !== "static-layout") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_REUSE_CLASS_UNSUPPORTED", {
57
+ entryKind: entry.kind,
58
+ reuseClass: proof.reuseClass
59
+ });
60
+ if (entry.id !== proof.candidateOutput.layoutId) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ENTRY_ID_MISMATCH", {
61
+ cacheEntryId: proof.candidateOutput.layoutId,
62
+ manifestEntryId: entry.id
63
+ });
64
+ const artifactProofMismatches = collectArtifactCompatibilityProofMismatches(input.artifact.compatibility, proof.candidateArtifactCompatibility);
65
+ if (artifactProofMismatches.length > 0) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ARTIFACT_PROOF_MISMATCH", { mismatchedFields: artifactProofMismatches });
66
+ const artifactCompatibility = evaluateArtifactCompatibility(input.artifact.compatibility, entry.artifactCompatibility, { compatibilityMap: input.compatibilityMap });
67
+ if (artifactCompatibility.kind === "unknown") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ARTIFACT_COMPATIBILITY_UNKNOWN", {
68
+ compatibilityFallback: artifactCompatibility.fallback,
69
+ reason: artifactCompatibility.reason
70
+ });
71
+ if (artifactCompatibility.kind === "incompatible") return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_ARTIFACT_COMPATIBILITY_INCOMPATIBLE", {
72
+ compatibilityFallback: artifactCompatibility.fallback,
73
+ reason: artifactCompatibility.reason
74
+ });
75
+ if (entry.variantCacheKey !== proof.variant.cacheKey) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_VARIANT_MISMATCH", {
76
+ cacheVariantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),
77
+ entryVariantCacheKeyHash: createClientReusePayloadHash(entry.variantCacheKey)
78
+ });
79
+ if (input.artifact.payloadHash === null) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PAYLOAD_HASH_MISSING");
80
+ if (entry.payloadHash !== input.artifact.payloadHash) return rejectSkipCacheCrossCheck(entry, "SKIP_CACHE_PAYLOAD_HASH_MISMATCH", {
81
+ cachePayloadHash: input.artifact.payloadHash,
82
+ entryPayloadHash: entry.payloadHash
83
+ });
84
+ const invalidationRejection = crossCheckInvalidationProof(entry, input.artifact.invalidation);
85
+ if (invalidationRejection) return invalidationRejection;
86
+ return {
87
+ kind: "verified",
88
+ code: "SKIP_CACHE_CROSS_CHECK_PASSED",
89
+ entryId: entry.id,
90
+ fields: {
91
+ entryKind: entry.kind,
92
+ reuseClass: proof.reuseClass,
93
+ variantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey)
94
+ },
95
+ skipDisposition: createDisabledSkipDisposition()
96
+ };
97
+ }
98
+ //#endregion
99
+ export { crossCheckClientReuseManifestEntryWithCache };
100
+
101
+ //# sourceMappingURL=skip-cache-proof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-cache-proof.js","names":[],"sources":["../../src/server/skip-cache-proof.ts"],"sourcesContent":["import {\n evaluateArtifactCompatibility,\n type ArtifactCompatibilityEnvelope,\n type ArtifactCompatibilityEvaluationOptions,\n} from \"./artifact-compatibility.js\";\nimport type { StaticLayoutArtifactReuseDecision } from \"./cache-proof.js\";\nimport {\n createClientReusePayloadHash,\n type ClientReuseManifestEntry,\n type ClientReuseManifestEntryRejection,\n type ClientReuseManifestSkipDisposition,\n type ClientReuseManifestTraceFields,\n} from \"./client-reuse-manifest.js\";\n\nexport type SkipCacheInvalidationProof =\n | Readonly<{ kind: \"invalidated\"; invalidationEpoch: string | null }>\n | Readonly<{ kind: \"unknown\" }>\n | Readonly<{ kind: \"valid\" }>;\n\ntype SkipCacheArtifactProof = Readonly<{\n compatibility: ArtifactCompatibilityEnvelope;\n invalidation: SkipCacheInvalidationProof;\n payloadHash: string | null;\n}>;\n\ntype SkipCacheCrossCheckAcceptanceCode = \"SKIP_CACHE_CROSS_CHECK_PASSED\";\n\ntype SkipCacheCrossCheckVerified = Readonly<{\n code: SkipCacheCrossCheckAcceptanceCode;\n entryId: string;\n fields: ClientReuseManifestTraceFields;\n kind: \"verified\";\n skipDisposition: ClientReuseManifestSkipDisposition;\n}>;\n\ntype SkipCacheCrossCheckRejected = Readonly<{\n kind: \"rejected\";\n rejection: ClientReuseManifestEntryRejection;\n skipDisposition: ClientReuseManifestSkipDisposition;\n}>;\n\ntype SkipCacheCrossCheckResult = SkipCacheCrossCheckRejected | SkipCacheCrossCheckVerified;\n\ntype CrossCheckClientReuseManifestEntryWithCacheInput = Readonly<{\n artifact: SkipCacheArtifactProof;\n cacheDecision: StaticLayoutArtifactReuseDecision | null;\n entry: ClientReuseManifestEntry;\n}> &\n ArtifactCompatibilityEvaluationOptions;\n\nconst ARTIFACT_COMPATIBILITY_PROOF_FIELDS: readonly (keyof ArtifactCompatibilityEnvelope)[] = [\n \"schemaVersion\",\n \"graphVersion\",\n \"deploymentVersion\",\n \"appElementsSchemaVersion\",\n \"rscPayloadSchemaVersion\",\n \"rootBoundaryId\",\n \"renderEpoch\",\n];\n\nfunction createDisabledSkipDisposition(): ClientReuseManifestSkipDisposition {\n return {\n code: \"SKIP_MODEL_DISABLED\",\n enabled: false,\n mode: \"renderAndSend\",\n };\n}\n\nfunction rejectSkipCacheCrossCheck(\n entry: ClientReuseManifestEntry,\n code: ClientReuseManifestEntryRejection[\"code\"],\n fields: ClientReuseManifestTraceFields = {},\n): SkipCacheCrossCheckRejected {\n return {\n kind: \"rejected\",\n rejection: {\n code,\n entryId: entry.id,\n fields,\n },\n skipDisposition: createDisabledSkipDisposition(),\n };\n}\n\nfunction collectArtifactCompatibilityProofMismatches(\n artifactCompatibility: ArtifactCompatibilityEnvelope,\n proofCompatibility: ArtifactCompatibilityEnvelope,\n): readonly string[] {\n const mismatchedFields: string[] = [];\n for (const field of ARTIFACT_COMPATIBILITY_PROOF_FIELDS) {\n if (artifactCompatibility[field] !== proofCompatibility[field]) {\n mismatchedFields.push(field);\n }\n }\n return mismatchedFields;\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unhandled skip/cache proof state: ${String(value)}`);\n}\n\nfunction crossCheckInvalidationProof(\n entry: ClientReuseManifestEntry,\n invalidation: SkipCacheInvalidationProof,\n): SkipCacheCrossCheckRejected | null {\n switch (invalidation.kind) {\n case \"valid\":\n return null;\n case \"unknown\":\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_INVALIDATION_UNKNOWN\");\n case \"invalidated\":\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_INVALIDATED\", {\n invalidationEpoch: invalidation.invalidationEpoch,\n });\n default:\n return assertNever(invalidation);\n }\n}\n\nexport function crossCheckClientReuseManifestEntryWithCache(\n input: CrossCheckClientReuseManifestEntryWithCacheInput,\n): SkipCacheCrossCheckResult {\n const { cacheDecision, entry } = input;\n if (cacheDecision === null) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PROOF_MISSING\");\n }\n if (cacheDecision.kind === \"fallback\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PROOF_REJECTED\", {\n cacheProofCode: cacheDecision.fallback.code,\n cacheProofMode: cacheDecision.fallback.mode,\n cacheProofScope: cacheDecision.fallback.scope,\n });\n }\n\n const { proof } = cacheDecision;\n if (entry.kind !== \"layout\" || proof.reuseClass !== \"static-layout\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_REUSE_CLASS_UNSUPPORTED\", {\n entryKind: entry.kind,\n reuseClass: proof.reuseClass,\n });\n }\n\n if (entry.id !== proof.candidateOutput.layoutId) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ENTRY_ID_MISMATCH\", {\n cacheEntryId: proof.candidateOutput.layoutId,\n manifestEntryId: entry.id,\n });\n }\n\n const artifactProofMismatches = collectArtifactCompatibilityProofMismatches(\n input.artifact.compatibility,\n proof.candidateArtifactCompatibility,\n );\n if (artifactProofMismatches.length > 0) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_PROOF_MISMATCH\", {\n mismatchedFields: artifactProofMismatches,\n });\n }\n\n const artifactCompatibility = evaluateArtifactCompatibility(\n input.artifact.compatibility,\n entry.artifactCompatibility,\n { compatibilityMap: input.compatibilityMap },\n );\n if (artifactCompatibility.kind === \"unknown\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_COMPATIBILITY_UNKNOWN\", {\n compatibilityFallback: artifactCompatibility.fallback,\n reason: artifactCompatibility.reason,\n });\n }\n if (artifactCompatibility.kind === \"incompatible\") {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_ARTIFACT_COMPATIBILITY_INCOMPATIBLE\", {\n compatibilityFallback: artifactCompatibility.fallback,\n reason: artifactCompatibility.reason,\n });\n }\n\n if (entry.variantCacheKey !== proof.variant.cacheKey) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_VARIANT_MISMATCH\", {\n cacheVariantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),\n entryVariantCacheKeyHash: createClientReusePayloadHash(entry.variantCacheKey),\n });\n }\n\n if (input.artifact.payloadHash === null) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PAYLOAD_HASH_MISSING\");\n }\n\n if (entry.payloadHash !== input.artifact.payloadHash) {\n return rejectSkipCacheCrossCheck(entry, \"SKIP_CACHE_PAYLOAD_HASH_MISMATCH\", {\n cachePayloadHash: input.artifact.payloadHash,\n entryPayloadHash: entry.payloadHash,\n });\n }\n\n const invalidationRejection = crossCheckInvalidationProof(entry, input.artifact.invalidation);\n if (invalidationRejection) return invalidationRejection;\n\n return {\n kind: \"verified\",\n code: \"SKIP_CACHE_CROSS_CHECK_PASSED\",\n entryId: entry.id,\n fields: {\n entryKind: entry.kind,\n reuseClass: proof.reuseClass,\n variantCacheKeyHash: createClientReusePayloadHash(proof.variant.cacheKey),\n },\n skipDisposition: createDisabledSkipDisposition(),\n };\n}\n"],"mappings":"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"}
@@ -21,7 +21,7 @@ type StaticFileEntry = {
21
21
  *
22
22
  * Usage:
23
23
  * const cache = await StaticFileCache.create(clientDir);
24
- * const entry = cache.lookup("/assets/app-abc123.js");
24
+ * const entry = cache.lookup("/_next/static/app-abc123.js");
25
25
  * // entry.br?.headers, entry.original.headers, etc.
26
26
  */
27
27
  declare class StaticFileCache {
@@ -1,5 +1,6 @@
1
+ import "../utils/asset-prefix.js";
1
2
  import path from "node:path";
2
- import fsp from "node:fs/promises";
3
+ import fs from "node:fs/promises";
3
4
  //#region src/server/static-file-cache.ts
4
5
  /**
5
6
  * Startup metadata cache for static file serving.
@@ -48,7 +49,7 @@ const BUFFER_THRESHOLD = 64 * 1024;
48
49
  *
49
50
  * Usage:
50
51
  * const cache = await StaticFileCache.create(clientDir);
51
- * const entry = cache.lookup("/assets/app-abc123.js");
52
+ * const entry = cache.lookup("/_next/static/app-abc123.js");
52
53
  * // entry.br?.headers, entry.original.headers, etc.
53
54
  */
54
55
  var StaticFileCache = class StaticFileCache {
@@ -74,7 +75,7 @@ var StaticFileCache = class StaticFileCache {
74
75
  if (relativePath.startsWith(".vite/") || relativePath === ".vite") continue;
75
76
  const ext = path.extname(relativePath);
76
77
  const contentType = CONTENT_TYPES[ext] ?? "application/octet-stream";
77
- const isHashed = relativePath.startsWith("assets/") || relativePath.startsWith("_next/static/") || relativePath.includes("/_next/static/");
78
+ const isHashed = relativePath.startsWith(`_next/static/`) || relativePath.includes(`/_next/static/`);
78
79
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
79
80
  const etag = isHashed && etagFromFilenameHash(relativePath, ext) || `W/"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1e3)}"`;
80
81
  const baseHeaders = {
@@ -134,7 +135,7 @@ var StaticFileCache = class StaticFileCache {
134
135
  }
135
136
  }
136
137
  for (let i = 0; i < toBuffer.length; i += 64) await Promise.all(toBuffer.slice(i, i + 64).map(async (v) => {
137
- v.buffer = await fsp.readFile(v.path);
138
+ v.buffer = await fs.readFile(v.path);
138
139
  }));
139
140
  return new StaticFileCache(entries);
140
141
  }
@@ -190,7 +191,7 @@ const STAT_BATCH_SIZE = 64;
190
191
  async function* walkFilesWithStats(dir, base = dir) {
191
192
  let entries;
192
193
  try {
193
- entries = await fsp.readdir(dir, { withFileTypes: true });
194
+ entries = await fs.readdir(dir, { withFileTypes: true });
194
195
  } catch {
195
196
  return;
196
197
  }
@@ -202,9 +203,9 @@ async function* walkFilesWithStats(dir, base = dir) {
202
203
  }
203
204
  for (let i = 0; i < files.length; i += STAT_BATCH_SIZE) {
204
205
  const batch = files.slice(i, i + STAT_BATCH_SIZE);
205
- const stats = await Promise.all(batch.map((f) => fsp.stat(f)));
206
+ const stats = await Promise.all(batch.map((f) => fs.stat(f)));
206
207
  for (let j = 0; j < batch.length; j++) yield {
207
- relativePath: path.relative(base, batch[j]),
208
+ relativePath: path.relative(base, batch[j]).replaceAll(path.sep, "/"),
208
209
  fullPath: batch[j],
209
210
  stat: {
210
211
  size: stats[j].size,
@@ -1 +1 @@
1
- {"version":3,"file":"static-file-cache.js","names":[],"sources":["../../src/server/static-file-cache.ts"],"sourcesContent":["/**\n * Startup metadata cache for static file serving.\n *\n * Walks dist/client/ once at server boot, pre-computes response headers for\n * every file variant (original, brotli, gzip, zstd), and caches everything\n * in memory. The per-request hot path is just: Map.get() → string compare\n * (ETag) → writeHead(precomputed) → pipe.\n *\n * Modeled after sirv's production mode. Key insight from sirv: pre-compute\n * ALL response headers at startup — Content-Type, Content-Length, ETag,\n * Cache-Control, Content-Encoding, Vary — as reusable objects. The common\n * per-request path (no extraHeaders) does zero object allocation for headers.\n */\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Content-type lookup for static assets. Shared with prod-server.ts. */\nexport const CONTENT_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".html\": \"text/html\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".map\": \"application/json\",\n \".rsc\": \"text/x-component\",\n};\n\n/**\n * Files below this size are buffered in memory at startup for zero-syscall\n * serving via res.end(buffer). Above this, files stream via createReadStream.\n * 64KB covers virtually all precompressed assets (a 200KB JS bundle compresses\n * to ~50KB with brotli q5).\n */\nconst BUFFER_THRESHOLD = 64 * 1024;\n\n/** A servable file variant with pre-computed response headers. */\ntype FileVariant = {\n /** Absolute file path (used for streaming large files). */\n path: string;\n /** Uncompressed or encoded byte size for buffer-threshold decisions. */\n size: number;\n /** Pre-computed response headers. */\n headers: Record<string, string>;\n /** In-memory buffer for small files (below BUFFER_THRESHOLD). */\n buffer?: Buffer;\n};\n\ntype StaticFileEntry = {\n /** Weak ETag for conditional request matching. */\n etag: string;\n /** Pre-computed headers for 304 Not Modified response. */\n notModifiedHeaders: Record<string, string>;\n /** Original file variant (uncompressed). */\n original: FileVariant;\n /** Brotli precompressed variant, if .br file exists. */\n br?: FileVariant;\n /** Gzip precompressed variant, if .gz file exists. */\n gz?: FileVariant;\n /** Zstandard precompressed variant, if .zst file exists. */\n zst?: FileVariant;\n};\n\n/**\n * In-memory cache of static file metadata, populated once at server startup.\n *\n * Usage:\n * const cache = await StaticFileCache.create(clientDir);\n * const entry = cache.lookup(\"/assets/app-abc123.js\");\n * // entry.br?.headers, entry.original.headers, etc.\n */\nexport class StaticFileCache {\n private readonly entries: Map<string, StaticFileEntry>;\n\n private constructor(entries: Map<string, StaticFileEntry>) {\n this.entries = entries;\n }\n\n /**\n * Scan the client directory and build the cache.\n *\n * Gracefully handles non-existent directories (returns an empty cache).\n */\n static async create(clientDir: string): Promise<StaticFileCache> {\n const entries = new Map<string, StaticFileEntry>();\n\n // First pass: collect all regular files with their metadata\n const allFiles = new Map<string, { fullPath: string; size: number; mtimeMs: number }>();\n\n for await (const { relativePath, fullPath, stat } of walkFilesWithStats(clientDir)) {\n allFiles.set(relativePath, { fullPath, size: stat.size, mtimeMs: stat.mtimeMs });\n }\n\n // Second pass: build cache entries with pre-computed headers per variant\n for (const [relativePath, fileInfo] of allFiles) {\n // Skip precompressed variants — they're linked to their originals\n if (\n relativePath.endsWith(\".br\") ||\n relativePath.endsWith(\".gz\") ||\n relativePath.endsWith(\".zst\")\n )\n continue;\n\n // Skip .vite/ internal directory\n if (relativePath.startsWith(\".vite/\") || relativePath === \".vite\") continue;\n\n const ext = path.extname(relativePath);\n const contentType = CONTENT_TYPES[ext] ?? \"application/octet-stream\";\n // Files under Vite's `assetsDir` are content-hashed. The historical\n // default is `assets/`; when `assetPrefix` is configured the layout\n // becomes `<prefix>/_next/static/...` (path-prefix) or `_next/static/...`\n // (absolute-URL prefix). All three forms get long-lived `immutable`\n // cache headers — the hash in the filename invalidates safely.\n //\n // `relativePath` is the path relative to `clientDir`, with no leading\n // slash. Because of that, `startsWith(\"_next/static/\")` and\n // `includes(\"/_next/static/\")` are NOT equivalent — the former covers\n // the absolute-URL prefix layout (no parent directory), the latter\n // covers the path-prefix layout (under an arbitrary parent like `cdn/`).\n const isHashed =\n relativePath.startsWith(\"assets/\") ||\n relativePath.startsWith(\"_next/static/\") ||\n relativePath.includes(\"/_next/static/\");\n const cacheControl = isHashed\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\";\n const etag =\n (isHashed && etagFromFilenameHash(relativePath, ext)) ||\n `W/\"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1000)}\"`;\n\n // Base headers shared by all variants (Content-Type, Cache-Control, ETag)\n const baseHeaders = {\n \"Content-Type\": contentType,\n \"Cache-Control\": cacheControl,\n ETag: etag,\n };\n\n // Pre-compute original variant headers\n const original: FileVariant = {\n path: fileInfo.fullPath,\n size: fileInfo.size,\n headers: { ...baseHeaders, \"Content-Length\": String(fileInfo.size) },\n };\n\n const entry: StaticFileEntry = {\n etag,\n notModifiedHeaders: { ETag: etag, \"Cache-Control\": cacheControl },\n original,\n };\n\n // Pre-compute compressed variant headers (with Content-Encoding, Vary, correct Content-Length)\n const brInfo = allFiles.get(relativePath + \".br\");\n if (brInfo) {\n entry.br = buildVariant(brInfo, baseHeaders, \"br\");\n }\n\n const gzInfo = allFiles.get(relativePath + \".gz\");\n if (gzInfo) {\n entry.gz = buildVariant(gzInfo, baseHeaders, \"gzip\");\n }\n\n const zstInfo = allFiles.get(relativePath + \".zst\");\n if (zstInfo) {\n entry.zst = buildVariant(zstInfo, baseHeaders, \"zstd\");\n }\n\n // When compressed variants exist, the original needs Vary too so\n // shared caches don't serve uncompressed to compression-capable clients.\n if (entry.br || entry.gz || entry.zst) {\n original.headers[\"Vary\"] = \"Accept-Encoding\";\n entry.notModifiedHeaders[\"Vary\"] = \"Accept-Encoding\";\n }\n\n // Register under the URL pathname (leading /)\n // NOTE: aliases below share the same entry by reference, so all header\n // mutations (e.g. Vary above) must happen before registration.\n const pathname = \"/\" + relativePath;\n entries.set(pathname, entry);\n\n // Register HTML fallback aliases (same entry object — no duplication)\n if (ext === \".html\") {\n if (relativePath.endsWith(\"/index.html\")) {\n const dirPath = \"/\" + relativePath.slice(0, -\"/index.html\".length);\n if (dirPath !== \"/\") {\n entries.set(dirPath, entry);\n }\n } else {\n const withoutExt = \"/\" + relativePath.slice(0, -ext.length);\n entries.set(withoutExt, entry);\n }\n }\n }\n\n // Third pass: buffer small files in memory for zero-syscall serving.\n // For small compressed variants (e.g. a 50KB JS bundle → ~15KB brotli),\n // res.end(buffer) is ~2x faster than createReadStream().pipe() because\n // it skips fd open/close and stream plumbing overhead.\n // Reads are chunked at 64 concurrent to avoid fd exhaustion on large projects.\n // Deduplicate at the entry level first: HTML aliases share the same\n // StaticFileEntry by reference, so entries.values() yields duplicates for\n // paths like /about and /about.html. Deduping entries avoids iterating\n // their variants multiple times on sites with many HTML pages.\n const toBuffer: FileVariant[] = [];\n const seenEntries = new Set<StaticFileEntry>();\n for (const entry of entries.values()) {\n if (seenEntries.has(entry)) continue;\n seenEntries.add(entry);\n for (const variant of [entry.original, entry.br, entry.gz, entry.zst]) {\n if (!variant || variant.size > BUFFER_THRESHOLD) continue;\n toBuffer.push(variant);\n }\n }\n for (let i = 0; i < toBuffer.length; i += 64) {\n await Promise.all(\n toBuffer.slice(i, i + 64).map(async (v) => {\n v.buffer = await fsp.readFile(v.path);\n }),\n );\n }\n\n return new StaticFileCache(entries);\n }\n\n /**\n * Look up cached metadata for a URL pathname.\n *\n * Returns undefined if the file is not in the cache. The root path \"/\"\n * always returns undefined — index.html is served by SSR/RSC.\n */\n lookup(pathname: string): StaticFileEntry | undefined {\n if (pathname === \"/\") return undefined;\n\n // Block .vite/ access (including encoded variants that were decoded before lookup)\n if (pathname.startsWith(\"/.vite/\") || pathname === \"/.vite\") return undefined;\n\n return this.entries.get(pathname);\n }\n}\n\n/**\n * Extract a stable weak ETag from a Vite hashed filename (e.g. `app-DqZc3R4n.js`).\n * The hash is a content hash computed by the bundler — deterministic across\n * identical builds regardless of filesystem timestamps.\n *\n * Must be a weak validator (W/) because the same tag is shared across\n * content-encoded variants (original, .br, .gz, .zst) which are byte-different.\n * Returns null if the filename doesn't contain a recognizable hash suffix,\n * so the caller can fall back to mtime-based ETags.\n */\nexport function etagFromFilenameHash(relativePath: string, ext: string): string | null {\n const basename = path.basename(relativePath, ext);\n const lastDash = basename.lastIndexOf(\"-\");\n if (lastDash === -1 || lastDash === basename.length - 1) return null;\n const suffix = basename.slice(lastDash + 1);\n // Vite emits 8-char base64url hashes; allow 6-12 for other bundlers.\n // If Rolldown changes its hash length, update this range.\n return suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)\n ? `W/\"${suffix}\"`\n : null;\n}\n\nfunction buildVariant(\n info: { fullPath: string; size: number },\n baseHeaders: Record<string, string>,\n encoding: string,\n): FileVariant {\n return {\n path: info.fullPath,\n size: info.size,\n headers: {\n ...baseHeaders,\n \"Content-Encoding\": encoding,\n \"Content-Length\": String(info.size),\n Vary: \"Accept-Encoding\",\n },\n };\n}\n\n/** Batch size for concurrent stat() calls during directory walk. */\nconst STAT_BATCH_SIZE = 64;\n\n/**\n * Walk a directory recursively, yielding file paths and stats.\n *\n * Batches stat() calls per directory to avoid sequential syscall overhead\n * for large dist/client/ directories.\n */\nasync function* walkFilesWithStats(\n dir: string,\n base: string = dir,\n): AsyncGenerator<{\n relativePath: string;\n fullPath: string;\n stat: { size: number; mtimeMs: number };\n}> {\n let entries;\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // directory doesn't exist or unreadable\n }\n\n // Recurse into subdirectories first (they yield their own batched stats)\n const files: string[] = [];\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkFilesWithStats(fullPath, base);\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n\n // Batch stat() calls for files in this directory\n for (let i = 0; i < files.length; i += STAT_BATCH_SIZE) {\n const batch = files.slice(i, i + STAT_BATCH_SIZE);\n const stats = await Promise.all(batch.map((f) => fsp.stat(f)));\n for (let j = 0; j < batch.length; j++) {\n yield {\n relativePath: path.relative(base, batch[j]),\n fullPath: batch[j],\n stat: { size: stats[j].size, mtimeMs: stats[j].mtimeMs },\n };\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"static-file-cache.js","names":["fsp"],"sources":["../../src/server/static-file-cache.ts"],"sourcesContent":["/**\n * Startup metadata cache for static file serving.\n *\n * Walks dist/client/ once at server boot, pre-computes response headers for\n * every file variant (original, brotli, gzip, zstd), and caches everything\n * in memory. The per-request hot path is just: Map.get() → string compare\n * (ETag) → writeHead(precomputed) → pipe.\n *\n * Modeled after sirv's production mode. Key insight from sirv: pre-compute\n * ALL response headers at startup — Content-Type, Content-Length, ETag,\n * Cache-Control, Content-Encoding, Vary — as reusable objects. The common\n * per-request path (no extraHeaders) does zero object allocation for headers.\n */\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ASSET_PREFIX_URL_DIR } from \"../utils/asset-prefix.js\";\n\n/** Content-type lookup for static assets. Shared with prod-server.ts. */\nexport const CONTENT_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".html\": \"text/html\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".map\": \"application/json\",\n \".rsc\": \"text/x-component\",\n};\n\n/**\n * Files below this size are buffered in memory at startup for zero-syscall\n * serving via res.end(buffer). Above this, files stream via createReadStream.\n * 64KB covers virtually all precompressed assets (a 200KB JS bundle compresses\n * to ~50KB with brotli q5).\n */\nconst BUFFER_THRESHOLD = 64 * 1024;\n\n/** A servable file variant with pre-computed response headers. */\ntype FileVariant = {\n /** Absolute file path (used for streaming large files). */\n path: string;\n /** Uncompressed or encoded byte size for buffer-threshold decisions. */\n size: number;\n /** Pre-computed response headers. */\n headers: Record<string, string>;\n /** In-memory buffer for small files (below BUFFER_THRESHOLD). */\n buffer?: Buffer;\n};\n\ntype StaticFileEntry = {\n /** Weak ETag for conditional request matching. */\n etag: string;\n /** Pre-computed headers for 304 Not Modified response. */\n notModifiedHeaders: Record<string, string>;\n /** Original file variant (uncompressed). */\n original: FileVariant;\n /** Brotli precompressed variant, if .br file exists. */\n br?: FileVariant;\n /** Gzip precompressed variant, if .gz file exists. */\n gz?: FileVariant;\n /** Zstandard precompressed variant, if .zst file exists. */\n zst?: FileVariant;\n};\n\n/**\n * In-memory cache of static file metadata, populated once at server startup.\n *\n * Usage:\n * const cache = await StaticFileCache.create(clientDir);\n * const entry = cache.lookup(\"/_next/static/app-abc123.js\");\n * // entry.br?.headers, entry.original.headers, etc.\n */\nexport class StaticFileCache {\n private readonly entries: Map<string, StaticFileEntry>;\n\n private constructor(entries: Map<string, StaticFileEntry>) {\n this.entries = entries;\n }\n\n /**\n * Scan the client directory and build the cache.\n *\n * Gracefully handles non-existent directories (returns an empty cache).\n */\n static async create(clientDir: string): Promise<StaticFileCache> {\n const entries = new Map<string, StaticFileEntry>();\n\n // First pass: collect all regular files with their metadata\n const allFiles = new Map<string, { fullPath: string; size: number; mtimeMs: number }>();\n\n for await (const { relativePath, fullPath, stat } of walkFilesWithStats(clientDir)) {\n allFiles.set(relativePath, { fullPath, size: stat.size, mtimeMs: stat.mtimeMs });\n }\n\n // Second pass: build cache entries with pre-computed headers per variant\n for (const [relativePath, fileInfo] of allFiles) {\n // Skip precompressed variants — they're linked to their originals\n if (\n relativePath.endsWith(\".br\") ||\n relativePath.endsWith(\".gz\") ||\n relativePath.endsWith(\".zst\")\n )\n continue;\n\n // Skip .vite/ internal directory\n if (relativePath.startsWith(\".vite/\") || relativePath === \".vite\") continue;\n\n const ext = path.extname(relativePath);\n const contentType = CONTENT_TYPES[ext] ?? \"application/octet-stream\";\n // Files under Vite's `assetsDir` are content-hashed. The default\n // layout writes to `<ASSET_PREFIX_URL_DIR>/` (Next.js's canonical\n // convention); when `assetPrefix` is a path prefix the layout\n // becomes `<prefix>/<ASSET_PREFIX_URL_DIR>/...`. Both forms get\n // long-lived `immutable` cache headers — the hash in the filename\n // invalidates safely.\n //\n // `relativePath` is the path relative to `clientDir`, with no\n // leading slash. Because of that, `startsWith(\"<dir>/\")` and\n // `includes(\"/<dir>/\")` are NOT equivalent — the former covers the\n // default and absolute-URL prefix layouts (no parent directory),\n // the latter covers the path-prefix layout (under an arbitrary\n // parent like `cdn/`).\n const isHashed =\n relativePath.startsWith(`${ASSET_PREFIX_URL_DIR}/`) ||\n relativePath.includes(`/${ASSET_PREFIX_URL_DIR}/`);\n const cacheControl = isHashed\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\";\n const etag =\n (isHashed && etagFromFilenameHash(relativePath, ext)) ||\n `W/\"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1000)}\"`;\n\n // Base headers shared by all variants (Content-Type, Cache-Control, ETag)\n const baseHeaders = {\n \"Content-Type\": contentType,\n \"Cache-Control\": cacheControl,\n ETag: etag,\n };\n\n // Pre-compute original variant headers\n const original: FileVariant = {\n path: fileInfo.fullPath,\n size: fileInfo.size,\n headers: { ...baseHeaders, \"Content-Length\": String(fileInfo.size) },\n };\n\n const entry: StaticFileEntry = {\n etag,\n notModifiedHeaders: { ETag: etag, \"Cache-Control\": cacheControl },\n original,\n };\n\n // Pre-compute compressed variant headers (with Content-Encoding, Vary, correct Content-Length)\n const brInfo = allFiles.get(relativePath + \".br\");\n if (brInfo) {\n entry.br = buildVariant(brInfo, baseHeaders, \"br\");\n }\n\n const gzInfo = allFiles.get(relativePath + \".gz\");\n if (gzInfo) {\n entry.gz = buildVariant(gzInfo, baseHeaders, \"gzip\");\n }\n\n const zstInfo = allFiles.get(relativePath + \".zst\");\n if (zstInfo) {\n entry.zst = buildVariant(zstInfo, baseHeaders, \"zstd\");\n }\n\n // When compressed variants exist, the original needs Vary too so\n // shared caches don't serve uncompressed to compression-capable clients.\n if (entry.br || entry.gz || entry.zst) {\n original.headers[\"Vary\"] = \"Accept-Encoding\";\n entry.notModifiedHeaders[\"Vary\"] = \"Accept-Encoding\";\n }\n\n // Register under the URL pathname (leading /)\n // NOTE: aliases below share the same entry by reference, so all header\n // mutations (e.g. Vary above) must happen before registration.\n const pathname = \"/\" + relativePath;\n entries.set(pathname, entry);\n\n // Register HTML fallback aliases (same entry object — no duplication)\n if (ext === \".html\") {\n if (relativePath.endsWith(\"/index.html\")) {\n const dirPath = \"/\" + relativePath.slice(0, -\"/index.html\".length);\n if (dirPath !== \"/\") {\n entries.set(dirPath, entry);\n }\n } else {\n const withoutExt = \"/\" + relativePath.slice(0, -ext.length);\n entries.set(withoutExt, entry);\n }\n }\n }\n\n // Third pass: buffer small files in memory for zero-syscall serving.\n // For small compressed variants (e.g. a 50KB JS bundle → ~15KB brotli),\n // res.end(buffer) is ~2x faster than createReadStream().pipe() because\n // it skips fd open/close and stream plumbing overhead.\n // Reads are chunked at 64 concurrent to avoid fd exhaustion on large projects.\n // Deduplicate at the entry level first: HTML aliases share the same\n // StaticFileEntry by reference, so entries.values() yields duplicates for\n // paths like /about and /about.html. Deduping entries avoids iterating\n // their variants multiple times on sites with many HTML pages.\n const toBuffer: FileVariant[] = [];\n const seenEntries = new Set<StaticFileEntry>();\n for (const entry of entries.values()) {\n if (seenEntries.has(entry)) continue;\n seenEntries.add(entry);\n for (const variant of [entry.original, entry.br, entry.gz, entry.zst]) {\n if (!variant || variant.size > BUFFER_THRESHOLD) continue;\n toBuffer.push(variant);\n }\n }\n for (let i = 0; i < toBuffer.length; i += 64) {\n await Promise.all(\n toBuffer.slice(i, i + 64).map(async (v) => {\n v.buffer = await fsp.readFile(v.path);\n }),\n );\n }\n\n return new StaticFileCache(entries);\n }\n\n /**\n * Look up cached metadata for a URL pathname.\n *\n * Returns undefined if the file is not in the cache. The root path \"/\"\n * always returns undefined — index.html is served by SSR/RSC.\n */\n lookup(pathname: string): StaticFileEntry | undefined {\n if (pathname === \"/\") return undefined;\n\n // Block .vite/ access (including encoded variants that were decoded before lookup)\n if (pathname.startsWith(\"/.vite/\") || pathname === \"/.vite\") return undefined;\n\n return this.entries.get(pathname);\n }\n}\n\n/**\n * Extract a stable weak ETag from a Vite hashed filename (e.g. `app-DqZc3R4n.js`).\n * The hash is a content hash computed by the bundler — deterministic across\n * identical builds regardless of filesystem timestamps.\n *\n * Must be a weak validator (W/) because the same tag is shared across\n * content-encoded variants (original, .br, .gz, .zst) which are byte-different.\n * Returns null if the filename doesn't contain a recognizable hash suffix,\n * so the caller can fall back to mtime-based ETags.\n */\nexport function etagFromFilenameHash(relativePath: string, ext: string): string | null {\n const basename = path.basename(relativePath, ext);\n const lastDash = basename.lastIndexOf(\"-\");\n if (lastDash === -1 || lastDash === basename.length - 1) return null;\n const suffix = basename.slice(lastDash + 1);\n // Vite emits 8-char base64url hashes; allow 6-12 for other bundlers.\n // If Rolldown changes its hash length, update this range.\n return suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)\n ? `W/\"${suffix}\"`\n : null;\n}\n\nfunction buildVariant(\n info: { fullPath: string; size: number },\n baseHeaders: Record<string, string>,\n encoding: string,\n): FileVariant {\n return {\n path: info.fullPath,\n size: info.size,\n headers: {\n ...baseHeaders,\n \"Content-Encoding\": encoding,\n \"Content-Length\": String(info.size),\n Vary: \"Accept-Encoding\",\n },\n };\n}\n\n/** Batch size for concurrent stat() calls during directory walk. */\nconst STAT_BATCH_SIZE = 64;\n\n/**\n * Walk a directory recursively, yielding file paths and stats.\n *\n * Batches stat() calls per directory to avoid sequential syscall overhead\n * for large dist/client/ directories.\n */\nasync function* walkFilesWithStats(\n dir: string,\n base: string = dir,\n): AsyncGenerator<{\n relativePath: string;\n fullPath: string;\n stat: { size: number; mtimeMs: number };\n}> {\n let entries;\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // directory doesn't exist or unreadable\n }\n\n // Recurse into subdirectories first (they yield their own batched stats)\n const files: string[] = [];\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkFilesWithStats(fullPath, base);\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n\n // Batch stat() calls for files in this directory\n for (let i = 0; i < files.length; i += STAT_BATCH_SIZE) {\n const batch = files.slice(i, i + STAT_BATCH_SIZE);\n const stats = await Promise.all(batch.map((f) => fsp.stat(f)));\n for (let j = 0; j < batch.length; j++) {\n yield {\n relativePath: path.relative(base, batch[j]).replaceAll(path.sep, \"/\"),\n fullPath: batch[j],\n stat: { size: stats[j].size, mtimeMs: stats[j].mtimeMs },\n };\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,gBAAwC;CACnD,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACT;;;;;;;AAQD,MAAM,mBAAmB,KAAK;;;;;;;;;AAqC9B,IAAa,kBAAb,MAAa,gBAAgB;CAC3B;CAEA,YAAoB,SAAuC;EACzD,KAAK,UAAU;;;;;;;CAQjB,aAAa,OAAO,WAA6C;EAC/D,MAAM,0BAAU,IAAI,KAA8B;EAGlD,MAAM,2BAAW,IAAI,KAAkE;EAEvF,WAAW,MAAM,EAAE,cAAc,UAAU,UAAU,mBAAmB,UAAU,EAChF,SAAS,IAAI,cAAc;GAAE;GAAU,MAAM,KAAK;GAAM,SAAS,KAAK;GAAS,CAAC;EAIlF,KAAK,MAAM,CAAC,cAAc,aAAa,UAAU;GAE/C,IACE,aAAa,SAAS,MAAM,IAC5B,aAAa,SAAS,MAAM,IAC5B,aAAa,SAAS,OAAO,EAE7B;GAGF,IAAI,aAAa,WAAW,SAAS,IAAI,iBAAiB,SAAS;GAEnE,MAAM,MAAM,KAAK,QAAQ,aAAa;GACtC,MAAM,cAAc,cAAc,QAAQ;GAc1C,MAAM,WACJ,aAAa,WAAW,gBAA2B,IACnD,aAAa,SAAS,iBAA4B;GACpD,MAAM,eAAe,WACjB,wCACA;GACJ,MAAM,OACH,YAAY,qBAAqB,cAAc,IAAI,IACpD,MAAM,SAAS,KAAK,GAAG,KAAK,MAAM,SAAS,UAAU,IAAK,CAAC;GAG7D,MAAM,cAAc;IAClB,gBAAgB;IAChB,iBAAiB;IACjB,MAAM;IACP;GAGD,MAAM,WAAwB;IAC5B,MAAM,SAAS;IACf,MAAM,SAAS;IACf,SAAS;KAAE,GAAG;KAAa,kBAAkB,OAAO,SAAS,KAAK;KAAE;IACrE;GAED,MAAM,QAAyB;IAC7B;IACA,oBAAoB;KAAE,MAAM;KAAM,iBAAiB;KAAc;IACjE;IACD;GAGD,MAAM,SAAS,SAAS,IAAI,eAAe,MAAM;GACjD,IAAI,QACF,MAAM,KAAK,aAAa,QAAQ,aAAa,KAAK;GAGpD,MAAM,SAAS,SAAS,IAAI,eAAe,MAAM;GACjD,IAAI,QACF,MAAM,KAAK,aAAa,QAAQ,aAAa,OAAO;GAGtD,MAAM,UAAU,SAAS,IAAI,eAAe,OAAO;GACnD,IAAI,SACF,MAAM,MAAM,aAAa,SAAS,aAAa,OAAO;GAKxD,IAAI,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK;IACrC,SAAS,QAAQ,UAAU;IAC3B,MAAM,mBAAmB,UAAU;;GAMrC,MAAM,WAAW,MAAM;GACvB,QAAQ,IAAI,UAAU,MAAM;GAG5B,IAAI,QAAQ,SACV,IAAI,aAAa,SAAS,cAAc,EAAE;IACxC,MAAM,UAAU,MAAM,aAAa,MAAM,GAAG,IAAsB;IAClE,IAAI,YAAY,KACd,QAAQ,IAAI,SAAS,MAAM;UAExB;IACL,MAAM,aAAa,MAAM,aAAa,MAAM,GAAG,CAAC,IAAI,OAAO;IAC3D,QAAQ,IAAI,YAAY,MAAM;;;EAcpC,MAAM,WAA0B,EAAE;EAClC,MAAM,8BAAc,IAAI,KAAsB;EAC9C,KAAK,MAAM,SAAS,QAAQ,QAAQ,EAAE;GACpC,IAAI,YAAY,IAAI,MAAM,EAAE;GAC5B,YAAY,IAAI,MAAM;GACtB,KAAK,MAAM,WAAW;IAAC,MAAM;IAAU,MAAM;IAAI,MAAM;IAAI,MAAM;IAAI,EAAE;IACrE,IAAI,CAAC,WAAW,QAAQ,OAAO,kBAAkB;IACjD,SAAS,KAAK,QAAQ;;;EAG1B,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,IACxC,MAAM,QAAQ,IACZ,SAAS,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,OAAO,MAAM;GACzC,EAAE,SAAS,MAAMA,GAAI,SAAS,EAAE,KAAK;IACrC,CACH;EAGH,OAAO,IAAI,gBAAgB,QAAQ;;;;;;;;CASrC,OAAO,UAA+C;EACpD,IAAI,aAAa,KAAK,OAAO,KAAA;EAG7B,IAAI,SAAS,WAAW,UAAU,IAAI,aAAa,UAAU,OAAO,KAAA;EAEpE,OAAO,KAAK,QAAQ,IAAI,SAAS;;;;;;;;;;;;;AAcrC,SAAgB,qBAAqB,cAAsB,KAA4B;CACrF,MAAM,WAAW,KAAK,SAAS,cAAc,IAAI;CACjD,MAAM,WAAW,SAAS,YAAY,IAAI;CAC1C,IAAI,aAAa,MAAM,aAAa,SAAS,SAAS,GAAG,OAAO;CAChE,MAAM,SAAS,SAAS,MAAM,WAAW,EAAE;CAG3C,OAAO,OAAO,UAAU,KAAK,OAAO,UAAU,MAAM,mBAAmB,KAAK,OAAO,GAC/E,MAAM,OAAO,KACb;;AAGN,SAAS,aACP,MACA,aACA,UACa;CACb,OAAO;EACL,MAAM,KAAK;EACX,MAAM,KAAK;EACX,SAAS;GACP,GAAG;GACH,oBAAoB;GACpB,kBAAkB,OAAO,KAAK,KAAK;GACnC,MAAM;GACP;EACF;;;AAIH,MAAM,kBAAkB;;;;;;;AAQxB,gBAAgB,mBACd,KACA,OAAe,KAKd;CACD,IAAI;CACJ,IAAI;EACF,UAAU,MAAMA,GAAI,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;SACnD;EACN;;CAIF,MAAM,QAAkB,EAAE;CAC1B,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,KAAK,MAAM,KAAK;EAC3C,IAAI,MAAM,aAAa,EACrB,OAAO,mBAAmB,UAAU,KAAK;OACpC,IAAI,MAAM,QAAQ,EACvB,MAAM,KAAK,SAAS;;CAKxB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,iBAAiB;EACtD,MAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,gBAAgB;EACjD,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,KAAK,MAAMA,GAAI,KAAK,EAAE,CAAC,CAAC;EAC9D,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAChC,MAAM;GACJ,cAAc,KAAK,SAAS,MAAM,MAAM,GAAG,CAAC,WAAW,KAAK,KAAK,IAAI;GACrE,UAAU,MAAM;GAChB,MAAM;IAAE,MAAM,MAAM,GAAG;IAAM,SAAS,MAAM,GAAG;IAAS;GACzD"}
@@ -0,0 +1,5 @@
1
+ //#region src/server/streaming-metadata.d.ts
2
+ declare function shouldServeStreamingMetadata(userAgent: string, htmlLimitedBots: string | undefined): boolean;
3
+ //#endregion
4
+ export { shouldServeStreamingMetadata };
5
+ //# sourceMappingURL=streaming-metadata.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { getHtmlLimitedBotRegex } from "../utils/html-limited-bots.js";
2
+ //#region src/server/streaming-metadata.ts
3
+ function shouldServeStreamingMetadata(userAgent, htmlLimitedBots) {
4
+ if (!userAgent) return true;
5
+ return !getHtmlLimitedBotRegex(htmlLimitedBots).test(userAgent);
6
+ }
7
+ //#endregion
8
+ export { shouldServeStreamingMetadata };
9
+
10
+ //# sourceMappingURL=streaming-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-metadata.js","names":[],"sources":["../../src/server/streaming-metadata.ts"],"sourcesContent":["import { getHtmlLimitedBotRegex } from \"../utils/html-limited-bots.js\";\n\nexport function shouldServeStreamingMetadata(\n userAgent: string,\n htmlLimitedBots: string | undefined,\n): boolean {\n if (!userAgent) return true;\n return !getHtmlLimitedBotRegex(htmlLimitedBots).test(userAgent);\n}\n"],"mappings":";;AAEA,SAAgB,6BACd,WACA,iBACS;CACT,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO,CAAC,uBAAuB,gBAAgB,CAAC,KAAK,UAAU"}
@@ -0,0 +1,12 @@
1
+ //#region src/shims/app-router-scroll-state.d.ts
2
+ type AppRouterScrollIntent = Readonly<{
3
+ hash: string | null;
4
+ id: number;
5
+ }>;
6
+ declare function beginAppRouterScrollIntent(hash: string | null): AppRouterScrollIntent;
7
+ declare function clearAppRouterScrollIntent(): void;
8
+ declare function getPendingAppRouterScrollIntent(): AppRouterScrollIntent | null;
9
+ declare function consumeAppRouterScrollIntent(expected?: AppRouterScrollIntent): AppRouterScrollIntent | null;
10
+ //#endregion
11
+ export { AppRouterScrollIntent, beginAppRouterScrollIntent, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent };
12
+ //# sourceMappingURL=app-router-scroll-state.d.ts.map
@@ -0,0 +1,38 @@
1
+ //#region src/shims/app-router-scroll-state.ts
2
+ const _SCROLL_INTENT_KEY = Symbol.for("vinext.appRouterScrollIntent");
3
+ function getScrollIntentStore() {
4
+ const globalState = globalThis;
5
+ globalState[_SCROLL_INTENT_KEY] ??= {
6
+ nextId: 0,
7
+ pending: null
8
+ };
9
+ return globalState[_SCROLL_INTENT_KEY];
10
+ }
11
+ function beginAppRouterScrollIntent(hash) {
12
+ const store = getScrollIntentStore();
13
+ store.nextId += 1;
14
+ const intent = {
15
+ hash,
16
+ id: store.nextId
17
+ };
18
+ store.pending = intent;
19
+ return intent;
20
+ }
21
+ function clearAppRouterScrollIntent() {
22
+ getScrollIntentStore().pending = null;
23
+ }
24
+ function getPendingAppRouterScrollIntent() {
25
+ return getScrollIntentStore().pending;
26
+ }
27
+ function consumeAppRouterScrollIntent(expected) {
28
+ const store = getScrollIntentStore();
29
+ const intent = store.pending;
30
+ if (intent === null) return null;
31
+ if (expected && intent.id !== expected.id) return null;
32
+ store.pending = null;
33
+ return intent;
34
+ }
35
+ //#endregion
36
+ export { beginAppRouterScrollIntent, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent };
37
+
38
+ //# sourceMappingURL=app-router-scroll-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-router-scroll-state.js","names":[],"sources":["../../src/shims/app-router-scroll-state.ts"],"sourcesContent":["export type AppRouterScrollIntent = Readonly<{\n hash: string | null;\n id: number;\n}>;\n\n// A scroll intent is staged by `navigateClientSide` (next/navigation) before an\n// RSC navigation and consumed by the committed `AppRouterScrollTarget`. Both run\n// in the browser, but next/navigation and this module can be loaded through\n// separate Vite module instances (see the Symbol.for navigation state in\n// navigation.ts and AGENTS.md \"RSC and SSR Are Separate Vite Environments\"). If\n// the writer and consumer held different module-level copies, the staged intent\n// would be invisible to the consumer and scroll/focus would silently no-op.\n// Store the single pending intent and the id counter on a Symbol.for global so\n// every instance shares one slot, matching the rest of the navigation state.\nconst _SCROLL_INTENT_KEY = Symbol.for(\"vinext.appRouterScrollIntent\");\n\ntype ScrollIntentStore = {\n nextId: number;\n pending: AppRouterScrollIntent | null;\n};\n\ntype ScrollIntentGlobal = typeof globalThis & {\n [_SCROLL_INTENT_KEY]?: ScrollIntentStore;\n};\n\nfunction getScrollIntentStore(): ScrollIntentStore {\n const globalState = globalThis as ScrollIntentGlobal;\n globalState[_SCROLL_INTENT_KEY] ??= { nextId: 0, pending: null };\n return globalState[_SCROLL_INTENT_KEY]!;\n}\n\nexport function beginAppRouterScrollIntent(hash: string | null): AppRouterScrollIntent {\n const store = getScrollIntentStore();\n store.nextId += 1;\n const intent = {\n hash,\n id: store.nextId,\n };\n store.pending = intent;\n return intent;\n}\n\nexport function clearAppRouterScrollIntent(): void {\n getScrollIntentStore().pending = null;\n}\n\nexport function getPendingAppRouterScrollIntent(): AppRouterScrollIntent | null {\n return getScrollIntentStore().pending;\n}\n\nexport function consumeAppRouterScrollIntent(\n expected?: AppRouterScrollIntent,\n): AppRouterScrollIntent | null {\n const store = getScrollIntentStore();\n const intent = store.pending;\n if (intent === null) return null;\n if (expected && intent.id !== expected.id) return null;\n\n store.pending = null;\n return intent;\n}\n"],"mappings":";AAcA,MAAM,qBAAqB,OAAO,IAAI,+BAA+B;AAWrE,SAAS,uBAA0C;CACjD,MAAM,cAAc;CACpB,YAAY,wBAAwB;EAAE,QAAQ;EAAG,SAAS;EAAM;CAChE,OAAO,YAAY;;AAGrB,SAAgB,2BAA2B,MAA4C;CACrF,MAAM,QAAQ,sBAAsB;CACpC,MAAM,UAAU;CAChB,MAAM,SAAS;EACb;EACA,IAAI,MAAM;EACX;CACD,MAAM,UAAU;CAChB,OAAO;;AAGT,SAAgB,6BAAmC;CACjD,sBAAsB,CAAC,UAAU;;AAGnC,SAAgB,kCAAgE;CAC9E,OAAO,sBAAsB,CAAC;;AAGhC,SAAgB,6BACd,UAC8B;CAC9B,MAAM,QAAQ,sBAAsB;CACpC,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,MAAM,OAAO;CAC5B,IAAI,YAAY,OAAO,OAAO,SAAS,IAAI,OAAO;CAElD,MAAM,UAAU;CAChB,OAAO"}
@@ -0,0 +1,14 @@
1
+ import * as React$1 from "react";
2
+
3
+ //#region src/shims/app-router-scroll.d.ts
4
+ declare class AppRouterScrollTarget extends React$1.Component<{
5
+ children: React$1.ReactNode;
6
+ }> {
7
+ handlePotentialScroll: () => void;
8
+ componentDidMount(): void;
9
+ componentDidUpdate(): void;
10
+ render(): React$1.ReactNode;
11
+ }
12
+ //#endregion
13
+ export { AppRouterScrollTarget };
14
+ //# sourceMappingURL=app-router-scroll.d.ts.map
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ import { decodeHashFragment } from "./hash-scroll.js";
3
+ import { consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent } from "./app-router-scroll-state.js";
4
+ import * as React$1 from "react";
5
+ import * as ReactDOM from "react-dom";
6
+ //#region src/shims/app-router-scroll.tsx
7
+ const reactDomInternalsKey = "__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE";
8
+ const rectProperties = [
9
+ "bottom",
10
+ "height",
11
+ "left",
12
+ "right",
13
+ "top",
14
+ "width",
15
+ "x",
16
+ "y"
17
+ ];
18
+ function readFindDOMNode() {
19
+ const internals = Reflect.get(ReactDOM, reactDomInternalsKey);
20
+ if (typeof internals !== "object" || internals === null) return null;
21
+ const findDOMNode = Reflect.get(internals, "findDOMNode");
22
+ return typeof findDOMNode === "function" ? findDOMNode : null;
23
+ }
24
+ function findDOMNode(instance) {
25
+ if (typeof window === "undefined") return null;
26
+ const findDOMNodeImpl = readFindDOMNode();
27
+ if (!findDOMNodeImpl) return null;
28
+ const node = findDOMNodeImpl(instance);
29
+ return node instanceof Element || node instanceof Text ? node : null;
30
+ }
31
+ function shouldSkipElement(element) {
32
+ const position = getComputedStyle(element).position;
33
+ if (position === "fixed" || position === "sticky") return true;
34
+ const rect = element.getBoundingClientRect();
35
+ return rectProperties.every((property) => rect[property] === 0);
36
+ }
37
+ function topOfElementInViewport(element, viewportHeight) {
38
+ const rects = element.getClientRects();
39
+ if (rects.length === 0) return false;
40
+ let elementTop = Number.POSITIVE_INFINITY;
41
+ for (const rect of rects) if (rect.top < elementTop) elementTop = rect.top;
42
+ return elementTop >= 0 && elementTop <= viewportHeight;
43
+ }
44
+ function getHashFragmentDomNode(hash) {
45
+ const fragment = decodeHashFragment(hash.startsWith("#") ? hash.slice(1) : hash);
46
+ if (fragment === "top") return document.body;
47
+ const element = document.getElementById(fragment) ?? document.getElementsByName(fragment)[0];
48
+ return element instanceof HTMLElement ? element : null;
49
+ }
50
+ function findNextScrollTarget(node) {
51
+ if (!(node instanceof Element)) return null;
52
+ let target = node;
53
+ while (!(target instanceof HTMLElement) || shouldSkipElement(target)) {
54
+ if (target.nextElementSibling === null) return null;
55
+ target = target.nextElementSibling;
56
+ }
57
+ return target;
58
+ }
59
+ function scrollToElement(target, hash) {
60
+ if (hash !== null) {
61
+ target.scrollIntoView({ behavior: "auto" });
62
+ return;
63
+ }
64
+ const htmlElement = document.documentElement;
65
+ const viewportHeight = htmlElement.clientHeight;
66
+ if (topOfElementInViewport(target, viewportHeight)) return;
67
+ htmlElement.scrollTop = 0;
68
+ if (!topOfElementInViewport(target, viewportHeight)) target.scrollIntoView({
69
+ behavior: "auto",
70
+ block: "start",
71
+ inline: "nearest"
72
+ });
73
+ }
74
+ var AppRouterScrollTarget = class extends React$1.Component {
75
+ handlePotentialScroll = () => {
76
+ const intent = getPendingAppRouterScrollIntent();
77
+ if (intent === null) return;
78
+ let target;
79
+ if (intent.hash !== null) target = getHashFragmentDomNode(intent.hash);
80
+ else target = findNextScrollTarget(findDOMNode(this));
81
+ if (target === null) return;
82
+ const consumed = consumeAppRouterScrollIntent(intent);
83
+ if (consumed === null) return;
84
+ scrollToElement(target, consumed.hash);
85
+ target.focus({ preventScroll: true });
86
+ };
87
+ componentDidMount() {
88
+ this.handlePotentialScroll();
89
+ }
90
+ componentDidUpdate() {
91
+ this.handlePotentialScroll();
92
+ }
93
+ render() {
94
+ return this.props.children;
95
+ }
96
+ };
97
+ //#endregion
98
+ export { AppRouterScrollTarget };
99
+
100
+ //# sourceMappingURL=app-router-scroll.js.map