@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
@@ -15,10 +15,6 @@ export interface TrieMatchResult {
15
15
  sp: string;
16
16
  /** Matched route params */
17
17
  params: Record<string, string>;
18
- /** Optional param names (absent params have empty string value) */
19
- optionalParams?: string[];
20
- /** Ancestry shortCodes for layout pruning */
21
- ancestry: string[];
22
18
  /** Redirect target if trailing slash requires it */
23
19
  redirectTo?: string;
24
20
  /** Route has pre-rendered data available */
@@ -43,14 +39,12 @@ export function tryTrieMatch(
43
39
  ): TrieMatchResult | null {
44
40
  if (!trie) return null;
45
41
 
46
- // Split pathname into segments, filtering empty strings from leading/trailing slashes
47
42
  const pathnameHasTrailingSlash =
48
43
  pathname.length > 1 && pathname.endsWith("/");
49
44
  const normalizedPath = pathnameHasTrailingSlash
50
45
  ? pathname.slice(0, -1)
51
46
  : pathname;
52
47
 
53
- // Handle root path
54
48
  if (normalizedPath === "" || normalizedPath === "/") {
55
49
  if (trie.r) {
56
50
  return validateAndBuild(
@@ -61,13 +55,24 @@ export function tryTrieMatch(
61
55
  pathnameHasTrailingSlash,
62
56
  );
63
57
  }
58
+ // A root-level wildcard ("/*") matches "/" with an empty remainder, the
59
+ // same value the regex matcher produces for the bare prefix. Without this
60
+ // the trie misses, the regex fallback runs, and its no-config branch emits
61
+ // a corrupt slice-off redirect. The static terminal still wins above.
62
+ if (trie.w) {
63
+ return validateAndBuild(
64
+ trie.w,
65
+ [],
66
+ "",
67
+ pathname,
68
+ pathnameHasTrailingSlash,
69
+ );
70
+ }
64
71
  return null;
65
72
  }
66
73
 
67
- // Remove leading slash and split
68
74
  const segments = normalizedPath.slice(1).split("/");
69
75
 
70
- // Try exact match with normalized path (no trailing slash)
71
76
  const result = walkTrie(trie, segments, 0, []);
72
77
  if (result) {
73
78
  return validateAndBuild(
@@ -89,8 +94,58 @@ interface WalkResult {
89
94
  }
90
95
 
91
96
  /**
92
- * Walk the trie by segments with priority: static > param > wildcard.
93
- * Uses backtracking to try all possible matches.
97
+ * Check a leaf's constraints (leaf.cv) against already-resolved named params.
98
+ * Empty/undefined values are exempt (optional params that were not bound).
99
+ */
100
+ function constraintsSatisfied(
101
+ leaf: TrieLeaf,
102
+ params: Record<string, string>,
103
+ ): boolean {
104
+ if (!leaf.cv) return true;
105
+ for (const paramName in leaf.cv) {
106
+ const allowed = leaf.cv[paramName]!;
107
+ const value = params[paramName];
108
+ if (value !== undefined && value !== "" && !allowed.includes(value)) {
109
+ return false;
110
+ }
111
+ }
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Constraint check for a candidate terminal DURING the walk. Builds the named
117
+ * params from positional walk values (decoded the same way validateAndBuild
118
+ * does) and validates leaf.cv. Returning false lets walkTrie unwind to a
119
+ * lower-priority sibling instead of committing to a leaf that would only be
120
+ * rejected post-walk — that post-walk rejection is what forced the regex
121
+ * fallback (and its false "trie gap" R3 warning) for perfectly valid configs.
122
+ */
123
+ function leafConstraintsPass(
124
+ leaf: TrieLeaf,
125
+ paramValues: string[],
126
+ wildcardValue: string | undefined,
127
+ ): boolean {
128
+ if (!leaf.cv) return true;
129
+ const params: Record<string, string> = {};
130
+ if (leaf.pa) {
131
+ for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
132
+ params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
133
+ }
134
+ }
135
+ if (wildcardValue !== undefined && "pn" in leaf) {
136
+ params[(leaf as TrieLeaf & { pn: string }).pn] =
137
+ safeDecodeURIComponent(wildcardValue);
138
+ }
139
+ return constraintsSatisfied(leaf, params);
140
+ }
141
+
142
+ /**
143
+ * Walk the trie by segments with priority: static > suffix-param > param >
144
+ * wildcard (Priority 1-4 below; matches the canonical M4 ordering in
145
+ * docs/internal/matching-and-lazy-discovery.md).
146
+ * Uses backtracking to try all possible matches. Per-leaf constraints are
147
+ * enforced at each candidate terminal so a constraint miss backtracks to a
148
+ * lower-priority sibling rather than aborting the whole match.
94
149
  */
95
150
  function walkTrie(
96
151
  node: TrieNode,
@@ -98,25 +153,34 @@ function walkTrie(
98
153
  index: number,
99
154
  paramValues: string[],
100
155
  ): WalkResult | null {
101
- // All segments consumed: check for terminal
102
156
  if (index === segments.length) {
103
- if (node.r) {
157
+ if (node.r && leafConstraintsPass(node.r, paramValues, undefined)) {
104
158
  return { leaf: node.r, paramValues: [...paramValues] };
105
159
  }
160
+ // A wildcard at this node matches the bare prefix with an empty remainder
161
+ // (e.g. "/files" against "/files/*"), mirroring the regex matcher's `*=""`.
162
+ // walkTrie otherwise only reaches node.w in the index<length branch below,
163
+ // so without this a request to the wildcard's own prefix misses the trie
164
+ // and the regex fallback emits a corrupt redirect. A static terminal
165
+ // (node.r) still wins.
166
+ if (node.w && leafConstraintsPass(node.w, paramValues, "")) {
167
+ return { leaf: node.w, paramValues: [...paramValues], wildcardValue: "" };
168
+ }
106
169
  return null;
107
170
  }
108
171
 
109
172
  const segment = segments[index];
110
173
  const staticChild = node.s?.[segment];
111
174
 
112
- // Priority 1: Static match
113
175
  if (staticChild) {
114
176
  const result = walkTrie(staticChild, segments, index + 1, paramValues);
115
177
  if (result) return result;
116
178
  }
117
179
 
118
- // Priority 2: Suffix-param match (e.g., :productId.html)
119
180
  if (node.xp) {
181
+ // node.xp keys are pre-sorted longest-suffix-first at build time
182
+ // (route-trie.ts sortSuffixParams), so the first match is the most specific
183
+ // suffix: `/app.min.js` matches `:file.min.js` before `:file.js`.
120
184
  for (const suffix in node.xp) {
121
185
  if (segment.endsWith(suffix) && segment.length > suffix.length) {
122
186
  const paramValue = segment.slice(0, -suffix.length);
@@ -133,7 +197,6 @@ function walkTrie(
133
197
  }
134
198
  }
135
199
 
136
- // Priority 3: Param match
137
200
  if (node.p) {
138
201
  paramValues.push(segment);
139
202
  const result = walkTrie(node.p.c, segments, index + 1, paramValues);
@@ -141,14 +204,15 @@ function walkTrie(
141
204
  if (result) return result;
142
205
  }
143
206
 
144
- // Priority 4: Wildcard match (consumes rest)
145
207
  if (node.w) {
146
208
  const rest = joinRemainingSegments(segments, index);
147
- return {
148
- leaf: node.w,
149
- paramValues: [...paramValues],
150
- wildcardValue: rest,
151
- };
209
+ if (leafConstraintsPass(node.w, paramValues, rest)) {
210
+ return {
211
+ leaf: node.w,
212
+ paramValues: [...paramValues],
213
+ wildcardValue: rest,
214
+ };
215
+ }
152
216
  }
153
217
 
154
218
  return null;
@@ -174,10 +238,6 @@ function validateAndBuild(
174
238
  originalPathname: string,
175
239
  pathnameHasTrailingSlash: boolean,
176
240
  ): TrieMatchResult | null {
177
- // Build named params by zipping leaf.pa with positional paramValues.
178
- // Params are URL-decoded at this boundary so ctx.params holds the values
179
- // apps expect (matching Express/React Router) and round-trip cleanly
180
- // through ctx.reverse.
181
241
  const params: Record<string, string> = {};
182
242
  if (leaf.pa) {
183
243
  for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
@@ -185,34 +245,15 @@ function validateAndBuild(
185
245
  }
186
246
  }
187
247
 
188
- // Add wildcard param (wildcard leaves have pn from TrieNode.w type)
189
248
  if (wildcardValue !== undefined && "pn" in leaf) {
190
249
  params[(leaf as TrieLeaf & { pn: string }).pn] =
191
250
  safeDecodeURIComponent(wildcardValue);
192
251
  }
193
252
 
194
- // Validate constraints against decoded values so constraint lists can be
195
- // written in decoded form (e.g. ["en-GB", "en US"]).
196
- if (leaf.cv) {
197
- for (const paramName in leaf.cv) {
198
- const allowed = leaf.cv[paramName]!;
199
- const value = params[paramName];
200
- if (value !== undefined && value !== "" && !allowed.includes(value)) {
201
- return null;
202
- }
203
- }
204
- }
205
-
206
- // Fill in empty strings for optional params that weren't matched
207
- if (leaf.op) {
208
- for (const name of leaf.op) {
209
- if (!(name in params)) {
210
- params[name] = "";
211
- }
212
- }
253
+ if (!constraintsSatisfied(leaf, params)) {
254
+ return null;
213
255
  }
214
256
 
215
- // Trailing slash handling
216
257
  const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
217
258
  let redirectTo: string | undefined;
218
259
 
@@ -230,10 +271,8 @@ function validateAndBuild(
230
271
  routeKey: leaf.n,
231
272
  sp: leaf.sp,
232
273
  params,
233
- ancestry: leaf.a,
234
274
  };
235
275
 
236
- if (leaf.op) result.optionalParams = leaf.op;
237
276
  if (redirectTo) result.redirectTo = redirectTo;
238
277
  if (leaf.pr) result.pr = true;
239
278
  if (leaf.pt) result.pt = true;
@@ -22,27 +22,11 @@ import type {
22
22
  ShouldRevalidateFn,
23
23
  } from "../types";
24
24
 
25
- /**
26
- * Result of resolving loaders with revalidation
27
- * Contains both segments to render and all matched segment IDs
28
- */
29
- export interface LoaderRevalidationResult {
30
- segments: ResolvedSegment[];
31
- matchedIds: string[];
32
- }
33
-
34
- /**
35
- * Result of resolving segments with revalidation
36
- * Contains both segments to render and all matched segment IDs
37
- */
38
25
  export interface SegmentRevalidationResult {
39
26
  segments: ResolvedSegment[];
40
27
  matchedIds: string[];
41
28
  }
42
29
 
43
- /**
44
- * Action context type for revalidation
45
- */
46
30
  export type ActionContext = {
47
31
  actionId?: string;
48
32
  actionUrl?: URL;
@@ -50,23 +34,6 @@ export type ActionContext = {
50
34
  formData?: FormData;
51
35
  };
52
36
 
53
- /**
54
- * Dependencies passed to segment resolution functions
55
- * These are created within createRouter and passed to extracted utilities
56
- */
57
- export interface RouterDependencies<TEnv> {
58
- findNearestErrorBoundary: (
59
- entry: EntryData | null,
60
- ) => ReactNode | ErrorBoundaryHandler | null;
61
- findNearestNotFoundBoundary: (
62
- entry: EntryData | null,
63
- ) => ReactNode | NotFoundBoundaryHandler | null;
64
- }
65
-
66
- /**
67
- * Dependencies injected from createRouter closure into extracted segment resolution functions.
68
- * These are the closure-bound helpers that cannot be imported directly.
69
- */
70
37
  export interface SegmentResolutionDeps<TEnv = any> {
71
38
  wrapLoaderPromise: <T>(
72
39
  promise: Promise<T>,
@@ -98,23 +65,16 @@ export interface SegmentResolutionDeps<TEnv = any> {
98
65
  ) => ReactNode | NotFoundBoundaryHandler | null;
99
66
  notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
100
67
  callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
68
+ /**
69
+ * Router-level default for the per-segment `transition({ viewTransition })`
70
+ * flag, from createRouter({ viewTransition }). Resolved into each segment's
71
+ * transition config during resolution (only `false` is stamped) so the render
72
+ * gate reads the boundary decision off the segment on both server and client.
73
+ * Undefined is treated as "auto" (wrap).
74
+ */
75
+ viewTransitionDefault?: "auto" | false;
101
76
  }
102
77
 
103
- /**
104
- * Dependencies injected from createRouter closure into extracted intercept resolution functions.
105
- */
106
- export interface InterceptResolutionDeps<TEnv = any> {
107
- wrapLoaderPromise: SegmentResolutionDeps<TEnv>["wrapLoaderPromise"];
108
- evaluateInterceptWhen: (
109
- intercept: InterceptEntry,
110
- selectorContext: InterceptSelectorContext | null,
111
- isAction: boolean,
112
- ) => boolean;
113
- }
114
-
115
- /**
116
- * Dependencies injected from createRouter closure into extracted match API functions.
117
- */
118
78
  export interface MatchApiDeps<TEnv = any> {
119
79
  findMatch: (pathname: string, ms?: any) => any;
120
80
  getMetricsStore: () => any;
@@ -129,23 +89,13 @@ export interface MatchApiDeps<TEnv = any> {
129
89
  getRouteMap: () => Record<string, string>;
130
90
  }
131
91
 
132
- /**
133
- * Title descriptor types for template support
134
- */
135
92
  export type TitleDescriptor =
136
93
  | string
137
94
  | { template: string; default: string } // For layouts - template applied to child titles
138
- | { absolute: string }; // Bypass parent template
95
+ | { absolute: string };
139
96
 
140
- /**
141
- * Unset descriptor to remove inherited meta
142
- * Key format matches getMetaKey output: "title", "name:description", "property:og:image"
143
- */
144
97
  export type UnsetDescriptor = { unset: string };
145
98
 
146
- /**
147
- * Base meta descriptor types (sync values)
148
- */
149
99
  export type MetaDescriptorBase =
150
100
  | { charSet: "utf-8" }
151
101
  | { title: TitleDescriptor }
@@ -157,10 +107,6 @@ export type MetaDescriptorBase =
157
107
  | UnsetDescriptor
158
108
  | { [name: string]: unknown };
159
109
 
160
- /**
161
- * Meta descriptor that can be sync or async.
162
- * Use Promise<MetaDescriptorBase> for streaming meta that resolves after initial render.
163
- */
164
110
  export type MetaDescriptor = MetaDescriptorBase | Promise<MetaDescriptorBase>;
165
111
 
166
112
  type LdJsonObject = { [Key in string]: LdJsonValue } & {
@@ -25,11 +25,6 @@ export function safeDecodeURIComponent(raw: string): string {
25
25
  }
26
26
  }
27
27
 
28
- // encodeURIComponent over-encodes for path segments. After running it,
29
- // un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
30
- // keeps human-readable characters that are legal in a path segment.
31
- // Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
32
- // encoded.
33
28
  const PATH_SAFE_ESCAPES: Record<string, string> = {
34
29
  "%3A": ":",
35
30
  "%40": "@",
package/src/router.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { createCacheScope } from "./cache/cache-scope.js";
3
- import {
4
- setCacheProfiles,
5
- resolveCacheProfiles,
6
- } from "./cache/profile-registry.js";
3
+ import { resolveCacheProfiles } from "./cache/profile-registry.js";
7
4
  import { isCachedFunction } from "./cache/taint.js";
8
5
  import { assertClientComponent } from "./component-utils.js";
9
6
  import { DefaultDocument } from "./components/DefaultDocument.js";
@@ -21,10 +18,11 @@ import type { AllUseItems } from "./route-types.js";
21
18
  import type { UrlPatterns } from "./urls.js";
22
19
  import type { UrlBuilder } from "./urls/pattern-types.js";
23
20
  import { urls } from "./urls.js";
21
+ import { buildPrecomputedByPrefix } from "./build/prefix-tree-utils.js";
24
22
  import {
25
23
  type EntryData,
26
24
  getContext,
27
- RSCRouterContext,
25
+ RangoContext,
28
26
  type MetricsStore,
29
27
  } from "./server/context";
30
28
  import { createHandleStore, type HandleStore } from "./server/handle-store.js";
@@ -56,6 +54,7 @@ import { buildDebugManifest } from "./router/debug-manifest.js";
56
54
 
57
55
  import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
58
56
  import { createHandlerContext } from "./router/handler-context.js";
57
+ import { normalizeBasename } from "./router/basename.js";
59
58
  import {
60
59
  setupLoaderAccess,
61
60
  setupLoaderAccessSilent,
@@ -70,6 +69,7 @@ import {
70
69
  } from "./router/middleware.js";
71
70
  import {
72
71
  extractStaticPrefix,
72
+ joinPrefix,
73
73
  traverseBack,
74
74
  } from "./router/pattern-matching.js";
75
75
  import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
@@ -90,13 +90,10 @@ import {
90
90
  RouterRegistry,
91
91
  nextRouterAutoId,
92
92
  } from "./router/router-registry.js";
93
+ import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
93
94
  import type {
94
- RSCRouterOptions,
95
- RootLayoutProps,
96
- } from "./router/router-options.js";
97
- import type {
98
- RSCRouter,
99
- RSCRouterInternal,
95
+ Rango,
96
+ RangoInternal,
100
97
  RouterRequestInput,
101
98
  } from "./router/router-interfaces.js";
102
99
 
@@ -111,26 +108,27 @@ import {
111
108
  matchForPrerender as _matchForPrerender,
112
109
  renderStaticSegment as _renderStaticSegment,
113
110
  } from "./router/prerender-match.js";
111
+ import { resolveStateCookieName } from "./router/state-cookie-name.js";
114
112
 
115
113
  // Re-export public types and values from extracted modules
116
114
  export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
117
115
  export type {
118
- RSCRouterOptions,
116
+ RangoOptions,
119
117
  RootLayoutProps,
120
118
  SSRStreamMode,
121
119
  SSROptions,
122
120
  ResolveStreamingContext,
123
121
  } from "./router/router-options.js";
124
122
  export type {
125
- RSCRouter,
126
- RSCRouterInternal,
123
+ Rango,
124
+ RangoInternal,
127
125
  RouterRequestInput,
128
126
  } from "./router/router-interfaces.js";
129
127
  export { toInternal } from "./router/router-interfaces.js";
130
128
 
131
129
  export function createRouter<TEnv = any>(
132
- options: RSCRouterOptions<TEnv> = {},
133
- ): RSCRouter<TEnv, {}> {
130
+ options: RangoOptions<TEnv> = {},
131
+ ): Rango<TEnv, {}> {
134
132
  const {
135
133
  id: userProvidedId,
136
134
  $$id: injectedId,
@@ -150,6 +148,7 @@ export function createRouter<TEnv = any>(
150
148
  nonce,
151
149
  version,
152
150
  prefetchCacheTTL: prefetchCacheTTLOption,
151
+ stateCookiePrefix: stateCookiePrefixOption,
153
152
  warmup: warmupOption,
154
153
  allowDebugManifest: allowDebugManifestOption = false,
155
154
  telemetry: telemetrySink,
@@ -158,23 +157,31 @@ export function createRouter<TEnv = any>(
158
157
  timeouts: timeoutsOption,
159
158
  onTimeout,
160
159
  originCheck: originCheckOption,
160
+ viewTransition: viewTransitionOption = "auto",
161
+ debugCacheSignal: debugCacheSignalOption = false,
161
162
  } = options;
162
163
 
164
+ // Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
165
+ // debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
166
+ // no X-Rango-Cache header is emitted and output is byte-identical.
167
+ const cacheSignalEnabled =
168
+ debugCacheSignalOption ||
169
+ (typeof process !== "undefined" &&
170
+ (process as { env?: Record<string, string | undefined> }).env
171
+ ?.RANGO_TEST_SIGNALS === "1");
172
+
163
173
  // Normalize basename: ensure leading slash, strip trailing slash.
164
- // A bare "/" is equivalent to no basename.
165
- const basename =
166
- basenameOption && basenameOption.replace(/^\/+|\/+$/g, "")
167
- ? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
168
- : undefined;
174
+ // A bare "/" is equivalent to no basename. Shared with the testing
175
+ // primitives via normalizeBasename so they can never drift.
176
+ const basename = normalizeBasename(basenameOption);
169
177
 
170
178
  // Resolve telemetry sink (no-op when not configured)
171
179
  const telemetry = resolveSink(telemetrySink);
172
180
 
173
- // Resolve cache profiles: merge user config with guaranteed default profile.
174
- // This resolved map is both stored on the router (for per-request context)
175
- // and written to the global registry (for DSL-time cache("profileName")).
181
+ // Resolve cache profiles: merge user config with the guaranteed default
182
+ // profile. This resolved map is threaded onto each request context; the
183
+ // "use cache: <profile>" runtime path reads it request-scoped.
176
184
  const resolvedCacheProfiles = resolveCacheProfiles(cacheProfilesOption);
177
- setCacheProfiles(resolvedCacheProfiles);
178
185
 
179
186
  // Source file: prefer Vite-injected path (zero cost), fall back to
180
187
  // stack trace parsing for non-Vite environments (e.g. tests).
@@ -209,6 +216,14 @@ export function createRouter<TEnv = any>(
209
216
  const routerId =
210
217
  userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
211
218
 
219
+ // Resolve the rango state cookie name once, here, so the two cookie writers
220
+ // (the client document.cookie writer and the server Set-Cookie writer)
221
+ // consume one pre-composed name and cannot drift.
222
+ const resolvedStateCookieName = resolveStateCookieName(
223
+ stateCookiePrefixOption,
224
+ routerId,
225
+ );
226
+
212
227
  // Resolve prefetch cache TTL (default: 300 seconds / 5 minutes)
213
228
  // Clamp to a non-negative integer for valid Cache-Control max-age.
214
229
  const rawTTL =
@@ -255,9 +270,14 @@ export function createRouter<TEnv = any>(
255
270
  invokeOnError(onError, error, phase, context, "Router");
256
271
  }
257
272
 
258
- // Validate document is a client component
273
+ // Validate document is a client component. Under a test runner the "use
274
+ // client" transform has not run, so a real exported document has no marker;
275
+ // allowServerInTest lets the router construct in a bare unit test (for
276
+ // dispatch / assertGeneratedRoutesMatch) while a real build still throws.
259
277
  if (documentOption !== undefined) {
260
- assertClientComponent(documentOption, "document");
278
+ assertClientComponent(documentOption, "document", {
279
+ allowServerInTest: true,
280
+ });
261
281
  }
262
282
 
263
283
  // Use default document if none provided (keeps internal name as rootLayout)
@@ -331,7 +351,6 @@ export function createRouter<TEnv = any>(
331
351
  regex,
332
352
  paramNames,
333
353
  handler,
334
- mountPrefix,
335
354
  });
336
355
  }
337
356
 
@@ -365,9 +384,11 @@ export function createRouter<TEnv = any>(
365
384
  getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
366
385
  if (current !== precomputedSource) {
367
386
  precomputedSource = current;
368
- precomputedByPrefix = current
369
- ? new Map(current.map((e) => [e.staticPrefix, e.routes]))
370
- : null;
387
+ // buildPrecomputedByPrefix drops any staticPrefix owned by more than one
388
+ // leaf include instead of collapsing it last-wins (which would mis-assign
389
+ // one include's routes to another's entry and 500 a valid sibling route).
390
+ // Such shared-prefix includes resolve via the handler path instead.
391
+ precomputedByPrefix = current ? buildPrecomputedByPrefix(current) : null;
371
392
  }
372
393
  return precomputedByPrefix;
373
394
  }
@@ -537,6 +558,7 @@ export function createRouter<TEnv = any>(
537
558
  findNearestNotFoundBoundary,
538
559
  notFoundComponent: notFound,
539
560
  callOnError,
561
+ viewTransitionDefault: viewTransitionOption,
540
562
  };
541
563
 
542
564
  // Match API dependencies
@@ -664,6 +686,7 @@ export function createRouter<TEnv = any>(
664
686
  findMatch,
665
687
  findInterceptForRoute,
666
688
  telemetry: telemetrySink,
689
+ cacheSignalEnabled,
667
690
  });
668
691
 
669
692
  const { match, matchPartial, matchError, previewMatch } = matchHandlers;
@@ -673,7 +696,7 @@ export function createRouter<TEnv = any>(
673
696
  * The type system tracks accumulated routes through the builder chain
674
697
  * Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
675
698
  */
676
- const router: RSCRouterInternal<TEnv, {}> = {
699
+ const router: RangoInternal<TEnv, {}> = {
677
700
  __brand: RSC_ROUTER_BRAND,
678
701
  id: routerId,
679
702
  basename,
@@ -721,7 +744,7 @@ export function createRouter<TEnv = any>(
721
744
  };
722
745
 
723
746
  let handlerResult: AllUseItems[] = [];
724
- RSCRouterContext.run(
747
+ RangoContext.run(
725
748
  {
726
749
  manifest,
727
750
  patterns: routePatterns,
@@ -833,10 +856,13 @@ export function createRouter<TEnv = any>(
833
856
 
834
857
  // Create placeholder RouteEntry for each lazy include
835
858
  for (const lazyInclude of lazyIncludes) {
836
- // Compute the full URL prefix (combining parent prefix if any)
837
- const fullPrefix = lazyInclude.context.urlPrefix
838
- ? lazyInclude.context.urlPrefix + lazyInclude.prefix
839
- : lazyInclude.prefix;
859
+ // Compute the full URL prefix (combining parent prefix if any). Use the
860
+ // slash-collapsing join so a trailing-slash parent prefix does not
861
+ // produce a double-slash staticPrefix the trie's sp can never match.
862
+ const fullPrefix = joinPrefix(
863
+ lazyInclude.context.urlPrefix,
864
+ lazyInclude.prefix,
865
+ );
840
866
 
841
867
  const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
842
868
  prefix: "",
@@ -932,6 +958,10 @@ export function createRouter<TEnv = any>(
932
958
  prefetchCacheControl,
933
959
  prefetchCacheTTL,
934
960
 
961
+ // Expose the resolved rango state cookie name for the server-side writer
962
+ // (invalidateClientCache) and for shipping to the client in metadata.
963
+ resolvedStateCookieName,
964
+
935
965
  // Expose warmup enabled flag for handler and client
936
966
  warmupEnabled,
937
967
 
@@ -999,6 +1029,13 @@ export function createRouter<TEnv = any>(
999
1029
  // Expose basename for runtime manifest generation
1000
1030
  __basename: basename,
1001
1031
 
1032
+ // Expose router-level boundary defaults for build-time clientChunks
1033
+ // discovery (so a "use client" default boundary lands in app-fallback).
1034
+ // These are createRouter options, never pushed onto EntryData.
1035
+ __defaultErrorBoundary: defaultErrorBoundary,
1036
+ __defaultNotFoundBoundary: defaultNotFoundBoundary,
1037
+ __notFound: notFound,
1038
+
1002
1039
  // RSC request handler (lazily created on first call)
1003
1040
  fetch: (() => {
1004
1041
  // Handler is created on first call and reused
@@ -1016,8 +1053,10 @@ export function createRouter<TEnv = any>(
1016
1053
  if (!handler) {
1017
1054
  // Lazy import deferred to first request to avoid dev mode issues
1018
1055
  const { createRSCHandler } = await import("./rsc/handler.js");
1019
- // Cast: handler.ts still accepts (request, env) will be updated
1020
- // separately to accept RouterRequestInput.
1056
+ // Cast: createRSCHandler receives `router as any`, which erases TEnv
1057
+ // and infers its handler as RouterRequestInput<unknown>. Re-narrow the
1058
+ // returned handler to RouterRequestInput<TEnv> so the call below stays
1059
+ // typed. (The handler already accepts (request, RouterRequestInput).)
1021
1060
  handler = createRSCHandler({
1022
1061
  router: router as any,
1023
1062
  cache,
@@ -1045,9 +1084,9 @@ export function createRouter<TEnv = any>(
1045
1084
 
1046
1085
  // If urls option was provided, auto-register them
1047
1086
  if (typeof urlsOption === "function") {
1048
- return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1087
+ return router.routes(urlsOption) as Rango<TEnv, {}>;
1049
1088
  } else if (urlsOption) {
1050
- return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1089
+ return router.routes(urlsOption) as Rango<TEnv, {}>;
1051
1090
  }
1052
1091
 
1053
1092
  return router;
@@ -6,14 +6,14 @@
6
6
  * RSC rendering) so they can be standalone modules without closure coupling.
7
7
  */
8
8
 
9
- import type { RSCRouterInternal } from "../router/router-interfaces.js";
9
+ import type { RangoInternal } from "../router/router-interfaces.js";
10
10
  import type { ErrorPhase } from "../types.js";
11
11
  import type { InvokeOnErrorContext } from "../router/error-handling.js";
12
12
  import type { RSCDependencies, LoadSSRModule } from "./types.js";
13
13
  import type { SSRStreamMode } from "../router/router-options.js";
14
14
 
15
15
  export interface HandlerContext<TEnv = unknown> {
16
- router: RSCRouterInternal<TEnv, any>;
16
+ router: RangoInternal<TEnv, any>;
17
17
  version: string;
18
18
  renderToReadableStream: RSCDependencies["renderToReadableStream"];
19
19
  decodeReply: RSCDependencies["decodeReply"];
@@ -31,6 +31,7 @@ export interface HandlerContext<TEnv = unknown> {
31
31
  createRedirectFlightResponse: (
32
32
  redirectUrl: string,
33
33
  locationState?: Record<string, unknown>,
34
+ external?: boolean,
34
35
  ) => Response;
35
36
 
36
37
  /**