@rangojs/router 0.0.0-experimental.5 → 0.0.0-experimental.50

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 (301) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +4567 -769
  5. package/package.json +77 -58
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +85 -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/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +89 -30
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +204 -1
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +318 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  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 +92 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +24 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +282 -557
  42. package/src/browser/navigation-client.ts +157 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +297 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +303 -310
  47. package/src/browser/prefetch/cache.ts +206 -0
  48. package/src/browser/prefetch/fetch.ts +144 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +48 -0
  51. package/src/browser/prefetch/queue.ts +128 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +193 -73
  54. package/src/browser/react/NavigationProvider.tsx +160 -13
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +24 -1
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +32 -79
  66. package/src/browser/react/use-href.tsx +2 -2
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-navigation.ts +22 -63
  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 +188 -55
  76. package/src/browser/scroll-restoration.ts +117 -44
  77. package/src/browser/segment-reconciler.ts +221 -0
  78. package/src/browser/segment-structure-assert.ts +16 -0
  79. package/src/browser/server-action-bridge.ts +504 -599
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +118 -47
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +235 -24
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +13 -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 +479 -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 +342 -0
  100. package/src/cache/cache-scope.ts +167 -309
  101. package/src/cache/cf/cf-cache-store.ts +571 -17
  102. package/src/cache/cf/index.ts +13 -3
  103. package/src/cache/document-cache.ts +116 -77
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +1 -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 +3 -1
  114. package/src/client.tsx +106 -126
  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 +19 -9
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +15 -29
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/breadcrumbs.ts +66 -0
  123. package/src/handles/index.ts +1 -0
  124. package/src/handles/meta.ts +30 -13
  125. package/src/host/cookie-handler.ts +165 -0
  126. package/src/host/errors.ts +97 -0
  127. package/src/host/index.ts +53 -0
  128. package/src/host/pattern-matcher.ts +214 -0
  129. package/src/host/router.ts +352 -0
  130. package/src/host/testing.ts +79 -0
  131. package/src/host/types.ts +146 -0
  132. package/src/host/utils.ts +25 -0
  133. package/src/href-client.ts +119 -29
  134. package/src/index.rsc.ts +153 -19
  135. package/src/index.ts +211 -30
  136. package/src/internal-debug.ts +11 -0
  137. package/src/loader.rsc.ts +26 -147
  138. package/src/loader.ts +27 -10
  139. package/src/network-error-thrower.tsx +3 -1
  140. package/src/outlet-provider.tsx +45 -0
  141. package/src/prerender/param-hash.ts +37 -0
  142. package/src/prerender/store.ts +185 -0
  143. package/src/prerender.ts +463 -0
  144. package/src/reverse.ts +330 -0
  145. package/src/root-error-boundary.tsx +41 -29
  146. package/src/route-content-wrapper.tsx +7 -4
  147. package/src/route-definition/dsl-helpers.ts +959 -0
  148. package/src/route-definition/helper-factories.ts +200 -0
  149. package/src/route-definition/helpers-types.ts +430 -0
  150. package/src/route-definition/index.ts +52 -0
  151. package/src/route-definition/redirect.ts +93 -0
  152. package/src/route-definition.ts +1 -1428
  153. package/src/route-map-builder.ts +217 -123
  154. package/src/route-name.ts +53 -0
  155. package/src/route-types.ts +59 -8
  156. package/src/router/content-negotiation.ts +116 -0
  157. package/src/router/debug-manifest.ts +72 -0
  158. package/src/router/error-handling.ts +9 -9
  159. package/src/router/find-match.ts +160 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +397 -0
  162. package/src/router/lazy-includes.ts +237 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +251 -0
  165. package/src/router/manifest.ts +154 -35
  166. package/src/router/match-api.ts +620 -0
  167. package/src/router/match-context.ts +5 -3
  168. package/src/router/match-handlers.ts +440 -0
  169. package/src/router/match-middleware/background-revalidation.ts +108 -93
  170. package/src/router/match-middleware/cache-lookup.ts +440 -10
  171. package/src/router/match-middleware/cache-store.ts +98 -26
  172. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  173. package/src/router/match-middleware/segment-resolution.ts +27 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +55 -33
  176. package/src/router/metrics.ts +240 -15
  177. package/src/router/middleware-cookies.ts +55 -0
  178. package/src/router/middleware-types.ts +222 -0
  179. package/src/router/middleware.ts +327 -369
  180. package/src/router/pattern-matching.ts +211 -43
  181. package/src/router/prerender-match.ts +402 -0
  182. package/src/router/preview-match.ts +170 -0
  183. package/src/router/revalidation.ts +137 -38
  184. package/src/router/router-context.ts +41 -21
  185. package/src/router/router-interfaces.ts +452 -0
  186. package/src/router/router-options.ts +592 -0
  187. package/src/router/router-registry.ts +24 -0
  188. package/src/router/segment-resolution/fresh.ts +677 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +199 -0
  191. package/src/router/segment-resolution/revalidation.ts +1296 -0
  192. package/src/router/segment-resolution/static-store.ts +67 -0
  193. package/src/router/segment-resolution.ts +21 -0
  194. package/src/router/segment-wrappers.ts +291 -0
  195. package/src/router/telemetry-otel.ts +299 -0
  196. package/src/router/telemetry.ts +300 -0
  197. package/src/router/timeout.ts +148 -0
  198. package/src/router/trie-matching.ts +239 -0
  199. package/src/router/types.ts +77 -3
  200. package/src/router.ts +665 -4182
  201. package/src/rsc/handler-context.ts +45 -0
  202. package/src/rsc/handler.ts +764 -754
  203. package/src/rsc/helpers.ts +140 -6
  204. package/src/rsc/index.ts +0 -20
  205. package/src/rsc/loader-fetch.ts +209 -0
  206. package/src/rsc/manifest-init.ts +86 -0
  207. package/src/rsc/nonce.ts +14 -0
  208. package/src/rsc/origin-guard.ts +141 -0
  209. package/src/rsc/progressive-enhancement.ts +379 -0
  210. package/src/rsc/response-error.ts +37 -0
  211. package/src/rsc/response-route-handler.ts +347 -0
  212. package/src/rsc/rsc-rendering.ts +237 -0
  213. package/src/rsc/runtime-warnings.ts +42 -0
  214. package/src/rsc/server-action.ts +348 -0
  215. package/src/rsc/ssr-setup.ts +128 -0
  216. package/src/rsc/types.ts +38 -11
  217. package/src/search-params.ts +230 -0
  218. package/src/segment-system.tsx +172 -21
  219. package/src/server/context.ts +266 -58
  220. package/src/server/cookie-store.ts +190 -0
  221. package/src/server/fetchable-loader-store.ts +37 -0
  222. package/src/server/handle-store.ts +94 -15
  223. package/src/server/loader-registry.ts +15 -56
  224. package/src/server/request-context.ts +439 -73
  225. package/src/server.ts +35 -128
  226. package/src/ssr/index.tsx +101 -31
  227. package/src/static-handler.ts +114 -0
  228. package/src/theme/ThemeProvider.tsx +21 -15
  229. package/src/theme/ThemeScript.tsx +5 -5
  230. package/src/theme/constants.ts +5 -2
  231. package/src/theme/index.ts +4 -14
  232. package/src/theme/theme-context.ts +4 -30
  233. package/src/theme/theme-script.ts +21 -18
  234. package/src/types/boundaries.ts +158 -0
  235. package/src/types/cache-types.ts +198 -0
  236. package/src/types/error-types.ts +192 -0
  237. package/src/types/global-namespace.ts +100 -0
  238. package/src/types/handler-context.ts +773 -0
  239. package/src/types/index.ts +88 -0
  240. package/src/types/loader-types.ts +183 -0
  241. package/src/types/route-config.ts +170 -0
  242. package/src/types/route-entry.ts +109 -0
  243. package/src/types/segments.ts +150 -0
  244. package/src/types.ts +1 -1623
  245. package/src/urls/include-helper.ts +197 -0
  246. package/src/urls/index.ts +53 -0
  247. package/src/urls/path-helper-types.ts +339 -0
  248. package/src/urls/path-helper.ts +329 -0
  249. package/src/urls/pattern-types.ts +95 -0
  250. package/src/urls/response-types.ts +106 -0
  251. package/src/urls/type-extraction.ts +372 -0
  252. package/src/urls/urls-function.ts +98 -0
  253. package/src/urls.ts +1 -802
  254. package/src/use-loader.tsx +85 -77
  255. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  256. package/src/vite/discovery/discover-routers.ts +344 -0
  257. package/src/vite/discovery/prerender-collection.ts +385 -0
  258. package/src/vite/discovery/route-types-writer.ts +258 -0
  259. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  260. package/src/vite/discovery/state.ts +108 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -782
  263. package/src/vite/plugin-types.ts +48 -0
  264. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  265. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  266. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  267. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  268. package/src/vite/plugins/expose-id-utils.ts +287 -0
  269. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  270. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  271. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  272. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  273. package/src/vite/plugins/expose-ids/types.ts +45 -0
  274. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  275. package/src/vite/plugins/refresh-cmd.ts +65 -0
  276. package/src/vite/plugins/use-cache-transform.ts +323 -0
  277. package/src/vite/plugins/version-injector.ts +83 -0
  278. package/src/vite/plugins/version-plugin.ts +266 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +27 -16
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +445 -0
  282. package/src/vite/router-discovery.ts +777 -0
  283. package/src/vite/utils/ast-handler-extract.ts +517 -0
  284. package/src/vite/utils/banner.ts +36 -0
  285. package/src/vite/utils/bundle-analysis.ts +137 -0
  286. package/src/vite/utils/manifest-utils.ts +70 -0
  287. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  288. package/src/vite/utils/prerender-utils.ts +189 -0
  289. package/src/vite/utils/shared-utils.ts +169 -0
  290. package/CLAUDE.md +0 -43
  291. package/src/browser/lru-cache.ts +0 -69
  292. package/src/browser/request-controller.ts +0 -164
  293. package/src/cache/memory-store.ts +0 -253
  294. package/src/href-context.ts +0 -33
  295. package/src/href.ts +0 -255
  296. package/src/server/route-manifest-cache.ts +0 -173
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -426
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. package/src/warmup/connection-warmup.tsx +0 -94
  301. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -1,7 +1,17 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import type { ReactNode } from "react";
3
- import type { PartialCacheOptions, ErrorBoundaryHandler, Handler, LoaderDefinition, MiddlewareFn, NotFoundBoundaryHandler, ShouldRevalidateFn } from "../types";
3
+ import type {
4
+ PartialCacheOptions,
5
+ ErrorBoundaryHandler,
6
+ Handler,
7
+ LoaderDefinition,
8
+ MiddlewareFn,
9
+ NotFoundBoundaryHandler,
10
+ ShouldRevalidateFn,
11
+ TransitionConfig,
12
+ } from "../types";
4
13
  import { invariant } from "../errors";
14
+ import type { DefaultRouteName } from "../types/global-namespace.js";
5
15
 
6
16
  // ============================================================================
7
17
  // Performance Metrics Types
@@ -13,9 +23,10 @@ import { invariant } from "../errors";
13
23
  * @internal This type is an implementation detail and may change without notice.
14
24
  */
15
25
  export interface PerformanceMetric {
16
- label: string; // e.g., "route-matching", "loader:UserLoader"
17
- duration: number; // milliseconds
18
- startTime: number; // relative to request start
26
+ label: string; // e.g., "route-matching", "loader:UserLoader"
27
+ duration: number; // milliseconds
28
+ startTime: number; // relative to request start
29
+ depth?: number; // nesting level for hierarchical display (0 = top-level)
19
30
  }
20
31
 
21
32
  /**
@@ -105,12 +116,14 @@ export type InterceptSegmentsState = {
105
116
  * @internal This type is an implementation detail and may change without notice.
106
117
  */
107
118
  export type InterceptSelectorContext<TEnv = any> = {
108
- from: URL; // Source URL (where user is coming from)
109
- to: URL; // Destination URL (where user is navigating to)
110
- params: Record<string, string>; // Matched route params
111
- request: Request; // The HTTP request object
112
- env: TEnv; // Platform bindings (Cloudflare env, etc.)
113
- segments: InterceptSegmentsState; // Client's current segments (where navigating FROM)
119
+ from: URL; // Source URL (where user is coming from)
120
+ to: URL; // Destination URL (where user is navigating to)
121
+ params: Record<string, string>; // Matched route params
122
+ request: Request; // The HTTP request object
123
+ env: TEnv; // Platform bindings (Cloudflare env, etc.)
124
+ segments: InterceptSegmentsState; // Client's current segments (where navigating FROM)
125
+ fromRouteName?: DefaultRouteName; // Named route being navigated away from (undefined for unnamed routes)
126
+ toRouteName?: DefaultRouteName; // Named route being navigated to (undefined for unnamed routes)
114
127
  };
115
128
 
116
129
  /**
@@ -119,7 +132,9 @@ export type InterceptSelectorContext<TEnv = any> = {
119
132
  *
120
133
  * @internal This type is an implementation detail and may change without notice.
121
134
  */
122
- export type InterceptWhenFn<TEnv = any> = (ctx: InterceptSelectorContext<TEnv>) => boolean;
135
+ export type InterceptWhenFn<TEnv = any> = (
136
+ ctx: InterceptSelectorContext<TEnv>,
137
+ ) => boolean;
123
138
 
124
139
  /**
125
140
  * Intercept entry stored in EntryData
@@ -128,55 +143,84 @@ export type InterceptWhenFn<TEnv = any> = (ctx: InterceptSelectorContext<TEnv>)
128
143
  * @internal This type is an implementation detail and may change without notice.
129
144
  */
130
145
  export type InterceptEntry = {
131
- slotName: `@${string}`; // e.g., "@modal"
132
- routeName: string; // e.g., "card"
133
- handler: ReactNode | Handler<any, any>;
146
+ slotName: `@${string}`; // e.g., "@modal"
147
+ routeName: string; // e.g., "card"
148
+ handler: ReactNode | Handler<any, any, any>;
134
149
  middleware: MiddlewareFn<any, any>[];
135
150
  revalidate: ShouldRevalidateFn<any, any>[];
136
151
  errorBoundary: (ReactNode | ErrorBoundaryHandler)[];
137
152
  notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
138
153
  loader: LoaderEntry[];
139
154
  loading?: ReactNode | false;
140
- layout?: ReactNode | Handler<any, any>; // Wrapper layout with <Outlet /> for content
141
- when: InterceptWhenFn[]; // Selector conditions - all must return true to intercept
155
+ transition?: TransitionConfig;
156
+ layout?: ReactNode | Handler<any, any, any>; // Wrapper layout with <Outlet /> for content
157
+ when: InterceptWhenFn[]; // Selector conditions - all must return true to intercept
142
158
  };
143
159
 
160
+ export interface ParallelEntryData
161
+ extends EntryPropCommon, EntryPropDatas, EntryPropSegments {
162
+ type: "parallel";
163
+ handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
164
+ loading?: ReactNode | false;
165
+ transition?: TransitionConfig;
166
+ /** Set when any parallel slot is a Static definition */
167
+ isStaticPrerender?: true;
168
+ /** Per-slot static handler $$ids for build-time store lookup */
169
+ staticHandlerIds?: Record<string, string>;
170
+ }
171
+
172
+ export type ParallelEntries = Partial<Record<`@${string}`, ParallelEntryData>>;
173
+
144
174
  export type EntryPropSegments = {
145
175
  loader: LoaderEntry[];
146
176
  layout: EntryData[];
147
- parallel: EntryData[]; // type: "parallel" entries with their own loaders/revalidate/loading
177
+ parallel: ParallelEntries; // slot -> parallel entry (same entry may back multiple slots)
148
178
  intercept: InterceptEntry[]; // intercept definitions for soft navigation
149
179
  };
150
180
 
151
181
  export type EntryData =
152
182
  | ({
153
183
  type: "route";
154
- handler: Handler<any, any>;
184
+ handler: Handler<any, any, any>;
155
185
  loading?: ReactNode | false;
186
+ transition?: TransitionConfig;
156
187
  /** URL pattern for this route (used by path() in urls()) */
157
188
  pattern?: string;
189
+ /** Set when handler is a Prerender definition */
190
+ isPrerender?: true;
191
+ /** Original PrerenderHandlerDefinition (for build-time getParams access) */
192
+ prerenderDef?: {
193
+ getParams?: (ctx: any) => Promise<any[]> | any[];
194
+ options?: { passthrough?: boolean };
195
+ };
196
+ /** Set when handler is a Static definition (build-time only) */
197
+ isStaticPrerender?: true;
198
+ /** Static handler $$id for build-time store lookup */
199
+ staticHandlerId?: string;
200
+ /** Response type for non-RSC routes (json, text, image, any) */
201
+ responseType?: string;
158
202
  } & EntryPropCommon &
159
203
  EntryPropDatas &
160
204
  EntryPropSegments)
161
205
  | ({
162
206
  type: "layout";
163
- handler: ReactNode | Handler<any, any>;
164
- loading?: ReactNode | false;
165
- } & EntryPropCommon &
166
- EntryPropDatas &
167
- EntryPropSegments)
168
- | ({
169
- type: "parallel";
170
- handler: Record<`@${string}`, Handler<any, any> | ReactNode>;
207
+ handler: ReactNode | Handler<any, any, any>;
171
208
  loading?: ReactNode | false;
209
+ transition?: TransitionConfig;
210
+ /** Set when handler is a Static definition (build-time only) */
211
+ isStaticPrerender?: true;
212
+ /** Static handler $$id for build-time store lookup */
213
+ staticHandlerId?: string;
172
214
  } & EntryPropCommon &
173
215
  EntryPropDatas &
174
216
  EntryPropSegments)
217
+ | ParallelEntryData
175
218
  | ({
176
219
  type: "cache";
177
220
  /** Cache entries create cache boundaries and render like layouts (with Outlet) */
178
- handler: ReactNode | Handler<any, any>;
221
+ handler: ReactNode | Handler<any, any, any>;
179
222
  loading?: ReactNode | false;
223
+ transition?: TransitionConfig;
180
224
  } & EntryPropCommon &
181
225
  EntryPropDatas &
182
226
  EntryPropSegments);
@@ -211,17 +255,34 @@ interface HelperContext {
211
255
  patternsByPrefix?: Map<string, Map<string, string>>;
212
256
  /** Trailing slash config per route name */
213
257
  trailingSlash?: Map<string, "never" | "always" | "ignore">;
258
+ /** Search param schemas per route name */
259
+ searchSchemas?: Map<string, Record<string, string>>;
214
260
  /** URL prefix from include() - applied to all path() patterns */
215
261
  urlPrefix?: string;
216
262
  /** Name prefix from include() - applied to all named routes */
217
263
  namePrefix?: string;
264
+ /** True when this scope is at root level (no named include boundary above).
265
+ * Routes at root scope allow dot-local reverse to fall back to bare names. */
266
+ rootScoped?: boolean;
218
267
  /** Run helper for cleaner middleware code */
219
268
  run?: <T>(fn: () => T | Promise<T>) => T | Promise<T>;
220
269
  /** Tracked includes for build-time manifest generation */
221
270
  trackedIncludes?: TrackedInclude[];
271
+ /** Cache profiles for DSL-time cache("profileName") resolution */
272
+ cacheProfiles?: Record<
273
+ string,
274
+ import("../cache/profile-registry.js").CacheProfile
275
+ >;
222
276
  }
223
- export const RSCRouterContext: AsyncLocalStorage<HelperContext> =
224
- new AsyncLocalStorage<HelperContext>();
277
+ // Use a global symbol key so the AsyncLocalStorage instance survives HMR
278
+ // module re-evaluation. Without this, Vite's RSC module runner may create
279
+ // a new instance when context.ts is re-evaluated, while other modules still
280
+ // hold references to the old instance — causing getStore() to return
281
+ // undefined even inside a run() callback.
282
+ const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
283
+ export const RSCRouterContext: AsyncLocalStorage<HelperContext> = ((
284
+ globalThis as any
285
+ )[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
225
286
 
226
287
  export const getContext = (): {
227
288
  context: AsyncLocalStorage<HelperContext>;
@@ -229,21 +290,21 @@ export const getContext = (): {
229
290
  getParent: () => EntryData | null;
230
291
  getOrCreateStore: (forRoute?: string) => HelperContext;
231
292
  getNextIndex: (
232
- type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
293
+ type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
233
294
  ) => string;
234
295
  getShortCode: (
235
- type: "layout" | "parallel" | "route" | "loader" | "cache"
296
+ type: "layout" | "parallel" | "route" | "loader" | "cache",
236
297
  ) => string;
237
298
  run: <T>(
238
299
  namespace: string,
239
300
  parent: EntryData | null,
240
- callback: (...args: any[]) => T
301
+ callback: (...args: any[]) => T,
241
302
  ) => T;
242
303
  runWithStore: <T>(
243
304
  store: HelperContext,
244
305
  namespace: string,
245
306
  parent: EntryData | null,
246
- callback: (...args: any[]) => T
307
+ callback: (...args: any[]) => T,
247
308
  ) => T;
248
309
  } => {
249
310
  const context = RSCRouterContext;
@@ -262,6 +323,7 @@ export const getContext = (): {
262
323
  patterns: new Map<string, string>(),
263
324
  patternsByPrefix: new Map<string, Map<string, string>>(),
264
325
  trailingSlash: new Map<string, "never" | "always" | "ignore">(),
326
+ searchSchemas: new Map<string, Record<string, string>>(),
265
327
  } satisfies HelperContext;
266
328
  }
267
329
  return store;
@@ -270,7 +332,7 @@ export const getContext = (): {
270
332
  const store = context.getStore();
271
333
  if (!store) {
272
334
  throw new Error(
273
- "RSC Router context store is not available. Make sure to run within RSC Router context."
335
+ "RSC Router context store is not available. Make sure to run within RSC Router context.",
274
336
  );
275
337
  }
276
338
  return store;
@@ -284,7 +346,7 @@ export const getContext = (): {
284
346
  return store.parent;
285
347
  },
286
348
  getNextIndex: (
287
- type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
349
+ type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
288
350
  ) => {
289
351
  const store = context.getStore();
290
352
  invariant(store, "No context RSCRouterContext available");
@@ -293,17 +355,31 @@ export const getContext = (): {
293
355
  store.counters[type] = index + 1;
294
356
  return `$${type}.${index}`;
295
357
  },
296
- getShortCode: (type: "layout" | "parallel" | "route" | "loader" | "cache") => {
358
+ getShortCode: (
359
+ type: "layout" | "parallel" | "route" | "loader" | "cache",
360
+ ) => {
297
361
  const store = context.getStore();
298
362
  invariant(store, "No context RSCRouterContext available");
299
363
 
300
364
  const parent = store.parent;
301
- const prefix = type === "layout" ? "L" : type === "parallel" ? "P" : type === "loader" ? "D" : type === "cache" ? "C" : "R";
302
- const mountPrefix = store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
365
+ const prefix =
366
+ type === "layout"
367
+ ? "L"
368
+ : type === "parallel"
369
+ ? "P"
370
+ : type === "loader"
371
+ ? "D"
372
+ : type === "cache"
373
+ ? "C"
374
+ : "R";
375
+ const mountPrefix =
376
+ store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
303
377
 
304
378
  if (!parent) {
305
379
  // Root entry: prefix with mount index and use mount-scoped counter
306
- const counterKey = mountPrefix ? `${mountPrefix}_root_${type}` : `root_${type}`;
380
+ const counterKey = mountPrefix
381
+ ? `${mountPrefix}_root_${type}`
382
+ : `root_${type}`;
307
383
  store.counters[counterKey] ??= 0;
308
384
  const index = store.counters[counterKey];
309
385
  store.counters[counterKey] = index + 1;
@@ -321,7 +397,7 @@ export const getContext = (): {
321
397
  store: HelperContext,
322
398
  namespace: string,
323
399
  parent: EntryData | null,
324
- callback: (...args: any[]) => T
400
+ callback: (...args: any[]) => T,
325
401
  ): T => {
326
402
  return context.run(
327
403
  {
@@ -335,24 +411,32 @@ export const getContext = (): {
335
411
  isSSR: store.isSSR,
336
412
  patterns: store.patterns,
337
413
  trailingSlash: store.trailingSlash,
414
+ searchSchemas: store.searchSchemas,
338
415
  urlPrefix: store.urlPrefix,
339
416
  namePrefix: store.namePrefix,
417
+ rootScoped: store.rootScoped,
340
418
  trackedIncludes: store.trackedIncludes,
419
+ cacheProfiles: store.cacheProfiles,
341
420
  },
342
- callback
421
+ callback,
343
422
  );
344
423
  },
345
424
  run: <T>(
346
425
  namespace: string,
347
426
  parent: EntryData | null,
348
- callback: (...args: any[]) => T
427
+ callback: (...args: any[]) => T,
349
428
  ) => {
350
429
  const store = context.getStore();
351
430
  // Preserve parent counters to ensure globally unique shortCodes
352
431
  const counters = store?.counters || {};
353
432
  const manifest = store ? store.manifest : new Map<string, EntryData>();
354
433
  const patterns = store?.patterns || new Map<string, string>();
355
- const trailingSlash = store?.trailingSlash || new Map<string, "never" | "always" | "ignore">();
434
+ const patternsByPrefix = store?.patternsByPrefix;
435
+ const trailingSlash =
436
+ store?.trailingSlash ||
437
+ new Map<string, "never" | "always" | "ignore">();
438
+ const searchSchemas =
439
+ store?.searchSchemas || new Map<string, Record<string, string>>();
356
440
  return context.run(
357
441
  {
358
442
  manifest,
@@ -364,12 +448,16 @@ export const getContext = (): {
364
448
  metrics: store?.metrics,
365
449
  isSSR: store?.isSSR,
366
450
  patterns,
451
+ patternsByPrefix,
367
452
  trailingSlash,
453
+ searchSchemas,
368
454
  urlPrefix: store?.urlPrefix,
369
455
  namePrefix: store?.namePrefix,
456
+ rootScoped: store?.rootScoped,
370
457
  trackedIncludes: store?.trackedIncludes,
458
+ cacheProfiles: store?.cacheProfiles,
371
459
  },
372
- callback
460
+ callback,
373
461
  );
374
462
  },
375
463
  };
@@ -382,30 +470,61 @@ export const getContext = (): {
382
470
  export function runWithPrefixes<T>(
383
471
  urlPrefix: string,
384
472
  namePrefix: string | undefined,
385
- callback: () => T
473
+ callback: () => T,
386
474
  ): T {
387
475
  const store = RSCRouterContext.getStore();
388
476
  if (!store) {
389
477
  throw new Error("runWithPrefixes must be called within router context");
390
478
  }
391
479
 
392
- // Combine prefixes if there are existing ones
393
- const combinedUrlPrefix = store.urlPrefix
394
- ? `${store.urlPrefix}${urlPrefix}`
395
- : urlPrefix;
396
- const combinedNamePrefix = namePrefix
397
- ? store.namePrefix
398
- ? `${store.namePrefix}.${namePrefix}`
399
- : namePrefix
400
- : store.namePrefix;
480
+ // Combine prefixes if there are existing ones, avoiding double slashes
481
+ let combinedUrlPrefix: string;
482
+ if (store.urlPrefix) {
483
+ if (store.urlPrefix.endsWith("/") && urlPrefix.startsWith("/")) {
484
+ combinedUrlPrefix = store.urlPrefix + urlPrefix.slice(1);
485
+ } else {
486
+ combinedUrlPrefix = store.urlPrefix + urlPrefix;
487
+ }
488
+ } else {
489
+ combinedUrlPrefix = urlPrefix;
490
+ }
491
+ const combinedNamePrefix =
492
+ namePrefix !== undefined
493
+ ? namePrefix === ""
494
+ ? store.namePrefix
495
+ : store.namePrefix
496
+ ? `${store.namePrefix}.${namePrefix}`
497
+ : namePrefix
498
+ : store.namePrefix;
499
+
500
+ // Track root scope for dot-local reverse resolution.
501
+ //
502
+ // The flag answers: "can this route reach bare names at root scope?"
503
+ // It propagates through the include chain:
504
+ //
505
+ // { name: "" } — transparent: inherit parent, default true
506
+ // { name: "foo" } — inherit parent if already set, else create boundary (false)
507
+ // no name — inherit parent unchanged
508
+ //
509
+ // This means { name: "" } + nested { name: "sub" } keeps rootScoped=true
510
+ // (the outer transparent include establishes root access, and the inner
511
+ // named include inherits it). But a direct { name: "sub" } at root gets
512
+ // rootScoped=false (no prior root-access grant, so it creates a boundary).
513
+ const combinedRootScoped =
514
+ namePrefix === ""
515
+ ? (store.rootScoped ?? true)
516
+ : namePrefix !== undefined
517
+ ? (store.rootScoped ?? false)
518
+ : store.rootScoped;
401
519
 
402
520
  return RSCRouterContext.run(
403
521
  {
404
522
  ...store,
405
523
  urlPrefix: combinedUrlPrefix,
406
524
  namePrefix: combinedNamePrefix,
525
+ rootScoped: combinedRootScoped,
407
526
  },
408
- callback
527
+ callback,
409
528
  );
410
529
  }
411
530
 
@@ -425,9 +544,92 @@ export function getNamePrefix(): string | undefined {
425
544
  return store?.namePrefix;
426
545
  }
427
546
 
547
+ /**
548
+ * Get whether the current scope is at root level (no named include boundary above).
549
+ * Returns true at root or inside { name: "" } includes, false inside named includes.
550
+ */
551
+ export function getRootScoped(): boolean {
552
+ const store = RSCRouterContext.getStore();
553
+ return store?.rootScoped ?? true;
554
+ }
555
+
428
556
  // Export HelperContext type for use in other modules
429
557
  export type { HelperContext };
430
558
 
559
+ /**
560
+ * Return an isolated copy of a lazy include's captured parent entry.
561
+ *
562
+ * DSL helpers (loader(), middleware(), etc.) mutate ctx.parent in place.
563
+ * Multiple include() scopes capture the *same* syntheticMapRoot as their
564
+ * parent, so without isolation one include's loaders/middleware leak into
565
+ * every other route that shares that root.
566
+ *
567
+ * The clone is shallow: only the mutable arrays are copied so each
568
+ * include pushes to its own list. The rest of the entry (id, shortCode,
569
+ * parent pointer, handler) stays shared, which is correct and cheap.
570
+ */
571
+ export function getIsolatedLazyParent(
572
+ captured: EntryData | null | undefined,
573
+ ): EntryData | null {
574
+ if (!captured) return null;
575
+ return {
576
+ ...captured,
577
+ loader: [...captured.loader],
578
+ middleware: [...captured.middleware],
579
+ revalidate: [...captured.revalidate],
580
+ errorBoundary: [...captured.errorBoundary],
581
+ notFoundBoundary: [...captured.notFoundBoundary],
582
+ layout: [...captured.layout],
583
+ parallel: { ...captured.parallel },
584
+ intercept: [...captured.intercept],
585
+ };
586
+ }
587
+
588
+ export function getParallelEntries(
589
+ parallels: ParallelEntries | EntryData[] | undefined,
590
+ ): ParallelEntryData[] {
591
+ if (!parallels) return [];
592
+ if (Array.isArray(parallels)) {
593
+ return parallels.filter(
594
+ (entry): entry is ParallelEntryData => entry.type === "parallel",
595
+ );
596
+ }
597
+ return Object.values(parallels).filter(
598
+ (entry): entry is ParallelEntryData => !!entry,
599
+ );
600
+ }
601
+
602
+ export function getParallelSlotEntries(
603
+ parallels: ParallelEntries | EntryData[] | undefined,
604
+ ): Array<{ slot: `@${string}`; entry: ParallelEntryData }> {
605
+ if (!parallels) return [];
606
+
607
+ if (Array.isArray(parallels)) {
608
+ return getParallelEntries(parallels).flatMap((entry) =>
609
+ (Object.keys(entry.handler) as `@${string}`[]).map((slot) => ({
610
+ slot,
611
+ entry,
612
+ })),
613
+ );
614
+ }
615
+
616
+ return Object.entries(parallels)
617
+ .filter(([, entry]) => !!entry)
618
+ .map(([slot, entry]) => ({
619
+ slot: slot as `@${string}`,
620
+ entry: entry!,
621
+ }));
622
+ }
623
+
624
+ export function getParallelSlotCount(
625
+ parallels: ParallelEntries | EntryData[] | undefined,
626
+ ): number {
627
+ if (!parallels) return 0;
628
+ return Array.isArray(parallels)
629
+ ? parallels.filter((entry) => entry?.type === "parallel").length
630
+ : Object.keys(parallels).length;
631
+ }
632
+
431
633
  // ============================================================================
432
634
  // Performance Metrics Helpers
433
635
  // ============================================================================
@@ -443,7 +645,7 @@ export type { HelperContext };
443
645
  * done(); // Records duration
444
646
  * ```
445
647
  */
446
- export function track(label: string): () => void {
648
+ export function track(label: string, depth?: number): () => void {
447
649
  const store = RSCRouterContext.getStore();
448
650
 
449
651
  // No-op if context unavailable or metrics not enabled
@@ -454,7 +656,13 @@ export function track(label: string): () => void {
454
656
  const startTime = performance.now() - store.metrics.requestStart;
455
657
 
456
658
  return () => {
457
- const duration = performance.now() - store.metrics!.requestStart - startTime;
458
- store.metrics!.metrics.push({ label, duration, startTime });
659
+ const duration =
660
+ performance.now() - store.metrics!.requestStart - startTime;
661
+ store.metrics!.metrics.push({
662
+ label,
663
+ duration,
664
+ startTime,
665
+ ...(depth != null ? { depth } : {}),
666
+ });
459
667
  };
460
668
  }