@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Shared Helpers for Segment Resolution
3
+ *
4
+ * Common utilities used by both fresh and revalidation resolution paths:
5
+ * - Handler result processing (Response vs ReactNode)
6
+ * - Static handler interception (build-time pre-rendered components)
7
+ * - Layout handler resolution with static fallback
8
+ * - Error boundary segment creation
9
+ */
10
+
11
+ import { createElement, type ReactNode } from "react";
12
+ import { DataNotFoundError } from "../../errors";
13
+ import {
14
+ createErrorInfo,
15
+ createErrorSegment,
16
+ createNotFoundInfo,
17
+ createNotFoundSegment,
18
+ } from "../error-handling.js";
19
+ import { getRequestContext } from "../../server/request-context.js";
20
+ import { DefaultErrorFallback } from "../../default-error-boundary.js";
21
+ import type { EntryData } from "../../server/context";
22
+ import type {
23
+ ResolvedSegment,
24
+ ErrorInfo,
25
+ HandlerContext,
26
+ InternalHandlerContext,
27
+ } from "../../types";
28
+ import type { SegmentResolutionDeps } from "../types.js";
29
+ import { debugLog } from "../logging.js";
30
+ import { tryStaticLookup } from "./static-store.js";
31
+ import { observeHandler } from "../instrument.js";
32
+ import type { TelemetrySink } from "../telemetry.js";
33
+ import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
34
+
35
+ /** The errorContext shape wrapLoaderPromise expects as its 5th argument. */
36
+ type LoaderErrorContext<TEnv> = NonNullable<
37
+ Parameters<SegmentResolutionDeps<TEnv>["wrapLoaderPromise"]>[4]
38
+ >;
39
+
40
+ /**
41
+ * Build the errorContext passed to wrapLoaderPromise so a throwing DSL loader
42
+ * fires createRouter({ onError }) (phase "loader") and emits the loader.error
43
+ * telemetry event. wrapLoaderPromise only wires the onError/telemetry path when
44
+ * this 5th argument is present; every real call site previously omitted it, so
45
+ * loaders were the one phase whose failures were silently dropped (handlers,
46
+ * actions, routing, rendering, and fetchable loaders all reported correctly).
47
+ *
48
+ * The fields come off the handler context, which already carries the request,
49
+ * url, params, env, and (on the internal shape) the matched route name.
50
+ */
51
+ export function buildLoaderErrorContext<TEnv>(
52
+ ctx: HandlerContext<any, TEnv>,
53
+ ): LoaderErrorContext<TEnv> {
54
+ const internal = ctx as InternalHandlerContext<any, TEnv>;
55
+ return {
56
+ request: ctx.request,
57
+ url: ctx.url,
58
+ routeKey: internal._routeName,
59
+ params: ctx.params as Record<string, string>,
60
+ env: ctx.env,
61
+ };
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Handler result processing
66
+ // ---------------------------------------------------------------------------
67
+
68
+ /**
69
+ * Handle Response returns from handlers.
70
+ * When a handler returns a Response (e.g., redirect), throw it to trigger
71
+ * the short-circuit mechanism. Otherwise return the ReactNode.
72
+ */
73
+ export function handleHandlerResult(
74
+ result: ReactNode | Response | Promise<ReactNode> | Promise<Response>,
75
+ ): ReactNode {
76
+ if (result instanceof Response) {
77
+ throw result;
78
+ }
79
+ if (result instanceof Promise) {
80
+ return result.then((resolved) => {
81
+ if (resolved instanceof Response) {
82
+ throw resolved;
83
+ }
84
+ return resolved;
85
+ }) as ReactNode;
86
+ }
87
+ return result;
88
+ }
89
+
90
+ /**
91
+ * Dev-only: warn when a handler on a route that declares loading() resolves or
92
+ * rejects with a Response (e.g. redirect()).
93
+ *
94
+ * On a non-loading route a returned/thrown Response short-circuits to an HTTP
95
+ * redirect. But when the route declares loading(), the handler result is
96
+ * streamed (not awaited at the resolution boundary), so the Response surfaces
97
+ * only during RSC serialization and is rendered into the stream instead of
98
+ * becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
99
+ * a loader, or a synchronous handler return instead. Compiled out in production.
100
+ */
101
+ export function warnOnStreamedResponse(
102
+ result: Promise<unknown>,
103
+ entryId: string,
104
+ ): void {
105
+ if (process.env.NODE_ENV === "production") return;
106
+ // A Response can surface either as a rejection (handleHandlerResult rethrows a
107
+ // resolved Response) or as a resolved value (the raw parallel-slot handler is
108
+ // not run through handleHandlerResult). Check both so every streamed path is
109
+ // covered. Each handler is an independent observer; it does not consume the
110
+ // rejection for the trackHandler/observeStreamedHandler chains.
111
+ const check = (value: unknown) => {
112
+ if (value instanceof Response) {
113
+ console.warn(
114
+ `[rango] Handler for "${entryId}" returned a Response (e.g. ` +
115
+ `redirect()), but it declares loading(): the Response is rendered ` +
116
+ `into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
117
+ `from middleware, a loader, or a synchronous handler return.`,
118
+ );
119
+ }
120
+ };
121
+ result.then(check, check);
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Static handler interception
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /**
129
+ * Try to resolve a component from the build-time static store.
130
+ * Returns undefined synchronously when the entry is not a static prerender,
131
+ * avoiding unnecessary promise wrapping on the hot path.
132
+ */
133
+ export function tryStaticHandler(
134
+ entry: EntryData,
135
+ segmentId: string,
136
+ ): Promise<ReactNode | undefined> | undefined {
137
+ const entryAny = entry as any;
138
+ if (entryAny.isStaticPrerender && entryAny.staticHandlerId) {
139
+ return tryStaticLookup(entryAny.staticHandlerId, segmentId);
140
+ }
141
+ return undefined;
142
+ }
143
+
144
+ /**
145
+ * Try to resolve a parallel slot component from the build-time static store.
146
+ * Returns undefined synchronously when no static handler ID exists for the slot.
147
+ */
148
+ export function tryStaticSlot(
149
+ parallelEntry: EntryData,
150
+ slot: string,
151
+ segmentId: string,
152
+ ): Promise<ReactNode | undefined> | undefined {
153
+ const slotStaticId = (parallelEntry as any).staticHandlerIds?.[slot];
154
+ if (slotStaticId) {
155
+ return tryStaticLookup(slotStaticId, segmentId);
156
+ }
157
+ return undefined;
158
+ }
159
+
160
+ /**
161
+ * Resolve a layout or cache entry's handler component.
162
+ * Checks build-time static store first, then invokes the handler.
163
+ */
164
+ export async function resolveLayoutComponent<TEnv>(
165
+ entry: EntryData,
166
+ context: HandlerContext<any, TEnv>,
167
+ ): Promise<ReactNode> {
168
+ // Static/prerender hit: no handler runs, so emit no rango.handler span.
169
+ const staticComponent = await tryStaticHandler(entry, entry.shortCode);
170
+ if (staticComponent !== undefined) return staticComponent;
171
+ const handler = entry.handler;
172
+ if (typeof handler !== "function") return handler as ReactNode;
173
+ // Wrap ONLY the handler call in the rango.handler span (the perf metric is owned
174
+ // by track("handler:<id>") at the call site). handleHandlerResult stays OUTSIDE
175
+ // the span so a handler that returns a Response (redirect control flow, which it
176
+ // rethrows) is not recorded as a span error — mirrors the route-handler sites.
177
+ return handleHandlerResult(await observeHandler(entry.id, handler, context));
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Error boundary segment creation
182
+ // ---------------------------------------------------------------------------
183
+
184
+ /**
185
+ * Context for error reporting in segment resolution.
186
+ * When provided, callOnError is invoked with this context.
187
+ */
188
+ export interface ErrorReportContext {
189
+ request?: Request;
190
+ url?: URL;
191
+ routeKey?: string;
192
+ env?: any;
193
+ isPartial?: boolean;
194
+ requestStartTime?: number;
195
+ telemetry?: TelemetrySink;
196
+ }
197
+
198
+ /**
199
+ * Handle a caught error during segment resolution by creating an
200
+ * error or not-found segment with the nearest boundary.
201
+ *
202
+ * Called by resolveWithErrorBoundary to produce error/notFound segments.
203
+ */
204
+ export function catchSegmentError<TEnv>(
205
+ error: unknown,
206
+ entry: EntryData,
207
+ params: Record<string, string>,
208
+ deps: SegmentResolutionDeps<TEnv>,
209
+ report?: ErrorReportContext,
210
+ pathname?: string,
211
+ ): ResolvedSegment {
212
+ const reportError = (
213
+ handledByBoundary: boolean,
214
+ metadata?: Record<string, unknown>,
215
+ ) => {
216
+ if (!report) return;
217
+ deps.callOnError(error, "handler", {
218
+ request: report.request as Request,
219
+ url: report.url,
220
+ routeKey: report.routeKey,
221
+ params,
222
+ segmentId: entry.shortCode,
223
+ segmentType: entry.type as any,
224
+ env: report.env,
225
+ isPartial: report.isPartial,
226
+ handledByBoundary,
227
+ metadata,
228
+ requestStartTime: report.requestStartTime,
229
+ });
230
+ if (report.telemetry) {
231
+ const errorObj =
232
+ error instanceof Error ? error : new Error(String(error));
233
+ safeEmit(resolveSink(report.telemetry), {
234
+ type: "handler.error",
235
+ timestamp: performance.now(),
236
+ requestId: report.request ? getRequestId(report.request) : undefined,
237
+ segmentId: entry.shortCode,
238
+ segmentType: entry.type,
239
+ error: errorObj,
240
+ handledByBoundary,
241
+ pathname,
242
+ routeKey: report.routeKey,
243
+ params,
244
+ });
245
+ }
246
+ };
247
+
248
+ const setResponseStatus = (status: number) => {
249
+ const reqCtx = getRequestContext();
250
+ if (reqCtx) {
251
+ reqCtx._setStatus(status);
252
+ }
253
+ };
254
+
255
+ if (error instanceof DataNotFoundError) {
256
+ const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
257
+ // Fall back to router's notFound component, then a plain default
258
+ const notFoundOption = deps.notFoundComponent;
259
+ const defaultFallback =
260
+ typeof notFoundOption === "function"
261
+ ? notFoundOption({ pathname: pathname ?? "" })
262
+ : (notFoundOption ?? createElement("h1", null, "Not Found"));
263
+ const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
264
+
265
+ const notFoundInfo = createNotFoundInfo(
266
+ error,
267
+ entry.shortCode,
268
+ entry.type,
269
+ pathname,
270
+ );
271
+
272
+ reportError(true, {
273
+ notFound: true,
274
+ message: notFoundInfo.message,
275
+ });
276
+
277
+ debugLog("segment", "notFound boundary handled error", {
278
+ segmentId: entry.shortCode,
279
+ message: notFoundInfo.message,
280
+ });
281
+
282
+ setResponseStatus(404);
283
+
284
+ return createNotFoundSegment(
285
+ notFoundInfo,
286
+ effectiveNotFoundFallback,
287
+ entry,
288
+ params,
289
+ );
290
+ }
291
+
292
+ const fallback = deps.findNearestErrorBoundary(entry);
293
+ const segmentType: ErrorInfo["segmentType"] = entry.type;
294
+ const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
295
+ const effectiveFallback = fallback ?? DefaultErrorFallback;
296
+
297
+ reportError(!!effectiveFallback);
298
+
299
+ debugLog("segment", "error boundary handled error", {
300
+ segmentId: entry.shortCode,
301
+ boundary: fallback ? "custom" : "default",
302
+ message: errorInfo.message,
303
+ });
304
+
305
+ setResponseStatus(500);
306
+
307
+ return createErrorSegment(errorInfo, effectiveFallback, entry, params);
308
+ }
309
+
310
+ /**
311
+ * Generic error boundary wrapper for segment resolution.
312
+ * Catches non-Response errors and produces an error/notFound segment
313
+ * via catchSegmentError. Response throws (e.g. redirects) are re-thrown.
314
+ *
315
+ * The caller provides a `wrapError` callback to shape the error segment
316
+ * into the expected return type (e.g. ResolvedSegment[] for the fresh
317
+ * path, or SegmentRevalidationResult for the revalidation path).
318
+ */
319
+ export async function resolveWithErrorBoundary<TEnv, TResult>(
320
+ entry: EntryData,
321
+ params: Record<string, string>,
322
+ resolveFn: () => Promise<TResult>,
323
+ wrapError: (segment: ResolvedSegment) => TResult,
324
+ deps: SegmentResolutionDeps<TEnv>,
325
+ report?: ErrorReportContext,
326
+ pathname?: string,
327
+ throwOnError?: boolean,
328
+ ): Promise<TResult> {
329
+ try {
330
+ return await resolveFn();
331
+ } catch (error) {
332
+ if (error instanceof Response) throw error;
333
+ // Pre-render surfaces render failures to the build instead of baking the
334
+ // error boundary as a frozen 200 (issue #587). A `throw new Skip()` in a
335
+ // render fn also propagates here so the build can skip that URL rather than
336
+ // bake its error page. The live request path leaves throwOnError unset.
337
+ if (throwOnError) throw error;
338
+ const segment = catchSegmentError(
339
+ error,
340
+ entry,
341
+ params,
342
+ deps,
343
+ report,
344
+ pathname,
345
+ );
346
+ return wrapError(segment);
347
+ }
348
+ }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Loader-Level Caching
3
+ *
4
+ * When a LoaderEntry has a cache config (set via loader(Fn, () => [cache({...})])),
5
+ * this module wraps the loader execution with cache lookup/store using the
6
+ * getItem()/setItem() methods on SegmentCacheStore.
7
+ *
8
+ * Cache key resolution (3-tier, matching CacheScope.resolveKey):
9
+ * 1. options.key(requestCtx) — full override
10
+ * 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
11
+ * 3. loader:{loaderId}:{host}{pathname}:{sortedParams} — default
12
+ *
13
+ * Values are serialized via RSC Flight (serializeResult/deserializeResult),
14
+ * supporting ReactNode, Promises, null, and all RSC-serializable types.
15
+ *
16
+ * On hit: returns cached data directly, skips loader execution.
17
+ * On stale hit (SWR): returns stale data, schedules background revalidation.
18
+ * On miss: executes loader, schedules non-blocking cache write.
19
+ */
20
+
21
+ import type { LoaderEntry } from "../../server/context.js";
22
+ import type { HandlerContext, InternalHandlerContext } from "../../types.js";
23
+ import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
24
+ import {
25
+ getRequestContext,
26
+ runWithRequestContext,
27
+ } from "../../server/request-context.js";
28
+ import { sortedRouteParams } from "../../cache/cache-key-utils.js";
29
+ import {
30
+ resolveTtl,
31
+ resolveSwrWindow,
32
+ resolveCacheKey,
33
+ resolveCacheStore,
34
+ resolveTagsOption,
35
+ DEFAULT_ROUTE_TTL,
36
+ } from "../../cache/cache-policy.js";
37
+ import { readThroughItem } from "../../cache/read-through-swr.js";
38
+ import { recordRequestTags } from "../../cache/cache-tag.js";
39
+ import {
40
+ isShellCaptureActive,
41
+ createMaskedLoaderPromise,
42
+ } from "./loader-mask.js";
43
+ // Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
44
+ // import segment-resolution but never use loader caching.
45
+ let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
46
+ let _deserializeResult: typeof import("../../cache/segment-codec.js").deserializeResult;
47
+ async function getCodec() {
48
+ if (!_serializeResult) {
49
+ const mod = await import("../../cache/segment-codec.js");
50
+ _serializeResult = mod.serializeResult;
51
+ _deserializeResult = mod.deserializeResult;
52
+ }
53
+ return {
54
+ serializeResult: _serializeResult,
55
+ deserializeResult: _deserializeResult,
56
+ };
57
+ }
58
+
59
+ function debugLoaderCacheLog(message: string): void {
60
+ if (INTERNAL_RANGO_DEBUG) {
61
+ console.log(message);
62
+ }
63
+ }
64
+
65
+ function getDefaultLoaderCacheKey(
66
+ loaderId: string,
67
+ host: string,
68
+ pathname: string,
69
+ params: Record<string, string>,
70
+ ): string {
71
+ const paramStr = sortedRouteParams(params);
72
+ const base = paramStr ? `${pathname}:${paramStr}` : pathname;
73
+ return `loader:${loaderId}:${host}${base}`;
74
+ }
75
+
76
+ /**
77
+ * Resolve cache key using the shared 3-tier priority.
78
+ */
79
+ async function resolveLoaderKey(
80
+ loaderEntry: LoaderEntry,
81
+ store: import("../../cache/types.js").SegmentCacheStore,
82
+ loaderId: string,
83
+ pathname: string,
84
+ params: Record<string, string>,
85
+ ): Promise<string> {
86
+ const options = loaderEntry.cache!.options;
87
+ // The host is part of the loader cache identity, matching the route-level
88
+ // cache (cache-scope getCacheKeyBase: `${host}${pathname}`) and "use cache"
89
+ // (cache-runtime pushes ctx.url.host). Without it, a multi-tenant host router
90
+ // serving the same pathname for different hosts would leak one host's cached
91
+ // loader data to another.
92
+ const host = getRequestContext()?.url?.host ?? "localhost";
93
+ const defaultKey = getDefaultLoaderCacheKey(loaderId, host, pathname, params);
94
+ if (options === false) return defaultKey;
95
+ return resolveCacheKey(options.key, store, defaultKey, "LoaderCache");
96
+ }
97
+
98
+ /**
99
+ * Resolve tags from cache options (static array or function).
100
+ * Fails open: a thrown tag callback falls back to no tags rather than
101
+ * aborting the request. Tags are additive metadata (not identity), so
102
+ * a missing tag does not cause cache collisions.
103
+ */
104
+ function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
105
+ const options = loaderEntry.cache?.options;
106
+ if (!options) return undefined;
107
+ return resolveTagsOption(options.tags, getRequestContext(), "LoaderCache");
108
+ }
109
+
110
+ function getLoaderStore(
111
+ loaderEntry: LoaderEntry,
112
+ ): import("../../cache/types.js").SegmentCacheStore | null {
113
+ const cacheConfig = loaderEntry.cache;
114
+ if (!cacheConfig || cacheConfig.options === false) return null;
115
+ return resolveCacheStore(cacheConfig.options.store);
116
+ }
117
+
118
+ /**
119
+ * Resolve loader data with optional caching.
120
+ *
121
+ * When the LoaderEntry has no cache config, delegates directly to ctx.use(loader).
122
+ * When cached, checks store first and stores on miss via waitUntil.
123
+ *
124
+ * Loader metering is NOT done here — it lives at the ctx.use execution funnel
125
+ * (observePhase; see instrument.ts). A cache HIT returns without calling ctx.use,
126
+ * so it emits no loader phase (the loader did not execute; the hit is only a
127
+ * LoaderCache debug log).
128
+ */
129
+ export function resolveLoaderData<TEnv>(
130
+ loaderEntry: LoaderEntry,
131
+ ctx: HandlerContext<any, TEnv>,
132
+ pathname: string,
133
+ ): Promise<any> {
134
+ // PPR shell capture: never execute the loader. Its slot gets a never-resolving
135
+ // promise so the Suspense subtree postpones (a hole). Gate here — the single
136
+ // funnel every loader segment path routes through (fresh resolveLoaders,
137
+ // cache-hit resolveLoadersOnly, revalidation resolveLoadersOnlyWithRevalidation)
138
+ // — so no loader fn runs and no loader-cache getItem/setItem round-trip happens
139
+ // during capture. See loader-mask.ts and docs/design/ppr-shell-resume.md.
140
+ if (isShellCaptureActive()) {
141
+ return createMaskedLoaderPromise();
142
+ }
143
+
144
+ const cacheConfig = loaderEntry.cache;
145
+
146
+ // No cache config or disabled — run fresh (zero overhead path)
147
+ if (!cacheConfig || cacheConfig.options === false) {
148
+ return ctx.use(loaderEntry.loader);
149
+ }
150
+
151
+ const store = getLoaderStore(loaderEntry);
152
+ if (!store?.getItem || !store?.setItem) {
153
+ return ctx.use(loaderEntry.loader);
154
+ }
155
+
156
+ // Evaluate runtime condition if provided
157
+ const options = cacheConfig.options;
158
+ if (options.condition) {
159
+ const requestCtx = getRequestContext();
160
+ if (requestCtx && !options.condition(requestCtx)) {
161
+ return ctx.use(loaderEntry.loader);
162
+ }
163
+ }
164
+
165
+ const loaderId = loaderEntry.loader.$$id;
166
+
167
+ // A handler that later awaits this same loader via ctx.use(loader) must get
168
+ // THIS memoized promise, not a fresh execution. Rather than rebind ctx.use
169
+ // once per cached loader (O(N) chained wrappers + a synchronous
170
+ // capture-before-overwrite invariant), install a single stable interceptor on
171
+ // the first cached loader that consults a per-ctx override table, then just
172
+ // prime the table for each subsequent cached loader. The captured pre-
173
+ // interceptor `originalUse` (whatever setup mode installed it) runs the
174
+ // cache-miss execute, so a loader never awaits its own in-flight promise.
175
+ const internal = ctx as InternalHandlerContext<any, TEnv>;
176
+ let overrides = internal._loaderCacheOverrides;
177
+ if (!overrides) {
178
+ overrides = internal._loaderCacheOverrides = new Map();
179
+ const originalUse = ctx.use;
180
+ internal._loaderCacheOriginalUse = originalUse;
181
+ ctx.use = ((item: any) => {
182
+ const cached = overrides!.get(item?.$$id);
183
+ if (cached) return cached;
184
+ return originalUse(item);
185
+ }) as typeof ctx.use;
186
+ }
187
+ const runMiss = internal._loaderCacheOriginalUse!;
188
+
189
+ // Dedup the cache read-through across repeated resolutions of the SAME
190
+ // loaderId in one request. An orphan layout with parallel slots inherits its
191
+ // parent route's loaders, so resolveOrphanLayout (fresh.ts) re-resolves the
192
+ // parent's loaders under a different shortCode — calling resolveLoaderData
193
+ // again for the same loaderId. The cache key (loader:{loaderId}:{host}
194
+ // {pathname}:{sortedParams}) does not include the shortCode and ctx/params
195
+ // are identical, so both resolutions produce the same data. Reuse the already
196
+ // in-flight dataPromise instead of issuing a second getItem/setItem (e.g. a
197
+ // second KV round-trip) for one logical cached loader. The shortCode only
198
+ // affects the emitted segmentId in resolveLoaders, not the cached value.
199
+ const existing = overrides.get(loaderId);
200
+ if (existing) return existing;
201
+
202
+ // Compute ttl/swr/tags only AFTER the dedup short-circuit: a deduped second
203
+ // resolution of the same loaderId (the orphan-layout inheritance path) must
204
+ // not re-run the user tags() callback. These values are only consumed inside
205
+ // the read-through below, so they belong here, past the dedup gate.
206
+ const ttl = resolveTtl(options.ttl, store.defaults, DEFAULT_ROUTE_TTL);
207
+ const swrWindow = resolveSwrWindow(options.swr, store.defaults);
208
+ const swr = swrWindow || undefined;
209
+ const tags = resolveTags(loaderEntry);
210
+ recordRequestTags(tags);
211
+
212
+ const dataPromise = (async () => {
213
+ const codec = await getCodec();
214
+ const key = await resolveLoaderKey(
215
+ loaderEntry,
216
+ store,
217
+ loaderId,
218
+ pathname,
219
+ ctx.params,
220
+ );
221
+
222
+ // Capture the request context up front (foreground, ALS present) so the
223
+ // background stale revalidation can re-establish it. On workerd a waitUntil
224
+ // task runs detached from the request's I/O context, so a loader body that
225
+ // reads the ambient getRequestContext() would otherwise throw "called
226
+ // outside of a request context" and the revalidation would fail silently.
227
+ // The wrap is applied via wrapBackground (background path only); the
228
+ // foreground miss runs execute() directly since its context is present.
229
+ const requestCtxForExecute = getRequestContext();
230
+ return readThroughItem({
231
+ getItem: (k) => store.getItem!(k),
232
+ setItem: (k, v, o) => store.setItem!(k, v, o),
233
+ key,
234
+ execute: () => runMiss(loaderEntry.loader),
235
+ wrapBackground: (run) => runWithRequestContext(requestCtxForExecute, run),
236
+ serialize: (d) => codec.serializeResult(d),
237
+ deserialize: (v) => codec.deserializeResult(v),
238
+ storeOptions: { ttl, swr, tags },
239
+ onHit: () => debugLoaderCacheLog(`[LoaderCache] HIT: ${key}`),
240
+ onStale: () => debugLoaderCacheLog(`[LoaderCache] STALE: ${key}`),
241
+ onMiss: () => debugLoaderCacheLog(`[LoaderCache] MISS: ${key}`),
242
+ onCached: () => debugLoaderCacheLog(`[LoaderCache] Cached: ${key}`),
243
+ host: requestCtxForExecute,
244
+ });
245
+ })();
246
+
247
+ overrides.set(loaderId, dataPromise);
248
+
249
+ return dataPromise;
250
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * PPR shell-capture loader masking.
3
+ *
4
+ * During a shell CAPTURE re-render (Axis 2, see docs/design/ppr-shell-resume.md)
5
+ * route loaders are the "live lane": they must NOT execute — no side effects, no
6
+ * cost, no cache round-trips. Instead every loader segment's value slot receives
7
+ * a never-resolving promise, so the loader-consuming Suspense subtree stays
8
+ * pending and React's static `prerender` marks it as a postponed hole. The frozen
9
+ * shell (prelude) captures only the fallback; the resumed serve pass runs the
10
+ * loaders fresh through the unchanged execution path and streams their output
11
+ * into the holes.
12
+ *
13
+ * Capture mode is signalled by `requestCtx._shellCaptureRun`, set to true ONLY on
14
+ * the derived request context of the background capture task (shell-capture.ts) —
15
+ * NOT by the foreground render, whose `_shellCapture` descriptor merely means "a
16
+ * capture is wanted" and must not change behavior. This module is the single home
17
+ * for the mask so every loader execution site gates the same way (loader-cache.ts
18
+ * `resolveLoaderData`, fresh.ts `resolveLoaders`).
19
+ */
20
+
21
+ import { _getRequestContext } from "../../server/request-context.js";
22
+
23
+ /**
24
+ * True when the current render is the active PPR shell capture and route loaders
25
+ * must be masked rather than executed. Reads `_shellCaptureRun` off the ALS
26
+ * request context (the capture task re-establishes its derived context via
27
+ * runWithRequestContext), so it is accurate at the loader resolution sites, which
28
+ * run synchronously inside the pipeline's context frame.
29
+ */
30
+ export function isShellCaptureActive(): boolean {
31
+ return _getRequestContext()?._shellCaptureRun === true;
32
+ }
33
+
34
+ /**
35
+ * A promise that never settles — the masked stand-in for a loader's value during
36
+ * shell capture. The consuming Suspense subtree suspends forever, so the static
37
+ * prerender postpones it as a hole instead of baking a per-request value into the
38
+ * shared shell. The capture abort (`maxWaitMs` in captureShellHTML) bounds how
39
+ * long the prerender waits before it freezes the prelude, so this never hangs the
40
+ * request.
41
+ */
42
+ export function createMaskedLoaderPromise<T = unknown>(): Promise<T> {
43
+ return new Promise<T>(() => {});
44
+ }