@rangojs/router 0.0.0-experimental.0f44aca1

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 (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  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 +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
package/src/reverse.ts ADDED
@@ -0,0 +1,330 @@
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
+ result = result.replace(
309
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
310
+ (_, key) => {
311
+ const value = params[key];
312
+ if (value === undefined) {
313
+ throw new Error(`Missing param "${key}" for route "${name}"`);
314
+ }
315
+ return encodeURIComponent(value);
316
+ },
317
+ );
318
+ }
319
+
320
+ // Append search params as query string
321
+ if (search) {
322
+ const qs = serializeSearchParams(search);
323
+ if (qs) {
324
+ result += `?${qs}`;
325
+ }
326
+ }
327
+
328
+ return result;
329
+ }) as ReverseFunction<TRoutes>;
330
+ }
@@ -0,0 +1,289 @@
1
+ "use client";
2
+
3
+ import { Component, useState, type ReactNode } from "react";
4
+ import type { ClientErrorBoundaryFallbackProps } from "./types.js";
5
+
6
+ /**
7
+ * Check if an error is a network-related error
8
+ */
9
+ function isNetworkError(error: Error): boolean {
10
+ return error.name === "NetworkError";
11
+ }
12
+
13
+ /**
14
+ * Network error fallback UI with retry functionality
15
+ * Shows a connection-specific message and allows retrying via page refresh
16
+ */
17
+ function NetworkErrorFallback({
18
+ error,
19
+ reset,
20
+ }: ClientErrorBoundaryFallbackProps): ReactNode {
21
+ const [isRetrying, setIsRetrying] = useState(false);
22
+
23
+ const handleRetry = (): void => {
24
+ setIsRetrying(true);
25
+ // Refresh the page to retry the request
26
+ window.location.reload();
27
+ };
28
+
29
+ return (
30
+ <div
31
+ style={{
32
+ fontFamily: "system-ui, -apple-system, sans-serif",
33
+ padding: "2rem",
34
+ maxWidth: "600px",
35
+ margin: "2rem auto",
36
+ textAlign: "center",
37
+ }}
38
+ >
39
+ <div
40
+ style={{
41
+ fontSize: "3rem",
42
+ marginBottom: "1rem",
43
+ }}
44
+ >
45
+ {/* Simple cloud with x icon using CSS */}
46
+ <span style={{ color: "#9ca3af" }}>&#9729;</span>
47
+ </div>
48
+ <h1
49
+ style={{
50
+ color: "#374151",
51
+ fontSize: "1.5rem",
52
+ marginBottom: "0.5rem",
53
+ }}
54
+ >
55
+ Connection Error
56
+ </h1>
57
+ <p
58
+ style={{
59
+ color: "#6b7280",
60
+ marginBottom: "1.5rem",
61
+ }}
62
+ >
63
+ {error.message ||
64
+ "Unable to connect to the server. Please check your internet connection."}
65
+ </p>
66
+ <div style={{ display: "flex", gap: "1rem", justifyContent: "center" }}>
67
+ <button
68
+ type="button"
69
+ onClick={handleRetry}
70
+ disabled={isRetrying}
71
+ style={{
72
+ padding: "0.75rem 1.5rem",
73
+ backgroundColor: isRetrying ? "#9ca3af" : "#2563eb",
74
+ color: "white",
75
+ border: "none",
76
+ borderRadius: "0.375rem",
77
+ cursor: isRetrying ? "not-allowed" : "pointer",
78
+ fontSize: "1rem",
79
+ fontWeight: 500,
80
+ }}
81
+ >
82
+ {isRetrying ? "Retrying..." : "Retry"}
83
+ </button>
84
+ <button
85
+ type="button"
86
+ onClick={() => window.history.back()}
87
+ style={{
88
+ padding: "0.75rem 1.5rem",
89
+ backgroundColor: "transparent",
90
+ color: "#6b7280",
91
+ border: "1px solid #d1d5db",
92
+ borderRadius: "0.375rem",
93
+ cursor: "pointer",
94
+ fontSize: "1rem",
95
+ }}
96
+ >
97
+ Go Back
98
+ </button>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Default fallback UI for root error boundary
106
+ * This is shown when an unhandled error bubbles up to the root
107
+ */
108
+ function RootErrorFallback({
109
+ error,
110
+ reset,
111
+ }: ClientErrorBoundaryFallbackProps): ReactNode {
112
+ const isDev = process.env.NODE_ENV !== "production";
113
+
114
+ return (
115
+ <div
116
+ style={{
117
+ fontFamily: "system-ui, -apple-system, sans-serif",
118
+ padding: "2rem",
119
+ maxWidth: "600px",
120
+ margin: "2rem auto",
121
+ }}
122
+ >
123
+ <h1
124
+ style={{
125
+ color: "#dc2626",
126
+ fontSize: "1.5rem",
127
+ marginBottom: "1rem",
128
+ }}
129
+ >
130
+ Internal Server Error
131
+ </h1>
132
+ <p
133
+ style={{
134
+ color: "#374151",
135
+ marginBottom: "1rem",
136
+ }}
137
+ >
138
+ An unexpected error occurred while processing your request.
139
+ </p>
140
+ {isDev && (
141
+ <div
142
+ style={{
143
+ background: "#fef2f2",
144
+ border: "1px solid #fecaca",
145
+ borderRadius: "0.5rem",
146
+ padding: "1rem",
147
+ marginBottom: "1rem",
148
+ }}
149
+ >
150
+ <p
151
+ style={{
152
+ fontWeight: 600,
153
+ color: "#991b1b",
154
+ marginBottom: "0.5rem",
155
+ }}
156
+ >
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
+ )}
174
+ <div style={{ display: "flex", gap: "1rem" }}>
175
+ <button
176
+ type="button"
177
+ onClick={reset}
178
+ style={{
179
+ padding: "0.5rem 1rem",
180
+ backgroundColor: "#2563eb",
181
+ color: "white",
182
+ border: "none",
183
+ borderRadius: "0.25rem",
184
+ cursor: "pointer",
185
+ }}
186
+ >
187
+ Try Again
188
+ </button>
189
+ <a
190
+ href="/"
191
+ style={{
192
+ display: "inline-block",
193
+ padding: "0.5rem 1rem",
194
+ color: "#2563eb",
195
+ textDecoration: "underline",
196
+ }}
197
+ >
198
+ Go to homepage
199
+ </a>
200
+ </div>
201
+ </div>
202
+ );
203
+ }
204
+
205
+ interface RootErrorBoundaryState {
206
+ hasError: boolean;
207
+ error: Error | null;
208
+ }
209
+
210
+ /**
211
+ * Root error boundary component
212
+ *
213
+ * Wraps the entire segment tree to catch any unhandled errors that bubble up.
214
+ * This prevents the entire app from crashing with a white screen.
215
+ *
216
+ * This is a client component with an inline fallback to avoid the
217
+ * "Functions cannot be passed to Client Components" RSC error.
218
+ */
219
+ export class RootErrorBoundary extends Component<
220
+ { children: ReactNode },
221
+ RootErrorBoundaryState
222
+ > {
223
+ constructor(props: { children: ReactNode }) {
224
+ super(props);
225
+ this.state = { hasError: false, error: null };
226
+ }
227
+
228
+ static getDerivedStateFromError(error: Error): RootErrorBoundaryState {
229
+ return { hasError: true, error };
230
+ }
231
+
232
+ componentDidMount(): void {
233
+ // Listen for popstate (back/forward navigation) to reset error state
234
+ window.addEventListener("popstate", this.handlePopState);
235
+ }
236
+
237
+ componentWillUnmount(): void {
238
+ window.removeEventListener("popstate", this.handlePopState);
239
+ }
240
+
241
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
242
+ console.error(
243
+ "[RootErrorBoundary] Unhandled error caught:",
244
+ error,
245
+ errorInfo,
246
+ );
247
+ }
248
+
249
+ componentDidUpdate(prevProps: { children: ReactNode }): void {
250
+ // Reset error state when children change (e.g., navigation)
251
+ // This allows the app to recover after navigation away from an errored route
252
+ if (this.state.hasError && prevProps.children !== this.props.children) {
253
+ this.setState({ hasError: false, error: null });
254
+ }
255
+ }
256
+
257
+ handlePopState = (): void => {
258
+ // Reset error state on back/forward navigation
259
+ if (this.state.hasError) {
260
+ this.setState({ hasError: false, error: null });
261
+ }
262
+ };
263
+
264
+ reset = (): void => {
265
+ this.setState({ hasError: false, error: null });
266
+ };
267
+
268
+ render(): ReactNode {
269
+ if (this.state.hasError && this.state.error) {
270
+ const errorInfo = {
271
+ message: this.state.error.message,
272
+ name: this.state.error.name,
273
+ stack: this.state.error.stack,
274
+ cause: this.state.error.cause,
275
+ segmentId: "root",
276
+ segmentType: "route" as const,
277
+ };
278
+
279
+ // Use specialized fallback for network errors
280
+ if (isNetworkError(this.state.error)) {
281
+ return <NetworkErrorFallback error={errorInfo} reset={this.reset} />;
282
+ }
283
+
284
+ return <RootErrorFallback error={errorInfo} reset={this.reset} />;
285
+ }
286
+
287
+ return this.props.children;
288
+ }
289
+ }