@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c

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 (392) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +126 -16
  3. package/dist/bin/rango.js +319 -95
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +2724 -1053
  6. package/package.json +68 -14
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +64 -2
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +224 -32
  11. package/skills/caching/SKILL.md +279 -17
  12. package/skills/composability/SKILL.md +27 -3
  13. package/skills/css/SKILL.md +76 -0
  14. package/skills/debug-manifest/SKILL.md +4 -2
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +11 -9
  17. package/skills/hooks/SKILL.md +243 -29
  18. package/skills/host-router/SKILL.md +83 -23
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +68 -19
  21. package/skills/layout/SKILL.md +13 -9
  22. package/skills/links/SKILL.md +190 -23
  23. package/skills/loader/SKILL.md +235 -9
  24. package/skills/middleware/SKILL.md +18 -10
  25. package/skills/migrate-nextjs/SKILL.md +43 -19
  26. package/skills/migrate-react-router/SKILL.md +8 -2
  27. package/skills/mime-routes/SKILL.md +28 -1
  28. package/skills/observability/SKILL.md +172 -0
  29. package/skills/parallel/SKILL.md +18 -7
  30. package/skills/prerender/SKILL.md +65 -60
  31. package/skills/rango/SKILL.md +251 -24
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +115 -48
  34. package/skills/route/SKILL.md +46 -5
  35. package/skills/router-setup/SKILL.md +30 -8
  36. package/skills/scripts/SKILL.md +179 -0
  37. package/skills/server-actions/SKILL.md +775 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +130 -0
  40. package/skills/testing/bindings.md +103 -0
  41. package/skills/testing/cache-prerender.md +127 -0
  42. package/skills/testing/client-components.md +124 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +91 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +122 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +123 -0
  54. package/skills/typesafety/SKILL.md +322 -29
  55. package/skills/use-cache/SKILL.md +57 -14
  56. package/skills/view-transitions/SKILL.md +337 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +0 -65
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/connection-warmup.ts +134 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +192 -150
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/navigation-bridge.ts +94 -25
  70. package/src/browser/navigation-client.ts +121 -84
  71. package/src/browser/navigation-store-handle.ts +38 -0
  72. package/src/browser/navigation-store.ts +115 -67
  73. package/src/browser/navigation-transaction.ts +9 -59
  74. package/src/browser/network-error-handler.ts +34 -7
  75. package/src/browser/partial-update.ts +147 -128
  76. package/src/browser/prefetch/cache.ts +107 -56
  77. package/src/browser/prefetch/fetch.ts +204 -34
  78. package/src/browser/prefetch/queue.ts +6 -3
  79. package/src/browser/rango-state.ts +158 -76
  80. package/src/browser/react/Link.tsx +30 -7
  81. package/src/browser/react/NavigationProvider.tsx +283 -118
  82. package/src/browser/react/ScrollRestoration.tsx +10 -6
  83. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  84. package/src/browser/react/filter-segment-order.ts +66 -7
  85. package/src/browser/react/index.ts +0 -48
  86. package/src/browser/react/location-state-shared.ts +178 -8
  87. package/src/browser/react/location-state.ts +39 -14
  88. package/src/browser/react/use-action.ts +6 -15
  89. package/src/browser/react/use-handle.ts +17 -14
  90. package/src/browser/react/use-href.tsx +8 -1
  91. package/src/browser/react/use-link-status.ts +33 -8
  92. package/src/browser/react/use-navigation.ts +10 -5
  93. package/src/browser/react/use-params.ts +11 -11
  94. package/src/browser/react/use-reverse.ts +106 -0
  95. package/src/browser/react/use-router.ts +25 -3
  96. package/src/browser/react/use-search-params.ts +0 -5
  97. package/src/browser/react/use-segments.ts +11 -21
  98. package/src/browser/response-adapter.ts +99 -8
  99. package/src/browser/rsc-router.tsx +91 -24
  100. package/src/browser/scroll-restoration.ts +30 -17
  101. package/src/browser/segment-structure-assert.ts +2 -2
  102. package/src/browser/server-action-bridge.ts +214 -55
  103. package/src/browser/types.ts +80 -9
  104. package/src/browser/validate-redirect-origin.ts +43 -16
  105. package/src/build/collect-fallback-refs.ts +107 -0
  106. package/src/build/generate-manifest.ts +60 -35
  107. package/src/build/generate-route-types.ts +2 -1
  108. package/src/build/index.ts +8 -2
  109. package/src/build/prefix-tree-utils.ts +123 -0
  110. package/src/build/route-trie.ts +117 -14
  111. package/src/build/route-types/ast-route-extraction.ts +15 -8
  112. package/src/build/route-types/codegen.ts +16 -5
  113. package/src/build/route-types/include-resolution.ts +117 -23
  114. package/src/build/route-types/param-extraction.ts +6 -3
  115. package/src/build/route-types/per-module-writer.ts +22 -6
  116. package/src/build/route-types/router-processing.ts +55 -28
  117. package/src/build/route-types/scan-filter.ts +1 -1
  118. package/src/build/route-types/source-scan.ts +216 -0
  119. package/src/build/runtime-discovery.ts +9 -20
  120. package/src/cache/cache-error.ts +104 -0
  121. package/src/cache/cache-key-utils.ts +29 -13
  122. package/src/cache/cache-policy.ts +108 -34
  123. package/src/cache/cache-runtime.ts +224 -41
  124. package/src/cache/cache-scope.ts +188 -82
  125. package/src/cache/cache-tag.ts +103 -0
  126. package/src/cache/cf/cf-base64.ts +33 -0
  127. package/src/cache/cf/cf-cache-constants.ts +127 -0
  128. package/src/cache/cf/cf-cache-store.ts +1989 -378
  129. package/src/cache/cf/cf-cache-types.ts +349 -0
  130. package/src/cache/cf/cf-kv-utils.ts +46 -0
  131. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  132. package/src/cache/cf/index.ts +6 -16
  133. package/src/cache/document-cache.ts +89 -21
  134. package/src/cache/handle-snapshot.ts +70 -0
  135. package/src/cache/index.ts +10 -20
  136. package/src/cache/memory-segment-store.ts +136 -37
  137. package/src/cache/profile-registry.ts +46 -31
  138. package/src/cache/read-through-swr.ts +56 -12
  139. package/src/cache/segment-codec.ts +9 -17
  140. package/src/cache/tag-invalidation.ts +230 -0
  141. package/src/cache/types.ts +37 -100
  142. package/src/client.rsc.tsx +44 -21
  143. package/src/client.tsx +36 -61
  144. package/src/cloudflare/index.ts +11 -0
  145. package/src/cloudflare/tracing.ts +109 -0
  146. package/src/component-utils.ts +19 -0
  147. package/src/components/DefaultDocument.tsx +8 -2
  148. package/src/context-var.ts +18 -6
  149. package/src/decode-loader-results.ts +52 -0
  150. package/src/defer.ts +196 -0
  151. package/src/deps/ssr.ts +0 -1
  152. package/src/encode-kv.ts +49 -0
  153. package/src/errors.ts +30 -4
  154. package/src/escape-script.ts +52 -0
  155. package/src/handle.ts +31 -23
  156. package/src/handles/MetaTags.tsx +62 -19
  157. package/src/handles/Scripts.tsx +183 -0
  158. package/src/handles/breadcrumbs.ts +37 -8
  159. package/src/handles/is-thenable.ts +19 -0
  160. package/src/handles/meta.ts +51 -40
  161. package/src/handles/script.ts +244 -0
  162. package/src/host/cookie-handler.ts +9 -60
  163. package/src/host/errors.ts +0 -24
  164. package/src/host/index.ts +8 -2
  165. package/src/host/pattern-matcher.ts +23 -52
  166. package/src/host/router.ts +107 -99
  167. package/src/host/testing.ts +40 -27
  168. package/src/host/types.ts +37 -4
  169. package/src/host/utils.ts +1 -1
  170. package/src/href-client.ts +137 -22
  171. package/src/index.rsc.ts +96 -12
  172. package/src/index.ts +94 -14
  173. package/src/internal-debug.ts +11 -10
  174. package/src/loader-store.ts +500 -0
  175. package/src/loader.rsc.ts +20 -13
  176. package/src/loader.ts +12 -11
  177. package/src/missing-id-error.ts +68 -0
  178. package/src/outlet-context.ts +1 -1
  179. package/src/outlet-provider.tsx +1 -5
  180. package/src/prerender/param-hash.ts +16 -16
  181. package/src/prerender/store.ts +32 -37
  182. package/src/prerender.ts +61 -6
  183. package/src/redirect-origin.ts +100 -0
  184. package/src/regex-escape.ts +8 -0
  185. package/src/render-error-thrower.tsx +20 -0
  186. package/src/response-utils.ts +34 -0
  187. package/src/reverse.ts +65 -40
  188. package/src/root-error-boundary.tsx +1 -19
  189. package/src/route-content-wrapper.tsx +19 -77
  190. package/src/route-definition/dsl-helpers.ts +304 -309
  191. package/src/route-definition/helper-factories.ts +28 -140
  192. package/src/route-definition/helpers-types.ts +82 -55
  193. package/src/route-definition/index.ts +1 -2
  194. package/src/route-definition/redirect.ts +44 -11
  195. package/src/route-definition/resolve-handler-use.ts +12 -1
  196. package/src/route-definition/use-item-types.ts +29 -0
  197. package/src/route-map-builder.ts +0 -16
  198. package/src/route-types.ts +19 -46
  199. package/src/router/basename.ts +14 -0
  200. package/src/router/content-negotiation.ts +73 -25
  201. package/src/router/error-handling.ts +45 -18
  202. package/src/router/find-match.ts +44 -23
  203. package/src/router/handler-context.ts +27 -43
  204. package/src/router/instrument.ts +350 -0
  205. package/src/router/intercept-resolution.ts +39 -20
  206. package/src/router/lazy-includes.ts +10 -47
  207. package/src/router/loader-resolution.ts +155 -72
  208. package/src/router/logging.ts +0 -6
  209. package/src/router/manifest.ts +18 -29
  210. package/src/router/match-api.ts +9 -24
  211. package/src/router/match-context.ts +0 -22
  212. package/src/router/match-handlers.ts +58 -58
  213. package/src/router/match-middleware/background-revalidation.ts +40 -24
  214. package/src/router/match-middleware/cache-lookup.ts +159 -285
  215. package/src/router/match-middleware/cache-store.ts +64 -52
  216. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  217. package/src/router/match-middleware/segment-resolution.ts +0 -22
  218. package/src/router/match-pipelines.ts +1 -42
  219. package/src/router/match-result.ts +44 -74
  220. package/src/router/metrics.ts +0 -34
  221. package/src/router/middleware-types.ts +7 -134
  222. package/src/router/middleware.ts +247 -166
  223. package/src/router/navigation-snapshot.ts +0 -51
  224. package/src/router/params-util.ts +23 -0
  225. package/src/router/pattern-matching.ts +85 -94
  226. package/src/router/prefetch-cache-ttl.ts +51 -0
  227. package/src/router/prerender-match.ts +104 -65
  228. package/src/router/preview-match.ts +3 -1
  229. package/src/router/request-classification.ts +28 -62
  230. package/src/router/revalidation.ts +123 -73
  231. package/src/router/route-snapshot.ts +0 -1
  232. package/src/router/router-context.ts +3 -28
  233. package/src/router/router-interfaces.ts +83 -35
  234. package/src/router/router-options.ts +136 -5
  235. package/src/router/router-registry.ts +2 -5
  236. package/src/router/segment-resolution/fresh.ts +97 -84
  237. package/src/router/segment-resolution/helpers.ts +86 -6
  238. package/src/router/segment-resolution/loader-cache.ts +76 -39
  239. package/src/router/segment-resolution/revalidation.ts +272 -320
  240. package/src/router/segment-resolution/static-store.ts +19 -5
  241. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  242. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  243. package/src/router/segment-resolution.ts +5 -1
  244. package/src/router/segment-wrappers.ts +6 -5
  245. package/src/router/state-cookie-name.ts +33 -0
  246. package/src/router/substitute-pattern-params.ts +56 -0
  247. package/src/router/telemetry-otel.ts +161 -199
  248. package/src/router/telemetry.ts +96 -19
  249. package/src/router/timeout.ts +0 -20
  250. package/src/router/tracing.ts +206 -0
  251. package/src/router/trie-matching.ts +162 -64
  252. package/src/router/types.ts +9 -63
  253. package/src/router/url-params.ts +0 -5
  254. package/src/router.ts +110 -55
  255. package/src/rsc/handler-context.ts +3 -2
  256. package/src/rsc/handler.ts +264 -220
  257. package/src/rsc/helpers.ts +100 -6
  258. package/src/rsc/index.ts +2 -5
  259. package/src/rsc/json-route-result.ts +38 -0
  260. package/src/rsc/loader-fetch.ts +114 -38
  261. package/src/rsc/manifest-init.ts +28 -41
  262. package/src/rsc/origin-guard.ts +39 -25
  263. package/src/rsc/progressive-enhancement.ts +117 -11
  264. package/src/rsc/redirect-guard.ts +99 -0
  265. package/src/rsc/response-cache-serve.ts +238 -0
  266. package/src/rsc/response-error.ts +79 -12
  267. package/src/rsc/response-route-handler.ts +88 -188
  268. package/src/rsc/rsc-rendering.ts +98 -76
  269. package/src/rsc/runtime-warnings.ts +23 -10
  270. package/src/rsc/server-action.ts +281 -117
  271. package/src/rsc/ssr-setup.ts +16 -0
  272. package/src/rsc/transition-gate.ts +89 -0
  273. package/src/rsc/types.ts +23 -5
  274. package/src/runtime-env.ts +18 -0
  275. package/src/search-params.ts +35 -30
  276. package/src/segment-loader-promise.ts +31 -4
  277. package/src/segment-system.tsx +254 -143
  278. package/src/serialize.ts +243 -0
  279. package/src/server/context.ts +163 -51
  280. package/src/server/cookie-parse.ts +32 -0
  281. package/src/server/cookie-store.ts +80 -5
  282. package/src/server/handle-store.ts +21 -38
  283. package/src/server/loader-registry.ts +33 -42
  284. package/src/server/request-context.ts +287 -178
  285. package/src/ssr/index.tsx +21 -16
  286. package/src/static-handler.ts +10 -13
  287. package/src/testing/cache-status.ts +162 -0
  288. package/src/testing/collect-handle.ts +40 -0
  289. package/src/testing/dispatch.ts +701 -0
  290. package/src/testing/dom.entry.ts +22 -0
  291. package/src/testing/e2e/fixture.ts +188 -0
  292. package/src/testing/e2e/index.ts +128 -0
  293. package/src/testing/e2e/matchers.ts +35 -0
  294. package/src/testing/e2e/page-helpers.ts +272 -0
  295. package/src/testing/e2e/parity.ts +387 -0
  296. package/src/testing/e2e/server.ts +195 -0
  297. package/src/testing/flight-matchers.ts +97 -0
  298. package/src/testing/flight-normalize.ts +11 -0
  299. package/src/testing/flight-runtime.d.ts +57 -0
  300. package/src/testing/flight-tree.ts +682 -0
  301. package/src/testing/flight.entry.ts +52 -0
  302. package/src/testing/flight.ts +257 -0
  303. package/src/testing/generated-routes.ts +183 -0
  304. package/src/testing/index.ts +105 -0
  305. package/src/testing/internal/context.ts +371 -0
  306. package/src/testing/internal/flight-client-globals.ts +30 -0
  307. package/src/testing/internal/seed-vars.ts +54 -0
  308. package/src/testing/render-handler.ts +357 -0
  309. package/src/testing/render-route.tsx +581 -0
  310. package/src/testing/run-loader.ts +385 -0
  311. package/src/testing/run-middleware.ts +205 -0
  312. package/src/testing/run-transition-when.ts +164 -0
  313. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  314. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  315. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  316. package/src/testing/vitest-stubs/version.ts +5 -0
  317. package/src/testing/vitest.ts +305 -0
  318. package/src/theme/ThemeProvider.tsx +20 -58
  319. package/src/theme/ThemeScript.tsx +7 -9
  320. package/src/theme/constants.ts +52 -13
  321. package/src/theme/index.ts +0 -7
  322. package/src/theme/theme-context.ts +1 -5
  323. package/src/theme/theme-script.ts +22 -21
  324. package/src/theme/use-theme.ts +0 -3
  325. package/src/types/boundaries.ts +0 -35
  326. package/src/types/cache-types.ts +13 -4
  327. package/src/types/error-types.ts +30 -90
  328. package/src/types/global-namespace.ts +54 -41
  329. package/src/types/handler-context.ts +110 -62
  330. package/src/types/index.ts +3 -10
  331. package/src/types/loader-types.ts +11 -9
  332. package/src/types/request-scope.ts +112 -0
  333. package/src/types/route-config.ts +6 -50
  334. package/src/types/route-entry.ts +0 -6
  335. package/src/types/segments.ts +135 -14
  336. package/src/urls/include-helper.ts +9 -56
  337. package/src/urls/index.ts +1 -11
  338. package/src/urls/path-helper-types.ts +29 -12
  339. package/src/urls/path-helper.ts +17 -106
  340. package/src/urls/pattern-types.ts +36 -19
  341. package/src/urls/response-types.ts +22 -29
  342. package/src/urls/type-extraction.ts +58 -139
  343. package/src/urls/urls-function.ts +1 -19
  344. package/src/use-loader.tsx +292 -107
  345. package/src/vite/debug.ts +185 -0
  346. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  347. package/src/vite/discovery/discover-routers.ts +126 -85
  348. package/src/vite/discovery/discovery-errors.ts +194 -0
  349. package/src/vite/discovery/gate-state.ts +171 -0
  350. package/src/vite/discovery/prerender-collection.ts +96 -68
  351. package/src/vite/discovery/route-types-writer.ts +40 -84
  352. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  353. package/src/vite/discovery/state.ts +44 -0
  354. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  355. package/src/vite/index.ts +2 -0
  356. package/src/vite/inject-client-debug.ts +36 -0
  357. package/src/vite/plugin-types.ts +126 -8
  358. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  359. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  360. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  361. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  362. package/src/vite/plugins/expose-action-id.ts +48 -95
  363. package/src/vite/plugins/expose-id-utils.ts +88 -55
  364. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  365. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  366. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  367. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  368. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  369. package/src/vite/plugins/performance-tracks.ts +26 -25
  370. package/src/vite/plugins/refresh-cmd.ts +1 -1
  371. package/src/vite/plugins/use-cache-transform.ts +73 -83
  372. package/src/vite/plugins/version-injector.ts +40 -29
  373. package/src/vite/plugins/version-plugin.ts +37 -40
  374. package/src/vite/plugins/virtual-entries.ts +39 -25
  375. package/src/vite/rango.ts +109 -118
  376. package/src/vite/router-discovery.ts +718 -119
  377. package/src/vite/utils/ast-handler-extract.ts +26 -35
  378. package/src/vite/utils/banner.ts +1 -1
  379. package/src/vite/utils/bundle-analysis.ts +10 -15
  380. package/src/vite/utils/client-chunks.ts +184 -0
  381. package/src/vite/utils/directive-prologue.ts +40 -0
  382. package/src/vite/utils/forward-user-plugins.ts +171 -0
  383. package/src/vite/utils/manifest-utils.ts +4 -59
  384. package/src/vite/utils/package-resolution.ts +20 -52
  385. package/src/vite/utils/prerender-utils.ts +54 -39
  386. package/src/vite/utils/shared-utils.ts +90 -41
  387. package/src/browser/action-response-classifier.ts +0 -99
  388. package/src/browser/react/use-client-cache.ts +0 -58
  389. package/src/browser/shallow.ts +0 -40
  390. package/src/handles/index.ts +0 -7
  391. package/src/network-error-thrower.tsx +0 -23
  392. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Canonical inbound-Cookie-header parser.
3
+ *
4
+ * Kept as a dependency-free leaf so any consumer (request-context, the host
5
+ * dispatcher, tests) can share one implementation without pulling a heavier
6
+ * module's graph. A duplicate copy in middleware-cookies.ts was removed; the
7
+ * host copy in cookie-handler.ts was collapsed onto this one. Not part of the
8
+ * public export surface.
9
+ */
10
+ export function parseCookiesFromHeader(
11
+ cookieHeader: string | null,
12
+ ): Record<string, string> {
13
+ if (!cookieHeader) return {};
14
+
15
+ const cookies: Record<string, string> = {};
16
+ const pairs = cookieHeader.split(";");
17
+
18
+ for (const pair of pairs) {
19
+ const [name, ...rest] = pair.trim().split("=");
20
+ if (name) {
21
+ const raw = rest.join("=");
22
+ try {
23
+ cookies[name] = decodeURIComponent(raw);
24
+ } catch {
25
+ // Malformed percent-encoding: fall back to raw value
26
+ cookies[name] = raw;
27
+ }
28
+ }
29
+ }
30
+
31
+ return cookies;
32
+ }
@@ -8,7 +8,8 @@
8
8
  */
9
9
 
10
10
  import type { CookieOptions } from "../router/middleware-types.js";
11
- import { getRequestContext } from "./request-context.js";
11
+ import { getRequestContext, _getRequestContext } from "./request-context.js";
12
+ import { isInsideCacheScope } from "./context.js";
12
13
  import { INSIDE_CACHE_EXEC } from "../cache/taint.js";
13
14
 
14
15
  /**
@@ -84,10 +85,23 @@ export interface ReadonlyHeaders {
84
85
  type HeadersIterator<T> = IterableIterator<T>;
85
86
 
86
87
  /**
87
- * Throw if called inside a "use cache" function.
88
- * Reading request-scoped data (cookies, headers) inside a cached function
89
- * produces results that vary per request but the cache key does not include
90
- * those values, leading to one user's data being served to another.
88
+ * Throw if called inside a cache boundary — either a "use cache" function
89
+ * (`INSIDE_CACHE_EXEC` stamped on ctx by the cache runtime) or a `cache()`
90
+ * DSL boundary (`isInsideCacheScope()` the render-store flag set while
91
+ * resolving a `type: "cache"` route entry).
92
+ *
93
+ * Reading request-scoped data (cookies, headers) inside a cached scope
94
+ * produces per-request values that are NOT reflected in the cache key, so
95
+ * they would be frozen into the shared cache entry and served to the wrong
96
+ * users. This is the same hazard for both scopes: a `cache()` boundary caches
97
+ * everything except loaders (it is the document-level "PPR shell"), so a read
98
+ * here is baked into the shell exactly like a `"use cache"` return value is
99
+ * baked into its cache entry.
100
+ *
101
+ * `isInsideCacheScope()` returns false inside loaders (loaders always run
102
+ * fresh on every request, even on a cache hit), so reading cookies()/headers()
103
+ * from a loader is allowed — loaders are the dynamic "holes" of a cached
104
+ * document.
91
105
  */
92
106
  function assertNotInsideCacheContext(ctx: unknown, fnName: string): void {
93
107
  if (
@@ -106,6 +120,16 @@ function assertNotInsideCacheContext(ctx: unknown, fnName: string): void {
106
120
  ` const data = await getCachedData(locale); // locale is now in the cache key`,
107
121
  );
108
122
  }
123
+ if (isInsideCacheScope()) {
124
+ throw new Error(
125
+ `${fnName}() cannot be called inside a cache() boundary. ` +
126
+ `A cache() scope caches everything except loaders, so request-scoped ` +
127
+ `data (cookies, headers) read here would be frozen into the shared ` +
128
+ `cached shell and served to other users. Read it inside a loader ` +
129
+ `instead — loaders always run fresh on every request, even on a cache hit:\n\n` +
130
+ ` loader("user", () => getUser(cookies().get("session")?.value));`,
131
+ );
132
+ }
109
133
  }
110
134
 
111
135
  const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
@@ -144,6 +168,57 @@ export function headers(): ReadonlyHeaders {
144
168
  }) as unknown as ReadonlyHeaders;
145
169
  }
146
170
 
171
+ /**
172
+ * Force the calling client's caches to miss from now on, from the server seat:
173
+ * write a rotated `Set-Cookie` for the rango state. The responding client
174
+ * applies it on receipt, and its history cache is marked stale by the
175
+ * jar-divergence observer at its next read. Per-client and lazy — it rotates
176
+ * only the client that receives this response, not every client.
177
+ *
178
+ * Idempotent within a request (one `Set-Cookie`). Inert (a dev warning) when
179
+ * called outside a request context. Like `cookies()`, it throws inside a
180
+ * `"use cache"` / `cache()` boundary, but is allowed from a loader (loaders are
181
+ * the dynamic holes of a cached document).
182
+ */
183
+ export function invalidateClientCache(): void {
184
+ const ctx = _getRequestContext();
185
+ if (!ctx) {
186
+ if (process.env.NODE_ENV !== "production") {
187
+ console.warn(
188
+ "[rango] invalidateClientCache() was called outside a request context; ignored.",
189
+ );
190
+ }
191
+ return;
192
+ }
193
+ assertNotInsideCacheContext(ctx, "invalidateClientCache");
194
+ ctx._rotateStateCookie();
195
+ }
196
+
197
+ /**
198
+ * Suppress a server action's automatic client-cache invalidation: tell the
199
+ * action bridge this action changed nothing a route renders, so it should leave
200
+ * the client's state and caches alone (no rotation, no prefetch wipe, no
201
+ * broadcast, no revalidation refetch). Per-response, not per-action-definition —
202
+ * only the execution knows whether anything changed.
203
+ *
204
+ * Sets an internal response header the bridge reads. Idempotent within a
205
+ * request. Inert (a dev warning) outside a request context — there is no
206
+ * automatic invalidation to suppress.
207
+ */
208
+ export function keepClientCache(): void {
209
+ const ctx = _getRequestContext();
210
+ if (!ctx) {
211
+ if (process.env.NODE_ENV !== "production") {
212
+ console.warn(
213
+ "[rango] keepClientCache() was called outside a request context; ignored.",
214
+ );
215
+ }
216
+ return;
217
+ }
218
+ assertNotInsideCacheContext(ctx, "keepClientCache");
219
+ ctx._setKeepCacheDirective();
220
+ }
221
+
147
222
  /**
148
223
  * Create a CookieStore backed by a RequestContext.
149
224
  * @internal Shared between cookies() shorthand and context methods.
@@ -45,10 +45,6 @@ function createLateHandlePushError(
45
45
  return error;
46
46
  }
47
47
 
48
- /**
49
- * Deep clone handle data to create a snapshot.
50
- * @internal
51
- */
52
48
  function cloneHandleData(data: HandleData): HandleData {
53
49
  const clone: HandleData = {};
54
50
  for (const handleName in data) {
@@ -178,8 +174,10 @@ export function createHandleStore(): HandleStore {
178
174
  notifyDrain();
179
175
  }
180
176
 
181
- // Queue for pending emissions and resolver for waiting consumer
182
- let pendingEmissions: HandleData[] = [];
177
+ // Dirty flag for pending emissions and resolver for waiting consumer.
178
+ // stream() only ever yields the latest full state, so we track a single
179
+ // dirty bit and clone `data` once at yield time instead of per push.
180
+ let hasPendingEmission = false;
183
181
  let emissionResolver: (() => void) | null = null;
184
182
  let completed = false;
185
183
 
@@ -194,7 +192,7 @@ export function createHandleStore(): HandleStore {
194
192
 
195
193
  // Wait for the next emission or completion
196
194
  function waitForEmission(): Promise<void> {
197
- if (pendingEmissions.length > 0 || completed) {
195
+ if (hasPendingEmission || completed) {
198
196
  return Promise.resolve();
199
197
  }
200
198
  return new Promise((resolve) => {
@@ -205,11 +203,9 @@ export function createHandleStore(): HandleStore {
205
203
  return {
206
204
  track<T>(promise: Promise<T>): Promise<T> {
207
205
  inflightCount++;
208
- // Use .then(onSettle, onSettle) instead of .finally() to avoid
209
- // creating an unhandled rejection branch when the tracked promise
210
- // rejects (e.g. error route handlers). .finally() re-throws the
211
- // rejection on a new branch that nobody catches, which can crash
212
- // the server process.
206
+ // Use .then() instead of .finally() to avoid creating an unhandled rejection
207
+ // branch when the promise rejects. .finally() re-throws on a new branch that
208
+ // can crash the process if not caught.
213
209
  const onSettle = () => {
214
210
  inflightCount--;
215
211
  notifyDrain();
@@ -244,8 +240,8 @@ export function createHandleStore(): HandleStore {
244
240
  }
245
241
  data[handleName][segmentId].push(value);
246
242
 
247
- // Queue a snapshot for emission
248
- pendingEmissions.push(cloneHandleData(data));
243
+ // Mark dirty; the actual snapshot is cloned once at yield time.
244
+ hasPendingEmission = true;
249
245
  signalEmission();
250
246
  },
251
247
 
@@ -255,43 +251,31 @@ export function createHandleStore(): HandleStore {
255
251
  },
256
252
 
257
253
  async *stream(): AsyncGenerator<HandleData, void, unknown> {
258
- // Auto-seal: stream() is called after all track() registrations.
259
254
  sealInternal();
260
255
 
261
- // Set up completion handler
262
256
  this.settled.then(() => {
263
257
  completed = true;
264
258
  signalEmission();
265
259
  });
266
260
 
267
- // Initial small delay to batch rapid synchronous pushes
268
- // This allows multiple handles pushing in quick succession to be batched
261
+ // Batch rapid synchronous pushes with initial delay
269
262
  await new Promise((resolve) => setTimeout(resolve, 0));
270
263
 
271
- // If we already have data, yield the accumulated state
272
264
  if (Object.keys(data).length > 0) {
273
- // Clear pending emissions since we're yielding current state
274
- pendingEmissions = [];
275
- const snapshot = cloneHandleData(data);
276
- yield snapshot;
265
+ hasPendingEmission = false;
266
+ yield cloneHandleData(data);
277
267
  }
278
268
 
279
- // Continue streaming on each push
280
269
  while (!completed) {
281
270
  await waitForEmission();
282
271
 
283
- // Yield all pending emissions (yield latest only)
284
- if (pendingEmissions.length > 0) {
285
- // Skip intermediate states, yield the latest
286
- const latest = pendingEmissions[pendingEmissions.length - 1];
287
- pendingEmissions = [];
288
- yield latest;
272
+ if (hasPendingEmission) {
273
+ hasPendingEmission = false;
274
+ yield cloneHandleData(data);
289
275
  }
290
276
  }
291
277
 
292
- // Final yield only if there are pending emissions that weren't yielded
293
- // (handles that pushed after our last yield but before completion)
294
- if (pendingEmissions.length > 0) {
278
+ if (hasPendingEmission) {
295
279
  yield cloneHandleData(data);
296
280
  }
297
281
  },
@@ -314,13 +298,12 @@ export function createHandleStore(): HandleStore {
314
298
  if (!data[handleName]) {
315
299
  data[handleName] = {};
316
300
  }
317
- // Replace with replayed data (not append) to avoid handle bleeding between routes.
318
- // When a cached segment is restored, its handles should replace any existing data
319
- // for that segment, not accumulate on top of data from a different route.
301
+ // Replace (not append) to avoid handle bleeding between routes.
302
+ // Cached segment restoration should replace existing data for that
303
+ // segment, not accumulate on top of data from a different route.
320
304
  data[handleName][segmentId] = [...segmentHandles[handleName]];
321
305
  }
322
- // Trigger emission for streaming
323
- pendingEmissions.push(cloneHandleData(data));
306
+ hasPendingEmission = true;
324
307
  signalEmission();
325
308
  },
326
309
  };
@@ -11,17 +11,10 @@ import {
11
11
  type LoaderRegistryEntry,
12
12
  } from "./fetchable-loader-store.js";
13
13
 
14
- // Server-side cache - maps loader $$id to function and middleware
15
- // This is a CACHE populated by getLoaderLazy() when loaders are first accessed.
16
- // The source of truth is fetchableLoaderRegistry in loader.ts, which is populated
17
- // when createLoader() runs. This cache exists to:
18
- // 1. Avoid repeated lookups/imports for the same loader
19
- // 2. Support lazy loading in production (loaders imported on-demand)
20
- // 3. Provide a stable reference for the RSC handler
14
+ // Cache populated by getLoaderLazy() when loaders are first accessed.
15
+ // Source of truth is fetchableLoaderRegistry in loader.ts (populated on createLoader).
21
16
  const loaderRegistry = new Map<string, LoaderRegistryEntry>();
22
17
 
23
- // Lazy import map - set by the loader manifest
24
- // Maps loader $$id to a function that imports the loader module
25
18
  type LazyLoaderImport = () => Promise<{ $$id: string }>;
26
19
  let lazyLoaderImports: Map<string, LazyLoaderImport> | null = null;
27
20
 
@@ -44,60 +37,61 @@ export function setLoaderImports(
44
37
  export async function getLoaderLazy(
45
38
  id: string,
46
39
  ): Promise<LoaderRegistryEntry | undefined> {
47
- // Always check fetchableLoaderRegistry first — it's the source of truth.
48
- // createLoader() updates it during module re-evaluation (HMR), so checking
49
- // here ensures we pick up the fresh function after a loader file change.
40
+ // Check fetchableLoaderRegistry first — it's the source of truth.
41
+ // createLoader() updates it on HMR, ensuring fresh functions after file changes.
50
42
  const fetchable = getFetchableLoader(id);
51
43
  if (fetchable) {
52
44
  loaderRegistry.set(id, fetchable);
53
45
  return fetchable;
54
46
  }
55
47
 
56
- // Fall back to local cache (populated by previous lazy imports in production)
57
48
  const existing = loaderRegistry.get(id);
58
49
  if (existing) {
59
50
  return existing;
60
51
  }
61
52
 
62
- // Try to lazy load from the import map (production mode)
63
53
  if (lazyLoaderImports && lazyLoaderImports.size > 0) {
64
54
  const lazyImport = lazyLoaderImports.get(id);
65
55
  if (lazyImport) {
66
- try {
67
- // Import the loader module - this triggers createLoader which registers fn
68
- await lazyImport();
56
+ // A failed import is a real server breakage (broken transitive import,
57
+ // syntax error, throw in module top-level code), not a "loader not
58
+ // registered" case. Rethrow so the caller can return a 500 and route
59
+ // the failure through onError, instead of collapsing it to a 404.
60
+ await lazyImport();
69
61
 
70
- // Now try to get from fetchable registry (createLoader registered it)
71
- const registered = getFetchableLoader(id);
72
- if (registered) {
73
- loaderRegistry.set(id, registered);
74
- return registered;
75
- }
76
- } catch (error) {
77
- console.error(`[LoaderRegistry] Failed to load loader "${id}":`, error);
62
+ const registered = getFetchableLoader(id);
63
+ if (registered) {
64
+ loaderRegistry.set(id, registered);
65
+ return registered;
78
66
  }
79
67
  }
80
68
  }
81
69
 
82
- // Dev mode fallback: parse the ID and use Vite's dynamic import
83
- // ID format in dev: "src/path/to/file.ts#ExportName"
70
+ // The remaining dev fallback (parse the id as "src/path/file.ts#ExportName"
71
+ // and import it by path) only makes sense in dev, where ids ARE file paths
72
+ // and the dev loader manifest is intentionally empty. In production ids are
73
+ // hashed ("<hash>#ExportName") and every resolvable loader is reached above
74
+ // via the in-memory registry or the lazy import manifest. The hash is not a
75
+ // path, so a production fall-through would run import("/<hash>") and throw a
76
+ // misleading "No such module <hash>" 500 instead of reporting the loader as
77
+ // unregistered. Return undefined in production so a genuinely unknown loader
78
+ // is a clean 404 "not found in registry" from handleLoaderFetch.
79
+ if (process.env.NODE_ENV === "production") {
80
+ return undefined;
81
+ }
82
+
84
83
  const hashIndex = id.indexOf("#");
85
84
  if (hashIndex !== -1) {
86
85
  const filePath = id.slice(0, hashIndex);
87
86
 
88
- try {
89
- // In dev mode, Vite handles dynamic imports
90
- // Just importing the module triggers createLoader which registers the fn
91
- await import(/* @vite-ignore */ `/${filePath}`);
87
+ // Same as the lazy branch: a thrown import is a server error, not a
88
+ // not-found. Let it propagate to the caller for a 500 + onError.
89
+ await import(/* @vite-ignore */ `/${filePath}`);
92
90
 
93
- // Now try to get from fetchable registry
94
- const registered = getFetchableLoader(id);
95
- if (registered) {
96
- loaderRegistry.set(id, registered);
97
- return registered;
98
- }
99
- } catch (error) {
100
- console.error(`[LoaderRegistry] Failed to load loader "${id}":`, error);
91
+ const registered = getFetchableLoader(id);
92
+ if (registered) {
93
+ loaderRegistry.set(id, registered);
94
+ return registered;
101
95
  }
102
96
  }
103
97
 
@@ -115,15 +109,12 @@ export function registerLoaderById(loader: {
115
109
  if (!loader.$$id) {
116
110
  return;
117
111
  }
118
- // For fetchable loaders, fn is stored in the fetchable registry by $$id.
119
- // Always re-check the fetchable registry so HMR picks up the new function.
120
112
  const fetchable = getFetchableLoader(loader.$$id);
121
113
  if (fetchable) {
122
114
  loaderRegistry.set(loader.$$id, fetchable);
123
115
  return;
124
116
  }
125
117
 
126
- // Fall back to using fn from the loader object (non-fetchable loaders)
127
118
  if (loader.fn) {
128
119
  loaderRegistry.set(loader.$$id, {
129
120
  fn: loader.fn,