@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -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 +121 -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 +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -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 +67 -40
  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/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  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/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +120 -204
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +190 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +63 -24
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +338 -126
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -1,9 +1,10 @@
1
1
  import { registerRouteMap } from "../route-map-builder.js";
2
- import { extractStaticPrefix } from "./pattern-matching.js";
2
+ import { extractStaticPrefix, joinPrefix } from "./pattern-matching.js";
3
3
  import {
4
- EntryData,
5
- RSCRouterContext,
4
+ type EntryData,
5
+ RangoContext,
6
6
  runWithPrefixes,
7
+ getIsolatedLazyParent,
7
8
  } from "../server/context";
8
9
  import type { UrlPatterns } from "../urls.js";
9
10
  import type { AllUseItems, IncludeItem } from "../route-types.js";
@@ -14,11 +15,9 @@ export interface LazyEvalDeps<TEnv = any> {
14
15
  mergedRouteMap: Record<string, string>;
15
16
  nextMountIndex: () => number;
16
17
  getPrecomputedByPrefix: () => Map<string, Record<string, string>> | null;
18
+ routerId?: string;
17
19
  }
18
20
 
19
- // Detect lazy includes in handler result and create placeholder entries
20
- // Lazy includes are IncludeItem with lazy: true and _lazyContext
21
- // Moved to outer scope so it can be reused by evaluateLazyEntry for nested includes
22
21
  export function findLazyIncludes<TEnv = any>(
23
22
  items: AllUseItems[],
24
23
  ): Array<{
@@ -54,7 +53,6 @@ export function findLazyIncludes<TEnv = any>(
54
53
  });
55
54
  }
56
55
  }
57
- // Recursively check nested items (in layouts, etc.)
58
56
  if ((item as any).uses && Array.isArray((item as any).uses)) {
59
57
  lazyItems.push(...findLazyIncludes((item as any).uses));
60
58
  }
@@ -76,14 +74,6 @@ export function evaluateLazyEntry<TEnv = any>(
76
74
  return;
77
75
  }
78
76
 
79
- // Check for pre-computed routes from build-time data.
80
- // Only leaf nodes (no nested includes) are precomputed, so entries with
81
- // nested lazy includes fall through to the handler below.
82
- // When multiple entries share the same staticPrefix (e.g., several
83
- // include("/", ...) calls), the precomputed data merges all their routes
84
- // into one entry. Assigning that merged set to the first matching entry
85
- // causes findMatch to pick the wrong handler for routes belonging to a
86
- // different include. Skip the shortcut when the prefix is shared.
87
77
  const currentPrecomputed = deps.getPrecomputedByPrefix();
88
78
  if (currentPrecomputed) {
89
79
  const routes = currentPrecomputed.get(entry.staticPrefix);
@@ -103,50 +93,41 @@ export function evaluateLazyEntry<TEnv = any>(
103
93
  }
104
94
  }
105
95
 
106
- // Mark as evaluated immediately to prevent concurrent evaluation.
107
- // JS is single-threaded but handlers.handler() could theoretically yield,
108
- // and the while-loop in findMatch retries after evaluation.
109
96
  entry.lazyEvaluated = true;
110
97
 
111
98
  const lazyPatterns = entry.lazyPatterns as UrlPatterns<TEnv>;
112
99
  const lazyContext = entry.lazyContext;
113
100
 
114
- // Create a new context for evaluating the lazy patterns
115
101
  const manifest = new Map<string, EntryData>();
116
102
  const patterns = new Map<string, string>();
117
103
  const patternsByPrefix = new Map<string, Map<string, string>>();
118
104
  const trailingSlashMap = new Map<string, TrailingSlashMode>();
119
105
 
120
- // Capture the handler result to detect nested lazy includes
121
106
  let handlerResult: AllUseItems[] = [];
122
107
 
123
- // Merge captured counters from include() to maintain consistent
124
- // shortCode indices with sibling entries from pattern extraction
125
108
  const lazyCounters: Record<string, number> = {};
126
- if (lazyContext && (lazyContext as any).counters) {
127
- const captured = (lazyContext as any).counters as Record<string, number>;
128
- for (const [key, value] of Object.entries(captured)) {
109
+ if (lazyContext?.counters) {
110
+ for (const [key, value] of Object.entries(lazyContext.counters)) {
129
111
  lazyCounters[key] = value;
130
112
  }
131
113
  }
132
114
 
133
- RSCRouterContext.run(
115
+ RangoContext.run(
134
116
  {
135
117
  manifest,
136
118
  patterns,
137
119
  patternsByPrefix,
138
120
  trailingSlash: trailingSlashMap,
139
121
  namespace: "lazy",
140
- parent: (lazyContext?.parent as EntryData | null) ?? null,
122
+ parent: getIsolatedLazyParent(lazyContext?.parent as EntryData | null),
141
123
  counters: lazyCounters,
142
- cacheProfiles: (lazyContext as any)?.cacheProfiles,
143
- rootScoped: (lazyContext as any)?.rootScoped,
124
+ cacheProfiles: lazyContext?.cacheProfiles,
125
+ rootScoped: lazyContext?.rootScoped,
126
+ includeScope: lazyContext?.includeScope,
144
127
  },
145
128
  () => {
146
- // Run the lazy patterns handler with the original context prefixes
147
- // The prefix comes from the IncludeItem stored in lazyPatterns
148
129
  const includePrefix = (entry as any)._lazyPrefix || "";
149
- const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
130
+ const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
150
131
 
151
132
  if (fullPrefix || lazyContext?.namePrefix) {
152
133
  runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () => {
@@ -158,11 +139,9 @@ export function evaluateLazyEntry<TEnv = any>(
158
139
  },
159
140
  );
160
141
 
161
- // Populate the entry's routes from the patterns
162
142
  const routesObject: Record<string, string> = {};
163
143
  for (const [name, pattern] of patterns.entries()) {
164
144
  routesObject[name] = pattern;
165
- // Also add to merged route map for reverse() support
166
145
  const existingPattern = deps.mergedRouteMap[name];
167
146
  if (existingPattern !== undefined && existingPattern !== pattern) {
168
147
  console.warn(
@@ -173,45 +152,33 @@ export function evaluateLazyEntry<TEnv = any>(
173
152
  deps.mergedRouteMap[name] = pattern;
174
153
  }
175
154
 
176
- // Update the entry in-place
177
155
  entry.routes = routesObject as ResolvedRouteMap<any>;
178
156
 
179
- // Note: Do NOT clear lazyPatterns/lazyContext here.
180
- // loadManifest() needs them on every request to re-run the handler
181
- // in the correct AsyncLocalStorage context (Store.manifest).
182
-
183
- // Update trailing slash config if available
184
157
  if (trailingSlashMap.size > 0) {
185
158
  entry.trailingSlash = Object.fromEntries(trailingSlashMap);
186
159
  }
187
160
 
188
- // Detect nested lazy includes and register them as new entries
189
161
  const nestedLazyIncludes = findLazyIncludes(handlerResult);
190
162
  for (const lazyInclude of nestedLazyIncludes) {
191
- // Compute the full URL prefix (combining parent prefix if any)
192
- const fullPrefix = lazyInclude.context.urlPrefix
193
- ? lazyInclude.context.urlPrefix + lazyInclude.prefix
194
- : lazyInclude.prefix;
163
+ const fullPrefix = joinPrefix(
164
+ lazyInclude.context.urlPrefix,
165
+ lazyInclude.prefix,
166
+ );
195
167
 
196
168
  const nestedEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
197
169
  prefix: "",
198
170
  staticPrefix: extractStaticPrefix(fullPrefix),
199
- routes: {} as ResolvedRouteMap<any>, // Empty until first match
171
+ routes: {} as ResolvedRouteMap<any>,
200
172
  trailingSlash: entry.trailingSlash,
201
173
  handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
202
174
  mountIndex: deps.nextMountIndex(),
203
- // Lazy evaluation fields
175
+ routerId: deps.routerId,
204
176
  lazy: true,
205
177
  lazyPatterns: lazyInclude.patterns,
206
178
  lazyContext: lazyInclude.context,
207
179
  lazyEvaluated: false,
208
- // Store the include prefix for evaluation
209
180
  _lazyPrefix: lazyInclude.prefix,
210
181
  };
211
- // Insert nested lazy entry before any entry whose staticPrefix is a
212
- // prefix of (but shorter than) this lazy entry's staticPrefix.
213
- // This ensures more specific lazy includes are matched before
214
- // less specific eager entries (e.g., "/href/nested" before "/href/:id").
215
182
  const nestedPrefix = nestedEntry.staticPrefix;
216
183
  let insertIndex = deps.routesEntries.length;
217
184
  if (nestedPrefix) {
@@ -229,6 +196,5 @@ export function evaluateLazyEntry<TEnv = any>(
229
196
  deps.routesEntries.splice(insertIndex, 0, nestedEntry);
230
197
  }
231
198
 
232
- // Re-register route map for runtime reverse() usage
233
199
  registerRouteMap(deps.mergedRouteMap);
234
200
  }
@@ -7,6 +7,7 @@
7
7
  import type { ReactNode } from "react";
8
8
  import { track } from "../server/context";
9
9
  import type { EntryData } from "../server/context";
10
+ import { contextGet } from "../context-var.js";
10
11
  import type {
11
12
  ResolvedSegment,
12
13
  HandlerContext,
@@ -18,11 +19,17 @@ import type {
18
19
  ErrorBoundaryFallbackProps,
19
20
  ErrorInfo,
20
21
  } from "../types";
21
- import type { LoaderRevalidationResult, ActionContext } from "./types";
22
- import { isHandle, type Handle } from "../handle.js";
23
- import type { HandleStore } from "../server/handle-store.js";
22
+ import { isHandle, collectHandleData, type Handle } from "../handle.js";
23
+ import { withDefer } from "../defer.js";
24
+ import { buildHandleSnapshot } from "../server/handle-store.js";
24
25
  import { getFetchableLoader } from "../server/fetchable-loader-store.js";
25
26
  import { _getRequestContext } from "../server/request-context.js";
27
+ import {
28
+ isInsideLoaderScope,
29
+ runInsideLoaderBodyScope,
30
+ isInsidePushCallbackScope,
31
+ runInsidePushCallbackScope,
32
+ } from "../server/context.js";
26
33
  import { debugLog } from "./logging.js";
27
34
 
28
35
  /**
@@ -64,7 +71,9 @@ export function wrapLoaderWithErrorHandling<T>(
64
71
  ) => ErrorInfo,
65
72
  onError?: LoaderErrorCallback,
66
73
  ): Promise<LoaderDataResult<T>> {
67
- // Extract loader name from segmentId (format: "M1L0D0.loaderName")
74
+ // Extract the trailing token from segmentId (format: "<shortCode>D<i>.<loaderId>").
75
+ // The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
76
+ // not a clean display name.
68
77
  const loaderName = segmentId.split(".").pop() || "unknown";
69
78
 
70
79
  return Promise.resolve(promise)
@@ -241,6 +250,21 @@ function createLoaderExecutor<TEnv>(
241
250
  pendingLoaders.add(loader.$$id);
242
251
 
243
252
  const currentLoaderId = loader.$$id;
253
+ const variables = (ctx as InternalHandlerContext<any, TEnv>)._variables;
254
+
255
+ // Capture whether this loader is being started from a DSL loader scope
256
+ // (runInsideLoaderScope in fresh.ts). Handler-invoked loaders are NOT
257
+ // inside loader scope. This determines whether rendered() is allowed.
258
+ const isDslLoader = isInsideLoaderScope();
259
+
260
+ let renderedResolved = false;
261
+ let renderedPromise: Promise<void> | null = null;
262
+
263
+ // Loader functions are always fresh (never cached), so they get an
264
+ // unguarded get that bypasses non-cacheable read guards. This applies
265
+ // to ALL loaders — DSL and handler-called — because the loader
266
+ // function itself always re-executes. Also handles nested deps
267
+ // (loaderA → use(loaderB)) since all share this unguarded get.
244
268
  const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
245
269
  params: ctx.params,
246
270
  routeParams: (ctx.params ?? {}) as Record<string, string>,
@@ -249,22 +273,129 @@ function createLoaderExecutor<TEnv>(
249
273
  search: (ctx as any).search,
250
274
  pathname: ctx.pathname,
251
275
  url: ctx.url,
276
+ originalUrl: ctx.originalUrl,
252
277
  env: ctx.env,
253
- var: ctx.var,
254
- get: ctx.get,
255
- use: <TDep, TDepParams = any>(
256
- dep: LoaderDefinition<TDep, TDepParams>,
257
- ): Promise<TDep> => {
258
- return useLoader(dep, currentLoaderId);
259
- },
278
+ waitUntil: ctx.waitUntil.bind(ctx),
279
+ executionContext: ctx.executionContext,
280
+ get: ((keyOrVar: any) =>
281
+ contextGet(variables, keyOrVar)) as typeof ctx.get,
282
+ use: ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
283
+ if (isHandle(item)) {
284
+ if (!renderedResolved) {
285
+ throw new Error(
286
+ `ctx.use(handle) in a loader requires "await ctx.rendered()" first. ` +
287
+ `Handle "${item.$$id}" cannot be read until the render tree has settled.`,
288
+ );
289
+ }
290
+ const reqCtx = reqCtxRef ?? _getRequestContext();
291
+ if (!reqCtx) {
292
+ throw new Error(
293
+ `ctx.use(handle) failed: request context not available.`,
294
+ );
295
+ }
296
+ const segmentOrder = reqCtx._renderBarrierSegmentOrder ?? [];
297
+ // The complete snapshot is cached at barrier resolution for
298
+ // non-streaming trees, and by rendered() after handleStore.settled for
299
+ // streaming trees (where the eager snapshot would have been incomplete
300
+ // because loading() handlers were still in flight). Either way it is
301
+ // present by the time a loader reads a handle; the fresh build is only
302
+ // a defensive fallback.
303
+ const snapshot =
304
+ reqCtx._renderBarrierHandleSnapshot ??
305
+ buildHandleSnapshot(reqCtx._handleStore, segmentOrder);
306
+ return collectHandleData(item, snapshot, segmentOrder);
307
+ }
308
+
309
+ // Loader case
310
+ return useLoader(item as LoaderDefinition<any, any>, currentLoaderId);
311
+ }) as LoaderContext["use"],
260
312
  method: "GET",
261
313
  body: undefined,
262
314
  reverse: ctx.reverse as LoaderContext["reverse"],
315
+ rendered: (): Promise<void> => {
316
+ // Guard: only DSL loaders may use rendered()
317
+ if (!isDslLoader) {
318
+ throw new Error(
319
+ `ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
320
+ `Handler-invoked loaders (ctx.use(Loader) inside a handler) cannot use rendered().`,
321
+ );
322
+ }
323
+
324
+ const reqCtx = reqCtxRef ?? _getRequestContext();
325
+
326
+ if (renderedPromise) return renderedPromise;
327
+
328
+ if (!reqCtx) {
329
+ throw new Error(
330
+ `ctx.rendered() failed: request context not available.`,
331
+ );
332
+ }
333
+
334
+ // Bidirectional deadlock check: if a handler already started
335
+ // awaiting this loader, calling rendered() would deadlock. This is the
336
+ // real cycle guard (it holds for both streaming and non-streaming): the
337
+ // handler blocks segment resolution, which blocks the barrier, which
338
+ // blocks this loader.
339
+ if (reqCtx._handlerLoaderDeps?.has(currentLoaderId)) {
340
+ throw new Error(
341
+ `Deadlock: loader "${currentLoaderId}" called ctx.rendered() but a handler ` +
342
+ `is already awaiting this loader via ctx.use(). The handler blocks ` +
343
+ `segment resolution, which blocks the barrier, which blocks this loader. ` +
344
+ `Move the data dependency to a loader-to-loader pattern instead.`,
345
+ );
346
+ }
347
+
348
+ // Register this loader as waiting for the barrier so that
349
+ // setupLoaderAccess can detect deadlocks when a handler
350
+ // tries to await the same loader via ctx.use().
351
+ if (!reqCtx._renderBarrierWaiters) {
352
+ reqCtx._renderBarrierWaiters = new Set();
353
+ }
354
+ reqCtx._renderBarrierWaiters.add(currentLoaderId);
355
+
356
+ // Streaming trees (loading()): the barrier resolves once the segment
357
+ // tree is resolved, but loading() handlers stream behind Suspense and
358
+ // their handle pushes are still in flight then. Their async execution
359
+ // IS tracked in the handle store (trackHandler -> store.track), so after
360
+ // the barrier we seal (no further handlers register once the tree is
361
+ // resolved) and wait for settled — every tracked handler, streaming
362
+ // included, has finished pushing. The loader's own segment streams in
363
+ // after, so this does not block the shell; the deadlock guard above
364
+ // keeps a handler from depending on this loader.
365
+ const streaming = reqCtx._treeHasStreaming === true;
366
+ renderedPromise = reqCtx._renderBarrier.then(async () => {
367
+ if (streaming) {
368
+ reqCtx._handleStore.seal();
369
+ await reqCtx._handleStore.settled;
370
+ // The eager snapshot was intentionally left unbuilt for streaming
371
+ // (it would have been incomplete). Build the complete one once, now
372
+ // that the store has settled, so every ctx.use(handle) reads the
373
+ // cached snapshot instead of rebuilding it per call.
374
+ reqCtx._renderBarrierHandleSnapshot ??= buildHandleSnapshot(
375
+ reqCtx._handleStore,
376
+ reqCtx._renderBarrierSegmentOrder ?? [],
377
+ );
378
+ }
379
+ renderedResolved = true;
380
+ });
381
+ return renderedPromise;
382
+ },
263
383
  };
264
384
 
265
385
  const doneLoader = track(`loader:${loader.$$id}`, 2);
386
+ // Run the loader body inside loader scope so request-scoped reads
387
+ // (cookies()/headers() and non-cacheable ctx.get) are exempt from the
388
+ // cache-purity guards: loaders always run fresh, so their reads never leak
389
+ // into a cached segment. DSL loaders are already wrapped by fresh.ts; this
390
+ // also covers handler-invoked loaders (ctx.use(Loader) from a handler),
391
+ // which otherwise execute in the caller's cache scope and would wrongly
392
+ // throw. rendered() gating uses the captured isDslLoader (above), so this
393
+ // does not grant rendered() to handler-invoked loaders. Uses a body-only
394
+ // scope, so isInsideLoaderScope() / barrier / deadlock gating is unchanged.
266
395
  const promise = Promise.resolve(
267
- loaderFn(loaderCtx as LoaderContext<any, TEnv>),
396
+ runInsideLoaderBodyScope(() =>
397
+ loaderFn(loaderCtx as LoaderContext<any, TEnv>),
398
+ ),
268
399
  ).finally(() => {
269
400
  pendingLoaders.delete(loader.$$id);
270
401
  doneLoader();
@@ -290,12 +421,13 @@ export function setupLoaderAccess<TEnv>(
290
421
  ctx: HandlerContext<any, TEnv>,
291
422
  loaderPromises: Map<string, Promise<any>>,
292
423
  ): void {
293
- // Eagerly capture the HandleStore at setup time (before pipeline async ops).
294
- // In workerd/Cloudflare, dynamic imports and fetch() in the match pipeline
295
- // can disrupt AsyncLocalStorage, causing getRequestContext() to return
296
- // undefined when handlers later call ctx.use(handle). Capturing early
297
- // ensures the store reference survives ALS disruption.
298
- const handleStoreRef = _getRequestContext()?._handleStore;
424
+ // Eagerly capture the request context and HandleStore at setup time
425
+ // (before pipeline async ops). In workerd/Cloudflare, dynamic imports and
426
+ // fetch() in the match pipeline can disrupt AsyncLocalStorage, causing
427
+ // getRequestContext() to return undefined when handlers later call
428
+ // ctx.use(handle). Capturing early ensures references survive ALS disruption.
429
+ const reqCtxRef = _getRequestContext();
430
+ const handleStoreRef = reqCtxRef?._handleStore;
299
431
 
300
432
  const useLoader = createLoaderExecutor(ctx, loaderPromises);
301
433
 
@@ -313,21 +445,72 @@ export function setupLoaderAccess<TEnv>(
313
445
  );
314
446
  }
315
447
 
316
- return (
317
- dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
318
- ) => {
319
- if (!store) return;
320
-
321
- const valueOrPromise =
322
- typeof dataOrFn === "function"
323
- ? (dataOrFn as () => Promise<unknown>)()
324
- : dataOrFn;
448
+ return withDefer(
449
+ (dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
450
+ if (!store) return;
451
+
452
+ if (typeof dataOrFn === "function") {
453
+ // Run the callback inside the push-callback scope so ctx.use(loader)
454
+ // calls it makes — including after its own awaits, for an async
455
+ // callback — are not registered as handler-to-loader deps and do not
456
+ // trip the deadlock guard. A pushed promise value is not tracked by
457
+ // handleStore.settled and does not block segment resolution, so it
458
+ // cannot form a rendered() deadlock. The ALS scope (not a plain
459
+ // boolean) is what survives the callback's awaits.
460
+ const result = runInsidePushCallbackScope(() =>
461
+ (dataOrFn as () => Promise<unknown>)(),
462
+ );
463
+ store.push(handle.$$id, segmentId, result);
464
+ return;
465
+ }
466
+
467
+ store.push(handle.$$id, segmentId, dataOrFn);
468
+ },
469
+ );
470
+ }
325
471
 
326
- store.push(handle.$$id, segmentId, valueOrPromise);
327
- };
472
+ // Deadlock guard and handler-to-loader dependency tracking.
473
+ // Skip when inside a DSL loader scope (resolveLoaderData also calls
474
+ // ctx.use() but that's DSL-to-DSL, not handler-to-loader) or when
475
+ // inside a handle push callback (push callbacks don't block segment
476
+ // resolution so they can't cause rendered() deadlocks). The push-callback
477
+ // check is an ALS scope so it also exempts an ASYNC callback's continuation
478
+ // after its first await — relevant on streaming trees, where the guard
479
+ // state now stays live until handleStore.settled.
480
+ const loader = item as LoaderDefinition<any, any>;
481
+ if (!isInsideLoaderScope() && !isInsidePushCallbackScope()) {
482
+ const reqCtx = reqCtxRef ?? _getRequestContext();
483
+ if (reqCtx) {
484
+ // Direction 1: handler awaits loader that already called rendered()
485
+ if (
486
+ loaderPromises.has(loader.$$id) &&
487
+ reqCtx._renderBarrierWaiters?.has(loader.$$id)
488
+ ) {
489
+ throw new Error(
490
+ `Deadlock: handler is awaiting loader "${loader.$$id}" which called ctx.rendered(). ` +
491
+ `The loader is waiting for segment resolution, but the handler blocks resolution. ` +
492
+ `Move the data dependency to a loader-to-loader pattern instead.`,
493
+ );
494
+ }
495
+ // Direction 2: track dep so rendered() can detect the deadlock if the
496
+ // loader calls it later. Skip once the guard window is CLOSED — for a
497
+ // non-streaming tree that is when the barrier resolves (rendered()
498
+ // resolves immediately), and for a streaming tree it is when
499
+ // handleStore.settled completes (rendered() keeps waiting until then, so
500
+ // a loading() handler resuming after the barrier can still form a
501
+ // cycle). Using the explicit guard-closed flag rather than
502
+ // _renderBarrierSegmentOrder keeps tracking live across the streaming
503
+ // settle wait. (Handle push callbacks are already excluded above via
504
+ // isInsidePushCallbackScope(), so they cannot produce false positives
505
+ // here.)
506
+ if (!reqCtx._renderBarrierGuardClosed) {
507
+ if (!reqCtx._handlerLoaderDeps) reqCtx._handlerLoaderDeps = new Set();
508
+ reqCtx._handlerLoaderDeps.add(loader.$$id);
509
+ }
510
+ }
328
511
  }
329
512
 
330
- return useLoader(item as LoaderDefinition<any, any>, null);
513
+ return useLoader(loader, null);
331
514
  }) as typeof ctx.use;
332
515
  }
333
516
 
@@ -1,8 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
3
3
 
4
- // -- Revalidation trace types --
5
-
6
4
  export interface RevalidationTraceEntry {
7
5
  segmentId: string;
8
6
  segmentType: string;
@@ -12,7 +10,10 @@ export interface RevalidationTraceEntry {
12
10
  | "cache-hit"
13
11
  | "loader"
14
12
  | "parallel"
15
- | "orphan-layout";
13
+ | "orphan-layout"
14
+ | "route-handler"
15
+ | "layout-handler"
16
+ | "intercept-loader";
16
17
  defaultShouldRevalidate: boolean;
17
18
  finalShouldRevalidate: boolean;
18
19
  reason: string;
@@ -33,8 +34,6 @@ export interface RevalidationTrace {
33
34
  entries: RevalidationTraceEntry[];
34
35
  }
35
36
 
36
- // -- Log context --
37
-
38
37
  interface RouterLogContext {
39
38
  requestId: string;
40
39
  transactionId: string;
@@ -71,7 +70,7 @@ function getHeaderRequestId(request: Request): string | null {
71
70
  return trimmed.length > 0 ? trimmed : null;
72
71
  }
73
72
 
74
- function getOrCreateRequestId(request: Request): string {
73
+ export function getOrCreateRequestId(request: Request): string {
75
74
  const existing = requestIds.get(request);
76
75
  if (existing) return existing;
77
76
 
@@ -192,8 +191,6 @@ export function debugWarn(
192
191
  console.warn(`${prefix} ${message}`);
193
192
  }
194
193
 
195
- // -- Revalidation trace helpers --
196
-
197
194
  export function isTraceActive(): boolean {
198
195
  if (!INTERNAL_RANGO_DEBUG) return false;
199
196
  const ctx = routerLogContext.getStore();