@rangojs/router 0.0.0-experimental.0f44aca1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
package/src/router.ts ADDED
@@ -0,0 +1,1002 @@
1
+ import { type ReactNode } from "react";
2
+ import { createCacheScope } from "./cache/cache-scope.js";
3
+ import {
4
+ setCacheProfiles,
5
+ resolveCacheProfiles,
6
+ } from "./cache/profile-registry.js";
7
+ import { isCachedFunction } from "./cache/taint.js";
8
+ import { assertClientComponent } from "./component-utils.js";
9
+ import { DefaultDocument } from "./components/DefaultDocument.js";
10
+ import type { SerializedManifest } from "./debug.js";
11
+ import { createReverse, type ReverseFunction } from "./reverse.js";
12
+ import {
13
+ registerRouteMap,
14
+ getPrecomputedEntries,
15
+ getRouterManifest,
16
+ getRouterPrecomputedEntries,
17
+ ensureRouterManifest,
18
+ } from "./route-map-builder.js";
19
+ import MapRootLayout from "./server/root-layout.js";
20
+ import type { AllUseItems } from "./route-types.js";
21
+ import type { UrlPatterns } from "./urls.js";
22
+ import {
23
+ EntryData,
24
+ InterceptSelectorContext,
25
+ getContext,
26
+ RSCRouterContext,
27
+ type MetricsStore,
28
+ } from "./server/context";
29
+ import { createHandleStore, type HandleStore } from "./server/handle-store.js";
30
+ import {
31
+ getRequestContext,
32
+ _getRequestContext,
33
+ } from "./server/request-context.js";
34
+ import type {
35
+ ErrorPhase,
36
+ HandlerContext,
37
+ LoaderDataResult,
38
+ ResolvedRouteMap,
39
+ RouteEntry,
40
+ TrailingSlashMode,
41
+ } from "./types";
42
+
43
+ // Extracted router utilities
44
+ import {
45
+ createErrorInfo,
46
+ findNearestErrorBoundary as findErrorBoundary,
47
+ findNearestNotFoundBoundary as findNotFoundBoundary,
48
+ invokeOnError,
49
+ } from "./router/error-handling.js";
50
+
51
+ // Extracted module factories
52
+ import { createSegmentWrappers } from "./router/segment-wrappers.js";
53
+ import { createMatchHandlers } from "./router/match-handlers.js";
54
+ import { buildDebugManifest } from "./router/debug-manifest.js";
55
+
56
+ import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
57
+ import { createHandlerContext } from "./router/handler-context.js";
58
+ import {
59
+ setupLoaderAccess,
60
+ setupLoaderAccessSilent,
61
+ wrapLoaderWithErrorHandling,
62
+ } from "./router/loader-resolution.js";
63
+ import { loadManifest } from "./router/manifest.js";
64
+ import { createMetricsStore } from "./router/metrics.js";
65
+ import {
66
+ parsePattern,
67
+ type MiddlewareEntry,
68
+ type MiddlewareFn,
69
+ } from "./router/middleware.js";
70
+ import {
71
+ extractStaticPrefix,
72
+ traverseBack,
73
+ } from "./router/pattern-matching.js";
74
+ import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
75
+ import { evaluateRevalidation } from "./router/revalidation.js";
76
+ import {
77
+ type RouterContext,
78
+ runWithRouterContext,
79
+ } from "./router/router-context.js";
80
+ import { resolveThemeConfig } from "./theme/constants.js";
81
+ import { resolveTimeouts } from "./router/timeout.js";
82
+
83
+ // Extracted content negotiation utilities
84
+ import { flattenNamedRoutes } from "./router/content-negotiation.js";
85
+
86
+ // Extracted router types and registry
87
+ import {
88
+ RSC_ROUTER_BRAND,
89
+ RouterRegistry,
90
+ nextRouterAutoId,
91
+ } from "./router/router-registry.js";
92
+ import type {
93
+ RSCRouterOptions,
94
+ RootLayoutProps,
95
+ } from "./router/router-options.js";
96
+ import type {
97
+ RSCRouter,
98
+ RSCRouterInternal,
99
+ RouterRequestInput,
100
+ } from "./router/router-interfaces.js";
101
+
102
+ // Extracted closure functions
103
+ import {
104
+ findLazyIncludes,
105
+ evaluateLazyEntry as _evaluateLazyEntry,
106
+ type LazyEvalDeps,
107
+ } from "./router/lazy-includes.js";
108
+ import { createFindMatch } from "./router/find-match.js";
109
+ import {
110
+ matchForPrerender as _matchForPrerender,
111
+ renderStaticSegment as _renderStaticSegment,
112
+ } from "./router/prerender-match.js";
113
+
114
+ // Re-export public types and values from extracted modules
115
+ export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
116
+ export type {
117
+ RSCRouterOptions,
118
+ RootLayoutProps,
119
+ SSRStreamMode,
120
+ SSROptions,
121
+ ResolveStreamingContext,
122
+ } from "./router/router-options.js";
123
+ export type {
124
+ RSCRouter,
125
+ RSCRouterInternal,
126
+ RouterRequestInput,
127
+ } from "./router/router-interfaces.js";
128
+ export { toInternal } from "./router/router-interfaces.js";
129
+
130
+ export function createRouter<TEnv = any>(
131
+ options: RSCRouterOptions<TEnv> = {},
132
+ ): RSCRouter<TEnv, {}> {
133
+ const {
134
+ id: userProvidedId,
135
+ $$id: injectedId,
136
+ debugPerformance = false,
137
+ document: documentOption,
138
+ defaultErrorBoundary,
139
+ defaultNotFoundBoundary,
140
+ notFound,
141
+ onError,
142
+ cache,
143
+ cacheProfiles: cacheProfilesOption,
144
+ theme: themeOption,
145
+ urls: urlsOption,
146
+ $$routeNames: staticRouteNames,
147
+ $$sourceFile: injectedSourceFile,
148
+ nonce,
149
+ version,
150
+ prefetchCacheTTL: prefetchCacheTTLOption,
151
+ warmup: warmupOption,
152
+ allowDebugManifest: allowDebugManifestOption = false,
153
+ telemetry: telemetrySink,
154
+ ssr: ssrOption,
155
+ timeout: timeoutShorthand,
156
+ timeouts: timeoutsOption,
157
+ onTimeout,
158
+ originCheck: originCheckOption,
159
+ } = options;
160
+
161
+ // Resolve telemetry sink (no-op when not configured)
162
+ const telemetry = resolveSink(telemetrySink);
163
+
164
+ // Resolve cache profiles: merge user config with guaranteed default profile.
165
+ // This resolved map is both stored on the router (for per-request context)
166
+ // and written to the global registry (for DSL-time cache("profileName")).
167
+ const resolvedCacheProfiles = resolveCacheProfiles(cacheProfilesOption);
168
+ setCacheProfiles(resolvedCacheProfiles);
169
+
170
+ // Source file: prefer Vite-injected path (zero cost), fall back to
171
+ // stack trace parsing for non-Vite environments (e.g. tests).
172
+ let __sourceFile: string | undefined = injectedSourceFile;
173
+ if (!__sourceFile) {
174
+ try {
175
+ const stack = new Error().stack;
176
+ if (stack) {
177
+ const lines = stack.split("\n");
178
+ for (const line of lines) {
179
+ const match = line.match(/\((.+?\.(ts|tsx|js|jsx)):\d+:\d+\)/);
180
+ if (
181
+ match &&
182
+ !match[1].endsWith("/router.ts") &&
183
+ !match[1].includes("@rangojs/router") &&
184
+ !match[1].includes("node_modules")
185
+ ) {
186
+ __sourceFile = match[1].startsWith("file:")
187
+ ? match[1].slice(5)
188
+ : match[1];
189
+ break;
190
+ }
191
+ }
192
+ }
193
+ } catch {}
194
+ }
195
+
196
+ // Router ID priority: explicit id > Vite-injected $$id > counter fallback.
197
+ // $$id is a hash of filename+line injected by the Vite transform at compile
198
+ // time, so it's stable across build/runtime regardless of module evaluation
199
+ // order (unlike the counter which depends on import order).
200
+ const routerId =
201
+ userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
202
+
203
+ // Resolve prefetch cache TTL (default: 300 seconds / 5 minutes)
204
+ // Clamp to a non-negative integer for valid Cache-Control max-age.
205
+ const rawTTL =
206
+ prefetchCacheTTLOption !== undefined ? prefetchCacheTTLOption : 300;
207
+ const prefetchCacheTTLSeconds =
208
+ rawTTL === false ? 0 : Math.max(0, Math.floor(rawTTL));
209
+ const prefetchCacheTTL = prefetchCacheTTLSeconds * 1000;
210
+ const prefetchCacheControl: string | false =
211
+ prefetchCacheTTLSeconds === 0
212
+ ? false
213
+ : `private, max-age=${prefetchCacheTTLSeconds}`;
214
+
215
+ // Resolve warmup enabled flag (default: true)
216
+ const warmupEnabled = warmupOption !== false;
217
+
218
+ // Resolve theme config (null if theme not enabled)
219
+ const resolvedThemeConfig = themeOption
220
+ ? resolveThemeConfig(themeOption)
221
+ : null;
222
+
223
+ // Resolve timeout config (merge shorthand + structured)
224
+ const resolvedTimeouts = resolveTimeouts(timeoutShorthand, timeoutsOption);
225
+
226
+ /**
227
+ * Wrapper for invokeOnError that binds the router's onError callback.
228
+ * Uses the shared utility from router/error-handling.ts for consistent behavior.
229
+ *
230
+ * Deduplicates via per-request WeakSet stored on the ALS request context.
231
+ * A closure-level WeakSet would silently swallow errors if the same object
232
+ * instance is thrown across separate requests (e.g. a singleton error).
233
+ */
234
+ function callOnError(
235
+ error: unknown,
236
+ phase: ErrorPhase,
237
+ context: Parameters<typeof invokeOnError<TEnv>>[3],
238
+ ): void {
239
+ if (error != null && typeof error === "object") {
240
+ const reportedErrors = _getRequestContext()?._reportedErrors;
241
+ if (reportedErrors) {
242
+ if (reportedErrors.has(error)) return;
243
+ reportedErrors.add(error);
244
+ }
245
+ }
246
+ invokeOnError(onError, error, phase, context, "Router");
247
+ }
248
+
249
+ // Validate document is a client component
250
+ if (documentOption !== undefined) {
251
+ assertClientComponent(documentOption, "document");
252
+ }
253
+
254
+ // Use default document if none provided (keeps internal name as rootLayout)
255
+ const rootLayout = documentOption ?? DefaultDocument;
256
+ const routesEntries: RouteEntry<TEnv>[] = [];
257
+ let mountIndex = 0;
258
+
259
+ // Store reference to urlpatterns for runtime manifest generation
260
+ let storedUrlPatterns: UrlPatterns<TEnv, any> | null = null;
261
+
262
+ // Global middleware storage
263
+ const globalMiddleware: MiddlewareEntry<TEnv>[] = [];
264
+
265
+ // Helper to add middleware entry
266
+ function addMiddleware(
267
+ patternOrMiddleware: string | MiddlewareFn<TEnv>,
268
+ middleware?: MiddlewareFn<TEnv>,
269
+ mountPrefix: string | null = null,
270
+ ): void {
271
+ let pattern: string | null = null;
272
+ let handler: MiddlewareFn<TEnv>;
273
+
274
+ if (typeof patternOrMiddleware === "string") {
275
+ // Pattern + middleware
276
+ pattern = patternOrMiddleware;
277
+ if (!middleware) {
278
+ throw new Error(
279
+ "Middleware function required when pattern is provided",
280
+ );
281
+ }
282
+ handler = middleware;
283
+ } else {
284
+ // Just middleware (no pattern)
285
+ handler = patternOrMiddleware;
286
+ }
287
+
288
+ // Prevent "use cache" functions from being used as middleware.
289
+ // They return data/JSX and do not call next() — silently accepting
290
+ // them would be a confusing no-op.
291
+ if (isCachedFunction(handler)) {
292
+ throw new Error(
293
+ `A "use cache" function cannot be used as middleware. ` +
294
+ `Cached functions return data and do not participate in the ` +
295
+ `middleware chain. Remove the "use cache" directive or use a ` +
296
+ `regular middleware function instead.`,
297
+ );
298
+ }
299
+
300
+ // If mount-scoped, prepend mount prefix to pattern
301
+ let fullPattern = pattern;
302
+ if (mountPrefix && pattern) {
303
+ // e.g., mountPrefix="/blog", pattern="/admin/*" → "/blog/admin/*"
304
+ fullPattern =
305
+ pattern === "*" ? `${mountPrefix}/*` : `${mountPrefix}${pattern}`;
306
+ } else if (mountPrefix && !pattern) {
307
+ // Mount-scoped middleware without pattern applies to all of mount
308
+ fullPattern = `${mountPrefix}/*`;
309
+ }
310
+
311
+ // Parse pattern into regex
312
+ let regex: RegExp | null = null;
313
+ let paramNames: string[] = [];
314
+ if (fullPattern) {
315
+ const parsed = parsePattern(fullPattern);
316
+ regex = parsed.regex;
317
+ paramNames = parsed.paramNames;
318
+ }
319
+
320
+ globalMiddleware.push({
321
+ pattern: fullPattern,
322
+ regex,
323
+ paramNames,
324
+ handler,
325
+ mountPrefix,
326
+ });
327
+ }
328
+
329
+ // Track all registered routes with their prefixes for reverse().
330
+ // Seed from injected NamedRoutes so reverse() works at module load time
331
+ // for routes that come from lazy includes.
332
+ const mergedRouteMap: Record<string, string> =
333
+ flattenNamedRoutes(staticRouteNames);
334
+
335
+ // Track names that came from the static seed so we can silently overwrite
336
+ // them during routes() registration. The gen file may be stale during HMR,
337
+ // so conflicts between seeded and runtime-registered values are expected.
338
+ const seededNames = new Set(Object.keys(mergedRouteMap));
339
+
340
+ // Lazy precomputed entries lookup: rebuilt when per-router data arrives.
341
+ // In production multi-router setups, per-router data is loaded lazily via
342
+ // ensureRouterManifest(). At createRouter() time the data isn't available yet,
343
+ // so we defer building the Map until first use and invalidate when the
344
+ // per-router source changes.
345
+ let precomputedByPrefix: Map<string, Record<string, string>> | null = null;
346
+ let precomputedSource:
347
+ | Array<{ staticPrefix: string; routes: Record<string, string> }>
348
+ | null
349
+ | undefined;
350
+
351
+ function getPrecomputedByPrefix(): Map<
352
+ string,
353
+ Record<string, string>
354
+ > | null {
355
+ const current =
356
+ getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
357
+ if (current !== precomputedSource) {
358
+ precomputedSource = current;
359
+ precomputedByPrefix = current
360
+ ? new Map(current.map((e) => [e.staticPrefix, e.routes]))
361
+ : null;
362
+ }
363
+ return precomputedByPrefix;
364
+ }
365
+
366
+ // Wrapper to pass debugPerformance to external createMetricsStore.
367
+ // Also checks per-request flag set by ctx.debugPerformance() in middleware.
368
+ const getMetricsStore = () => {
369
+ const reqCtx = _getRequestContext();
370
+ const enabled = debugPerformance || !!reqCtx?._debugPerformance;
371
+ if (!enabled) return undefined;
372
+ if (!reqCtx) {
373
+ return createMetricsStore(true);
374
+ }
375
+ reqCtx._metricsStore ??= createMetricsStore(true);
376
+ return reqCtx._metricsStore;
377
+ };
378
+
379
+ // Wrapper to pass defaults to error/notFound boundary finders
380
+ const findNearestErrorBoundary = (entry: EntryData | null) =>
381
+ findErrorBoundary(entry, defaultErrorBoundary);
382
+
383
+ const findNearestNotFoundBoundary = (entry: EntryData | null) =>
384
+ findNotFoundBoundary(entry, defaultNotFoundBoundary);
385
+
386
+ // Helper to get handleStore from request context
387
+ const getHandleStore = (): HandleStore | undefined => {
388
+ return _getRequestContext()?._handleStore;
389
+ };
390
+
391
+ // Track a pending handler promise (non-blocking).
392
+ // Attaches a side-effect .catch() to report streaming handler errors to onError
393
+ // without altering the rejection chain (React's streaming error boundary still handles it).
394
+ const trackHandler = <T>(
395
+ promise: Promise<T>,
396
+ errorContext?: {
397
+ segmentId?: string;
398
+ segmentType?: string;
399
+ },
400
+ ): Promise<T> => {
401
+ const store = getHandleStore();
402
+ const tracked = store ? store.track(promise) : promise;
403
+
404
+ // Report streaming handler errors to onError as a side-effect.
405
+ // The rejection still propagates to the RSC stream for client error boundaries.
406
+ // Captures request context eagerly (closure) so the catch handler has full context.
407
+ const reqCtx = _getRequestContext();
408
+ if (reqCtx && onError) {
409
+ tracked.catch((error) => {
410
+ callOnError(error, "handler", {
411
+ request: reqCtx.request,
412
+ url: reqCtx.url,
413
+ routeKey: reqCtx._routeName,
414
+ params: reqCtx.params as Record<string, string>,
415
+ env: reqCtx.env as TEnv,
416
+ segmentId: errorContext?.segmentId,
417
+ segmentType: errorContext?.segmentType as any,
418
+ handledByBoundary: true,
419
+ });
420
+ });
421
+ }
422
+
423
+ return tracked;
424
+ };
425
+
426
+ // Wrapper for wrapLoaderWithErrorHandling that uses router's error boundary finder
427
+ // Includes onError callback for loader error notification and telemetry emission.
428
+ function wrapLoaderPromise<T>(
429
+ promise: Promise<T>,
430
+ entry: EntryData,
431
+ segmentId: string,
432
+ pathname: string,
433
+ errorContext?: {
434
+ request: Request;
435
+ url: URL;
436
+ routeKey?: string;
437
+ params?: Record<string, string>;
438
+ env?: TEnv;
439
+ isPartial?: boolean;
440
+ requestStartTime?: number;
441
+ },
442
+ ): Promise<LoaderDataResult<T>> {
443
+ const loaderStart = telemetrySink ? performance.now() : 0;
444
+ const loaderRequestId = telemetrySink
445
+ ? errorContext?.request
446
+ ? getRequestId(errorContext.request)
447
+ : undefined
448
+ : undefined;
449
+ if (telemetrySink) {
450
+ const loaderName = segmentId.split(".").pop() || "unknown";
451
+ safeEmit(telemetry, {
452
+ type: "loader.start",
453
+ timestamp: loaderStart,
454
+ requestId: loaderRequestId,
455
+ segmentId,
456
+ loaderName,
457
+ pathname,
458
+ });
459
+ }
460
+
461
+ const result = wrapLoaderWithErrorHandling(
462
+ promise,
463
+ entry,
464
+ segmentId,
465
+ pathname,
466
+ findNearestErrorBoundary,
467
+ createErrorInfo,
468
+ // Invoke onError when loader fails
469
+ errorContext
470
+ ? (error, ctx) => {
471
+ callOnError(error, "loader", {
472
+ request: errorContext.request,
473
+ url: errorContext.url,
474
+ routeKey: errorContext.routeKey,
475
+ params: errorContext.params,
476
+ segmentId: ctx.segmentId,
477
+ segmentType: "loader",
478
+ loaderName: ctx.loaderName,
479
+ env: errorContext.env,
480
+ isPartial: errorContext.isPartial,
481
+ handledByBoundary: ctx.handledByBoundary,
482
+ requestStartTime: errorContext.requestStartTime,
483
+ });
484
+ if (telemetrySink) {
485
+ const errorObj =
486
+ error instanceof Error ? error : new Error(String(error));
487
+ safeEmit(telemetry, {
488
+ type: "loader.error",
489
+ timestamp: performance.now(),
490
+ requestId: loaderRequestId,
491
+ segmentId: ctx.segmentId,
492
+ loaderName: ctx.loaderName,
493
+ pathname,
494
+ error: errorObj,
495
+ handledByBoundary: ctx.handledByBoundary,
496
+ });
497
+ }
498
+ }
499
+ : undefined,
500
+ );
501
+
502
+ // Emit loader.end after the promise settles (fire-and-forget)
503
+ if (telemetrySink) {
504
+ const loaderName = segmentId.split(".").pop() || "unknown";
505
+ result.then((r) => {
506
+ safeEmit(telemetry, {
507
+ type: "loader.end",
508
+ timestamp: performance.now(),
509
+ requestId: loaderRequestId,
510
+ segmentId,
511
+ loaderName,
512
+ pathname,
513
+ durationMs: performance.now() - loaderStart,
514
+ ok: r.ok,
515
+ });
516
+ });
517
+ }
518
+
519
+ return result;
520
+ }
521
+
522
+ // Dependencies object for extracted segment resolution functions.
523
+ // Captures closure-bound helpers from createRouter.
524
+ const segmentDeps: SegmentResolutionDeps<TEnv> = {
525
+ wrapLoaderPromise,
526
+ trackHandler,
527
+ findNearestErrorBoundary,
528
+ findNearestNotFoundBoundary,
529
+ callOnError,
530
+ };
531
+
532
+ // Match API dependencies
533
+ const matchApiDeps: MatchApiDeps<TEnv> = {
534
+ findMatch: (pathname: string, ms?: any) => findMatch(pathname, ms),
535
+ getMetricsStore,
536
+ findInterceptForRoute: (routeKey, parentEntry, selectorContext, isAction) =>
537
+ findInterceptForRoute(routeKey, parentEntry, selectorContext, isAction),
538
+ callOnError,
539
+ findNearestErrorBoundary,
540
+ // Use per-router manifest when available, otherwise the static named map
541
+ // seeded into mergedRouteMap at router creation.
542
+ getRouteMap: () => getRouterManifest(routerId) ?? mergedRouteMap,
543
+ };
544
+
545
+ // Create segment resolution wrappers bound to segmentDeps
546
+ const {
547
+ resolveAllSegments,
548
+ resolveLoadersOnly,
549
+ resolveLoadersOnlyWithRevalidation,
550
+ buildEntryRevalidateMap,
551
+ resolveAllSegmentsWithRevalidation,
552
+ findInterceptForRoute,
553
+ resolveInterceptEntry,
554
+ resolveInterceptLoadersOnly,
555
+ } = createSegmentWrappers<TEnv>(segmentDeps);
556
+
557
+ // Lazy evaluation deps — captures closure state for extracted evaluateLazyEntry
558
+ const lazyEvalDeps: LazyEvalDeps<TEnv> = {
559
+ routesEntries,
560
+ mergedRouteMap,
561
+ nextMountIndex: () => mountIndex++,
562
+ getPrecomputedByPrefix,
563
+ };
564
+
565
+ function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
566
+ _evaluateLazyEntry(entry, lazyEvalDeps);
567
+ }
568
+
569
+ // Create findMatch with single-entry cache, bound to router state
570
+ const findMatch = createFindMatch<TEnv>({
571
+ routesEntries,
572
+ evaluateLazyEntry,
573
+ routerId,
574
+ });
575
+
576
+ // Build a RouterContext once — shared by match, matchPartial, matchForPrerender
577
+ function buildRouterContext(): RouterContext<TEnv> {
578
+ return {
579
+ findMatch,
580
+ loadManifest,
581
+ traverseBack,
582
+ createHandlerContext,
583
+ setupLoaderAccess,
584
+ setupLoaderAccessSilent,
585
+ getContext,
586
+ getMetricsStore,
587
+ createCacheScope,
588
+ findInterceptForRoute,
589
+ resolveAllSegmentsWithRevalidation,
590
+ resolveInterceptEntry,
591
+ evaluateRevalidation,
592
+ getRequestContext,
593
+ resolveAllSegments,
594
+ createHandleStore,
595
+ buildEntryRevalidateMap,
596
+ resolveLoadersOnlyWithRevalidation,
597
+ resolveInterceptLoadersOnly,
598
+ resolveLoadersOnly,
599
+ telemetry: telemetrySink,
600
+ };
601
+ }
602
+
603
+ // Prerender/static match deps (bind closure state for extracted functions)
604
+ const prerenderDeps = {
605
+ findMatch,
606
+ buildRouterContext,
607
+ mergedRouteMap,
608
+ resolveAllSegments,
609
+ };
610
+
611
+ async function matchForPrerender(
612
+ pathname: string,
613
+ params: Record<string, string>,
614
+ buildVars?: Record<string, any>,
615
+ isPassthroughRoute?: boolean,
616
+ ) {
617
+ return _matchForPrerender(
618
+ pathname,
619
+ params,
620
+ prerenderDeps,
621
+ buildVars,
622
+ isPassthroughRoute,
623
+ );
624
+ }
625
+
626
+ async function renderStaticSegment(
627
+ handler: Function,
628
+ handlerId: string,
629
+ routeName?: string,
630
+ ) {
631
+ return _renderStaticSegment<TEnv>(
632
+ handler,
633
+ handlerId,
634
+ mergedRouteMap,
635
+ routeName,
636
+ );
637
+ }
638
+
639
+ // Create match handler functions bound to router state
640
+ const matchHandlers = createMatchHandlers<TEnv>({
641
+ buildRouterContext,
642
+ callOnError,
643
+ matchApiDeps,
644
+ defaultErrorBoundary,
645
+ findMatch,
646
+ findInterceptForRoute,
647
+ telemetry: telemetrySink,
648
+ });
649
+
650
+ const { match, matchPartial, matchError, previewMatch } = matchHandlers;
651
+
652
+ /**
653
+ * Router instance
654
+ * The type system tracks accumulated routes through the builder chain
655
+ * Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
656
+ */
657
+ const router: RSCRouterInternal<TEnv, {}> = {
658
+ __brand: RSC_ROUTER_BRAND,
659
+ id: routerId,
660
+
661
+ routes(urlPatterns: UrlPatterns<TEnv>): any {
662
+ // Store reference for runtime manifest generation
663
+ storedUrlPatterns = urlPatterns;
664
+ const currentMountIndex = mountIndex++;
665
+
666
+ // Create manifest and patterns maps for route registration
667
+ const manifest = new Map<string, EntryData>();
668
+ const routePatterns = new Map<string, string>();
669
+ const patternsByPrefix = new Map<string, Map<string, string>>();
670
+ const trailingSlashMap = new Map<string, TrailingSlashMode>();
671
+
672
+ // Run the handler once to extract patterns for route matching.
673
+ // Note: loadManifest will re-run the handler to register entries in its context.
674
+ // Lazy includes are detected in the return value and handled separately.
675
+ //
676
+ // Pattern extraction must use the same mountIndex and MapRootLayout root
677
+ // parent as loadManifest so that shortCodes produced here match those at
678
+ // runtime. include() captures the current parent and counters; if those
679
+ // shortCodes diverge from the runtime tree the segment reconciliation on
680
+ // the client will see a full mismatch and remount the entire page.
681
+ const syntheticMapRoot: EntryData = {
682
+ type: "layout",
683
+ id: `#synthetic-maproot-M${currentMountIndex}`,
684
+ shortCode: `M${currentMountIndex}L0`,
685
+ parent: null,
686
+ handler: MapRootLayout,
687
+ middleware: [],
688
+ revalidate: [],
689
+ errorBoundary: [],
690
+ notFoundBoundary: [],
691
+ layout: [],
692
+ parallel: [],
693
+ intercept: [],
694
+ loader: [],
695
+ };
696
+
697
+ let handlerResult: AllUseItems[] = [];
698
+ RSCRouterContext.run(
699
+ {
700
+ manifest,
701
+ patterns: routePatterns,
702
+ patternsByPrefix,
703
+ trailingSlash: trailingSlashMap,
704
+ namespace: "root",
705
+ parent: syntheticMapRoot,
706
+ counters: {},
707
+ mountIndex: currentMountIndex,
708
+ cacheProfiles: resolvedCacheProfiles,
709
+ },
710
+ () => {
711
+ handlerResult = urlPatterns.handler() as AllUseItems[];
712
+ },
713
+ );
714
+
715
+ // Convert trailingSlash map to object for the router
716
+ const trailingSlashConfig =
717
+ trailingSlashMap.size > 0
718
+ ? Object.fromEntries(trailingSlashMap)
719
+ : undefined;
720
+
721
+ // Collect route keys that have prerender handlers (for non-trie match path)
722
+ let prerenderRouteKeys: Set<string> | undefined;
723
+ let passthroughRouteKeys: Set<string> | undefined;
724
+ for (const [name, entry] of manifest.entries()) {
725
+ if (entry.type === "route" && entry.isPrerender) {
726
+ if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
727
+ prerenderRouteKeys.add(name);
728
+ if (entry.prerenderDef?.options?.passthrough === true) {
729
+ if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
730
+ passthroughRouteKeys.add(name);
731
+ }
732
+ }
733
+ }
734
+
735
+ // Create separate RouteEntry for each URL prefix group
736
+ // This enables prefix-based short-circuit optimization
737
+ if (patternsByPrefix.size > 0) {
738
+ for (const [prefix, prefixPatterns] of patternsByPrefix.entries()) {
739
+ const routesObject: Record<string, string> = {};
740
+ for (const [name, pattern] of prefixPatterns.entries()) {
741
+ routesObject[name] = pattern;
742
+ }
743
+
744
+ routesEntries.push({
745
+ // prefix is "" because patterns already include the URL prefix
746
+ // (e.g., "/site/:locale/user1/:id" not just "/user1/:id")
747
+ prefix: "",
748
+ // staticPrefix is the actual prefix for short-circuit optimization
749
+ staticPrefix: extractStaticPrefix(prefix),
750
+ routes: routesObject as ResolvedRouteMap<any>,
751
+ trailingSlash: trailingSlashConfig,
752
+ handler: urlPatterns.handler,
753
+ mountIndex: currentMountIndex,
754
+ cacheProfiles: resolvedCacheProfiles,
755
+ ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
756
+ ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
757
+ });
758
+ }
759
+ } else {
760
+ // Fallback: no prefix grouping, use flat patterns map
761
+ const routesObject: Record<string, string> = {};
762
+ for (const [name, pattern] of routePatterns.entries()) {
763
+ routesObject[name] = pattern;
764
+ }
765
+
766
+ routesEntries.push({
767
+ prefix: "",
768
+ staticPrefix: "",
769
+ routes: routesObject as ResolvedRouteMap<any>,
770
+ trailingSlash: trailingSlashConfig,
771
+ handler: urlPatterns.handler,
772
+ mountIndex: currentMountIndex,
773
+ cacheProfiles: resolvedCacheProfiles,
774
+ ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
775
+ ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
776
+ });
777
+ }
778
+
779
+ // Build route map from registered patterns
780
+ for (const [name, pattern] of routePatterns.entries()) {
781
+ // Runtime validation: warn if key already exists with different pattern.
782
+ // Skip warning for entries that came from the static seed — the gen file
783
+ // can be stale during HMR, so runtime registration is authoritative.
784
+ const existingPattern = mergedRouteMap[name];
785
+ if (
786
+ existingPattern !== undefined &&
787
+ existingPattern !== pattern &&
788
+ !seededNames.has(name)
789
+ ) {
790
+ console.warn(
791
+ `[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
792
+ `overwriting with "${pattern}". Use unique route names to avoid this.`,
793
+ );
794
+ }
795
+ mergedRouteMap[name] = pattern;
796
+ seededNames.delete(name);
797
+ }
798
+
799
+ // Detect lazy includes in handler result and create placeholder entries
800
+ const lazyIncludes = findLazyIncludes(handlerResult);
801
+
802
+ // Create placeholder RouteEntry for each lazy include
803
+ for (const lazyInclude of lazyIncludes) {
804
+ // Compute the full URL prefix (combining parent prefix if any)
805
+ const fullPrefix = lazyInclude.context.urlPrefix
806
+ ? lazyInclude.context.urlPrefix + lazyInclude.prefix
807
+ : lazyInclude.prefix;
808
+
809
+ const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
810
+ prefix: "",
811
+ staticPrefix: extractStaticPrefix(fullPrefix),
812
+ routes: {} as ResolvedRouteMap<any>, // Empty until first match
813
+ trailingSlash: trailingSlashConfig,
814
+ handler: urlPatterns.handler,
815
+ mountIndex: mountIndex++,
816
+ // Lazy evaluation fields
817
+ lazy: true,
818
+ lazyPatterns: lazyInclude.patterns,
819
+ lazyContext: lazyInclude.context,
820
+ lazyEvaluated: false,
821
+ _lazyPrefix: lazyInclude.prefix,
822
+ };
823
+ // Insert lazy entry before any entry whose staticPrefix is a
824
+ // prefix of (but shorter than) this lazy entry's staticPrefix.
825
+ // This ensures more specific lazy includes are matched before
826
+ // less specific eager entries (e.g., "/href/nested" before "/href/:id").
827
+ const lazyPrefix = lazyEntry.staticPrefix;
828
+ let insertIndex = routesEntries.length;
829
+ if (lazyPrefix) {
830
+ for (let i = 0; i < routesEntries.length; i++) {
831
+ const existing = routesEntries[i]!;
832
+ if (
833
+ lazyPrefix.startsWith(existing.staticPrefix) &&
834
+ lazyPrefix.length > existing.staticPrefix.length
835
+ ) {
836
+ insertIndex = i;
837
+ break;
838
+ }
839
+ }
840
+ }
841
+ routesEntries.splice(insertIndex, 0, lazyEntry);
842
+ }
843
+
844
+ // Auto-register route map for runtime reverse() usage
845
+ registerRouteMap(mergedRouteMap);
846
+
847
+ return router;
848
+ },
849
+
850
+ use(
851
+ patternOrMiddleware: string | MiddlewareFn<TEnv>,
852
+ middleware?: MiddlewareFn<TEnv>,
853
+ ): any {
854
+ // Global middleware - no mount prefix
855
+ addMiddleware(patternOrMiddleware, middleware, null);
856
+ return router;
857
+ },
858
+
859
+ // Type-safe URL builder using merged route map
860
+ // Types are tracked through the builder chain via TRoutes parameter
861
+ // Seeded with static route names from the generated file (injected by Vite)
862
+ reverse: createReverse(mergedRouteMap),
863
+
864
+ // Expose accumulated route map for typeof extraction
865
+ // Returns {} initially, but builder chain accumulates specific route types
866
+ get routeMap() {
867
+ return mergedRouteMap as {};
868
+ },
869
+
870
+ // Expose rootLayout for renderSegments
871
+ rootLayout,
872
+
873
+ // Expose onError callback for error handling
874
+ onError,
875
+
876
+ // Expose cache configuration for RSC handler
877
+ cache,
878
+
879
+ // Expose notFound component for RSC handler
880
+ notFound,
881
+
882
+ // Expose resolved theme configuration for NavigationProvider and MetaTags
883
+ themeConfig: resolvedThemeConfig,
884
+
885
+ // Expose resolved cache profiles for per-request resolution
886
+ cacheProfiles: resolvedCacheProfiles,
887
+
888
+ // Expose prefetch cache settings
889
+ prefetchCacheControl,
890
+ prefetchCacheTTL,
891
+
892
+ // Expose warmup enabled flag for handler and client
893
+ warmupEnabled,
894
+
895
+ // Expose router-wide performance debugging for request-level metrics setup
896
+ debugPerformance,
897
+
898
+ // Expose debug manifest flag for handler
899
+ allowDebugManifest: allowDebugManifestOption,
900
+
901
+ // Expose origin check configuration for handler (default: enabled)
902
+ originCheck: originCheckOption ?? true,
903
+
904
+ // Expose SSR configuration for handler
905
+ ssr: ssrOption,
906
+
907
+ // Expose resolved timeouts for RSC handler
908
+ timeouts: resolvedTimeouts,
909
+ onTimeout,
910
+
911
+ // Expose global middleware for RSC handler
912
+ middleware: globalMiddleware,
913
+
914
+ match: (request: Request, input: RouterRequestInput<TEnv> = {}) => {
915
+ const env = input.env ?? ({} as TEnv);
916
+ return match(request, env);
917
+ },
918
+ matchForPrerender,
919
+ renderStaticSegment,
920
+ matchPartial: (
921
+ request: Request,
922
+ input: RouterRequestInput<TEnv> = {},
923
+ actionContext?: Parameters<typeof matchPartial>[2],
924
+ ) => {
925
+ const env = input.env ?? ({} as TEnv);
926
+ return matchPartial(request, env, actionContext);
927
+ },
928
+ matchError: (
929
+ request: Request,
930
+ input: RouterRequestInput<TEnv> | undefined,
931
+ error: unknown,
932
+ segmentType?: Parameters<typeof matchError>[3],
933
+ ) => {
934
+ const env = input?.env ?? ({} as TEnv);
935
+ return matchError(request, env, error, segmentType);
936
+ },
937
+ previewMatch: (request: Request, input: RouterRequestInput<TEnv> = {}) => {
938
+ const env = input.env ?? ({} as TEnv);
939
+ return previewMatch(request, env);
940
+ },
941
+
942
+ // Expose nonce provider for fetch
943
+ nonce,
944
+
945
+ // Expose version for fetch
946
+ version,
947
+
948
+ // Expose urlpatterns for runtime manifest generation
949
+ get urlpatterns() {
950
+ return storedUrlPatterns ?? undefined;
951
+ },
952
+
953
+ // Expose source file for per-router type generation
954
+ __sourceFile,
955
+
956
+ // RSC request handler (lazily created on first call)
957
+ fetch: (() => {
958
+ // Handler is created on first call and reused
959
+ let handler:
960
+ | ((
961
+ request: Request,
962
+ input: RouterRequestInput<TEnv>,
963
+ ) => Promise<Response>)
964
+ | null = null;
965
+
966
+ return async (request: Request, input: RouterRequestInput<TEnv> = {}) => {
967
+ // Trigger lazy import of per-router manifest data before route matching.
968
+ // No-op if data is already loaded or no loader is registered.
969
+ await ensureRouterManifest(routerId);
970
+ if (!handler) {
971
+ // Lazy import deferred to first request to avoid dev mode issues
972
+ const { createRSCHandler } = await import("./rsc/handler.js");
973
+ // Cast: handler.ts still accepts (request, env) — will be updated
974
+ // separately to accept RouterRequestInput.
975
+ handler = createRSCHandler({
976
+ router: router as any,
977
+ cache,
978
+ nonce,
979
+ version,
980
+ }) as (
981
+ request: Request,
982
+ input: RouterRequestInput<TEnv>,
983
+ ) => Promise<Response>;
984
+ }
985
+ return handler!(request, input);
986
+ };
987
+ })(),
988
+
989
+ // Debug utility for manifest inspection
990
+ debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
991
+ };
992
+
993
+ // Register router in the global registry for build-time discovery
994
+ RouterRegistry.set(routerId, router);
995
+
996
+ // If urls option was provided, auto-register them
997
+ if (urlsOption) {
998
+ return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
999
+ }
1000
+
1001
+ return router;
1002
+ }