@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80

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 (312) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4960 -935
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/handler-use/SKILL.md +362 -0
  14. package/skills/hooks/SKILL.md +334 -72
  15. package/skills/host-router/SKILL.md +218 -0
  16. package/skills/intercept/SKILL.md +151 -8
  17. package/skills/layout/SKILL.md +122 -3
  18. package/skills/links/SKILL.md +92 -31
  19. package/skills/loader/SKILL.md +404 -44
  20. package/skills/middleware/SKILL.md +205 -37
  21. package/skills/migrate-nextjs/SKILL.md +560 -0
  22. package/skills/migrate-react-router/SKILL.md +764 -0
  23. package/skills/mime-routes/SKILL.md +128 -0
  24. package/skills/parallel/SKILL.md +263 -1
  25. package/skills/prerender/SKILL.md +685 -0
  26. package/skills/rango/SKILL.md +87 -16
  27. package/skills/response-routes/SKILL.md +411 -0
  28. package/skills/route/SKILL.md +281 -14
  29. package/skills/router-setup/SKILL.md +210 -32
  30. package/skills/tailwind/SKILL.md +129 -0
  31. package/skills/theme/SKILL.md +9 -8
  32. package/skills/typesafety/SKILL.md +328 -89
  33. package/skills/use-cache/SKILL.md +324 -0
  34. package/src/__internal.ts +102 -4
  35. package/src/bin/rango.ts +321 -0
  36. package/src/browser/action-coordinator.ts +97 -0
  37. package/src/browser/action-response-classifier.ts +99 -0
  38. package/src/browser/app-version.ts +14 -0
  39. package/src/browser/event-controller.ts +92 -64
  40. package/src/browser/history-state.ts +80 -0
  41. package/src/browser/intercept-utils.ts +52 -0
  42. package/src/browser/link-interceptor.ts +24 -4
  43. package/src/browser/logging.ts +55 -0
  44. package/src/browser/merge-segment-loaders.ts +20 -12
  45. package/src/browser/navigation-bridge.ts +317 -560
  46. package/src/browser/navigation-client.ts +206 -68
  47. package/src/browser/navigation-store.ts +73 -55
  48. package/src/browser/navigation-transaction.ts +297 -0
  49. package/src/browser/network-error-handler.ts +61 -0
  50. package/src/browser/partial-update.ts +343 -316
  51. package/src/browser/prefetch/cache.ts +216 -0
  52. package/src/browser/prefetch/fetch.ts +206 -0
  53. package/src/browser/prefetch/observer.ts +65 -0
  54. package/src/browser/prefetch/policy.ts +48 -0
  55. package/src/browser/prefetch/queue.ts +160 -0
  56. package/src/browser/prefetch/resource-ready.ts +77 -0
  57. package/src/browser/rango-state.ts +112 -0
  58. package/src/browser/react/Link.tsx +253 -74
  59. package/src/browser/react/NavigationProvider.tsx +87 -11
  60. package/src/browser/react/context.ts +11 -0
  61. package/src/browser/react/filter-segment-order.ts +11 -0
  62. package/src/browser/react/index.ts +12 -12
  63. package/src/browser/react/location-state-shared.ts +95 -53
  64. package/src/browser/react/location-state.ts +60 -15
  65. package/src/browser/react/mount-context.ts +6 -1
  66. package/src/browser/react/nonce-context.ts +23 -0
  67. package/src/browser/react/shallow-equal.ts +27 -0
  68. package/src/browser/react/use-action.ts +29 -51
  69. package/src/browser/react/use-client-cache.ts +5 -3
  70. package/src/browser/react/use-handle.ts +30 -126
  71. package/src/browser/react/use-href.tsx +2 -2
  72. package/src/browser/react/use-link-status.ts +6 -5
  73. package/src/browser/react/use-navigation.ts +44 -65
  74. package/src/browser/react/use-params.ts +65 -0
  75. package/src/browser/react/use-pathname.ts +47 -0
  76. package/src/browser/react/use-router.ts +76 -0
  77. package/src/browser/react/use-search-params.ts +56 -0
  78. package/src/browser/react/use-segments.ts +80 -97
  79. package/src/browser/response-adapter.ts +73 -0
  80. package/src/browser/rsc-router.tsx +214 -58
  81. package/src/browser/scroll-restoration.ts +127 -52
  82. package/src/browser/segment-reconciler.ts +243 -0
  83. package/src/browser/segment-structure-assert.ts +16 -0
  84. package/src/browser/server-action-bridge.ts +510 -603
  85. package/src/browser/shallow.ts +6 -1
  86. package/src/browser/types.ts +141 -48
  87. package/src/browser/validate-redirect-origin.ts +29 -0
  88. package/src/build/generate-manifest.ts +235 -24
  89. package/src/build/generate-route-types.ts +39 -0
  90. package/src/build/index.ts +13 -0
  91. package/src/build/route-trie.ts +291 -0
  92. package/src/build/route-types/ast-helpers.ts +25 -0
  93. package/src/build/route-types/ast-route-extraction.ts +98 -0
  94. package/src/build/route-types/codegen.ts +102 -0
  95. package/src/build/route-types/include-resolution.ts +418 -0
  96. package/src/build/route-types/param-extraction.ts +48 -0
  97. package/src/build/route-types/per-module-writer.ts +128 -0
  98. package/src/build/route-types/router-processing.ts +618 -0
  99. package/src/build/route-types/scan-filter.ts +85 -0
  100. package/src/build/runtime-discovery.ts +231 -0
  101. package/src/cache/background-task.ts +34 -0
  102. package/src/cache/cache-key-utils.ts +44 -0
  103. package/src/cache/cache-policy.ts +125 -0
  104. package/src/cache/cache-runtime.ts +342 -0
  105. package/src/cache/cache-scope.ts +167 -309
  106. package/src/cache/cf/cf-cache-store.ts +571 -17
  107. package/src/cache/cf/index.ts +13 -3
  108. package/src/cache/document-cache.ts +116 -77
  109. package/src/cache/handle-capture.ts +81 -0
  110. package/src/cache/handle-snapshot.ts +41 -0
  111. package/src/cache/index.ts +1 -15
  112. package/src/cache/memory-segment-store.ts +191 -13
  113. package/src/cache/profile-registry.ts +73 -0
  114. package/src/cache/read-through-swr.ts +134 -0
  115. package/src/cache/segment-codec.ts +256 -0
  116. package/src/cache/taint.ts +153 -0
  117. package/src/cache/types.ts +72 -122
  118. package/src/client.rsc.tsx +3 -1
  119. package/src/client.tsx +135 -301
  120. package/src/component-utils.ts +4 -4
  121. package/src/components/DefaultDocument.tsx +5 -1
  122. package/src/context-var.ts +156 -0
  123. package/src/debug.ts +19 -9
  124. package/src/errors.ts +108 -2
  125. package/src/handle.ts +55 -29
  126. package/src/handles/MetaTags.tsx +73 -20
  127. package/src/handles/breadcrumbs.ts +66 -0
  128. package/src/handles/index.ts +1 -0
  129. package/src/handles/meta.ts +30 -13
  130. package/src/host/cookie-handler.ts +21 -15
  131. package/src/host/errors.ts +8 -8
  132. package/src/host/index.ts +4 -7
  133. package/src/host/pattern-matcher.ts +27 -27
  134. package/src/host/router.ts +61 -39
  135. package/src/host/testing.ts +8 -8
  136. package/src/host/types.ts +15 -7
  137. package/src/host/utils.ts +1 -1
  138. package/src/href-client.ts +119 -29
  139. package/src/index.rsc.ts +155 -19
  140. package/src/index.ts +251 -30
  141. package/src/internal-debug.ts +11 -0
  142. package/src/loader.rsc.ts +26 -157
  143. package/src/loader.ts +27 -10
  144. package/src/network-error-thrower.tsx +3 -1
  145. package/src/outlet-provider.tsx +45 -0
  146. package/src/prerender/param-hash.ts +37 -0
  147. package/src/prerender/store.ts +186 -0
  148. package/src/prerender.ts +524 -0
  149. package/src/reverse.ts +354 -0
  150. package/src/root-error-boundary.tsx +41 -29
  151. package/src/route-content-wrapper.tsx +7 -4
  152. package/src/route-definition/dsl-helpers.ts +1121 -0
  153. package/src/route-definition/helper-factories.ts +200 -0
  154. package/src/route-definition/helpers-types.ts +478 -0
  155. package/src/route-definition/index.ts +55 -0
  156. package/src/route-definition/redirect.ts +101 -0
  157. package/src/route-definition/resolve-handler-use.ts +149 -0
  158. package/src/route-definition.ts +1 -1428
  159. package/src/route-map-builder.ts +217 -123
  160. package/src/route-name.ts +53 -0
  161. package/src/route-types.ts +77 -8
  162. package/src/router/content-negotiation.ts +215 -0
  163. package/src/router/debug-manifest.ts +72 -0
  164. package/src/router/error-handling.ts +9 -9
  165. package/src/router/find-match.ts +160 -0
  166. package/src/router/handler-context.ts +438 -86
  167. package/src/router/intercept-resolution.ts +402 -0
  168. package/src/router/lazy-includes.ts +237 -0
  169. package/src/router/loader-resolution.ts +356 -128
  170. package/src/router/logging.ts +251 -0
  171. package/src/router/manifest.ts +163 -35
  172. package/src/router/match-api.ts +555 -0
  173. package/src/router/match-context.ts +5 -3
  174. package/src/router/match-handlers.ts +440 -0
  175. package/src/router/match-middleware/background-revalidation.ts +108 -93
  176. package/src/router/match-middleware/cache-lookup.ts +460 -10
  177. package/src/router/match-middleware/cache-store.ts +98 -26
  178. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  179. package/src/router/match-middleware/segment-resolution.ts +80 -6
  180. package/src/router/match-pipelines.ts +10 -45
  181. package/src/router/match-result.ts +135 -35
  182. package/src/router/metrics.ts +240 -15
  183. package/src/router/middleware-cookies.ts +55 -0
  184. package/src/router/middleware-types.ts +220 -0
  185. package/src/router/middleware.ts +324 -369
  186. package/src/router/navigation-snapshot.ts +182 -0
  187. package/src/router/pattern-matching.ts +211 -43
  188. package/src/router/prerender-match.ts +502 -0
  189. package/src/router/preview-match.ts +98 -0
  190. package/src/router/request-classification.ts +310 -0
  191. package/src/router/revalidation.ts +137 -38
  192. package/src/router/route-snapshot.ts +245 -0
  193. package/src/router/router-context.ts +41 -21
  194. package/src/router/router-interfaces.ts +484 -0
  195. package/src/router/router-options.ts +618 -0
  196. package/src/router/router-registry.ts +24 -0
  197. package/src/router/segment-resolution/fresh.ts +748 -0
  198. package/src/router/segment-resolution/helpers.ts +268 -0
  199. package/src/router/segment-resolution/loader-cache.ts +199 -0
  200. package/src/router/segment-resolution/revalidation.ts +1379 -0
  201. package/src/router/segment-resolution/static-store.ts +67 -0
  202. package/src/router/segment-resolution.ts +21 -0
  203. package/src/router/segment-wrappers.ts +291 -0
  204. package/src/router/telemetry-otel.ts +299 -0
  205. package/src/router/telemetry.ts +300 -0
  206. package/src/router/timeout.ts +148 -0
  207. package/src/router/trie-matching.ts +239 -0
  208. package/src/router/types.ts +78 -3
  209. package/src/router.ts +740 -4252
  210. package/src/rsc/handler-context.ts +45 -0
  211. package/src/rsc/handler.ts +907 -797
  212. package/src/rsc/helpers.ts +140 -6
  213. package/src/rsc/index.ts +0 -20
  214. package/src/rsc/loader-fetch.ts +229 -0
  215. package/src/rsc/manifest-init.ts +90 -0
  216. package/src/rsc/nonce.ts +14 -0
  217. package/src/rsc/origin-guard.ts +141 -0
  218. package/src/rsc/progressive-enhancement.ts +391 -0
  219. package/src/rsc/response-error.ts +37 -0
  220. package/src/rsc/response-route-handler.ts +347 -0
  221. package/src/rsc/rsc-rendering.ts +246 -0
  222. package/src/rsc/runtime-warnings.ts +42 -0
  223. package/src/rsc/server-action.ts +356 -0
  224. package/src/rsc/ssr-setup.ts +128 -0
  225. package/src/rsc/types.ts +46 -11
  226. package/src/search-params.ts +230 -0
  227. package/src/segment-content-promise.ts +67 -0
  228. package/src/segment-loader-promise.ts +122 -0
  229. package/src/segment-system.tsx +134 -36
  230. package/src/server/context.ts +341 -61
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +113 -15
  234. package/src/server/loader-registry.ts +24 -64
  235. package/src/server/request-context.ts +607 -81
  236. package/src/server.ts +35 -130
  237. package/src/ssr/index.tsx +103 -30
  238. package/src/static-handler.ts +126 -0
  239. package/src/theme/ThemeProvider.tsx +21 -15
  240. package/src/theme/ThemeScript.tsx +5 -5
  241. package/src/theme/constants.ts +5 -2
  242. package/src/theme/index.ts +4 -14
  243. package/src/theme/theme-context.ts +4 -30
  244. package/src/theme/theme-script.ts +21 -18
  245. package/src/types/boundaries.ts +158 -0
  246. package/src/types/cache-types.ts +198 -0
  247. package/src/types/error-types.ts +192 -0
  248. package/src/types/global-namespace.ts +100 -0
  249. package/src/types/handler-context.ts +791 -0
  250. package/src/types/index.ts +88 -0
  251. package/src/types/loader-types.ts +210 -0
  252. package/src/types/route-config.ts +170 -0
  253. package/src/types/route-entry.ts +120 -0
  254. package/src/types/segments.ts +150 -0
  255. package/src/types.ts +1 -1623
  256. package/src/urls/include-helper.ts +207 -0
  257. package/src/urls/index.ts +53 -0
  258. package/src/urls/path-helper-types.ts +372 -0
  259. package/src/urls/path-helper.ts +364 -0
  260. package/src/urls/pattern-types.ts +107 -0
  261. package/src/urls/response-types.ts +116 -0
  262. package/src/urls/type-extraction.ts +372 -0
  263. package/src/urls/urls-function.ts +98 -0
  264. package/src/urls.ts +1 -802
  265. package/src/use-loader.tsx +161 -81
  266. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  267. package/src/vite/discovery/discover-routers.ts +348 -0
  268. package/src/vite/discovery/prerender-collection.ts +439 -0
  269. package/src/vite/discovery/route-types-writer.ts +258 -0
  270. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  271. package/src/vite/discovery/state.ts +117 -0
  272. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  273. package/src/vite/index.ts +15 -1133
  274. package/src/vite/plugin-types.ts +103 -0
  275. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  276. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  277. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  278. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  279. package/src/vite/plugins/expose-id-utils.ts +299 -0
  280. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  281. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  282. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  283. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  284. package/src/vite/plugins/expose-ids/types.ts +45 -0
  285. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  286. package/src/vite/plugins/performance-tracks.ts +88 -0
  287. package/src/vite/plugins/refresh-cmd.ts +127 -0
  288. package/src/vite/plugins/use-cache-transform.ts +323 -0
  289. package/src/vite/plugins/version-injector.ts +83 -0
  290. package/src/vite/plugins/version-plugin.ts +266 -0
  291. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  292. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  293. package/src/vite/rango.ts +462 -0
  294. package/src/vite/router-discovery.ts +918 -0
  295. package/src/vite/utils/ast-handler-extract.ts +517 -0
  296. package/src/vite/utils/banner.ts +36 -0
  297. package/src/vite/utils/bundle-analysis.ts +137 -0
  298. package/src/vite/utils/manifest-utils.ts +70 -0
  299. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  300. package/src/vite/utils/prerender-utils.ts +221 -0
  301. package/src/vite/utils/shared-utils.ts +170 -0
  302. package/CLAUDE.md +0 -43
  303. package/src/browser/lru-cache.ts +0 -69
  304. package/src/browser/request-controller.ts +0 -164
  305. package/src/cache/memory-store.ts +0 -253
  306. package/src/href-context.ts +0 -33
  307. package/src/href.ts +0 -255
  308. package/src/server/route-manifest-cache.ts +0 -173
  309. package/src/vite/expose-handle-id.ts +0 -209
  310. package/src/vite/expose-loader-id.ts +0 -426
  311. package/src/vite/expose-location-state-id.ts +0 -177
  312. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/reverse.ts ADDED
@@ -0,0 +1,354 @@
1
+ import type { ExtractParams } from "./types.js";
2
+ import type { SearchSchema, ResolveSearchSchema } from "./search-params.js";
3
+ import { serializeSearchParams } from "./search-params.js";
4
+
5
+ /**
6
+ * Sanitize prefix string by removing leading slash
7
+ * "/shop" -> "shop", "blog" -> "blog", "" -> ""
8
+ */
9
+ export type SanitizePrefix<T extends string> = T extends `/${infer P}` ? P : T;
10
+
11
+ /**
12
+ * Helper type to merge multiple route definitions into a single accumulated type.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * type AppRoutes = MergeRoutes<[typeof siteRoutes, typeof apiRoutes]>;
17
+ * ```
18
+ */
19
+ export type MergeRoutes<T extends unknown[]> = T extends [
20
+ infer First,
21
+ ...infer Rest,
22
+ ]
23
+ ? First & MergeRoutes<Rest>
24
+ : {};
25
+
26
+ /**
27
+ * Helper to safely extract route patterns from a routes object
28
+ * Handles string values, { path, response } objects, and interface types (like RegisteredRoutes)
29
+ */
30
+ type RoutePatternFor<
31
+ TRoutes,
32
+ TName extends keyof TRoutes,
33
+ > = TRoutes[TName] extends string
34
+ ? TRoutes[TName]
35
+ : TRoutes[TName] extends { readonly path: infer P extends string }
36
+ ? P
37
+ : string;
38
+
39
+ /**
40
+ * Extract params type for a route
41
+ */
42
+ export type ParamsFor<TRoutes, TName extends keyof TRoutes> = ExtractParams<
43
+ RoutePatternFor<TRoutes, TName>
44
+ >;
45
+
46
+ /**
47
+ * Check if an object type has any keys
48
+ */
49
+ type IsEmptyObject<T> = keyof T extends never ? true : false;
50
+
51
+ /**
52
+ * Extract search schema from a route entry.
53
+ * Returns {} if no search schema is defined.
54
+ */
55
+ type ExtractSearchSchema<
56
+ TRoutes,
57
+ TName extends keyof TRoutes,
58
+ > = TRoutes[TName] extends { readonly search: infer S extends SearchSchema }
59
+ ? S
60
+ : {};
61
+
62
+ /**
63
+ * Type-safe reverse function signature (Django-style URL reversal)
64
+ *
65
+ * Validates route names and params at compile time.
66
+ * Use route names instead of raw paths for full type safety.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * reverse("cart") // ✓ Validates route exists
71
+ * reverse("product.detail", { id: "123" }) // ✓ Validates route + params
72
+ * ```
73
+ */
74
+ export type ReverseFunction<TRoutes> = {
75
+ /**
76
+ * Route without params - validates route name exists
77
+ */
78
+ <TName extends keyof TRoutes & string>(
79
+ name: IsEmptyObject<
80
+ ExtractParams<RoutePatternFor<TRoutes, TName>>
81
+ > extends true
82
+ ? TName
83
+ : never,
84
+ ): string;
85
+
86
+ /**
87
+ * Route with params - validates both route name and params
88
+ */
89
+ <TName extends keyof TRoutes & string>(
90
+ name: TName,
91
+ params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
92
+ ): string;
93
+
94
+ /**
95
+ * Route with params and search - validates route name, params, and search
96
+ */
97
+ <TName extends keyof TRoutes & string>(
98
+ name: TName,
99
+ params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
100
+ search: ResolveSearchSchema<ExtractSearchSchema<TRoutes, TName>>,
101
+ ): string;
102
+
103
+ /**
104
+ * Dot-prefixed route without params - strictly local resolution
105
+ */
106
+ <TName extends keyof TRoutes & string>(
107
+ name: IsEmptyObject<
108
+ ExtractParams<RoutePatternFor<TRoutes, TName>>
109
+ > extends true
110
+ ? `.${TName}`
111
+ : never,
112
+ ): string;
113
+
114
+ /**
115
+ * Dot-prefixed route with params - strictly local resolution
116
+ */
117
+ <TName extends keyof TRoutes & string>(
118
+ name: `.${TName}`,
119
+ params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
120
+ ): string;
121
+
122
+ /**
123
+ * Dot-prefixed route with params and search - strictly local resolution
124
+ */
125
+ <TName extends keyof TRoutes & string>(
126
+ name: `.${TName}`,
127
+ params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
128
+ search: ResolveSearchSchema<ExtractSearchSchema<TRoutes, TName>>,
129
+ ): string;
130
+ };
131
+
132
+ /**
133
+ * Type-safe scoped reverse function with separate local and global namespaces.
134
+ *
135
+ * - `.name` — local resolution within the current include() scope
136
+ * - `name` — global resolution against the named-routes definition
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * reverse(".article", { slug: "hello" }) // ✓ Local route (resolves with mount prefix)
141
+ * reverse(".index") // ✓ Local route (no params)
142
+ * reverse("magazine.index") // ✓ Global route (fully qualified)
143
+ * reverse("blog.post", { slug: "hello" }) // ✓ Global route + params
144
+ * reverse(".typo") // ✗ Compile error (not in local routes)
145
+ * reverse("typo") // ✗ Compile error (not in global routes)
146
+ * ```
147
+ */
148
+ export type ScopedReverseFunction<
149
+ TLocalRoutes,
150
+ TGlobalRoutes = TLocalRoutes,
151
+ > = {
152
+ /**
153
+ * Global route without params
154
+ */
155
+ <TName extends keyof TGlobalRoutes & string>(
156
+ name: IsEmptyObject<
157
+ ExtractParams<RoutePatternFor<TGlobalRoutes, TName>>
158
+ > extends true
159
+ ? TName
160
+ : never,
161
+ ): string;
162
+
163
+ /**
164
+ * Global route with params
165
+ */
166
+ <TName extends keyof TGlobalRoutes & string>(
167
+ name: TName,
168
+ params: ExtractParams<RoutePatternFor<TGlobalRoutes, TName>>,
169
+ ): string;
170
+
171
+ /**
172
+ * Global route with params and search
173
+ */
174
+ <TName extends keyof TGlobalRoutes & string>(
175
+ name: TName,
176
+ params: ExtractParams<RoutePatternFor<TGlobalRoutes, TName>>,
177
+ search: ResolveSearchSchema<ExtractSearchSchema<TGlobalRoutes, TName>>,
178
+ ): string;
179
+
180
+ /**
181
+ * Dot-prefixed local route without params
182
+ */
183
+ <TName extends keyof TLocalRoutes & string>(
184
+ name: IsEmptyObject<
185
+ ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
186
+ > extends true
187
+ ? `.${TName}`
188
+ : never,
189
+ ): string;
190
+
191
+ /**
192
+ * Dot-prefixed local route with params
193
+ */
194
+ <TName extends keyof TLocalRoutes & string>(
195
+ name: `.${TName}`,
196
+ params: ExtractParams<RoutePatternFor<TLocalRoutes, TName>>,
197
+ ): string;
198
+
199
+ /**
200
+ * Dot-prefixed local route with params and search
201
+ */
202
+ <TName extends keyof TLocalRoutes & string>(
203
+ name: `.${TName}`,
204
+ params: ExtractParams<RoutePatternFor<TLocalRoutes, TName>>,
205
+ search: ResolveSearchSchema<ExtractSearchSchema<TLocalRoutes, TName>>,
206
+ ): string;
207
+ };
208
+
209
+ /**
210
+ * Extract local routes type from UrlPatterns
211
+ * Used with scopedReverse() to get the routes type from patterns
212
+ */
213
+ export type ExtractLocalRoutes<TPatterns> = TPatterns extends {
214
+ readonly _routes?: infer TRoutes;
215
+ }
216
+ ? TRoutes
217
+ : TPatterns extends Record<string, string>
218
+ ? TPatterns
219
+ : Record<string, string>;
220
+
221
+ /**
222
+ * Extract the response data type for a named route from a UrlPatterns instance.
223
+ * Re-exported from urls.ts for consumer convenience.
224
+ */
225
+ export type { RouteResponse } from "./urls.js";
226
+
227
+ /**
228
+ * Get a locally-typed reverse function from ctx.reverse for composable modules.
229
+ *
230
+ * This is a type-only cast - ctx.reverse already resolves names at runtime.
231
+ * Provides type safety: `.name` validates against local routes,
232
+ * `name` validates against global named-routes.
233
+ *
234
+ * @param reverse - The ctx.reverse function from HandlerContext
235
+ * @returns The same reverse function, typed with local + global routes
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * // urls/blog.tsx
240
+ * export const blogPatterns = urls(({ path }) => [
241
+ * path("/", (ctx) => {
242
+ * const reverse = scopedReverse<typeof blogPatterns>(ctx.reverse);
243
+ *
244
+ * reverse(".index"); // ✓ Local route
245
+ * reverse(".post", { slug: "x" }); // ✓ Local with params
246
+ * reverse("shop.cart"); // ✓ Global route
247
+ *
248
+ * return <BlogIndex />;
249
+ * }, { name: "index" }),
250
+ *
251
+ * path("/:slug", BlogPost, { name: "post" }),
252
+ * ]);
253
+ * ```
254
+ */
255
+ export function scopedReverse<TPatterns>(
256
+ reverse: (...args: any[]) => string,
257
+ ): ScopedReverseFunction<ExtractLocalRoutes<TPatterns>> {
258
+ return reverse as ScopedReverseFunction<ExtractLocalRoutes<TPatterns>>;
259
+ }
260
+
261
+ /**
262
+ * Create a type-safe reverse function for URL generation
263
+ *
264
+ * @param routeMap - Flattened route map with all registered routes
265
+ * @returns Type-safe reverse function
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * // Given routes: { cart: "/shop/cart", detail: "/shop/product/:slug" }
270
+ * const reverse = createReverse(routeMap);
271
+ * reverse("cart"); // "/shop/cart"
272
+ * reverse("detail", { slug: "my-product" }); // "/shop/product/my-product"
273
+ * ```
274
+ */
275
+ type RouteMapEntry = string | { path: string; search?: Record<string, string> };
276
+
277
+ function resolveRoutePattern(
278
+ entry: RouteMapEntry | undefined,
279
+ ): string | undefined {
280
+ if (!entry) return undefined;
281
+ return typeof entry === "string" ? entry : entry.path;
282
+ }
283
+
284
+ export function createReverse<TRoutes extends Record<string, string>>(
285
+ routeMap: TRoutes,
286
+ ): ReverseFunction<TRoutes & Record<string, string>> {
287
+ return ((
288
+ name: string,
289
+ params?: Record<string, string>,
290
+ search?: Record<string, unknown>,
291
+ ) => {
292
+ const pattern = resolveRoutePattern(
293
+ routeMap[name] as unknown as RouteMapEntry,
294
+ );
295
+ if (!pattern) {
296
+ // During build-time discovery, lazy includes haven't resolved yet.
297
+ // Return a placeholder instead of crashing the build.
298
+ if ((globalThis as any).__rscRouterDiscoveryActive) {
299
+ return `/__unresolved_reverse/${name}`;
300
+ }
301
+ throw new Error(`Unknown route: ${name}`);
302
+ }
303
+
304
+ let result = pattern;
305
+ if (params) {
306
+ // Replace :param placeholders with actual values
307
+ // Strip constraint syntax: :param(a|b) -> use "param" as key
308
+ // Optional params (:param?) are omitted when not provided
309
+ let hadOmittedOptional = false;
310
+ result = result.replace(
311
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
312
+ (_, key, _constraint, optional) => {
313
+ const value = params[key];
314
+ // Empty string is treated as omitted — the trie matcher fills
315
+ // unmatched optional params with "" (not undefined), so reverse
316
+ // must collapse those segments instead of leaving empty slots.
317
+ if (value === undefined || value === "") {
318
+ hadOmittedOptional = true;
319
+ return "";
320
+ }
321
+ return encodeURIComponent(value);
322
+ },
323
+ );
324
+ // Second pass: required params (no trailing ?)
325
+ result = result.replace(
326
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
327
+ (_, key) => {
328
+ const value = params[key];
329
+ if (value === undefined) {
330
+ throw new Error(`Missing param "${key}" for route "${name}"`);
331
+ }
332
+ return encodeURIComponent(value);
333
+ },
334
+ );
335
+ // Clean up slashes only when an optional param was actually omitted,
336
+ // so intentional trailing-slash patterns like "/blog/" are preserved.
337
+ if (hadOmittedOptional) {
338
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
339
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
340
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
341
+ }
342
+ }
343
+
344
+ // Append search params as query string
345
+ if (search) {
346
+ const qs = serializeSearchParams(search);
347
+ if (qs) {
348
+ result += `?${qs}`;
349
+ }
350
+ }
351
+
352
+ return result;
353
+ }) as ReverseFunction<TRoutes>;
354
+ }
@@ -60,7 +60,8 @@ function NetworkErrorFallback({
60
60
  marginBottom: "1.5rem",
61
61
  }}
62
62
  >
63
- {error.message || "Unable to connect to the server. Please check your internet connection."}
63
+ {error.message ||
64
+ "Unable to connect to the server. Please check your internet connection."}
64
65
  </p>
65
66
  <div style={{ display: "flex", gap: "1rem", justifyContent: "center" }}>
66
67
  <button
@@ -104,7 +105,12 @@ function NetworkErrorFallback({
104
105
  * Default fallback UI for root error boundary
105
106
  * This is shown when an unhandled error bubbles up to the root
106
107
  */
107
- function RootErrorFallback({ error, reset }: ClientErrorBoundaryFallbackProps): ReactNode {
108
+ function RootErrorFallback({
109
+ error,
110
+ reset,
111
+ }: ClientErrorBoundaryFallbackProps): ReactNode {
112
+ const isDev = process.env.NODE_ENV !== "production";
113
+
108
114
  return (
109
115
  <div
110
116
  style={{
@@ -131,38 +137,40 @@ function RootErrorFallback({ error, reset }: ClientErrorBoundaryFallbackProps):
131
137
  >
132
138
  An unexpected error occurred while processing your request.
133
139
  </p>
134
- <div
135
- style={{
136
- background: "#fef2f2",
137
- border: "1px solid #fecaca",
138
- borderRadius: "0.5rem",
139
- padding: "1rem",
140
- marginBottom: "1rem",
141
- }}
142
- >
143
- <p
140
+ {isDev && (
141
+ <div
144
142
  style={{
145
- fontWeight: 600,
146
- color: "#991b1b",
147
- marginBottom: "0.5rem",
143
+ background: "#fef2f2",
144
+ border: "1px solid #fecaca",
145
+ borderRadius: "0.5rem",
146
+ padding: "1rem",
147
+ marginBottom: "1rem",
148
148
  }}
149
149
  >
150
- {error.name}: {error.message}
151
- </p>
152
- {error.stack && (
153
- <pre
150
+ <p
154
151
  style={{
155
- fontSize: "0.75rem",
156
- color: "#6b7280",
157
- overflow: "auto",
158
- whiteSpace: "pre-wrap",
159
- wordBreak: "break-word",
152
+ fontWeight: 600,
153
+ color: "#991b1b",
154
+ marginBottom: "0.5rem",
160
155
  }}
161
156
  >
162
- {error.stack}
163
- </pre>
164
- )}
165
- </div>
157
+ {error.name}: {error.message}
158
+ </p>
159
+ {error.stack && (
160
+ <pre
161
+ style={{
162
+ fontSize: "0.75rem",
163
+ color: "#6b7280",
164
+ overflow: "auto",
165
+ whiteSpace: "pre-wrap",
166
+ wordBreak: "break-word",
167
+ }}
168
+ >
169
+ {error.stack}
170
+ </pre>
171
+ )}
172
+ </div>
173
+ )}
166
174
  <div style={{ display: "flex", gap: "1rem" }}>
167
175
  <button
168
176
  type="button"
@@ -231,7 +239,11 @@ export class RootErrorBoundary extends Component<
231
239
  }
232
240
 
233
241
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
234
- console.error("[RootErrorBoundary] Unhandled error caught:", error, errorInfo);
242
+ console.error(
243
+ "[RootErrorBoundary] Unhandled error caught:",
244
+ error,
245
+ errorInfo,
246
+ );
235
247
  }
236
248
 
237
249
  componentDidUpdate(prevProps: { children: ReactNode }): void {
@@ -2,7 +2,7 @@
2
2
  import type { ReactNode } from "react";
3
3
  import { Suspense, use, useId } from "react";
4
4
  import { invariant } from "./errors";
5
- import { OutletProvider } from "./client.js";
5
+ import { OutletProvider } from "./outlet-provider.js";
6
6
  import type { ResolvedSegment } from "./types.js";
7
7
  import { isLoaderDataResult } from "./types.js";
8
8
 
@@ -31,7 +31,10 @@ export function RouteContentWrapper({
31
31
  return content as ReactNode;
32
32
  }
33
33
  return (
34
- <Suspense fallback={fallback ?? null} key={segmentId ? "route-content-suspense-" + segmentId : undefined}>
34
+ <Suspense
35
+ fallback={fallback ?? null}
36
+ key={segmentId ? "route-content-suspense-" + segmentId : undefined}
37
+ >
35
38
  <Suspender content={content} key={segmentId} />
36
39
  </Suspense>
37
40
  );
@@ -50,11 +53,11 @@ export function RouteContentWrapperCallback<T>({
50
53
  invariant(children, "RouteContentWrapperCallback requires children");
51
54
  invariant(
52
55
  typeof children === "function",
53
- "RouteContentWrapperCallback requires children to be a function"
56
+ "RouteContentWrapperCallback requires children to be a function",
54
57
  );
55
58
  invariant(
56
59
  resolve !== undefined,
57
- "RouteContentWrapperCallback requires resolve"
60
+ "RouteContentWrapperCallback requires resolve",
58
61
  );
59
62
  return (
60
63
  <Suspense