@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Router Error Handling Utilities
3
3
  *
4
- * Error boundary and not-found boundary handling for RSC Router.
4
+ * Error boundary and not-found boundary handling for Rango.
5
5
  * Also includes the shared invokeOnError utility for error callback invocation.
6
6
  */
7
7
 
@@ -117,16 +117,10 @@ export function findNearestErrorBoundary(
117
117
  let current: EntryData | null = entry;
118
118
 
119
119
  while (current) {
120
- // Check if this entry has error boundaries defined
121
120
  if (current.errorBoundary && current.errorBoundary.length > 0) {
122
- // Return the last error boundary (most recently defined takes precedence)
123
121
  return current.errorBoundary[current.errorBoundary.length - 1];
124
122
  }
125
123
 
126
- // Check orphan layouts for error boundaries
127
- // Orphan layouts are siblings that render alongside the main route chain
128
- // They can define error boundaries that catch errors from routes in the same route group
129
- // Check from first to last (first sibling takes precedence as the "outer" wrapper)
130
124
  if (current.layout && current.layout.length > 0) {
131
125
  for (const orphan of current.layout) {
132
126
  if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
@@ -153,11 +147,21 @@ export function findNearestNotFoundBoundary(
153
147
  let current: EntryData | null = entry;
154
148
 
155
149
  while (current) {
156
- // Check if this entry has notFound boundaries defined
157
150
  if (current.notFoundBoundary && current.notFoundBoundary.length > 0) {
158
- // Return the last notFound boundary (most recently defined takes precedence)
159
151
  return current.notFoundBoundary[current.notFoundBoundary.length - 1];
160
152
  }
153
+
154
+ // Check orphan layouts mirroring findNearestErrorBoundary: notFoundBoundary
155
+ // attaches identically (onto parent.notFoundBoundary), and an orphan layout
156
+ // (parent=null) is reachable only via this scan. First sibling is "outer".
157
+ if (current.layout && current.layout.length > 0) {
158
+ for (const orphan of current.layout) {
159
+ if (orphan.notFoundBoundary && orphan.notFoundBoundary.length > 0) {
160
+ return orphan.notFoundBoundary[orphan.notFoundBoundary.length - 1];
161
+ }
162
+ }
163
+ }
164
+
161
165
  current = current.parent;
162
166
  }
163
167
 
@@ -165,6 +169,37 @@ export function findNearestNotFoundBoundary(
165
169
  return defaultNotFoundBoundary || null;
166
170
  }
167
171
 
172
+ /**
173
+ * Normalize an error's cause into a Flight-serializable shape.
174
+ * ErrorInfo.cause crosses the RSC serialization boundary (via
175
+ * LoaderDataResult.error + error-segment fallback props); a non-serializable
176
+ * cause (function, class instance, circular object) would make Flight
177
+ * serialization throw and mask the original loader error.
178
+ */
179
+ function normalizeCause(cause: unknown): unknown {
180
+ if (cause == null) return undefined;
181
+ const t = typeof cause;
182
+ if (t === "string" || t === "number" || t === "boolean") return cause;
183
+ // The whole body is guarded: even `instanceof`/clone can run user code (a
184
+ // Proxy trap, a throwing getter), and this helper must never throw —
185
+ // throwing here would mask the original loader error it exists to protect.
186
+ try {
187
+ if (cause instanceof Error) {
188
+ return { name: cause.name, message: cause.message, stack: cause.stack };
189
+ }
190
+ // Prefer preserving a serializable object/array intact (Flight uses
191
+ // structured-clone-like semantics); fall back to a string when the value
192
+ // is circular, host-bound, or otherwise non-serializable.
193
+ return structuredClone(cause);
194
+ } catch {
195
+ try {
196
+ return String(cause);
197
+ } catch {
198
+ return "[unstringifiable cause]";
199
+ }
200
+ }
201
+ }
202
+
168
203
  /**
169
204
  * Create ErrorInfo from an error object
170
205
  * Sanitizes error details in production
@@ -182,7 +217,7 @@ export function createErrorInfo(
182
217
  name: error.name,
183
218
  code: (error as any).code,
184
219
  stack: isDev ? error.stack : undefined,
185
- cause: isDev ? error.cause : undefined,
220
+ cause: isDev ? normalizeCause(error.cause) : undefined,
186
221
  segmentId,
187
222
  segmentType,
188
223
  };
@@ -207,22 +242,17 @@ export function createErrorSegment(
207
242
  entry: EntryData,
208
243
  params: Record<string, string>,
209
244
  ): ResolvedSegment {
210
- // Determine the component to render
211
245
  let component: ReactNode;
212
246
 
213
247
  if (typeof fallback === "function") {
214
- // ErrorBoundaryHandler - call with error info
215
248
  const props: ErrorBoundaryFallbackProps = {
216
249
  error: errorInfo,
217
250
  };
218
251
  component = fallback(props);
219
252
  } else {
220
- // Static ReactNode fallback
221
253
  component = fallback;
222
254
  }
223
255
 
224
- // Error segment uses the same ID as the layout that has the error boundary
225
- // The error boundary content replaces the layout's outlet content
226
256
  return {
227
257
  id: entry.shortCode,
228
258
  namespace: entry.id,
@@ -261,17 +291,14 @@ export function createNotFoundSegment(
261
291
  entry: EntryData,
262
292
  params: Record<string, string>,
263
293
  ): ResolvedSegment {
264
- // Determine the component to render
265
294
  let component: ReactNode;
266
295
 
267
296
  if (typeof fallback === "function") {
268
- // NotFoundBoundaryHandler - call with props
269
297
  const props: NotFoundBoundaryFallbackProps = {
270
298
  notFound: notFoundInfo,
271
299
  };
272
300
  component = fallback(props);
273
301
  } else {
274
- // Static ReactNode fallback
275
302
  component = fallback;
276
303
  }
277
304
 
@@ -1,5 +1,5 @@
1
1
  import { tryTrieMatch } from "./trie-matching.js";
2
- import { getRouteTrie, getRouterTrie } from "../route-map-builder.js";
2
+ import { getRouterTrie } from "../route-map-builder.js";
3
3
  import {
4
4
  findMatch as findRouteMatch,
5
5
  isLazyEvaluationNeeded,
@@ -8,6 +8,16 @@ import {
8
8
  import type { MetricsStore } from "../server/context";
9
9
  import type { RouteEntry } from "../types";
10
10
 
11
+ // The single-entry cache is module-lifetime, keyed only on pathname, so the same
12
+ // result object is handed to every same-pathname request. ctx.params aliases
13
+ // this object, so handlers mutating it would corrupt the cache for later requests.
14
+ // Clone params; entry/flags are read-only and shared safely.
15
+ function cloneMatchResult<TEnv>(
16
+ r: RouteMatchResult<TEnv> | null,
17
+ ): RouteMatchResult<TEnv> | null {
18
+ return r ? { ...r, params: { ...r.params } } : null;
19
+ }
20
+
11
21
  export interface FindMatchDeps<TEnv = any> {
12
22
  routesEntries: RouteEntry<TEnv>[];
13
23
  evaluateLazyEntry: (entry: RouteEntry<TEnv>) => void;
@@ -27,20 +37,14 @@ export function createFindMatch<TEnv = any>(
27
37
  let lastFindMatchPathname: string | null = null;
28
38
  let lastFindMatchResult: RouteMatchResult<TEnv> | null = null;
29
39
 
30
- // Wrapper for findMatch that uses routesEntries
31
- // Handles lazy evaluation by evaluating lazy entries on first match.
32
- // Phase 1: try O(path_length) trie match.
33
- // Phase 2: fall back to regex iteration.
34
40
  return function findMatch(
35
41
  pathname: string,
36
42
  ms?: MetricsStore,
37
43
  ): RouteMatchResult<TEnv> | null {
38
- // Return cached result if same pathname (avoids double-match per request)
39
44
  if (lastFindMatchPathname === pathname) {
40
- return lastFindMatchResult;
45
+ return cloneMatchResult(lastFindMatchResult);
41
46
  }
42
47
 
43
- // Helper to push sub-metrics
44
48
  const pushMetric = ms
45
49
  ? (label: string, start: number) => {
46
50
  ms.metrics.push({
@@ -51,17 +55,15 @@ export function createFindMatch<TEnv = any>(
51
55
  }
52
56
  : undefined;
53
57
 
54
- // Phase 1: Try trie match (O(path_length))
55
- // Only use the per-router trie. The global trie merges routes from ALL
56
- // routers and must not be used — in multi-router setups (host routing)
57
- // overlapping paths like "/" would match the wrong app's route.
58
58
  const routeTrie = getRouterTrie(deps.routerId);
59
+ let trieMatched = false;
59
60
  if (routeTrie) {
60
61
  const trieStart = performance.now();
61
62
  const trieResult = tryTrieMatch(routeTrie, pathname);
62
63
  pushMetric?.("match:trie", trieStart);
63
64
 
64
65
  if (trieResult) {
66
+ trieMatched = true;
65
67
  // Find the RouteEntry that contains this route.
66
68
  // Multiple entries can share the same staticPrefix (e.g., several
67
69
  // include("/", patterns) calls all produce staticPrefix=""). Evaluate
@@ -83,12 +85,8 @@ export function createFindMatch<TEnv = any>(
83
85
  }
84
86
  }
85
87
 
86
- // If no entry had the route in its routes map, use the first matching
87
- // entry as fallback (handles main entry with inline routes not yet
88
- // reflected in its routes object).
89
88
  if (!entry) entry = fallbackEntry;
90
89
 
91
- // If entry not found (nested include not yet discovered), evaluate parent
92
90
  if (!entry) {
93
91
  const parent = deps.routesEntries.find(
94
92
  (e) =>
@@ -112,9 +110,7 @@ export function createFindMatch<TEnv = any>(
112
110
  entry,
113
111
  routeKey: trieResult.routeKey,
114
112
  params: trieResult.params,
115
- optionalParams: new Set(trieResult.optionalParams || []),
116
113
  redirectTo: trieResult.redirectTo,
117
- ancestry: trieResult.ancestry,
118
114
  ...(trieResult.pr ? { pr: true } : {}),
119
115
  ...(trieResult.pt ? { pt: true } : {}),
120
116
  ...(trieResult.responseType
@@ -125,17 +121,14 @@ export function createFindMatch<TEnv = any>(
125
121
  : {}),
126
122
  ...(trieResult.rscFirst ? { rscFirst: true } : {}),
127
123
  };
128
- return lastFindMatchResult;
124
+ return cloneMatchResult(lastFindMatchResult);
129
125
  }
130
126
  }
131
127
  }
132
128
 
133
- // Phase 2: Fall back to existing matching (regex iteration)
134
129
  const regexStart = performance.now();
135
130
  let result = findRouteMatch(pathname, deps.routesEntries);
136
131
 
137
- // If we hit a lazy entry that needs evaluation, evaluate and retry.
138
- // Cap iterations to prevent infinite loops from pathological nesting.
139
132
  const MAX_LAZY_ITERATIONS = 100;
140
133
  let iterations = 0;
141
134
  while (isLazyEvaluationNeeded(result)) {
@@ -153,8 +146,36 @@ export function createFindMatch<TEnv = any>(
153
146
  }
154
147
  pushMetric?.("match:regex-fallback", regexStart);
155
148
 
149
+ // The trie is the single source of truth and is built before findMatch in
150
+ // both dev (handler rebuild) and production (ensureRouterManifest). If the
151
+ // trie was present yet the regex fallback resolved a real match, the trie
152
+ // has a gap (e.g. a route shape it cannot represent) and dev/prod could
153
+ // diverge if the trie were ever absent. Surface it in dev; folded out in
154
+ // production builds.
155
+ //
156
+ // Suppress when the trie DID match (`trieMatched`): that path falls through
157
+ // to the regex fallback only on the first request to a not-yet-spliced lazy
158
+ // entry (e.g. a 2+-level nested include whose deeper parent has not been
159
+ // evaluated). The trie knew the route; runtime lazy discovery simply lagged.
160
+ // That is the supported lazy-include flow, not a trie gap, so warning on it
161
+ // is a false positive (it manufactures bug reports and erodes the signal).
162
+ if (
163
+ process.env.NODE_ENV !== "production" &&
164
+ routeTrie &&
165
+ !trieMatched &&
166
+ result &&
167
+ !isLazyEvaluationNeeded(result)
168
+ ) {
169
+ console.warn(
170
+ `[@rangojs/router] Route "${pathname}" resolved via the regex fallback ` +
171
+ `even though the route trie was present. The trie should be the single ` +
172
+ `matching source of truth; this indicates a trie gap. Please report this ` +
173
+ `with your route configuration.`,
174
+ );
175
+ }
176
+
156
177
  lastFindMatchPathname = pathname;
157
178
  lastFindMatchResult = result;
158
- return result;
179
+ return cloneMatchResult(result);
159
180
  };
160
181
  }
@@ -18,6 +18,13 @@ import { isInsideCacheScope } from "../server/context.js";
18
18
  import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
19
19
  import { isAutoGeneratedRouteName } from "../route-name.js";
20
20
  import { PRERENDER_PASSTHROUGH } from "../prerender.js";
21
+ import { substitutePatternParams } from "./substitute-pattern-params.js";
22
+ import { fireAndForgetWaitUntil } from "../types/request-scope.js";
23
+
24
+ // Mutating Headers methods guarded so they throw inside "use cache" / cache()
25
+ // scope. Module-level constant (read-only, .has() lookups) so it is allocated
26
+ // once at module load instead of per createHandlerContext (per-request) call.
27
+ const MUTATING_HEADERS_METHODS = new Set(["set", "append", "delete"]);
21
28
 
22
29
  /**
23
30
  * Strip internal _rsc* query params from a URL.
@@ -114,9 +121,9 @@ function createPrerenderPassthroughFn(
114
121
  }
115
122
  if (!isPassthroughRoute) {
116
123
  throw new Error(
117
- "ctx.passthrough() is only available on routes declared with " +
118
- "{ passthrough: true }. Remove the passthrough() call or add " +
119
- "{ passthrough: true } to the Prerender options.",
124
+ "ctx.passthrough() is only available on routes wrapped with " +
125
+ "Passthrough(). Remove the passthrough() call or wrap the " +
126
+ "Prerender definition with Passthrough(prerenderDef, liveHandler).",
120
127
  );
121
128
  }
122
129
  return PRERENDER_PASSTHROUGH;
@@ -158,26 +165,14 @@ export function createReverseFunction(
158
165
  );
159
166
  }
160
167
 
161
- let result = pattern;
162
-
163
168
  // Merge current request params as defaults, explicit params override
164
169
  const effectiveParams = currentParams
165
170
  ? { ...currentParams, ...hrefParams }
166
171
  : hrefParams;
167
172
 
168
- // Substitute params (strip constraint and optional syntax: :param(a|b)? -> value)
169
- if (effectiveParams) {
170
- result = result.replace(
171
- /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
172
- (_, key) => {
173
- const value = effectiveParams[key];
174
- if (value === undefined) {
175
- throw new Error(`Missing param "${key}" for route "${name}"`);
176
- }
177
- return encodeURIComponent(value);
178
- },
179
- );
180
- }
173
+ let result = effectiveParams
174
+ ? substitutePatternParams(pattern, effectiveParams, name)
175
+ : pattern;
181
176
 
182
177
  // Append search params as query string
183
178
  if (search) {
@@ -207,7 +202,7 @@ export function createHandlerContext<TEnv>(
207
202
  // Get variables from request context - this is the unified context
208
203
  // shared between middleware and route handlers
209
204
  const requestContext = _getRequestContext();
210
- const variables: any = requestContext?.var ?? {};
205
+ const variables: any = requestContext?._variables ?? {};
211
206
 
212
207
  // If route has a search schema, parse URLSearchParams into typed object
213
208
  const searchSchema = routeName ? getSearchSchema(routeName) : undefined;
@@ -222,7 +217,7 @@ export function createHandlerContext<TEnv>(
222
217
  // Guard mutating Headers methods so they throw inside "use cache" or cache() scope.
223
218
  // Uses lazy `ctx` reference (assigned below) — only the specific handler ctx
224
219
  // is stamped by cache-runtime, not the shared request context.
225
- const MUTATING_HEADERS_METHODS = new Set(["set", "append", "delete"]);
220
+ // MUTATING_HEADERS_METHODS is hoisted to module scope (constant, read-only).
226
221
  let ctx: InternalHandlerContext<any, TEnv>;
227
222
  const guardedHeaders = new Proxy(stubResponse.headers, {
228
223
  get(target, prop, receiver) {
@@ -250,14 +245,19 @@ export function createHandlerContext<TEnv>(
250
245
  ctx = {
251
246
  params,
252
247
  build: false,
248
+ dev: false,
253
249
  request,
254
250
  searchParams,
255
251
  search: searchSchema ? resolvedSearchParams : {},
256
252
  pathname,
257
253
  url,
258
- originalUrl: new URL(request.url),
254
+ originalUrl: requestContext?.originalUrl ?? new URL(request.url),
259
255
  env: bindings,
260
- var: variables,
256
+ waitUntil: requestContext
257
+ ? requestContext.waitUntil.bind(requestContext)
258
+ : fireAndForgetWaitUntil,
259
+ executionContext: requestContext?.executionContext,
260
+ _variables: variables,
261
261
  get: ((keyOrVar: any) => {
262
262
  // Read-time guard: non-cacheable var inside cache() → throw.
263
263
  // Works for both ContextVar tokens and string keys.
@@ -320,7 +320,7 @@ export function createHandlerContext<TEnv>(
320
320
  *
321
321
  * Returns an InternalHandlerContext where params, pathname, url, searchParams,
322
322
  * search, reverse, and use(handle) work. Request-time properties
323
- * (request, env, headers, cookies, var, get, set, res) throw with a clear error.
323
+ * (request, env, headers, cookies, get, set, res) throw with a clear error.
324
324
  */
325
325
  export function createPrerenderContext<TEnv>(
326
326
  params: Record<string, string>,
@@ -329,6 +329,8 @@ export function createPrerenderContext<TEnv>(
329
329
  routeName?: string,
330
330
  buildVars?: Record<string, any>,
331
331
  isPassthroughRoute?: boolean,
332
+ buildEnv?: TEnv,
333
+ devMode?: boolean,
332
334
  ): InternalHandlerContext<any, TEnv> {
333
335
  const syntheticUrl = new URL(`http://prerender${pathname}`);
334
336
  const variables = buildVars ?? {};
@@ -343,6 +345,7 @@ export function createPrerenderContext<TEnv>(
343
345
  return {
344
346
  params,
345
347
  build: true,
348
+ dev: devMode ?? false,
346
349
  get request(): Request {
347
350
  return throwUnavailable("request");
348
351
  },
@@ -352,11 +355,19 @@ export function createPrerenderContext<TEnv>(
352
355
  url: syntheticUrl,
353
356
  originalUrl: syntheticUrl,
354
357
  get env(): TEnv {
355
- return throwUnavailable("env");
356
- },
357
- get var(): any {
358
- return throwUnavailable("var");
358
+ if (buildEnv !== undefined) return buildEnv;
359
+ throw new Error(
360
+ "ctx.env is not available during pre-rendering. " +
361
+ "Configure buildEnv in your rango() plugin options to enable build-time env access.",
362
+ );
359
363
  },
364
+ // Build-time prerender has no live request. waitUntil is a true no-op
365
+ // (running fn() here would fire side effects during build, which is
366
+ // incorrect — these are meant to outlive the live response).
367
+ // executionContext is absent for the same reason.
368
+ waitUntil: () => {},
369
+ executionContext: undefined,
370
+ _variables: variables,
360
371
  get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
361
372
  set: ((keyOrVar: any, value: any) => {
362
373
  contextSet(variables, keyOrVar, value);
@@ -402,6 +413,8 @@ export function createPrerenderContext<TEnv>(
402
413
  export function createStaticContext<TEnv>(
403
414
  routeMap: Record<string, string>,
404
415
  routeName?: string,
416
+ buildEnv?: TEnv,
417
+ devMode?: boolean,
405
418
  ): InternalHandlerContext<any, TEnv> {
406
419
  const variables: Record<string, any> = {};
407
420
 
@@ -417,6 +430,7 @@ export function createStaticContext<TEnv>(
417
430
  return throwUnavailable("params");
418
431
  },
419
432
  build: true,
433
+ dev: devMode ?? false,
420
434
  get request(): Request {
421
435
  return throwUnavailable("request");
422
436
  },
@@ -436,11 +450,18 @@ export function createStaticContext<TEnv>(
436
450
  return throwUnavailable("originalUrl");
437
451
  },
438
452
  get env(): TEnv {
439
- return throwUnavailable("env");
440
- },
441
- get var(): any {
442
- return throwUnavailable("var");
453
+ if (buildEnv !== undefined) return buildEnv;
454
+ throw new Error(
455
+ "ctx.env is not available in Static() handlers. " +
456
+ "Configure buildEnv in your rango() plugin options to enable build-time env access.",
457
+ );
443
458
  },
459
+ // Static() handlers have no live request. waitUntil is a true no-op
460
+ // (running fn() here would fire side effects during build, which is
461
+ // incorrect). executionContext is absent for the same reason.
462
+ waitUntil: () => {},
463
+ executionContext: undefined,
464
+ _variables: variables,
444
465
  get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
445
466
  set: ((keyOrVar: any, value: any) => {
446
467
  contextSet(variables, keyOrVar, value);