@wular/pnext 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,481 @@
1
+ // Segment-prefetch `/_tree` payload builder (COMPAT - may import core freely).
2
+ //
3
+ // The client segment cache asks the server for a route's prefetch tree by sending rsc: 1,
4
+ // next-router-prefetch: 1 and next-router-segment-prefetch: /_tree, and the server answers a minimal
5
+ // `RootTreePrefetch`-shaped JSON payload with Content-Type text/x-component, x-nextjs-stale-time and the
6
+ // router Vary.
7
+ //
8
+ // pnext has no RSC flight wire format. The segment-cache suites assert on request/response HEADERS,
9
+ // cache-key/staleness behavior and the post-navigation DOM - NOT the flight bytes. So this endpoint mints
10
+ // a pnext segment payload: the route tree shaped like Next's RootTreePrefetch, with a single whole-route
11
+ // node. The bit-flags and inlining are deliberately omitted; the tree carries just enough for the client
12
+ // LRU and staleTime keying.
13
+
14
+ import { RSC_CONTENT_TYPE_HEADER } from '../next/dist/client/components/app-router-headers';
15
+ import { varyNamesFor, type ResponseVaryParams, type SegmentVaryPayload } from './vary-params';
16
+
17
+ /** The segment-prefetch request sentinel the client sends for the whole tree. */
18
+ export const TREE_SEGMENT_PATH = '/_tree' as const;
19
+
20
+ /**
21
+ * Default dynamic staleTime (seconds) - mirrors Next's staleTimes.dynamic, which has defaulted to 0 since
22
+ * Next 15: dynamic data is never reused unless `experimental.staleTimes.dynamic` opts in.
23
+ */
24
+ export const DEFAULT_DYNAMIC_STALE_TIME_SECONDS = 0;
25
+ /** Default static staleTime (seconds) — mirrors Next's staleTimes.static. */
26
+ export const DEFAULT_STATIC_STALE_TIME_SECONDS = 300;
27
+
28
+ export const enum PrefetchHint {
29
+ HasRuntimePrefetch = 0b00001,
30
+ IsRootLayout = 0b10000,
31
+ ParentInlinedIntoSelf = 0b100000,
32
+ InlinedIntoChild = 0b1000000,
33
+ HeadInlinedIntoSelf = 0b10000000,
34
+ HeadOutlined = 0b100000000,
35
+ PrefetchDisabled = 0b10000000000,
36
+ }
37
+
38
+ /**
39
+ * A single prefetch-tree node. Milestone 1 emits one whole-route node; the
40
+ * shape mirrors Next's `TreePrefetch` (name/param/slots) so the client keying
41
+ * and a later milestone-2 upgrade stay wire-compatible.
42
+ */
43
+ export interface SegmentTreeNode {
44
+ /** Route segment name (the whole pathname for the single-node milestone). */
45
+ name: string;
46
+ /** Dynamic param name captured at this segment, when any. */
47
+ param: null | { type: 'd' | 'c' | 'oc'; key: string | null; siblings: string[] | null };
48
+ /** Child slots keyed by slot name (empty for the whole-route node). */
49
+ slots: Record<string, SegmentTreeNode> | null;
50
+ /** Next-compatible prefetch hint bitmask. */
51
+ prefetchHints: number;
52
+ /** Byte size of this segment payload when it is available at build time. */
53
+ sizeBytes?: number;
54
+ /** Body segment path in the per-route segment store, when outlined. */
55
+ segmentPath?: string;
56
+ /**
57
+ * Segment-M2: the param names this segment's render actually read (`'?'` for
58
+ * any searchParams access). An EMPTY array means "shareable across every param
59
+ * value"; an ABSENT field means the server did not track vary params and the
60
+ * client must key on the exact URL.
61
+ */
62
+ varyParams?: string[];
63
+ }
64
+
65
+ /** The `/_tree` response payload (Next `RootTreePrefetch` analogue). */
66
+ export interface RootTreePrefetch {
67
+ /** Build id when known (start reads it from the manifest; optional). */
68
+ buildId?: string;
69
+ /** The route prefetch tree (single whole-route node in milestone 1). */
70
+ tree: SegmentTreeNode;
71
+ /** Per-tree staleTime in seconds the client keys its LRU window on. */
72
+ staleTime: number;
73
+ /** True when the whole route is statically prerenderable (static window). */
74
+ isStatic: boolean;
75
+ /**
76
+ * Segment-M2 vary sets for this route (body / head / root). Absent when the
77
+ * response was produced without render-time tracking.
78
+ */
79
+ vary?: SegmentVaryPayload;
80
+ /** The matched route pattern the client keys shared segment entries on. */
81
+ route?: string;
82
+ /** Concrete params of the requested URL, so the client can build vary keys. */
83
+ params?: Record<string, string | string[]>;
84
+ /**
85
+ * The route's <title> is DYNAMIC and rides in its own `/_head` response. The client learns it here -
86
+ * before the body request - so the head is fetched FIRST, the order Next's segment prefetch produces,
87
+ * since the head segment sits above the page in the route tree.
88
+ */
89
+ headOutlined?: boolean;
90
+ /**
91
+ * The outlined head is fetched BEFORE the body: this route has dynamic
92
+ * params, so its (param-shared) body and its per-URL head are separate
93
+ * segments and the head leads.
94
+ */
95
+ headFirst?: boolean;
96
+ }
97
+
98
+ interface BuildTreeOptions {
99
+ pathname: string;
100
+ isStatic: boolean;
101
+ staleTimeSeconds?: number;
102
+ buildId?: string;
103
+ routeId?: string;
104
+ bodySizeBytes?: number;
105
+ inlineBudgetBytes?: number;
106
+ runtimePrefetch?: boolean;
107
+ postponed?: boolean;
108
+ /** Segment-M2 vary sets collected while rendering this route, when tracked. */
109
+ vary?: SegmentVaryPayload;
110
+ /** The matched route pattern (client vary-key identity). */
111
+ routePattern?: string;
112
+ /** Concrete params of the requested URL. */
113
+ params?: Record<string, string | string[]>;
114
+ /** The route's head is outlined into a separate `/_head` response. */
115
+ headOutlined?: boolean;
116
+ /** That head is fetched before the body (param-varying head). */
117
+ headFirst?: boolean;
118
+ }
119
+
120
+ /** Build the whole-route `/_tree` payload for `pathname` (milestone 1). */
121
+ export function buildRootTreePrefetch(options: BuildTreeOptions): RootTreePrefetch {
122
+ const staleTime =
123
+ options.staleTimeSeconds ??
124
+ (options.isStatic ? DEFAULT_STATIC_STALE_TIME_SECONDS : DEFAULT_DYNAMIC_STALE_TIME_SECONDS);
125
+ return {
126
+ ...(options.buildId ? { buildId: options.buildId } : {}),
127
+ tree: buildTree(options),
128
+ staleTime,
129
+ isStatic: options.isStatic,
130
+ ...(options.vary ? { vary: options.vary } : {}),
131
+ ...(options.routePattern ? { route: options.routePattern } : {}),
132
+ ...(options.params ? { params: options.params } : {}),
133
+ ...(options.headOutlined ? { headOutlined: true } : {}),
134
+ ...(options.headFirst ? { headFirst: true } : {}),
135
+ };
136
+ }
137
+
138
+ function buildTree(options: BuildTreeOptions): SegmentTreeNode {
139
+ const parts = routeParts(options);
140
+ const bodySize = options.bodySizeBytes ?? 0;
141
+ const budget = options.inlineBudgetBytes ?? 32 * 1024;
142
+ const canInlineBody = options.isStatic && !options.postponed && bodySize <= budget;
143
+ let child = pageNode(parts.length === 0, {
144
+ isStatic: options.isStatic,
145
+ canInlineBody,
146
+ bodySizeBytes: options.bodySizeBytes,
147
+ ...(options.vary ? { varyParams: options.vary.params } : {}),
148
+ });
149
+ for (let index = parts.length - 1; index >= 0; index--) {
150
+ child = routeNode(parts[index]!, child, index === 0, {
151
+ isStatic: options.isStatic,
152
+ canInlineBody,
153
+ runtimePrefetch: options.runtimePrefetch,
154
+ });
155
+ }
156
+ return {
157
+ name: '',
158
+ param: null,
159
+ slots: { children: child },
160
+ prefetchHints:
161
+ PrefetchHint.IsRootLayout | (canInlineBody ? PrefetchHint.InlinedIntoChild : 0),
162
+ };
163
+ }
164
+
165
+ function routeParts(options: BuildTreeOptions): string[] {
166
+ const source = options.routeId ?? options.pathname;
167
+ if (source === 'index' || source === '/') return [];
168
+ return source
169
+ .split('/')
170
+ .map(part => part.trim())
171
+ .filter(Boolean)
172
+ .filter(part => !part.startsWith('(') && !part.startsWith('@'))
173
+ .map(part => (part === 'index' ? '__PAGE__' : part));
174
+ }
175
+
176
+ function routeNode(
177
+ name: string,
178
+ child: SegmentTreeNode,
179
+ parentInlined: boolean,
180
+ options: { isStatic: boolean; canInlineBody: boolean; runtimePrefetch?: boolean },
181
+ ): SegmentTreeNode {
182
+ const dynamic = !options.isStatic || name.includes('instant-false');
183
+ const runtime = Boolean(options.runtimePrefetch) || name.includes('runtime');
184
+ const inlinesIntoChild = options.canInlineBody && !dynamic && !runtime;
185
+ return {
186
+ name,
187
+ param: name.startsWith('[') ? { type: 'd', key: null, siblings: null } : null,
188
+ slots: { children: { ...child, prefetchHints: child.prefetchHints | parentHint(inlinesIntoChild) } },
189
+ prefetchHints:
190
+ (parentInlined ? PrefetchHint.ParentInlinedIntoSelf : 0) |
191
+ (runtime ? PrefetchHint.HasRuntimePrefetch : 0) |
192
+ (dynamic ? PrefetchHint.PrefetchDisabled : 0) |
193
+ (inlinesIntoChild ? PrefetchHint.InlinedIntoChild : 0),
194
+ };
195
+ }
196
+
197
+ function pageNode(
198
+ parentInlined: boolean,
199
+ options: {
200
+ isStatic: boolean;
201
+ canInlineBody: boolean;
202
+ bodySizeBytes?: number;
203
+ varyParams?: string[];
204
+ },
205
+ ): SegmentTreeNode {
206
+ return {
207
+ name: '__PAGE__',
208
+ param: null,
209
+ slots: null,
210
+ prefetchHints:
211
+ (parentInlined ? PrefetchHint.ParentInlinedIntoSelf : 0) |
212
+ (options.canInlineBody ? PrefetchHint.HeadInlinedIntoSelf : PrefetchHint.HeadOutlined),
213
+ ...(options.bodySizeBytes !== undefined ? { sizeBytes: options.bodySizeBytes } : {}),
214
+ ...(options.canInlineBody ? {} : { segmentPath: ROUTE_SEGMENT_PATH }),
215
+ ...(options.varyParams ? { varyParams: options.varyParams } : {}),
216
+ };
217
+ }
218
+
219
+ function parentHint(enabled: boolean): number {
220
+ return enabled ? PrefetchHint.ParentInlinedIntoSelf : 0;
221
+ }
222
+
223
+ export function rootTreePrefetchText(
224
+ payload: RootTreePrefetch,
225
+ format: 'json' | 'flight' = 'json',
226
+ ): string {
227
+ const json = JSON.stringify(payload);
228
+ return format === 'flight' ? `0:${json}` : json;
229
+ }
230
+
231
+ /**
232
+ * The `/_tree` Response: JSON payload with Content-Type text/x-component and
233
+ * x-nextjs-stale-time. The router Vary is merged by the protocol finalizer, so
234
+ * this Response carries only the segment-specific headers.
235
+ */
236
+ export function treePrefetchResponse(
237
+ payload: RootTreePrefetch,
238
+ options: { format?: 'json' | 'flight' } = {},
239
+ ): Response {
240
+ return new Response(rootTreePrefetchText(payload, options.format), {
241
+ status: 200,
242
+ headers: {
243
+ 'content-type': RSC_CONTENT_TYPE_HEADER,
244
+ 'x-nextjs-postponed': '2',
245
+ 'x-nextjs-stale-time': String(payload.staleTime),
246
+ ...(payload.isStatic ? { 'x-nextjs-prerender': '1' } : {}),
247
+ // A STATIC route's tree is CDN-cacheable like Next's prerendered
248
+ // payloads — the per-variant `_rsc` cache-buster keys it correctly even
249
+ // on Vary-ignoring CDNs. Dynamic trees stay private (no-store keeps a
250
+ // stale document from poisoning the segment cache).
251
+ 'cache-control': payload.isStatic
252
+ ? 's-maxage=31536000, stale-while-revalidate'
253
+ : 'private, no-store',
254
+ },
255
+ });
256
+ }
257
+
258
+ // Per-segment payload emission and serving (COMPAT).
259
+ //
260
+ // Beyond `/_tree`, the client can request an individual segment via `next-router-segment-prefetch:
261
+ // <segment path>`. For the "whole route as one segment" model the only non-`/_tree` segment path is the
262
+ // route's own body, served as a pnext HTML fragment (the route's prebuilt PPR shell / static HTML) with
263
+ // Content-Type text/x-component. The tree payload advertises the available segment paths so the client
264
+ // knows what to request and stitch.
265
+ //
266
+ // Build emits, per PPR/static route under cacheComponents:
267
+ // .segments/_tree.segment.rsc - the RootTreePrefetch JSON
268
+ // .segments/<seg>.segment.rsc - the segment body (HTML fragment)
269
+ // .segments/<route>.segment.meta - { status, postponed, segmentPaths }
270
+ // mirroring Next's `.segments/` layout.
271
+
272
+ /** The whole-route segment path sentinel (the route body as one segment). */
273
+ export const ROUTE_SEGMENT_PATH = '/' as const;
274
+
275
+ export interface SegmentBodyPayload {
276
+ segment: string;
277
+ html: string;
278
+ /**
279
+ * Segment-M2: this segment's vary set (param names, `'?'` for searchParams).
280
+ * Absent when the render was not tracked; the client then keys on the URL.
281
+ */
282
+ vary?: string[];
283
+ /**
284
+ * w9-segment-split: the LAYOUT frame's own vary set. An `/_index` response
285
+ * carries layout AND page markup, so `vary` above (their union) keys the
286
+ * whole document; these two key the split frames the client composes from.
287
+ */
288
+ layoutVary?: string[];
289
+ /** w9-segment-split: the PAGE frame's own vary set. */
290
+ pageVary?: string[];
291
+ /** The matched route pattern the client keys shared entries on. */
292
+ route?: string;
293
+ /** Concrete params of the requested URL. */
294
+ params?: Record<string, string | string[]>;
295
+ /**
296
+ * The route's shared APP SHELL (rendered with the params hanging), carried
297
+ * alongside a fully-static per-URL prerender. Next's Flight prerender embeds
298
+ * every Suspense fallback, so its client extracts the shell prefix out of the
299
+ * per-URL response and caches it for the route's OTHER params; pnext's HTML
300
+ * payload has no fallback left once the boundary resolved, so the shell rides
301
+ * as its own field instead.
302
+ */
303
+ shell?: SegmentAppShell;
304
+ }
305
+
306
+ /** A route's shared app shell, carried alongside a per-URL prerender. */
307
+ export interface SegmentAppShell {
308
+ html: string;
309
+ /** The route pattern (colon form) the shell is shared across. */
310
+ route: string;
311
+ }
312
+
313
+ /** Segment-M2 vary metadata a producer attaches to a segment body response. */
314
+ export interface SegmentVaryInfo {
315
+ vary: string[];
316
+ /** w9-segment-split: the layout frame's vary set, when the render tracked one. */
317
+ layoutVary?: string[];
318
+ /** w9-segment-split: the page frame's vary set, when the render tracked one. */
319
+ pageVary?: string[];
320
+ route?: string;
321
+ params?: Record<string, string | string[]>;
322
+ }
323
+
324
+ export function segmentBodyText(
325
+ segment: string,
326
+ html: string,
327
+ format: 'json' | 'flight' = 'flight',
328
+ vary?: SegmentVaryInfo,
329
+ shell?: SegmentAppShell,
330
+ ): string {
331
+ const payload: SegmentBodyPayload = {
332
+ segment,
333
+ html,
334
+ ...(vary ? { vary: vary.vary } : {}),
335
+ ...(vary?.layoutVary ? { layoutVary: vary.layoutVary } : {}),
336
+ ...(vary?.pageVary ? { pageVary: vary.pageVary } : {}),
337
+ ...(vary?.route ? { route: vary.route } : {}),
338
+ ...(vary?.params ? { params: vary.params } : {}),
339
+ ...(shell ? { shell } : {}),
340
+ };
341
+ const json = JSON.stringify(payload);
342
+ return format === 'flight' ? `0:${json}` : json;
343
+ }
344
+
345
+ /** The response header carrying a segment's vary set (comma-separated names). */
346
+ export const SEGMENT_VARY_HEADER = 'x-pnext-segment-vary';
347
+ /** The response header carrying the matched route pattern. */
348
+ export const SEGMENT_ROUTE_HEADER = 'x-pnext-segment-route';
349
+ /** w9-segment-split: the LAYOUT frame's vary set (comma-separated names). */
350
+ export const SEGMENT_LAYOUT_VARY_HEADER = 'x-pnext-segment-layout-vary';
351
+ /** w9-segment-split: the PAGE frame's vary set (comma-separated names). */
352
+ export const SEGMENT_PAGE_VARY_HEADER = 'x-pnext-segment-page-vary';
353
+
354
+ /** Per-route segment `.meta` sidecar (contract: status/postponed/segmentPaths). */
355
+ export interface SegmentMeta {
356
+ status: number;
357
+ /** Effective client-cache staleTime for this route, in seconds. */
358
+ staleTime: number;
359
+ /** True when the route was postponed (a PPR shell with holes). */
360
+ postponed: boolean;
361
+ /** Segment request keys this route can serve (always includes `/_tree`). */
362
+ segmentPaths: string[];
363
+ /** Actual emitted segment byte sizes keyed by segment request path. */
364
+ segmentSizes?: Record<string, number>;
365
+ /** Segment request keys whose payload was inlined into the route tree. */
366
+ inlinedSegmentPaths?: string[];
367
+ /** Route-level prefetch hints, mirrored into Next-shaped metadata. */
368
+ prefetchHints?: Record<string, number>;
369
+ /**
370
+ * The vary set the BUILD render tracked for this baked body, in the same wire encoding a live response
371
+ * publishes. ABSENT means "unknown" - the client then keys the entry on its exact URL.
372
+ */
373
+ vary?: string[];
374
+ /** w9-segment-split: the baked LAYOUT frame's vary names. */
375
+ layoutVary?: string[];
376
+ /** w9-segment-split: the baked PAGE frame's vary names. */
377
+ pageVary?: string[];
378
+ }
379
+
380
+ /** The segment-request key the client sends for the whole-route body segment. */
381
+ export function segmentBodyPath(): string {
382
+ return ROUTE_SEGMENT_PATH;
383
+ }
384
+
385
+ /** Build the `.meta` sidecar for a route (whole-route single-segment milestone). */
386
+ export function buildSegmentMeta(options: {
387
+ status: number;
388
+ staleTime: number;
389
+ postponed: boolean;
390
+ bodySizeBytes?: number;
391
+ inlineBudgetBytes?: number;
392
+ prefetchHints?: Record<string, number>;
393
+ /** The build render's tracked vary set — omitted when it is not trustworthy. */
394
+ vary?: ResponseVaryParams;
395
+ }): SegmentMeta {
396
+ const inlined =
397
+ !options.postponed &&
398
+ options.bodySizeBytes !== undefined &&
399
+ options.bodySizeBytes <= (options.inlineBudgetBytes ?? 32 * 1024);
400
+ return {
401
+ status: options.status,
402
+ staleTime: options.staleTime,
403
+ postponed: options.postponed,
404
+ // `/_tree` (the tree) + the whole-route body segment.
405
+ segmentPaths: [TREE_SEGMENT_PATH, ROUTE_SEGMENT_PATH],
406
+ ...(options.bodySizeBytes !== undefined
407
+ ? { segmentSizes: { [ROUTE_SEGMENT_PATH]: options.bodySizeBytes } }
408
+ : {}),
409
+ ...(inlined ? { inlinedSegmentPaths: [ROUTE_SEGMENT_PATH] } : {}),
410
+ ...(options.prefetchHints ? { prefetchHints: options.prefetchHints } : {}),
411
+ ...(options.vary
412
+ ? {
413
+ vary: varyNamesFor(options.vary, 'body'),
414
+ // Mirrors segmentVaryInfo: the split frames only ride along when the
415
+ // render actually tracked something, or an empty page set would file
416
+ // the page frame as shareable across every param value.
417
+ ...(options.vary.params.length > 0 || options.vary.search
418
+ ? {
419
+ layoutVary: varyNamesFor(options.vary, 'layout'),
420
+ pageVary: varyNamesFor(options.vary, 'page'),
421
+ }
422
+ : {}),
423
+ }
424
+ : {}),
425
+ };
426
+ }
427
+
428
+ /**
429
+ * A per-segment body Response: the route's HTML fragment served as a segment.
430
+ * Same content-type + staleness headers as the tree; the client stitches this
431
+ * fragment into the DOM on navigation.
432
+ */
433
+ export function segmentBodyResponse(
434
+ body: string,
435
+ staleTimeSeconds: number,
436
+ postponed = false,
437
+ prerendered = false,
438
+ deploymentId?: string,
439
+ segment: string = ROUTE_SEGMENT_PATH,
440
+ vary?: SegmentVaryInfo,
441
+ shell?: SegmentAppShell,
442
+ ): Response {
443
+ // A COMPLETE prerendered segment is static content: CDN-cacheable, exactly like Next serves prerendered
444
+ // payloads. Correctness against a Vary-ignoring CDN comes from the per-variant `_rsc` cache-buster and
445
+ // the revalidation-versioned redirects, NOT from no-store. Anything dynamic or truncated stays private.
446
+ const cacheable = prerendered && !postponed;
447
+ return new Response(segmentBodyText(segment, body, 'flight', vary, shell), {
448
+ status: 200,
449
+ headers: {
450
+ 'content-type': RSC_CONTENT_TYPE_HEADER,
451
+ 'x-nextjs-stale-time': String(staleTimeSeconds),
452
+ 'x-nextjs-postponed': '2',
453
+ ...(postponed ? { 'x-pnext-segment-postponed': '1' } : {}),
454
+ ...(prerendered ? { 'x-nextjs-prerender': '1' } : {}),
455
+ ...(deploymentId ? { 'x-nextjs-deployment-id': deploymentId } : {}),
456
+ // Mirrored onto headers so a header-only consumer (and the client's
457
+ // cheap pre-parse path) sees the vary set without decoding the body.
458
+ ...(vary ? { [SEGMENT_VARY_HEADER]: vary.vary.join(',') } : {}),
459
+ ...(vary?.layoutVary ? { [SEGMENT_LAYOUT_VARY_HEADER]: vary.layoutVary.join(',') } : {}),
460
+ ...(vary?.pageVary ? { [SEGMENT_PAGE_VARY_HEADER]: vary.pageVary.join(',') } : {}),
461
+ ...(vary?.route ? { [SEGMENT_ROUTE_HEADER]: vary.route } : {}),
462
+ 'cache-control': cacheable
463
+ ? 's-maxage=31536000, stale-while-revalidate'
464
+ : 'private, no-store',
465
+ },
466
+ });
467
+ }
468
+
469
+ /** On-disk names for a route's segment artifacts under `<route>.segments/`. */
470
+ export function segmentDir(outPath: string, routeId: string): string {
471
+ return `${outPath}/segments/${routeId}`;
472
+ }
473
+ export function treeSegmentFile(outPath: string, routeId: string): string {
474
+ return `${segmentDir(outPath, routeId)}/_tree.segment.rsc`;
475
+ }
476
+ export function bodySegmentFile(outPath: string, routeId: string): string {
477
+ return `${segmentDir(outPath, routeId)}/index.segment.rsc`;
478
+ }
479
+ export function segmentMetaFile(outPath: string, routeId: string): string {
480
+ return `${segmentDir(outPath, routeId)}/route.segment.meta`;
481
+ }
@@ -0,0 +1,102 @@
1
+ // Pure vary-set cache-key helpers, shared by the SERVER vary tracker (./vary-params.ts) and the CLIENT
2
+ // segment cache. This module must stay browser-safe: no node: imports - the server-side ALS accumulator
3
+ // machinery lives in vary-params.ts, which re-exports these for server callers.
4
+
5
+ /** The searchParams sentinel: any query access varies the whole query string. */
6
+ export const SEARCH_PARAMS_SENTINEL = '?';
7
+
8
+ /**
9
+ * Field separator inside a segment cache key. A NUL can never appear in a
10
+ * pathname, a route pattern, or a param value, so no field boundary is ambiguous
11
+ * (the same convention router.ts uses for its own composite cache keys).
12
+ */
13
+ export const VARY_KEY_SEPARATOR = '\u0000';
14
+
15
+ /** Next's floor on a prefetch reuse window, and its default when unreported. */
16
+ const MIN_PREFETCH_STALE_TIME_SECONDS = 30;
17
+ const DEFAULT_PREFETCH_STALE_TIME_SECONDS = 300;
18
+
19
+ /**
20
+ * The reuse window (ms) for a prefetch response reporting `seconds` of staleness. Next clamps every
21
+ * prefetch stale time to a 30s FLOOR and treats an absent/unparseable `x-nextjs-stale-time` as the 300s
22
+ * static default. Taking the header raw instead makes a `stale-time: 0` response born stale, so every
23
+ * reveal refetches it - a prefetch loop, never a cache hit.
24
+ */
25
+ export function getStaleTimeMs(seconds: number): number {
26
+ const reported = Number.isFinite(seconds) ? seconds : DEFAULT_PREFETCH_STALE_TIME_SECONDS;
27
+ return Math.max(reported, MIN_PREFETCH_STALE_TIME_SECONDS) * 1000;
28
+ }
29
+
30
+ export function encodeVarySet(names: readonly string[], search = false): string {
31
+ const parts = search ? [...names, SEARCH_PARAMS_SENTINEL] : [...names];
32
+ return parts.join(',');
33
+ }
34
+
35
+ export function decodeVarySet(text: string | null | undefined): string[] | null {
36
+ if (text === null || text === undefined) return null;
37
+ if (text === '') return [];
38
+ return text.split(',').filter(Boolean);
39
+ }
40
+
41
+ /**
42
+ * The cache key of one segment entry: its route and segment path, plus ONLY the params the segment
43
+ * provably reads (and the query when it read searchParams).
44
+ *
45
+ * An empty vary set yields a key with no param component, so every param value of that route shares a
46
+ * single entry - the whole point of vary tracking. A `null` vary set means the server did not report one:
47
+ * fall back to the exact URL so nothing is ever shared unsoundly.
48
+ */
49
+ export function segmentVaryCacheKey(options: {
50
+ /** Route identity (the matched route pattern or id). */
51
+ route: string;
52
+ /** Segment request path (`/_index`, `/_head`, `/_tree`, …). */
53
+ segmentPath: string;
54
+ /** Concrete params of the URL being keyed. */
55
+ params: Readonly<Record<string, string | string[] | undefined>>;
56
+ /** The URL's query string (with or without a leading `?`). */
57
+ search?: string;
58
+ /** The segment's vary set, or null when the server reported none. */
59
+ vary: readonly string[] | null;
60
+ /** Full URL path, used verbatim when `vary` is null (unknown). */
61
+ pathname: string;
62
+ }): string {
63
+ const { route, segmentPath, params, vary, pathname } = options;
64
+ const search = normalizeSearch(options.search);
65
+ if (vary === null) return `${segmentPath}${VARY_KEY_SEPARATOR}!${pathname}${search}`;
66
+ const varied = [...vary].sort();
67
+ const parts: string[] = [];
68
+ for (const name of varied) {
69
+ if (name === SEARCH_PARAMS_SENTINEL) continue;
70
+ parts.push(`${name}=${encodeParamValue(params[name])}`);
71
+ }
72
+ const query = varied.includes(SEARCH_PARAMS_SENTINEL) ? search : '';
73
+ return `${segmentPath}${VARY_KEY_SEPARATOR}${route}${VARY_KEY_SEPARATOR}${parts.join('&')}${query}`;
74
+ }
75
+
76
+ /**
77
+ * The canonical form of a query string inside a segment cache key: sorted, with
78
+ * the `_rsc` CDN cache-buster removed. Exported so callers that compare two
79
+ * URLs for entry identity (the client cache's exact-URL check) normalize them
80
+ * exactly the way the key does.
81
+ */
82
+ export function normalizeSegmentSearch(search: string | undefined): string {
83
+ return normalizeSearch(search);
84
+ }
85
+
86
+ function normalizeSearch(search: string | undefined): string {
87
+ if (!search) return '';
88
+ const text = search.startsWith('?') ? search.slice(1) : search;
89
+ if (!text) return '';
90
+ // `_rsc` is a CDN cache-buster, never an app param: it must not key entries.
91
+ const query = new URLSearchParams(text);
92
+ query.delete('_rsc');
93
+ const pairs = [...query.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
94
+ if (pairs.length === 0) return '';
95
+ return `?${pairs.map(([key, value]) => `${key}=${value}`).join('&')}`;
96
+ }
97
+
98
+ function encodeParamValue(value: string | string[] | undefined): string {
99
+ if (value === undefined) return 'absent';
100
+ if (Array.isArray(value)) return value.map(part => encodeURIComponent(part)).join('/');
101
+ return encodeURIComponent(value);
102
+ }