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

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 (300) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4474 -867
  5. package/package.json +60 -51
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  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 +78 -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 +87 -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 +285 -553
  42. package/src/browser/navigation-client.ts +124 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +295 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +258 -308
  47. package/src/browser/prefetch/cache.ts +146 -0
  48. package/src/browser/prefetch/fetch.ts +135 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +42 -0
  51. package/src/browser/prefetch/queue.ts +88 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +185 -73
  54. package/src/browser/react/NavigationProvider.tsx +51 -11
  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 +6 -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 +107 -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 +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 +109 -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 +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 +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 +17 -7
  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 +21 -15
  126. package/src/host/errors.ts +8 -8
  127. package/src/host/index.ts +4 -7
  128. package/src/host/pattern-matcher.ts +27 -27
  129. package/src/host/router.ts +61 -39
  130. package/src/host/testing.ts +8 -8
  131. package/src/host/types.ts +15 -7
  132. package/src/host/utils.ts +1 -1
  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 -157
  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 +934 -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 +211 -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 +158 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +395 -0
  162. package/src/router/lazy-includes.ts +234 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +248 -0
  165. package/src/router/manifest.ts +148 -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 +80 -93
  170. package/src/router/match-middleware/cache-lookup.ts +382 -9
  171. package/src/router/match-middleware/cache-store.ts +51 -22
  172. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  173. package/src/router/match-middleware/segment-resolution.ts +24 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +34 -28
  176. package/src/router/metrics.ts +235 -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 +324 -367
  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 +36 -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 +570 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +198 -0
  191. package/src/router/segment-resolution/revalidation.ts +1241 -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 +289 -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 +692 -4257
  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 +235 -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 +25 -13
  219. package/src/server/context.ts +182 -51
  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 +430 -70
  225. package/src/server.ts +35 -130
  226. package/src/ssr/index.tsx +100 -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 +687 -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 +102 -0
  243. package/src/types/segments.ts +148 -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 +110 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -1133
  263. package/src/vite/plugin-types.ts +131 -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 -51
  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 +254 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +510 -0
  282. package/src/vite/router-discovery.ts +785 -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/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -1,115 +1,12 @@
1
1
  /**
2
- * Client-safe route map builder
2
+ * Route manifest storage and retrieval.
3
3
  *
4
- * Provides a fluent API for building route maps with prefixes.
5
- * Can be imported in client code without pulling in server dependencies.
4
+ * The route manifest maps route names to URL patterns. It is populated
5
+ * by the virtual module (which imports from .named-routes.gen.ts files)
6
+ * and consumed by reverse() and href() at runtime.
6
7
  *
7
- * @example
8
- * ```typescript
9
- * import { createRouteMap, registerRouteMap } from "rsc-router/browser";
10
- *
11
- * const routeMap = createRouteMap()
12
- * .add(homeRoutes)
13
- * .add(blogRoutes, "blog")
14
- * .add(shopRoutes, "shop");
15
- *
16
- * registerRouteMap(routeMap.routes);
17
- *
18
- * declare global {
19
- * namespace RSCRouter {
20
- * interface RegisteredRoutes extends typeof routeMap.routes {}
21
- * }
22
- * }
23
- * ```
24
- */
25
-
26
- import type { PrefixRoutePatterns } from "./href.js";
27
-
28
- /**
29
- * Route map builder interface
30
- *
31
- * Accumulates route types through the builder chain for type-safe href.
32
- */
33
- export interface RouteMapBuilder<TRoutes extends Record<string, string> = {}> {
34
- /**
35
- * Add routes without prefix
36
- */
37
- add<T extends Record<string, string>>(routes: T): RouteMapBuilder<TRoutes & T>;
38
-
39
- /**
40
- * Add routes with prefix (only URL patterns are prefixed, keys stay unchanged)
41
- * @param routes - Route definitions to add
42
- * @param prefix - URL prefix WITHOUT leading slash (e.g., "blog" not "/blog")
43
- */
44
- add<T extends Record<string, string>, P extends string>(
45
- routes: T,
46
- prefix: P
47
- ): RouteMapBuilder<TRoutes & PrefixRoutePatterns<T, `/${P}`>>;
48
-
49
- /**
50
- * The accumulated route map (for typeof extraction in module augmentation)
51
- */
52
- readonly routes: TRoutes;
53
- }
54
-
55
- /**
56
- * Add routes to a map with optional prefix
57
- * Keys stay unchanged for composability - only URL patterns get prefixed.
58
- *
59
- * @param routeMap - The map to add routes to
60
- * @param routes - Routes to add
61
- * @param prefix - Optional prefix for URL paths WITHOUT leading slash (keys stay unchanged)
8
+ * See docs/manifests.md for the full data flow.
62
9
  */
63
- function addRoutes(
64
- routeMap: Record<string, string>,
65
- routes: Record<string, string>,
66
- prefix: string = ""
67
- ): void {
68
- // Normalize prefix: remove leading slash if accidentally provided
69
- const normalizedPrefix = prefix.startsWith("/") ? prefix.slice(1) : prefix;
70
-
71
- for (const [key, pattern] of Object.entries(routes)) {
72
- const prefixedPattern =
73
- normalizedPrefix && pattern !== "/"
74
- ? `/${normalizedPrefix}${pattern}`
75
- : normalizedPrefix && pattern === "/"
76
- ? `/${normalizedPrefix}`
77
- : pattern;
78
- // Use original key - enables reusable route modules
79
- routeMap[key] = prefixedPattern;
80
- }
81
- }
82
-
83
- /**
84
- * Create a new route map builder
85
- *
86
- * @returns A builder for accumulating routes with type-safe prefixes
87
- *
88
- * @example
89
- * ```typescript
90
- * const routeMap = createRouteMap()
91
- * .add(homeRoutes)
92
- * .add(blogRoutes, "blog");
93
- *
94
- * // Types are accumulated through the chain
95
- * type AppRoutes = typeof routeMap.routes;
96
- * ```
97
- */
98
- export function createRouteMap(): RouteMapBuilder<{}> {
99
- const routeMap: Record<string, string> = {};
100
-
101
- const builder: RouteMapBuilder<any> = {
102
- add(routes: Record<string, string>, prefix?: string) {
103
- addRoutes(routeMap, routes, prefix);
104
- return builder;
105
- },
106
- get routes() {
107
- return routeMap;
108
- },
109
- };
110
-
111
- return builder;
112
- }
113
10
 
114
11
  // Singleton route map instance - populated incrementally as routes are encountered
115
12
  let globalRouteMap: Record<string, string> = {};
@@ -118,22 +15,17 @@ let globalRouteMap: Record<string, string> = {};
118
15
  // Set from runtime cache or build-time import
119
16
  let cachedManifest: Record<string, string> | null = null;
120
17
 
18
+ // Pre-computed route entries from build-time prefix tree leaf nodes.
19
+ // Used by evaluateLazyEntry() to skip running the handler for route matching.
20
+ let cachedPrecomputedEntries: Array<{
21
+ staticPrefix: string;
22
+ routes: Record<string, string>;
23
+ }> | null = null;
24
+
121
25
  /**
122
- * Register the route map globally for href to use at runtime
123
- *
124
- * Call this after building your route map to make it available to href.
26
+ * Register routes into the global route map.
125
27
  * Routes are merged with any existing registered routes.
126
- *
127
- * @param map - The route map to register
128
- *
129
- * @example
130
- * ```typescript
131
- * const routeMap = createRouteMap()
132
- * .add(homeRoutes)
133
- * .add(blogRoutes, "blog");
134
- *
135
- * registerRouteMap(routeMap.routes);
136
- * ```
28
+ * Called by createRouter() during module evaluation.
137
29
  */
138
30
  export function registerRouteMap(map: Record<string, string>): void {
139
31
  // Always merge with existing map (don't replace)
@@ -143,11 +35,12 @@ export function registerRouteMap(map: Record<string, string>): void {
143
35
  /**
144
36
  * Get the globally registered route map
145
37
  *
146
- * Used internally by href to resolve route names to URLs at runtime.
38
+ * Used internally by reverse to resolve route names to URLs at runtime.
147
39
  * Returns the cached manifest if available (complete with lazy includes),
148
40
  * otherwise returns the runtime-accumulated route map.
149
41
  *
150
42
  * @returns The registered route map
43
+ * @internal
151
44
  */
152
45
  export function getGlobalRouteMap(): Record<string, string> {
153
46
  // Cached manifest is complete (includes lazy routes), so prefer it
@@ -185,3 +78,198 @@ export function hasCachedManifest(): boolean {
185
78
  export function clearCachedManifest(): void {
186
79
  cachedManifest = null;
187
80
  }
81
+
82
+ /**
83
+ * Set pre-computed route entries from build-time data.
84
+ *
85
+ * Each entry corresponds to a leaf node in the prefix tree (no nested includes).
86
+ * evaluateLazyEntry() checks these before running the handler, avoiding the
87
+ * 5-50ms cost of handler evaluation for route matching on the first request.
88
+ *
89
+ * @param entries - Array of { staticPrefix, routes } from build-time prefix tree leaves
90
+ */
91
+ export function setPrecomputedEntries(
92
+ entries: Array<{
93
+ staticPrefix: string;
94
+ routes: Record<string, string>;
95
+ }> | null,
96
+ ): void {
97
+ cachedPrecomputedEntries = entries;
98
+ }
99
+
100
+ /**
101
+ * Get pre-computed route entries (if available)
102
+ */
103
+ export function getPrecomputedEntries(): typeof cachedPrecomputedEntries {
104
+ return cachedPrecomputedEntries;
105
+ }
106
+
107
+ // Route trie for O(path_length) matching at runtime.
108
+ // Built at build time from the route manifest and serialized into the virtual module.
109
+ let cachedRouteTrie: import("./build/route-trie.js").TrieNode | null = null;
110
+
111
+ export function setRouteTrie(trie: typeof cachedRouteTrie): void {
112
+ cachedRouteTrie = trie;
113
+ }
114
+
115
+ export function getRouteTrie(): typeof cachedRouteTrie {
116
+ return cachedRouteTrie;
117
+ }
118
+
119
+ // Per-router isolated data: each router gets its own manifest, trie, and
120
+ // precomputed entries so multi-router setups (e.g. site + admin via
121
+ // createHostRouter()) don't see each other's routes.
122
+ const perRouterManifestMap: Map<string, Record<string, string>> = new Map();
123
+ const perRouterTrieMap: Map<string, import("./build/route-trie.js").TrieNode> =
124
+ new Map();
125
+ const perRouterPrecomputedEntriesMap: Map<
126
+ string,
127
+ Array<{ staticPrefix: string; routes: Record<string, string> }>
128
+ > = new Map();
129
+
130
+ /**
131
+ * Clear all cached route data (global and per-router).
132
+ * Called during HMR when route definitions change so the handler rebuilds
133
+ * the trie from the updated router.urlpatterns on the next request.
134
+ *
135
+ * The virtual module calls this before repopulating with fresh data,
136
+ * preventing stale entries from removed routes from accumulating.
137
+ */
138
+ export function clearAllRouterData(): void {
139
+ globalRouteMap = {};
140
+ cachedManifest = null;
141
+ cachedPrecomputedEntries = null;
142
+ cachedRouteTrie = null;
143
+ rootScopeRoutes.clear();
144
+ globalSearchSchemas.clear();
145
+ perRouterManifestMap.clear();
146
+ perRouterTrieMap.clear();
147
+ perRouterPrecomputedEntriesMap.clear();
148
+ }
149
+
150
+ export function setRouterManifest(
151
+ routerId: string,
152
+ manifest: Record<string, string>,
153
+ ): void {
154
+ perRouterManifestMap.set(routerId, manifest);
155
+ }
156
+
157
+ /** @internal */
158
+ export function getRouterManifest(
159
+ routerId: string,
160
+ ): Record<string, string> | undefined {
161
+ return perRouterManifestMap.get(routerId);
162
+ }
163
+
164
+ export function setRouterTrie(
165
+ routerId: string,
166
+ trie: import("./build/route-trie.js").TrieNode,
167
+ ): void {
168
+ perRouterTrieMap.set(routerId, trie);
169
+ }
170
+
171
+ export function getRouterTrie(
172
+ routerId: string,
173
+ ): import("./build/route-trie.js").TrieNode | undefined {
174
+ return perRouterTrieMap.get(routerId);
175
+ }
176
+
177
+ export function setRouterPrecomputedEntries(
178
+ routerId: string,
179
+ entries: Array<{ staticPrefix: string; routes: Record<string, string> }>,
180
+ ): void {
181
+ perRouterPrecomputedEntriesMap.set(routerId, entries);
182
+ }
183
+
184
+ export function getRouterPrecomputedEntries(
185
+ routerId: string,
186
+ ): Array<{ staticPrefix: string; routes: Record<string, string> }> | undefined {
187
+ return perRouterPrecomputedEntriesMap.get(routerId);
188
+ }
189
+
190
+ // Lazy loader registry: per-router manifest modules are loaded on first request
191
+ // via import() to keep startup fast and allow Rollup to code-split per router.
192
+ const routerManifestLoaders: Map<string, () => Promise<any>> = new Map();
193
+
194
+ export function registerRouterManifestLoader(
195
+ routerId: string,
196
+ loader: () => Promise<any>,
197
+ ): void {
198
+ routerManifestLoaders.set(routerId, loader);
199
+ }
200
+
201
+ export async function ensureRouterManifest(routerId: string): Promise<void> {
202
+ if (perRouterManifestMap.has(routerId)) return;
203
+ const loader = routerManifestLoaders.get(routerId);
204
+ if (loader) {
205
+ const mod = await loader();
206
+ if (mod.manifest) perRouterManifestMap.set(routerId, mod.manifest);
207
+ if (mod.trie) perRouterTrieMap.set(routerId, mod.trie);
208
+ if (mod.precomputedEntries)
209
+ perRouterPrecomputedEntriesMap.set(routerId, mod.precomputedEntries);
210
+ routerManifestLoaders.delete(routerId);
211
+ }
212
+ }
213
+
214
+ // Dev-mode manifest readiness gate.
215
+ // The Vite discovery plugin calls setManifestReadyPromise() before starting
216
+ // discovery, and resolves it when discovery completes. The handler awaits
217
+ // waitForManifestReady() on first request if the manifest isn't yet available.
218
+ let manifestReadyPromise: Promise<void> | null = null;
219
+
220
+ export function setManifestReadyPromise(promise: Promise<void>): void {
221
+ manifestReadyPromise = promise;
222
+ }
223
+
224
+ export function waitForManifestReady(): Promise<void> | null {
225
+ return manifestReadyPromise;
226
+ }
227
+
228
+ // ============================================================================
229
+ // Route Scope Registry
230
+ // ============================================================================
231
+
232
+ // Tracks whether each route is at root scope (no named include boundary above).
233
+ // Used by dot-local reverse resolution to decide whether bare-name fallback
234
+ // is allowed after scoped lookups are exhausted.
235
+ const rootScopeRoutes: Map<string, boolean> = new Map();
236
+
237
+ /**
238
+ * Register whether a route is at root scope.
239
+ * Called by path() during route evaluation.
240
+ */
241
+ export function registerRouteRootScope(
242
+ routeName: string,
243
+ rootScoped: boolean,
244
+ ): void {
245
+ rootScopeRoutes.set(routeName, rootScoped);
246
+ }
247
+
248
+ /**
249
+ * Check if a route is at root scope.
250
+ * Returns undefined if the route has not been registered (e.g. in unit tests).
251
+ */
252
+ export function isRouteRootScoped(routeName: string): boolean | undefined {
253
+ return rootScopeRoutes.get(routeName);
254
+ }
255
+
256
+ // ============================================================================
257
+ // Search Schema Registry
258
+ // ============================================================================
259
+
260
+ import type { SearchSchema } from "./search-params.js";
261
+
262
+ // Global search schema map: route name -> search schema descriptor.
263
+ // Populated by path() when a search option is provided.
264
+ const globalSearchSchemas: Map<string, SearchSchema> = new Map();
265
+
266
+ export function registerSearchSchema(
267
+ routeName: string,
268
+ schema: SearchSchema,
269
+ ): void {
270
+ globalSearchSchemas.set(routeName, schema);
271
+ }
272
+
273
+ export function getSearchSchema(routeName: string): SearchSchema | undefined {
274
+ return globalSearchSchemas.get(routeName);
275
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Route name utilities for filtering internal route names.
3
+ *
4
+ * Internal names stay active in the runtime manifest for matching and local
5
+ * reverse() resolution, but they must not leak into public APIs or generated
6
+ * route maps.
7
+ */
8
+
9
+ export const AUTO_GENERATED_ROUTE_PREFIX = "$path_";
10
+ export const INTERNAL_INCLUDE_SCOPE_PREFIX = "$prefix_";
11
+
12
+ const RESERVED_PREFIXES = [
13
+ AUTO_GENERATED_ROUTE_PREFIX,
14
+ INTERNAL_INCLUDE_SCOPE_PREFIX,
15
+ ] as const;
16
+
17
+ /**
18
+ * Check if a route name is internal.
19
+ * Internal names include:
20
+ * - unnamed path() routes like "$path__health" or "docs.$path__health"
21
+ * - hidden include scopes like "$prefix_0.index" or "blog.$prefix_1.post"
22
+ *
23
+ * User-defined names containing "$" (e.g. "docs.$admin") are valid and must
24
+ * be preserved.
25
+ */
26
+ export function isAutoGeneratedRouteName(name: string): boolean {
27
+ return name.split(".").some((segment) => {
28
+ return (
29
+ segment.startsWith(AUTO_GENERATED_ROUTE_PREFIX) ||
30
+ segment.startsWith(INTERNAL_INCLUDE_SCOPE_PREFIX)
31
+ );
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Validate that a user-provided route name does not collide with
37
+ * reserved internal prefixes. Checks every dot-separated segment,
38
+ * mirroring the same rule used by isAutoGeneratedRouteName().
39
+ *
40
+ * Throws with a clear message when a reserved prefix is detected.
41
+ */
42
+ export function validateUserRouteName(name: string): void {
43
+ for (const segment of name.split(".")) {
44
+ for (const prefix of RESERVED_PREFIXES) {
45
+ if (segment.startsWith(prefix)) {
46
+ throw new Error(
47
+ `Route name "${name}" contains segment "${segment}" which uses reserved internal prefix "${prefix}". ` +
48
+ `Choose a different name to avoid collision with auto-generated route names.`,
49
+ );
50
+ }
51
+ }
52
+ }
53
+ }
@@ -19,6 +19,7 @@ export declare const ErrorBoundaryBrand: unique symbol;
19
19
  export declare const NotFoundBoundaryBrand: unique symbol;
20
20
  export declare const WhenBrand: unique symbol;
21
21
  export declare const CacheBrand: unique symbol;
22
+ export declare const TransitionBrand: unique symbol;
22
23
  export declare const IncludeBrand: unique symbol;
23
24
  export declare const UrlPatternsBrand: unique symbol;
24
25
 
@@ -34,9 +35,11 @@ export type LayoutItem = {
34
35
  * Used for type inference in urls() API
35
36
  */
36
37
  export type TypedLayoutItem<
37
- TChildRoutes extends Record<string, string> = Record<string, string>
38
+ TChildRoutes extends Record<string, any> = Record<string, string>,
39
+ TChildResponses extends Record<string, unknown> = Record<string, unknown>,
38
40
  > = LayoutItem & {
39
41
  readonly __childRoutes?: TChildRoutes;
42
+ readonly __childResponses?: TChildResponses;
40
43
  };
41
44
  export type RouteItem = {
42
45
  name: string;
@@ -51,10 +54,14 @@ export type RouteItem = {
51
54
  */
52
55
  export type TypedRouteItem<
53
56
  TName extends string = string,
54
- TPattern extends string = string
57
+ TPattern extends string = string,
58
+ TData = unknown,
59
+ TSearch = {},
55
60
  > = RouteItem & {
56
61
  readonly __name?: TName;
57
62
  readonly __pattern?: TPattern;
63
+ readonly __data?: TData;
64
+ readonly __search?: TSearch;
58
65
  };
59
66
  export type ParallelItem = {
60
67
  name: string;
@@ -114,15 +121,34 @@ export type CacheItem = {
114
121
  uses?: AllUseItems[];
115
122
  [CacheBrand]: void;
116
123
  };
124
+ export type TransitionItem = {
125
+ name: string;
126
+ type: "transition";
127
+ [TransitionBrand]: void;
128
+ };
129
+
130
+ /**
131
+ * Typed transition item that carries child routes as phantom type
132
+ * Used for type inference when transition() wraps child routes
133
+ */
134
+ export type TypedTransitionItem<
135
+ TChildRoutes extends Record<string, any> = Record<string, string>,
136
+ TChildResponses extends Record<string, unknown> = Record<string, unknown>,
137
+ > = TransitionItem & {
138
+ readonly __childRoutes?: TChildRoutes;
139
+ readonly __childResponses?: TChildResponses;
140
+ };
117
141
 
118
142
  /**
119
143
  * Typed cache item that carries child routes as phantom type
120
144
  * Used for type inference in urls() API
121
145
  */
122
146
  export type TypedCacheItem<
123
- TChildRoutes extends Record<string, string> = Record<string, string>
147
+ TChildRoutes extends Record<string, any> = Record<string, string>,
148
+ TChildResponses extends Record<string, unknown> = Record<string, unknown>,
124
149
  > = CacheItem & {
125
150
  readonly __childRoutes?: TChildRoutes;
151
+ readonly __childResponses?: TChildResponses;
126
152
  };
127
153
 
128
154
  /**
@@ -141,6 +167,15 @@ export type IncludeItem = {
141
167
  urlPrefix: string;
142
168
  namePrefix: string | undefined;
143
169
  parent: unknown; // EntryData - avoid circular import
170
+ /** Counter snapshot from pattern extraction for consistent shortCode indices */
171
+ counters?: Record<string, number>;
172
+ /** Cache profiles for DSL-time cache("profileName") resolution */
173
+ cacheProfiles?: Record<
174
+ string,
175
+ import("./cache/profile-registry.js").CacheProfile
176
+ >;
177
+ /** Root scope flag for dot-local reverse resolution */
178
+ rootScoped?: boolean;
144
179
  };
145
180
  [IncludeBrand]: void;
146
181
  };
@@ -150,13 +185,15 @@ export type IncludeItem = {
150
185
  * Used for type inference in urls() API
151
186
  */
152
187
  export type TypedIncludeItem<
153
- TRoutes extends Record<string, string> = Record<string, string>,
188
+ TRoutes extends Record<string, any> = Record<string, string>,
154
189
  TNamePrefix extends string = string,
155
- TUrlPrefix extends string = string
190
+ TUrlPrefix extends string = string,
191
+ TResponses extends Record<string, unknown> = Record<string, unknown>,
156
192
  > = IncludeItem & {
157
193
  readonly __routes?: TRoutes;
158
194
  readonly __namePrefix?: TNamePrefix;
159
195
  readonly __urlPrefix?: TUrlPrefix;
196
+ readonly __responses?: TResponses;
160
197
  };
161
198
 
162
199
  /**
@@ -174,6 +211,7 @@ export type AllUseItems =
174
211
  | ErrorBoundaryItem
175
212
  | NotFoundBoundaryItem
176
213
  | CacheItem
214
+ | TransitionItem
177
215
  | IncludeItem;
178
216
 
179
217
  /** Items that can be used inside a layout callback */
@@ -188,13 +226,17 @@ export type RouteUseItem =
188
226
  | LoadingItem
189
227
  | ErrorBoundaryItem
190
228
  | NotFoundBoundaryItem
191
- | CacheItem;
229
+ | CacheItem
230
+ | TransitionItem;
231
+ /** Items that can be used inside a response route (path.json(), etc.) */
232
+ export type ResponseRouteUseItem = MiddlewareItem | CacheItem;
192
233
  export type ParallelUseItem =
193
234
  | RevalidateItem
194
235
  | LoaderItem
195
236
  | LoadingItem
196
237
  | ErrorBoundaryItem
197
- | NotFoundBoundaryItem;
238
+ | NotFoundBoundaryItem
239
+ | TransitionItem;
198
240
  export type InterceptUseItem =
199
241
  | MiddlewareItem
200
242
  | RevalidateItem
@@ -204,5 +246,14 @@ export type InterceptUseItem =
204
246
  | NotFoundBoundaryItem
205
247
  | LayoutItem
206
248
  | RouteItem
207
- | WhenItem;
249
+ | WhenItem
250
+ | TransitionItem;
208
251
  export type LoaderUseItem = RevalidateItem | CacheItem;
252
+
253
+ /**
254
+ * Allow composition factories in use() callbacks.
255
+ * Factories return T[], which placed inside a use() callback array
256
+ * creates nested arrays like (T | T[])[]. These are flattened at
257
+ * runtime via .flat(3).
258
+ */
259
+ export type UseItems<T> = (T | readonly T[])[];
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Content Negotiation Utilities
3
+ *
4
+ * Pure functions for HTTP Accept header parsing and response type matching.
5
+ * Used by createRouter's previewMatch for content negotiation between
6
+ * RSC routes and response routes (JSON, text, image, stream, etc.).
7
+ */
8
+
9
+ // Response type -> MIME type used for Accept header matching
10
+ export const RESPONSE_TYPE_MIME: Record<string, string> = {
11
+ json: "application/json",
12
+ text: "text/plain",
13
+ xml: "application/xml",
14
+ html: "text/html",
15
+ md: "text/markdown",
16
+ };
17
+
18
+ // Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
19
+ export const MIME_RESPONSE_TYPE: Record<string, string> = Object.fromEntries(
20
+ Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]),
21
+ );
22
+
23
+ export type NamedRouteEntry =
24
+ | string
25
+ | { path: string; search?: Record<string, string> };
26
+
27
+ export function flattenNamedRoutes(
28
+ routeNames?: Record<string, NamedRouteEntry>,
29
+ ): Record<string, string> {
30
+ if (!routeNames) return {};
31
+ const flattened: Record<string, string> = {};
32
+ for (const [name, entry] of Object.entries(routeNames)) {
33
+ flattened[name] = typeof entry === "string" ? entry : entry.path;
34
+ }
35
+ return flattened;
36
+ }
37
+
38
+ export interface AcceptEntry {
39
+ mime: string;
40
+ q: number;
41
+ order: number;
42
+ }
43
+
44
+ /**
45
+ * Parse an Accept header into a sorted array of MIME entries.
46
+ * Respects q-values (default 1.0) and uses client order as tiebreaker
47
+ * when q-values are equal (matching Express/Hono behavior).
48
+ */
49
+ export function parseAcceptTypes(accept: string): AcceptEntry[] {
50
+ const entries: AcceptEntry[] = [];
51
+ const parts = accept.split(",");
52
+ for (let i = 0; i < parts.length; i++) {
53
+ const part = parts[i]!;
54
+ const segments = part.split(";");
55
+ const mime = segments[0]!.trim().toLowerCase();
56
+ if (!mime) continue;
57
+ let q = 1.0;
58
+ for (let j = 1; j < segments.length; j++) {
59
+ const param = segments[j]!.trim();
60
+ if (param.startsWith("q=")) {
61
+ q = Math.max(0, Math.min(1, Number(param.slice(2)) || 0));
62
+ }
63
+ }
64
+ entries.push({ mime, q, order: i });
65
+ }
66
+ // Sort: highest q first, then lowest client order first (stable)
67
+ entries.sort((a, b) => b.q - a.q || a.order - b.order);
68
+ return entries;
69
+ }
70
+
71
+ // Sentinel response type for RSC routes in negotiation candidates
72
+ export const RSC_RESPONSE_TYPE = "__rsc__";
73
+
74
+ /**
75
+ * Pick the best negotiate variant by walking the client's sorted Accept list.
76
+ * For each accepted MIME type (in q-value/order priority), check if any
77
+ * candidate serves that type. Wildcards match the first candidate.
78
+ * Falls back to the first candidate if nothing matches.
79
+ */
80
+ export function pickNegotiateVariant(
81
+ acceptEntries: AcceptEntry[],
82
+ candidates: Array<{ routeKey: string; responseType: string }>,
83
+ ): { routeKey: string; responseType: string } {
84
+ // Build a MIME -> candidate lookup for O(1) matching
85
+ const byCandidateMime = new Map<
86
+ string,
87
+ { routeKey: string; responseType: string }
88
+ >();
89
+ for (const c of candidates) {
90
+ const mime =
91
+ c.responseType === RSC_RESPONSE_TYPE
92
+ ? "text/html"
93
+ : RESPONSE_TYPE_MIME[c.responseType];
94
+ if (mime && !byCandidateMime.has(mime)) {
95
+ byCandidateMime.set(mime, c);
96
+ }
97
+ }
98
+
99
+ for (const entry of acceptEntries) {
100
+ if (entry.q === 0) continue;
101
+ // Wildcard matches first candidate
102
+ if (entry.mime === "*/*") return candidates[0]!;
103
+ // Type wildcard (e.g. "text/*") -- match first candidate with that type
104
+ if (entry.mime.endsWith("/*")) {
105
+ const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
106
+ for (const [mime, candidate] of byCandidateMime) {
107
+ if (mime.startsWith(typePrefix + "/")) return candidate;
108
+ }
109
+ continue;
110
+ }
111
+ const match = byCandidateMime.get(entry.mime);
112
+ if (match) return match;
113
+ }
114
+ // No match -- use first candidate as default
115
+ return candidates[0]!;
116
+ }