@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30

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 (297) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +883 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4655 -747
  5. package/package.json +78 -50
  6. package/skills/cache-guide/SKILL.md +262 -0
  7. package/skills/caching/SKILL.md +54 -25
  8. package/skills/composability/SKILL.md +172 -0
  9. package/skills/debug-manifest/SKILL.md +12 -8
  10. package/skills/document-cache/SKILL.md +23 -21
  11. package/skills/fonts/SKILL.md +167 -0
  12. package/skills/hooks/SKILL.md +390 -63
  13. package/skills/host-router/SKILL.md +218 -0
  14. package/skills/intercept/SKILL.md +133 -10
  15. package/skills/layout/SKILL.md +102 -5
  16. package/skills/links/SKILL.md +239 -0
  17. package/skills/loader/SKILL.md +366 -29
  18. package/skills/middleware/SKILL.md +173 -36
  19. package/skills/mime-routes/SKILL.md +128 -0
  20. package/skills/parallel/SKILL.md +80 -3
  21. package/skills/prerender/SKILL.md +643 -0
  22. package/skills/rango/SKILL.md +86 -16
  23. package/skills/response-routes/SKILL.md +411 -0
  24. package/skills/route/SKILL.md +227 -14
  25. package/skills/router-setup/SKILL.md +225 -32
  26. package/skills/tailwind/SKILL.md +129 -0
  27. package/skills/theme/SKILL.md +12 -11
  28. package/skills/typesafety/SKILL.md +401 -75
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +10 -4
  31. package/src/bin/rango.ts +321 -0
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +20 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +201 -553
  41. package/src/browser/navigation-client.ts +124 -71
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +267 -317
  46. package/src/browser/prefetch/cache.ts +146 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +42 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +173 -73
  53. package/src/browser/react/NavigationProvider.tsx +138 -27
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +37 -0
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +49 -65
  65. package/src/browser/react/use-href.tsx +20 -188
  66. package/src/browser/react/use-link-status.ts +6 -5
  67. package/src/browser/react/use-mount.ts +31 -0
  68. package/src/browser/react/use-navigation.ts +27 -78
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +111 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +83 -0
  79. package/src/browser/server-action-bridge.ts +504 -584
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +92 -57
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +438 -0
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +35 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +10 -15
  114. package/src/client.tsx +114 -135
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +34 -19
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +165 -0
  124. package/src/host/errors.ts +97 -0
  125. package/src/host/index.ts +53 -0
  126. package/src/host/pattern-matcher.ts +214 -0
  127. package/src/host/router.ts +352 -0
  128. package/src/host/testing.ts +79 -0
  129. package/src/host/types.ts +146 -0
  130. package/src/host/utils.ts +25 -0
  131. package/src/href-client.ts +135 -49
  132. package/src/index.rsc.ts +182 -17
  133. package/src/index.ts +238 -24
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +27 -142
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +37 -0
  140. package/src/prerender/store.ts +185 -0
  141. package/src/prerender.ts +463 -0
  142. package/src/reverse.ts +330 -0
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +9 -11
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1388
  151. package/src/route-map-builder.ts +241 -112
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +70 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +371 -81
  159. package/src/router/intercept-resolution.ts +395 -0
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +155 -32
  164. package/src/router/match-api.ts +620 -0
  165. package/src/router/match-context.ts +5 -3
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +382 -9
  169. package/src/router/match-middleware/cache-store.ts +51 -22
  170. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  171. package/src/router/match-middleware/segment-resolution.ts +24 -6
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -29
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +321 -30
  179. package/src/router/prerender-match.ts +400 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1241 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -0
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +239 -0
  197. package/src/router/types.ts +77 -3
  198. package/src/router.ts +688 -3656
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +786 -760
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +5 -25
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +235 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +40 -14
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +57 -61
  217. package/src/server/context.ts +202 -51
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +422 -70
  223. package/src/server.ts +36 -120
  224. package/src/ssr/index.tsx +157 -26
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1577
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -726
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -782
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -3
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/href.ts +0 -255
  294. package/src/vite/expose-handle-id.ts +0 -209
  295. package/src/vite/expose-loader-id.ts +0 -357
  296. package/src/vite/expose-location-state-id.ts +0 -177
  297. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/client.tsx CHANGED
@@ -20,6 +20,8 @@ import {
20
20
  RouteContentWrapper,
21
21
  LoaderBoundary,
22
22
  } from "./route-content-wrapper.js";
23
+ import { OutletProvider } from "./outlet-provider.js";
24
+ import { MountContextProvider } from "./browser/react/mount-context.js";
23
25
 
24
26
  /**
25
27
  * Outlet component - renders child content in layouts
@@ -79,12 +81,15 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
79
81
  : Promise.resolve(segment.component)
80
82
  }
81
83
  fallback={segment.loading}
84
+ segmentId={segment.id}
82
85
  />
83
86
  );
84
87
  } else {
85
88
  content = segment.component ?? null;
86
89
  }
87
90
 
91
+ let result: ReactNode;
92
+
88
93
  // If segment has a layout, wrap appropriately
89
94
  if (segment.layout) {
90
95
  // Check if this segment has loaders that need streaming
@@ -104,25 +109,23 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
104
109
  </LoaderBoundary>
105
110
  );
106
111
 
107
- return (
112
+ result = (
108
113
  <OutletProvider content={loaderAwareContent} segment={segment}>
109
114
  {segment.layout}
110
115
  </OutletProvider>
111
116
  );
117
+ } else {
118
+ // No loaders - wrap in OutletProvider so layout can use <Outlet />
119
+ result = (
120
+ <OutletProvider content={content} segment={segment}>
121
+ {segment.layout}
122
+ </OutletProvider>
123
+ );
112
124
  }
113
-
114
- // No loaders - wrap in OutletProvider so layout can use <Outlet />
115
- return (
116
- <OutletProvider content={content} segment={segment}>
117
- {segment.layout}
118
- </OutletProvider>
119
- );
120
- }
121
-
122
- // No layout but has loaders - wrap content with LoaderBoundary for useLoader context
123
- // This is common for intercept routes that use useLoader without a custom layout
124
- if (segment.loaderDataPromise && segment.loaderIds) {
125
- return (
125
+ } else if (segment.loaderDataPromise && segment.loaderIds) {
126
+ // No layout but has loaders - wrap content with LoaderBoundary for useLoader context
127
+ // This is common for intercept routes that use useLoader without a custom layout
128
+ result = (
126
129
  <LoaderBoundary
127
130
  loaderDataPromise={segment.loaderDataPromise}
128
131
  loaderIds={segment.loaderIds}
@@ -134,9 +137,20 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
134
137
  {content}
135
138
  </LoaderBoundary>
136
139
  );
140
+ } else {
141
+ result = content;
142
+ }
143
+
144
+ // Wrap with MountContextProvider for include() scoped parallel/intercept slots
145
+ if (segment.mountPath) {
146
+ return (
147
+ <MountContextProvider value={segment.mountPath}>
148
+ {result}
149
+ </MountContextProvider>
150
+ );
137
151
  }
138
152
 
139
- return content;
153
+ return result;
140
154
  }
141
155
 
142
156
  // Default: render child content
@@ -193,12 +207,15 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
193
207
  : Promise.resolve(segment.component)
194
208
  }
195
209
  fallback={segment.loading}
210
+ segmentId={segment.id}
196
211
  />
197
212
  );
198
213
  } else {
199
214
  content = segment.component ?? null;
200
215
  }
201
216
 
217
+ let result: ReactNode;
218
+
202
219
  // If segment has a layout, wrap appropriately
203
220
  if (segment.layout) {
204
221
  // Check if this segment has loaders that need streaming
@@ -217,25 +234,23 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
217
234
  </LoaderBoundary>
218
235
  );
219
236
 
220
- return (
237
+ result = (
221
238
  <OutletProvider content={loaderAwareContent} segment={segment}>
222
239
  {segment.layout}
223
240
  </OutletProvider>
224
241
  );
242
+ } else {
243
+ // No loaders - wrap in OutletProvider so layout can use <Outlet />
244
+ result = (
245
+ <OutletProvider content={content} segment={segment}>
246
+ {segment.layout}
247
+ </OutletProvider>
248
+ );
225
249
  }
226
-
227
- // No loaders - wrap in OutletProvider so layout can use <Outlet />
228
- return (
229
- <OutletProvider content={content} segment={segment}>
230
- {segment.layout}
231
- </OutletProvider>
232
- );
233
- }
234
-
235
- // No layout but has loaders - wrap content with LoaderBoundary for useLoader context
236
- // This is common for intercept routes that use useLoader without a custom layout
237
- if (segment.loaderDataPromise && segment.loaderIds) {
238
- return (
250
+ } else if (segment.loaderDataPromise && segment.loaderIds) {
251
+ // No layout but has loaders - wrap content with LoaderBoundary for useLoader context
252
+ // This is common for intercept routes that use useLoader without a custom layout
253
+ result = (
239
254
  <LoaderBoundary
240
255
  loaderDataPromise={segment.loaderDataPromise}
241
256
  loaderIds={segment.loaderIds}
@@ -247,51 +262,28 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
247
262
  {content}
248
263
  </LoaderBoundary>
249
264
  );
265
+ } else {
266
+ result = content;
250
267
  }
251
268
 
252
- return content;
253
- }
269
+ // Wrap with MountContextProvider for include() scoped parallel/intercept slots
270
+ if (segment.mountPath) {
271
+ return (
272
+ <MountContextProvider value={segment.mountPath}>
273
+ {result}
274
+ </MountContextProvider>
275
+ );
276
+ }
254
277
 
255
- /**
256
- * Provider for outlet content - used internally by renderSegments
257
- *
258
- * Stores a reference to parent context so useLoader can walk up the chain
259
- * to find loader data from parent layouts. If this segment defines a loading
260
- * component, Outlet will wrap content with Suspense using that as fallback.
261
- */
262
- export function OutletProvider({
263
- content,
264
- parallel,
265
- segment,
266
- loaderData,
267
- children,
268
- }: {
269
- content: ReactNode;
270
- parallel?: ResolvedSegment[];
271
- segment?: ResolvedSegment;
272
- loaderData?: Record<string, any>;
273
- children: ReactNode;
274
- }): ReactNode {
275
- // Get parent context to enable walking up the chain for loader lookups
276
- const parentContext = useContext(OutletContext);
277
-
278
- const value = useMemo(
279
- () => ({
280
- content,
281
- parallel,
282
- segment,
283
- loaderData,
284
- parent: parentContext,
285
- loading: segment?.loading,
286
- }),
287
- [content, parallel, segment, loaderData, parentContext]
288
- );
289
-
290
- return (
291
- <OutletContext.Provider value={value}>{children}</OutletContext.Provider>
292
- );
278
+ return result;
293
279
  }
294
280
 
281
+ // OutletProvider is defined in outlet-provider.tsx to break a circular
282
+ // dependency between client.tsx and route-content-wrapper.tsx.
283
+ // Imported at the top of this file for local use in Outlet/ParallelOutlet,
284
+ // and re-exported here for backwards compatibility.
285
+ export { OutletProvider };
286
+
295
287
  /**
296
288
  * Hook to access outlet content programmatically
297
289
  *
@@ -321,52 +313,6 @@ export {
321
313
  type UseLoaderOptions,
322
314
  } from "./use-loader.js";
323
315
 
324
- /**
325
- * Hook to access all loader data in the current context
326
- *
327
- * Returns a record of all loader data available in the current outlet context
328
- * and all parent contexts. Useful for debugging or when you need access to
329
- * multiple loaders.
330
- *
331
- * @returns Record of loader name to data, or empty object if no loaders
332
- *
333
- * @example
334
- * ```tsx
335
- * "use client";
336
- * import { useLoaderData } from "rsc-router/client";
337
- *
338
- * export function DebugPanel() {
339
- * const loaderData = useLoaderData();
340
- * return <pre>{JSON.stringify(loaderData, null, 2)}</pre>;
341
- * }
342
- * ```
343
- */
344
- export function useLoaderData(): Record<string, any> {
345
- const context = useContext(OutletContext);
346
-
347
- // Collect all loader data from the context chain
348
- // Child loaders override parent loaders with the same name
349
- const result: Record<string, any> = {};
350
- const stack: OutletContextValue[] = [];
351
-
352
- // Build stack from current to root
353
- let current: OutletContextValue | null | undefined = context;
354
- while (current) {
355
- stack.push(current);
356
- current = current.parent;
357
- }
358
-
359
- // Apply from root to current (so children override parents)
360
- for (let i = stack.length - 1; i >= 0; i--) {
361
- const ctx = stack[i];
362
- if (ctx.loaderData) {
363
- Object.assign(result, ctx.loaderData);
364
- }
365
- }
366
-
367
- return result;
368
- }
369
-
370
316
  /**
371
317
  * Client-safe createLoader factory
372
318
  *
@@ -396,13 +342,13 @@ export function useLoaderData(): Record<string, any> {
396
342
  */
397
343
  // Overload 1: With function only (not fetchable)
398
344
  export function createLoader<T>(
399
- fn: LoaderFn<T, Record<string, string | undefined>, any>
345
+ fn: LoaderFn<T, Record<string, string | undefined>, any>,
400
346
  ): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
401
347
 
402
348
  // Overload 2: With function and fetchable flag
403
349
  export function createLoader<T>(
404
350
  fn: LoaderFn<T, Record<string, string | undefined>, any>,
405
- fetchable: true
351
+ fetchable: true,
406
352
  ): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
407
353
 
408
354
  // Implementation - function is ignored at runtime on client
@@ -410,7 +356,7 @@ export function createLoader<T>(
410
356
  export function createLoader(
411
357
  _fn: LoaderFn<any, Record<string, string | undefined>, any>,
412
358
  _fetchable?: true,
413
- __injectedId?: string
359
+ __injectedId?: string,
414
360
  ): LoaderDefinition<any, Record<string, string | undefined>> {
415
361
  return {
416
362
  __brand: "loader",
@@ -532,11 +478,16 @@ export class ErrorBoundary extends Component<
532
478
  // ============================================================================
533
479
 
534
480
  // Navigation hooks
535
- export {
536
- useNavigation,
537
- type NavigationMethods,
538
- type NavigationValue,
539
- } from "./browser/react/use-navigation.js";
481
+ export { useNavigation } from "./browser/react/use-navigation.js";
482
+ export { useRouter } from "./browser/react/use-router.js";
483
+ export { usePathname } from "./browser/react/use-pathname.js";
484
+ export { useSearchParams } from "./browser/react/use-search-params.js";
485
+ export { useParams } from "./browser/react/use-params.js";
486
+ export type {
487
+ RouterInstance,
488
+ RouterNavigateOptions,
489
+ ReadonlyURLSearchParams,
490
+ } from "./browser/types.js";
540
491
 
541
492
  // Action state tracking hook
542
493
  export {
@@ -600,22 +551,50 @@ export {
600
551
  useLocationState,
601
552
  type LocationStateDefinition,
602
553
  type LocationStateEntry,
554
+ type LocationStateOptions,
603
555
  } from "./browser/react/location-state.js";
604
556
 
605
557
  // Type-safe href for client-side path validation
606
- export { href, type ValidPaths, type PatternToPath } from "./href-client.js";
607
-
608
- // useHref hook for Django-style route name resolution
609
558
  export {
610
- useHref,
611
- HrefProvider,
612
- HrefContext,
613
- type HrefFn,
614
- type HrefContextValue,
615
- } from "./browser/react/use-href.js";
616
-
617
- // Type-safe scoped href function for useHref<typeof patterns>()
618
- export type { ScopedHrefFunction } from "./href.js";
559
+ href,
560
+ type ValidPaths,
561
+ type PatternToPath,
562
+ type PathResponse,
563
+ } from "./href-client.js";
564
+
565
+ // Response envelope types for consuming JSON response routes
566
+ export type { ResponseEnvelope, ResponseError } from "./urls.js";
567
+
568
+ /**
569
+ * Type guard for checking if a response envelope contains an error.
570
+ *
571
+ * @example
572
+ * ```typescript
573
+ * const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
574
+ * if (isResponseError(result)) {
575
+ * console.log(result.error.message, result.error.code);
576
+ * return;
577
+ * }
578
+ * result.data // fully typed as Product
579
+ * ```
580
+ */
581
+ export function isResponseError<T>(
582
+ result: import("./urls.js").ResponseEnvelope<T>,
583
+ ): result is import("./urls.js").ResponseEnvelope<T> & {
584
+ error: import("./urls.js").ResponseError;
585
+ } {
586
+ return result.error !== undefined;
587
+ }
588
+
589
+ // Mount context for include() scoped components
590
+ export { useMount } from "./browser/react/use-mount.js";
591
+ export { MountContext } from "./browser/react/mount-context.js";
592
+
593
+ // Mount-aware href hook - auto-prefixes paths with include() mount
594
+ export { useHref } from "./browser/react/use-href.js";
595
+
596
+ // Type-safe scoped reverse function for scopedReverse<typeof patterns>()
597
+ export type { ScopedReverseFunction } from "./reverse.js";
619
598
 
620
599
  // Loader definition type - for typing loader props in client components
621
600
  export type { LoaderDefinition } from "./types.js";
@@ -33,7 +33,7 @@ const CLIENT_REFERENCE = Symbol.for("react.client.reference");
33
33
  * ```
34
34
  */
35
35
  export function isClientComponent(
36
- component: ComponentType<unknown> | unknown
36
+ component: ComponentType<unknown> | unknown,
37
37
  ): boolean {
38
38
  if (typeof component !== "function") {
39
39
  return false;
@@ -52,13 +52,13 @@ export function isClientComponent(
52
52
  */
53
53
  export function assertClientComponent(
54
54
  component: ComponentType<unknown> | unknown,
55
- name: string
55
+ name: string,
56
56
  ): asserts component is ComponentType<unknown> {
57
57
  if (typeof component !== "function") {
58
58
  throw new Error(
59
59
  `${name} must be a client component function with "use client" directive. ` +
60
60
  `Make sure to pass the component itself, not a JSX element: ` +
61
- `${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)`
61
+ `${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)`,
62
62
  );
63
63
  }
64
64
 
@@ -66,7 +66,7 @@ export function assertClientComponent(
66
66
  throw new Error(
67
67
  `${name} must be a client component with "use client" directive at the top of the file. ` +
68
68
  `Server components cannot be used as the ${name} because their function reference ` +
69
- `cannot be serialized in the RSC payload. Add "use client" to your ${name} file.`
69
+ `cannot be serialized in the RSC payload. Add "use client" to your ${name} file.`,
70
70
  );
71
71
  }
72
72
  }
@@ -11,7 +11,11 @@ import { MetaTags } from "../handles/MetaTags.js";
11
11
  * Uses suppressHydrationWarning on <html> because the theme script
12
12
  * may modify class/style attributes before React hydrates.
13
13
  */
14
- export function DefaultDocument({ children }: { children: ReactNode }): ReactElement {
14
+ export function DefaultDocument({
15
+ children,
16
+ }: {
17
+ children: ReactNode;
18
+ }): ReactElement {
15
19
  return (
16
20
  <html lang="en" suppressHydrationWarning>
17
21
  <head>
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Typed context variables for ctx.set() / ctx.get().
3
+ *
4
+ * createVar<T>() produces a typed token that handlers set and layouts/middleware
5
+ * read. The token carries a unique Symbol used as the property key on the
6
+ * per-request variables object — no build-time processing, no IDs.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { createVar } from "@rangojs/router";
11
+ *
12
+ * interface PaginationData { current: number; total: number }
13
+ * export const Pagination = createVar<PaginationData>();
14
+ *
15
+ * // handler
16
+ * ctx.set(Pagination, { current: 1, total: 4 });
17
+ *
18
+ * // layout
19
+ * const pg = ctx.get(Pagination); // PaginationData | undefined
20
+ * ```
21
+ */
22
+
23
+ export interface ContextVar<T> {
24
+ readonly __brand: "context-var";
25
+ readonly key: symbol;
26
+ /** Phantom field to carry the type parameter. Never set at runtime. */
27
+ readonly __type?: T;
28
+ }
29
+
30
+ /**
31
+ * Create a typed context variable token.
32
+ *
33
+ * The returned object is used with ctx.set(token, value) and ctx.get(token)
34
+ * for compile-time-checked data flow between handlers, layouts, and middleware.
35
+ */
36
+ export function createVar<T>(): ContextVar<T> {
37
+ return { __brand: "context-var" as const, key: Symbol() };
38
+ }
39
+
40
+ /**
41
+ * Type guard: is the value a ContextVar token?
42
+ */
43
+ export function isContextVar(value: unknown): value is ContextVar<unknown> {
44
+ return (
45
+ typeof value === "object" &&
46
+ value !== null &&
47
+ "__brand" in value &&
48
+ (value as { __brand: unknown }).__brand === "context-var"
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Read a variable from the variables store.
54
+ * Accepts either a string key (legacy) or a ContextVar token (typed).
55
+ */
56
+ export function contextGet(
57
+ variables: any,
58
+ keyOrVar: string | ContextVar<any>,
59
+ ): any {
60
+ if (typeof keyOrVar === "string") return variables[keyOrVar];
61
+ return variables[keyOrVar.key];
62
+ }
63
+
64
+ /** Keys that must never be used as string variable names */
65
+ const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
66
+
67
+ /**
68
+ * Write a variable to the variables store.
69
+ * Accepts either a string key (legacy) or a ContextVar token (typed).
70
+ */
71
+ export function contextSet(
72
+ variables: any,
73
+ keyOrVar: string | ContextVar<any>,
74
+ value: any,
75
+ ): void {
76
+ if (typeof keyOrVar === "string") {
77
+ if (FORBIDDEN_KEYS.has(keyOrVar)) {
78
+ throw new Error(
79
+ `ctx.set(): "${keyOrVar}" is a reserved key and cannot be used as a variable name.`,
80
+ );
81
+ }
82
+ variables[keyOrVar] = value;
83
+ } else {
84
+ variables[keyOrVar.key] = value;
85
+ }
86
+ }
package/src/debug.ts CHANGED
@@ -34,7 +34,7 @@ export interface SerializedManifest {
34
34
  * Serialize a manifest Map into a JSON-friendly structure
35
35
  */
36
36
  export function serializeManifest(
37
- manifest: Map<string, EntryData>
37
+ manifest: Map<string, EntryData>,
38
38
  ): SerializedManifest {
39
39
  const routes: Record<string, SerializedEntry> = {};
40
40
  const layouts: Record<string, SerializedEntry> = {};
@@ -92,7 +92,7 @@ export function serializeManifest(
92
92
  */
93
93
  export function compareManifests(
94
94
  oldManifest: SerializedManifest,
95
- newManifest: SerializedManifest
95
+ newManifest: SerializedManifest,
96
96
  ): {
97
97
  addedRoutes: string[];
98
98
  removedRoutes: string[];
@@ -113,10 +113,20 @@ export function compareManifests(
113
113
  } {
114
114
  const addedRoutes: string[] = [];
115
115
  const removedRoutes: string[] = [];
116
- const changedRoutes: Array<{ key: string; field: string; old: any; new: any }> = [];
116
+ const changedRoutes: Array<{
117
+ key: string;
118
+ field: string;
119
+ old: any;
120
+ new: any;
121
+ }> = [];
117
122
  const addedLayouts: string[] = [];
118
123
  const removedLayouts: string[] = [];
119
- const changedLayouts: Array<{ key: string; field: string; old: any; new: any }> = [];
124
+ const changedLayouts: Array<{
125
+ key: string;
126
+ field: string;
127
+ old: any;
128
+ new: any;
129
+ }> = [];
120
130
 
121
131
  // Compare routes
122
132
  const oldRouteKeys = new Set(Object.keys(oldManifest.routes));
@@ -191,7 +201,7 @@ export function compareManifests(
191
201
  * Format manifest diff as a human-readable string
192
202
  */
193
203
  export function formatManifestDiff(
194
- diff: ReturnType<typeof compareManifests>
204
+ diff: ReturnType<typeof compareManifests>,
195
205
  ): string {
196
206
  const lines: string[] = [];
197
207
 
@@ -208,7 +218,7 @@ export function formatManifestDiff(
208
218
  if (diff.changedRoutes.length > 0) {
209
219
  lines.push("Changed routes:");
210
220
  diff.changedRoutes.forEach((c) =>
211
- lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`)
221
+ lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`),
212
222
  );
213
223
  }
214
224
 
@@ -225,7 +235,7 @@ export function formatManifestDiff(
225
235
  if (diff.changedLayouts.length > 0) {
226
236
  lines.push("Changed layouts:");
227
237
  diff.changedLayouts.forEach((c) =>
228
- lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`)
238
+ lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`),
229
239
  );
230
240
  }
231
241