@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -73,7 +73,7 @@ export interface RootLayoutProps {
73
73
  /**
74
74
  * Router configuration options
75
75
  */
76
- export interface RSCRouterOptions<TEnv = any> {
76
+ export interface RangoOptions<TEnv = any> {
77
77
  /**
78
78
  * Unique identifier for this router instance.
79
79
  * Used to namespace static output files and route maps.
@@ -132,6 +132,21 @@ export interface RSCRouterOptions<TEnv = any> {
132
132
  */
133
133
  allowDebugManifest?: boolean;
134
134
 
135
+ /**
136
+ * DEVELOPMENT/TEST ONLY. Emit an `X-Rango-Cache` response header describing
137
+ * the cache status of the matched route, for use by testing primitives such
138
+ * as `assertCacheStatus`.
139
+ *
140
+ * Defaults to `false`. When neither this option nor the
141
+ * `RANGO_TEST_SIGNALS=1` environment flag is set, NO header is emitted and
142
+ * router output is byte-identical to the default.
143
+ *
144
+ * The header encodes per-segment (v1: coarse route-level) status keyed by the
145
+ * route NAME, e.g. `X-Rango-Cache: product.detail=hit`. Do NOT enable in
146
+ * production — it exposes internal cache decisions.
147
+ */
148
+ debugCacheSignal?: boolean;
149
+
135
150
  /**
136
151
  * Document component that wraps the entire application.
137
152
  *
@@ -357,6 +372,30 @@ export interface RSCRouterOptions<TEnv = any> {
357
372
  */
358
373
  theme?: import("../theme/types.js").ThemeConfig | true;
359
374
 
375
+ /**
376
+ * Default for whether the router wraps `transition()` segments in its own
377
+ * React `<ViewTransition>` boundary (experimental React only).
378
+ *
379
+ * - "auto" (default): every route/layout that opts in via `transition()`
380
+ * gets a router-owned cross-fade.
381
+ * - false: the router never places its own boundary. Routes that use
382
+ * `transition()` still drive navigation through startTransition (so loaders
383
+ * hold instead of flashing a skeleton) and still let consumer-placed
384
+ * `<ViewTransition>` elements animate — the router just contributes no
385
+ * cross-fade of its own. This is the "router triggers, you place the
386
+ * transitions" model.
387
+ *
388
+ * A per-segment `transition({ viewTransition })` overrides this default.
389
+ *
390
+ * @example
391
+ * ```typescript
392
+ * // App-wide: drive + hold, but never auto-wrap. Place <ViewTransition>
393
+ * // yourself in components where you want a morph.
394
+ * const router = createRouter<AppEnv>({ viewTransition: false });
395
+ * ```
396
+ */
397
+ viewTransition?: "auto" | false;
398
+
360
399
  /**
361
400
  * URL patterns to register with the router.
362
401
  *
@@ -457,6 +496,21 @@ export interface RSCRouterOptions<TEnv = any> {
457
496
  */
458
497
  prefetchCacheTTL?: number | false;
459
498
 
499
+ /**
500
+ * Prefix for the rango state cookie name. The resolved name is
501
+ * `{prefix}_{routerId}`; the prefix is sanitized to cookie-name-safe
502
+ * characters (`[A-Za-z0-9-]`) and an empty result falls back to the default.
503
+ *
504
+ * The rango state cookie keys the client's prefetch / HTTP caches. Overriding
505
+ * the prefix lets you align it with cookie-naming policies or consent-manager
506
+ * classification lists, or avoid colliding with an existing `rango-state`
507
+ * cookie. It is not a full-name override: the `_{routerId}` suffix is what
508
+ * keeps sibling apps on one origin from clobbering each other's state.
509
+ *
510
+ * @default "rango-state"
511
+ */
512
+ stateCookiePrefix?: string;
513
+
460
514
  /**
461
515
  * Enable connection warmup to keep TCP+TLS alive after idle periods.
462
516
  *
@@ -1,4 +1,4 @@
1
- import type { RSCRouterInternal } from "./router-interfaces.js";
1
+ import type { RangoInternal } from "./router-interfaces.js";
2
2
 
3
3
  /**
4
4
  * Brand marker for identifying router instances at build time.
@@ -12,10 +12,7 @@ export const RSC_ROUTER_BRAND = "__rsc_router__" as const;
12
12
  * Used by the Vite plugin at build time to discover routers and extract
13
13
  * manifests, prefix trees, and pre-render candidates.
14
14
  */
15
- export const RouterRegistry: Map<
16
- string,
17
- RSCRouterInternal<any, any>
18
- > = new Map();
15
+ export const RouterRegistry: Map<string, RangoInternal<any, any>> = new Map();
19
16
 
20
17
  export let routerAutoId = 0;
21
18
 
@@ -27,58 +27,17 @@ import {
27
27
  tryStaticSlot,
28
28
  resolveLayoutComponent,
29
29
  resolveWithErrorBoundary,
30
+ warnOnStreamedResponse,
30
31
  } from "./helpers.js";
32
+ import { applyViewTransitionDefault } from "./view-transition-default.js";
31
33
  import { getRouterContext } from "../router-context.js";
32
- import { resolveSink, safeEmit } from "../telemetry.js";
34
+ import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
33
35
  import {
34
36
  track,
35
- RSCRouterContext,
37
+ RangoContext,
36
38
  runInsideLoaderScope,
37
39
  } from "../../server/context.js";
38
40
 
39
- // ---------------------------------------------------------------------------
40
- // Streamed handler telemetry
41
- // ---------------------------------------------------------------------------
42
-
43
- /**
44
- * Attach a fire-and-forget rejection observer to a streamed handler promise.
45
- * React catches the actual error via its error boundary; this only emits
46
- * the handler.error telemetry event.
47
- */
48
- function observeStreamedHandler(
49
- promise: Promise<ReactNode>,
50
- segmentId: string,
51
- segmentType: string,
52
- pathname?: string,
53
- routeKey?: string,
54
- params?: Record<string, string>,
55
- ): void {
56
- let routerCtx;
57
- try {
58
- routerCtx = getRouterContext();
59
- } catch {
60
- return;
61
- }
62
- if (!routerCtx?.telemetry) return;
63
- const sink = resolveSink(routerCtx.telemetry);
64
- const reqId = routerCtx.requestId;
65
- promise.catch((err: unknown) => {
66
- const errorObj = err instanceof Error ? err : new Error(String(err));
67
- safeEmit(sink, {
68
- type: "handler.error",
69
- timestamp: performance.now(),
70
- requestId: reqId,
71
- segmentId,
72
- segmentType,
73
- error: errorObj,
74
- handledByBoundary: true,
75
- pathname,
76
- routeKey,
77
- params,
78
- });
79
- });
80
- }
81
-
82
41
  // ---------------------------------------------------------------------------
83
42
  // Fresh path (full match, no revalidation)
84
43
  // ---------------------------------------------------------------------------
@@ -132,18 +91,32 @@ export async function resolveLoaders<TEnv>(
132
91
 
133
92
  // Loading disabled: still start all loaders in parallel, but only emit
134
93
  // settled promises so handlers don't stream loading placeholders.
135
- const pendingLoaderData = loaderEntries.map((loaderEntry) => {
94
+ //
95
+ // Wrap each loader promise with wrapLoaderPromise BEFORE awaiting. The wrapped
96
+ // promise resolves to a LoaderDataResult and never rejects, routing a failed
97
+ // loader to its own per-loader error boundary. Awaiting the RAW promises here
98
+ // instead would (1) propagate a rejection to the segment-level boundary,
99
+ // collapsing the whole entry and discarding successful sibling data, and
100
+ // (2) leave the other in-flight raw promises without a .catch, producing
101
+ // unhandled rejections. Mirrors the loading path and intercept-resolution.
102
+ const pendingLoaderData = loaderEntries.map((loaderEntry, i) => {
103
+ const { loader } = loaderEntry;
104
+ const segmentId = `${shortCode}D${i}.${loader.$$id}`;
136
105
  const start = performance.now();
137
- const promise = runInsideLoaderScope(() =>
138
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
106
+ const wrapped = deps.wrapLoaderPromise(
107
+ runInsideLoaderScope(() =>
108
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
109
+ ),
110
+ entry,
111
+ segmentId,
112
+ ctx.pathname,
139
113
  );
140
- return { promise, start, loaderId: loaderEntry.loader.$$id };
114
+ return { wrapped, start, segmentId, loaderId: loader.$$id };
141
115
  });
142
- await Promise.all(pendingLoaderData.map((p) => p.promise));
116
+ await Promise.all(pendingLoaderData.map((p) => p.wrapped));
143
117
 
144
118
  return loaderEntries.map((loaderEntry, i) => {
145
119
  const { loader } = loaderEntry;
146
- const segmentId = `${shortCode}D${i}.${loader.$$id}`;
147
120
  const pending = pendingLoaderData[i]!;
148
121
  if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
149
122
  // All loaders ran in parallel via Promise.all — each span covers
@@ -159,19 +132,14 @@ export async function resolveLoaders<TEnv>(
159
132
  );
160
133
  }
161
134
  return {
162
- id: segmentId,
135
+ id: pending.segmentId,
163
136
  namespace: entry.id,
164
137
  type: "loader" as const,
165
138
  index: i,
166
139
  component: null,
167
140
  params: ctx.params,
168
141
  loaderId: loader.$$id,
169
- loaderData: deps.wrapLoaderPromise(
170
- pending.promise,
171
- entry,
172
- segmentId,
173
- ctx.pathname,
174
- ),
142
+ loaderData: pending.wrapped,
175
143
  belongsToRoute,
176
144
  };
177
145
  });
@@ -224,7 +192,10 @@ export async function resolveSegment<TEnv>(
224
192
  index: 0,
225
193
  component,
226
194
  loading: entry.loading === false ? null : entry.loading,
227
- transition: entry.transition,
195
+ transition: applyViewTransitionDefault(
196
+ entry.transition,
197
+ deps.viewTransitionDefault,
198
+ ),
228
199
  params,
229
200
  belongsToRoute: false,
230
201
  layoutName: entry.id,
@@ -293,6 +264,7 @@ export async function resolveSegment<TEnv>(
293
264
  if (entry.loading) {
294
265
  const result = handleHandlerResult(handler(context));
295
266
  if (result instanceof Promise) {
267
+ warnOnStreamedResponse(result, entry.id);
296
268
  result.finally(doneRouteHandler).catch(() => {});
297
269
  const tracked = deps.trackHandler(result, {
298
270
  segmentId: entry.shortCode,
@@ -359,7 +331,10 @@ export async function resolveSegment<TEnv>(
359
331
  index: 0,
360
332
  component: component ?? null,
361
333
  loading: entry.loading === false ? null : entry.loading,
362
- transition: entry.transition,
334
+ transition: applyViewTransitionDefault(
335
+ entry.transition,
336
+ deps.viewTransitionDefault,
337
+ ),
363
338
  params,
364
339
  belongsToRoute: true,
365
340
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
@@ -443,7 +418,10 @@ export async function resolveOrphanLayout<TEnv>(
443
418
  belongsToRoute,
444
419
  layoutName: orphan.id,
445
420
  loading: orphan.loading === false ? null : orphan.loading,
446
- transition: orphan.transition,
421
+ transition: applyViewTransitionDefault(
422
+ orphan.transition,
423
+ deps.viewTransitionDefault,
424
+ ),
447
425
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
448
426
  });
449
427
 
@@ -565,7 +543,10 @@ export async function resolveParallelEntry<TEnv>(
565
543
  index: 0,
566
544
  component,
567
545
  loading: parallelEntry.loading === false ? null : parallelEntry.loading,
568
- transition: parallelEntry.transition,
546
+ transition: applyViewTransitionDefault(
547
+ parallelEntry.transition,
548
+ deps.viewTransitionDefault,
549
+ ),
569
550
  params,
570
551
  slot,
571
552
  belongsToRoute,
@@ -632,7 +613,7 @@ export async function resolveAllSegments<TEnv>(
632
613
  // can guard non-cacheable variable reads. Also guards response-level
633
614
  // side effects (headers.set). Persists for all descendant entries.
634
615
  if (entry.type === "cache") {
635
- const store = RSCRouterContext.getStore();
616
+ const store = RangoContext.getStore();
636
617
  if (store) store.insideCacheScope = true;
637
618
  }
638
619
  const doneEntry = track(`segment:${entry.id}`, 1);
@@ -52,6 +52,40 @@ export function handleHandlerResult(
52
52
  return result;
53
53
  }
54
54
 
55
+ /**
56
+ * Dev-only: warn when a handler on a route that declares loading() resolves or
57
+ * rejects with a Response (e.g. redirect()).
58
+ *
59
+ * On a non-loading route a returned/thrown Response short-circuits to an HTTP
60
+ * redirect. But when the route declares loading(), the handler result is
61
+ * streamed (not awaited at the resolution boundary), so the Response surfaces
62
+ * only during RSC serialization and is rendered into the stream instead of
63
+ * becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
64
+ * a loader, or a synchronous handler return instead. Compiled out in production.
65
+ */
66
+ export function warnOnStreamedResponse(
67
+ result: Promise<unknown>,
68
+ entryId: string,
69
+ ): void {
70
+ if (process.env.NODE_ENV === "production") return;
71
+ // A Response can surface either as a rejection (handleHandlerResult rethrows a
72
+ // resolved Response) or as a resolved value (the raw parallel-slot handler is
73
+ // not run through handleHandlerResult). Check both so every streamed path is
74
+ // covered. Each handler is an independent observer; it does not consume the
75
+ // rejection for the trackHandler/observeStreamedHandler chains.
76
+ const check = (value: unknown) => {
77
+ if (value instanceof Response) {
78
+ console.warn(
79
+ `[rango] Handler for "${entryId}" returned a Response (e.g. ` +
80
+ `redirect()), but it declares loading(): the Response is rendered ` +
81
+ `into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
82
+ `from middleware, a loader, or a synchronous handler return.`,
83
+ );
84
+ }
85
+ };
86
+ result.then(check, check);
87
+ }
88
+
55
89
  // ---------------------------------------------------------------------------
56
90
  // Static handler interception
57
91
  // ---------------------------------------------------------------------------
@@ -8,7 +8,7 @@
8
8
  * Cache key resolution (3-tier, matching CacheScope.resolveKey):
9
9
  * 1. options.key(requestCtx) — full override
10
10
  * 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
11
- * 3. loader:{loaderId}:{pathname}:{sortedParams} — default
11
+ * 3. loader:{loaderId}:{host}{pathname}:{sortedParams} — default
12
12
  *
13
13
  * Values are serialized via RSC Flight (serializeResult/deserializeResult),
14
14
  * supporting ReactNode, Promises, null, and all RSC-serializable types.
@@ -19,7 +19,7 @@
19
19
  */
20
20
 
21
21
  import type { LoaderEntry } from "../../server/context.js";
22
- import type { HandlerContext } from "../../types.js";
22
+ import type { HandlerContext, InternalHandlerContext } from "../../types.js";
23
23
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
24
24
  import { getRequestContext } from "../../server/request-context.js";
25
25
  import { sortedRouteParams } from "../../cache/cache-key-utils.js";
@@ -28,9 +28,11 @@ import {
28
28
  resolveSwrWindow,
29
29
  resolveCacheKey,
30
30
  resolveCacheStore,
31
+ resolveTagsOption,
31
32
  DEFAULT_ROUTE_TTL,
32
33
  } from "../../cache/cache-policy.js";
33
34
  import { readThroughItem } from "../../cache/read-through-swr.js";
35
+ import { recordRequestTags } from "../../cache/cache-tag.js";
34
36
  // Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
35
37
  // import segment-resolution but never use loader caching.
36
38
  let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
@@ -55,12 +57,13 @@ function debugLoaderCacheLog(message: string): void {
55
57
 
56
58
  function getDefaultLoaderCacheKey(
57
59
  loaderId: string,
60
+ host: string,
58
61
  pathname: string,
59
62
  params: Record<string, string>,
60
63
  ): string {
61
64
  const paramStr = sortedRouteParams(params);
62
65
  const base = paramStr ? `${pathname}:${paramStr}` : pathname;
63
- return `loader:${loaderId}:${base}`;
66
+ return `loader:${loaderId}:${host}${base}`;
64
67
  }
65
68
 
66
69
  /**
@@ -74,7 +77,13 @@ async function resolveLoaderKey(
74
77
  params: Record<string, string>,
75
78
  ): Promise<string> {
76
79
  const options = loaderEntry.cache!.options;
77
- const defaultKey = getDefaultLoaderCacheKey(loaderId, pathname, params);
80
+ // The host is part of the loader cache identity, matching the route-level
81
+ // cache (cache-scope getCacheKeyBase: `${host}${pathname}`) and "use cache"
82
+ // (cache-runtime pushes ctx.url.host). Without it, a multi-tenant host router
83
+ // serving the same pathname for different hosts would leak one host's cached
84
+ // loader data to another.
85
+ const host = getRequestContext()?.url?.host ?? "localhost";
86
+ const defaultKey = getDefaultLoaderCacheKey(loaderId, host, pathname, params);
78
87
  if (options === false) return defaultKey;
79
88
  return resolveCacheKey(options.key, store, defaultKey, "LoaderCache");
80
89
  }
@@ -87,23 +96,8 @@ async function resolveLoaderKey(
87
96
  */
88
97
  function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
89
98
  const options = loaderEntry.cache?.options;
90
- if (!options || !options.tags) return undefined;
91
-
92
- if (typeof options.tags === "function") {
93
- const requestCtx = getRequestContext();
94
- if (!requestCtx) return undefined;
95
- try {
96
- return options.tags(requestCtx);
97
- } catch (error) {
98
- console.error(
99
- `[LoaderCache] Tags function failed, caching without tags:`,
100
- error,
101
- );
102
- return undefined;
103
- }
104
- }
105
-
106
- return options.tags;
99
+ if (!options) return undefined;
100
+ return resolveTagsOption(options.tags, getRequestContext(), "LoaderCache");
107
101
  }
108
102
 
109
103
  function getLoaderStore(
@@ -152,11 +146,30 @@ export function resolveLoaderData<TEnv>(
152
146
  const swrWindow = resolveSwrWindow(options.swr, store.defaults);
153
147
  const swr = swrWindow || undefined;
154
148
  const tags = resolveTags(loaderEntry);
149
+ recordRequestTags(tags);
150
+
151
+ // A handler that later awaits this same loader via ctx.use(loader) must get
152
+ // THIS memoized promise, not a fresh execution. Rather than rebind ctx.use
153
+ // once per cached loader (O(N) chained wrappers + a synchronous
154
+ // capture-before-overwrite invariant), install a single stable interceptor on
155
+ // the first cached loader that consults a per-ctx override table, then just
156
+ // prime the table for each subsequent cached loader. The captured pre-
157
+ // interceptor `originalUse` (whatever setup mode installed it) runs the
158
+ // cache-miss execute, so a loader never awaits its own in-flight promise.
159
+ const internal = ctx as InternalHandlerContext<any, TEnv>;
160
+ let overrides = internal._loaderCacheOverrides;
161
+ if (!overrides) {
162
+ overrides = internal._loaderCacheOverrides = new Map();
163
+ const originalUse = ctx.use;
164
+ internal._loaderCacheOriginalUse = originalUse;
165
+ ctx.use = ((item: any) => {
166
+ const cached = overrides!.get(item?.$$id);
167
+ if (cached) return cached;
168
+ return originalUse(item);
169
+ }) as typeof ctx.use;
170
+ }
171
+ const runMiss = internal._loaderCacheOriginalUse!;
155
172
 
156
- // Wrap ctx.use() so cache HIT primes the handler's memoization map.
157
- // ctx.use() closes over the match context's loaderPromises (not request context's).
158
- // By intercepting ctx.use(), we inject cached data into the correct map.
159
- const originalUse = ctx.use;
160
173
  const dataPromise = (async () => {
161
174
  const codec = await getCodec();
162
175
  const key = await resolveLoaderKey(
@@ -171,7 +184,7 @@ export function resolveLoaderData<TEnv>(
171
184
  getItem: (k) => store.getItem!(k),
172
185
  setItem: (k, v, o) => store.setItem!(k, v, o),
173
186
  key,
174
- execute: () => originalUse(loaderEntry.loader),
187
+ execute: () => runMiss(loaderEntry.loader),
175
188
  serialize: (d) => codec.serializeResult(d),
176
189
  deserialize: (v) => codec.deserializeResult(v),
177
190
  storeOptions: { ttl, swr, tags },
@@ -183,17 +196,7 @@ export function resolveLoaderData<TEnv>(
183
196
  });
184
197
  })();
185
198
 
186
- // Temporarily replace ctx.use() so the handler's call returns cached data.
187
- // This is needed because ctx.use() closes over the match context's loaderPromises
188
- // map which is separate from the request context. By wrapping use(), we intercept
189
- // the handler's call and return the shared dataPromise.
190
- const wrappedUse = ((item: any) => {
191
- if (item === loaderEntry.loader || item?.$$id === loaderId) {
192
- return dataPromise;
193
- }
194
- return originalUse(item);
195
- }) as typeof ctx.use;
196
- ctx.use = wrappedUse;
199
+ overrides.set(loaderId, dataPromise);
197
200
 
198
201
  return dataPromise;
199
202
  }