@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
@@ -0,0 +1,620 @@
1
+ /**
2
+ * Match API
3
+ *
4
+ * Extracted from createRouter closure. Contains match context creation functions
5
+ * and the matchError function for error boundary resolution.
6
+ */
7
+
8
+ import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
9
+ import { RouteNotFoundError } from "../errors";
10
+ import {
11
+ createErrorInfo,
12
+ createErrorSegment,
13
+ findNearestErrorBoundary as findErrorBoundary,
14
+ } from "./error-handling.js";
15
+ import {
16
+ createHandlerContext,
17
+ stripInternalParams,
18
+ } from "./handler-context.js";
19
+ import { setupLoaderAccess } from "./loader-resolution.js";
20
+ import { loadManifest, clearManifestCache } from "./manifest.js";
21
+ import { collectRouteMiddleware } from "./middleware.js";
22
+ import { traverseBack } from "./pattern-matching.js";
23
+ import { DefaultErrorFallback } from "../default-error-boundary.js";
24
+ import {
25
+ EntryData,
26
+ LoaderEntry,
27
+ getContext,
28
+ InterceptSelectorContext,
29
+ } from "../server/context";
30
+ import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
31
+ import type { ReactNode } from "react";
32
+ import type { MatchContext } from "./match-context.js";
33
+ import type { MatchApiDeps, ActionContext } from "./types.js";
34
+ import {
35
+ getRequestContext,
36
+ setRequestContextPrevRouteKey,
37
+ } from "../server/request-context.js";
38
+ import { isAutoGeneratedRouteName } from "../route-name.js";
39
+ import { debugLog, debugWarn } from "./logging.js";
40
+
41
+ /**
42
+ * Create match context for full requests (document/SSR).
43
+ */
44
+ export async function createMatchContextForFull<TEnv>(
45
+ request: Request,
46
+ env: TEnv,
47
+ deps: MatchApiDeps<TEnv>,
48
+ findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
49
+ ): Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }> {
50
+ const url = new URL(request.url);
51
+ const pathname = url.pathname;
52
+
53
+ const metricsStore = deps.getMetricsStore();
54
+
55
+ const routeMatchStart = metricsStore ? performance.now() : 0;
56
+ const matched = deps.findMatch(pathname, metricsStore);
57
+ if (metricsStore) {
58
+ metricsStore.metrics.push({
59
+ label: "route-matching",
60
+ duration: performance.now() - routeMatchStart,
61
+ startTime: routeMatchStart - metricsStore.requestStart,
62
+ });
63
+ }
64
+
65
+ if (!matched) {
66
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
67
+ cause: { pathname, method: request.method },
68
+ });
69
+ }
70
+
71
+ if (matched.redirectTo) {
72
+ return {
73
+ type: "redirect",
74
+ redirectUrl: matched.redirectTo + url.search,
75
+ };
76
+ }
77
+
78
+ const manifestStart = metricsStore ? performance.now() : 0;
79
+ const manifestEntry = await loadManifest(
80
+ matched.entry,
81
+ matched.routeKey,
82
+ pathname,
83
+ metricsStore,
84
+ true,
85
+ );
86
+ if (metricsStore) {
87
+ metricsStore.metrics.push({
88
+ label: "manifest-loading",
89
+ duration: performance.now() - manifestStart,
90
+ startTime: manifestStart - metricsStore.requestStart,
91
+ });
92
+ }
93
+
94
+ if (
95
+ manifestEntry.type === "route" &&
96
+ manifestEntry.prerenderDef?.options?.passthrough === true
97
+ ) {
98
+ matched.pt = true;
99
+ }
100
+
101
+ const routeMiddleware = collectRouteMiddleware(
102
+ traverseBack(manifestEntry),
103
+ matched.params,
104
+ );
105
+
106
+ // Clean URL without internal _rsc* params for userland access
107
+ const cleanUrl = stripInternalParams(url);
108
+
109
+ const handlerContext = createHandlerContext(
110
+ matched.params,
111
+ request,
112
+ cleanUrl.searchParams,
113
+ pathname,
114
+ cleanUrl,
115
+ env,
116
+ deps.getRouteMap(),
117
+ matched.routeKey,
118
+ matched.responseType,
119
+ matched.pt === true,
120
+ );
121
+
122
+ const loaderPromises = new Map<string, Promise<any>>();
123
+ setupLoaderAccess(handlerContext, loaderPromises);
124
+
125
+ const Store = getContext().getOrCreateStore(matched.routeKey);
126
+ Store.run = <T>(fn: () => T | Promise<T>) =>
127
+ getContext().runWithStore(
128
+ Store,
129
+ Store.namespace || "#router",
130
+ Store.parent,
131
+ fn,
132
+ );
133
+ if (metricsStore) {
134
+ Store.metrics = metricsStore;
135
+ }
136
+
137
+ const entries = [...traverseBack(manifestEntry)];
138
+ let cacheScope: CacheScope | null = null;
139
+ for (const entry of entries) {
140
+ if (entry.cache) {
141
+ cacheScope = createCacheScope(entry.cache, cacheScope);
142
+ }
143
+ }
144
+
145
+ return {
146
+ request,
147
+ url: cleanUrl,
148
+ pathname,
149
+ env,
150
+ clientSegmentIds: [],
151
+ clientSegmentSet: new Set(),
152
+ stale: false,
153
+ prevUrl: cleanUrl,
154
+ prevParams: {},
155
+ prevMatch: null,
156
+ matched,
157
+ manifestEntry,
158
+ entries,
159
+ routeKey: matched.routeKey,
160
+ localRouteName: matched.routeKey.includes(".")
161
+ ? matched.routeKey.split(".").pop()!
162
+ : matched.routeKey,
163
+ handlerContext,
164
+ loaderPromises,
165
+ routeMap: deps.getRouteMap(),
166
+ metricsStore,
167
+ Store,
168
+ interceptContextMatch: null,
169
+ interceptSelectorContext: {
170
+ from: cleanUrl,
171
+ to: cleanUrl,
172
+ params: matched.params,
173
+ request,
174
+ env,
175
+ segments: { path: [], ids: [] },
176
+ toRouteName:
177
+ matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
178
+ ? matched.routeKey
179
+ : undefined,
180
+ },
181
+ isSameRouteNavigation: false,
182
+ interceptResult: null,
183
+ cacheScope,
184
+ isIntercept: false,
185
+ actionContext: undefined,
186
+ isAction: false,
187
+ routeMiddleware,
188
+ isFullMatch: true,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Create match context for partial requests (navigation/actions).
194
+ */
195
+ export async function createMatchContextForPartial<TEnv>(
196
+ request: Request,
197
+ env: TEnv,
198
+ deps: MatchApiDeps<TEnv>,
199
+ findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
200
+ actionContext?: ActionContext,
201
+ ): Promise<MatchContext<TEnv> | null> {
202
+ const url = new URL(request.url);
203
+ const pathname = url.pathname;
204
+
205
+ const metricsStore = deps.getMetricsStore();
206
+
207
+ const clientSegmentIds =
208
+ url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
209
+ const stale = url.searchParams.get("_rsc_stale") === "true";
210
+ const previousUrl =
211
+ request.headers.get("X-RSC-Router-Client-Path") ||
212
+ request.headers.get("Referer");
213
+ const interceptSourceUrl = request.headers.get(
214
+ "X-RSC-Router-Intercept-Source",
215
+ );
216
+
217
+ // HMR: clear manifest cache so stale handler references are discarded
218
+ if (request.headers.get("X-RSC-HMR")) {
219
+ clearManifestCache();
220
+ }
221
+
222
+ if (!previousUrl) {
223
+ return null;
224
+ }
225
+
226
+ let prevUrl: URL;
227
+ try {
228
+ prevUrl = new URL(previousUrl, url.origin);
229
+ } catch {
230
+ return null;
231
+ }
232
+
233
+ let interceptContextUrl: URL;
234
+ try {
235
+ interceptContextUrl = interceptSourceUrl
236
+ ? new URL(interceptSourceUrl, url.origin)
237
+ : prevUrl;
238
+ } catch {
239
+ interceptContextUrl = prevUrl;
240
+ }
241
+
242
+ const routeMatchStart = metricsStore ? performance.now() : 0;
243
+ const prevMatch = deps.findMatch(prevUrl.pathname);
244
+ const prevParams = prevMatch?.params || {};
245
+ const interceptContextMatch = interceptSourceUrl
246
+ ? deps.findMatch(interceptContextUrl.pathname)
247
+ : prevMatch;
248
+
249
+ const matched = deps.findMatch(pathname, metricsStore);
250
+
251
+ if (metricsStore) {
252
+ metricsStore.metrics.push({
253
+ label: "route-matching",
254
+ duration: performance.now() - routeMatchStart,
255
+ startTime: routeMatchStart - metricsStore.requestStart,
256
+ });
257
+ }
258
+
259
+ if (!matched) {
260
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
261
+ cause: { pathname, method: request.method, previousUrl },
262
+ });
263
+ }
264
+
265
+ if (matched.redirectTo) {
266
+ return null;
267
+ }
268
+
269
+ if (prevMatch && prevMatch.entry !== matched.entry && !matched.pr) {
270
+ debugLog("matchPartial", "route group changed", {
271
+ from: prevMatch.routeKey,
272
+ to: matched.routeKey,
273
+ });
274
+ }
275
+
276
+ const manifestStart = metricsStore ? performance.now() : 0;
277
+ const manifestEntry = await loadManifest(
278
+ matched.entry,
279
+ matched.routeKey,
280
+ pathname,
281
+ metricsStore,
282
+ false,
283
+ );
284
+ if (metricsStore) {
285
+ metricsStore.metrics.push({
286
+ label: "manifest-loading",
287
+ duration: performance.now() - manifestStart,
288
+ startTime: manifestStart - metricsStore.requestStart,
289
+ });
290
+ }
291
+
292
+ if (
293
+ manifestEntry.type === "route" &&
294
+ manifestEntry.prerenderDef?.options?.passthrough === true
295
+ ) {
296
+ matched.pt = true;
297
+ }
298
+
299
+ const routeMiddleware = collectRouteMiddleware(
300
+ traverseBack(manifestEntry),
301
+ matched.params,
302
+ );
303
+
304
+ // Clean URL without internal _rsc* params for userland access
305
+ const cleanUrl = stripInternalParams(url);
306
+
307
+ const handlerContext = createHandlerContext(
308
+ matched.params,
309
+ request,
310
+ cleanUrl.searchParams,
311
+ pathname,
312
+ cleanUrl,
313
+ env,
314
+ deps.getRouteMap(),
315
+ matched.routeKey,
316
+ matched.responseType,
317
+ matched.pt === true,
318
+ );
319
+
320
+ const clientSegmentSet = new Set(clientSegmentIds);
321
+ debugLog("matchPartial", "client segments", {
322
+ segments: Array.from(clientSegmentSet),
323
+ });
324
+
325
+ const loaderPromises = new Map<string, Promise<any>>();
326
+ setupLoaderAccess(handlerContext, loaderPromises);
327
+
328
+ const Store = getContext().getOrCreateStore(matched.routeKey);
329
+ Store.run = <T>(fn: () => T | Promise<T>) =>
330
+ getContext().runWithStore(
331
+ Store,
332
+ Store.namespace || "#router",
333
+ Store.parent,
334
+ fn,
335
+ );
336
+ if (metricsStore) {
337
+ Store.metrics = metricsStore;
338
+ }
339
+
340
+ const isSameRouteNavigation = !!(
341
+ interceptContextMatch && interceptContextMatch.routeKey === matched.routeKey
342
+ );
343
+
344
+ if (interceptSourceUrl) {
345
+ debugLog("matchPartial.intercept", "intercept context detected", {
346
+ currentUrl: pathname,
347
+ interceptSource: interceptSourceUrl,
348
+ contextRoute: interceptContextMatch?.routeKey,
349
+ currentRoute: matched.routeKey,
350
+ sameRouteNavigation: isSameRouteNavigation,
351
+ });
352
+ }
353
+
354
+ const localRouteName = matched.routeKey.includes(".")
355
+ ? matched.routeKey.split(".").pop()!
356
+ : matched.routeKey;
357
+
358
+ const filteredSegmentIds = clientSegmentIds.filter((id) => {
359
+ if (id.includes(".@")) return false;
360
+ if (/D\d+\./.test(id)) return false;
361
+ return true;
362
+ });
363
+ const effectiveFromUrl = interceptSourceUrl ? interceptContextUrl : prevUrl;
364
+ const effectiveFromMatch = interceptSourceUrl
365
+ ? interceptContextMatch
366
+ : prevMatch;
367
+
368
+ // Store previous route key on the request context for revalidation
369
+ // fromRouteName. Uses effectiveFromMatch so intercept-source navigations
370
+ // see the intercept origin route, not the plain previous URL route.
371
+ setRequestContextPrevRouteKey(effectiveFromMatch?.routeKey);
372
+
373
+ const interceptSelectorContext: InterceptSelectorContext = {
374
+ from: effectiveFromUrl,
375
+ to: cleanUrl,
376
+ params: matched.params,
377
+ request,
378
+ env,
379
+ segments: {
380
+ path: effectiveFromUrl.pathname.split("/").filter(Boolean),
381
+ ids: filteredSegmentIds,
382
+ },
383
+ fromRouteName:
384
+ effectiveFromMatch?.routeKey &&
385
+ !isAutoGeneratedRouteName(effectiveFromMatch.routeKey)
386
+ ? effectiveFromMatch.routeKey
387
+ : undefined,
388
+ toRouteName:
389
+ matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
390
+ ? matched.routeKey
391
+ : undefined,
392
+ };
393
+ const isAction = !!actionContext;
394
+
395
+ const clientHasInterceptSegments = [...clientSegmentSet].some((id) =>
396
+ id.includes(".@"),
397
+ );
398
+ const skipInterceptForAction = isAction && !clientHasInterceptSegments;
399
+ const interceptResult =
400
+ isSameRouteNavigation || skipInterceptForAction
401
+ ? null
402
+ : findInterceptForRoute(
403
+ matched.routeKey,
404
+ manifestEntry.parent,
405
+ interceptSelectorContext,
406
+ isAction,
407
+ ) ||
408
+ (localRouteName !== matched.routeKey
409
+ ? findInterceptForRoute(
410
+ localRouteName,
411
+ manifestEntry.parent,
412
+ interceptSelectorContext,
413
+ isAction,
414
+ )
415
+ : null);
416
+
417
+ if (
418
+ isSameRouteNavigation &&
419
+ manifestEntry.type === "route" &&
420
+ interceptSourceUrl
421
+ ) {
422
+ debugLog("matchPartial.intercept", "forcing route segment render", {
423
+ segmentId: manifestEntry.shortCode,
424
+ });
425
+ clientSegmentSet.delete(manifestEntry.shortCode);
426
+ }
427
+
428
+ const entries = [...traverseBack(manifestEntry)];
429
+ let cacheScope: CacheScope | null = null;
430
+ for (const entry of entries) {
431
+ if (entry.cache) {
432
+ cacheScope = createCacheScope(entry.cache, cacheScope);
433
+ }
434
+ }
435
+
436
+ const isIntercept = !!interceptResult;
437
+
438
+ return {
439
+ request,
440
+ url: cleanUrl,
441
+ pathname,
442
+ env,
443
+ clientSegmentIds,
444
+ clientSegmentSet,
445
+ stale,
446
+ prevUrl,
447
+ prevParams,
448
+ prevMatch,
449
+ matched,
450
+ manifestEntry,
451
+ entries,
452
+ routeKey: matched.routeKey,
453
+ localRouteName,
454
+ handlerContext,
455
+ loaderPromises,
456
+ routeMap: deps.getRouteMap(),
457
+ metricsStore,
458
+ Store,
459
+ interceptContextMatch,
460
+ interceptSelectorContext,
461
+ isSameRouteNavigation,
462
+ interceptResult,
463
+ cacheScope,
464
+ isIntercept,
465
+ actionContext,
466
+ isAction,
467
+ routeMiddleware,
468
+ isFullMatch: false,
469
+ };
470
+ }
471
+
472
+ /**
473
+ * Match an error to the nearest error boundary and return error segments.
474
+ */
475
+ export async function matchError<TEnv>(
476
+ request: Request,
477
+ _context: TEnv,
478
+ error: unknown,
479
+ deps: MatchApiDeps<TEnv>,
480
+ defaultErrorBoundary: ReactNode | ErrorBoundaryHandler | undefined,
481
+ segmentType: ErrorInfo["segmentType"] = "route",
482
+ ): Promise<MatchResult | null> {
483
+ const url = new URL(request.url);
484
+ const pathname = url.pathname;
485
+
486
+ debugLog("matchError", "matching error", { pathname });
487
+
488
+ const matched = deps.findMatch(pathname);
489
+ if (!matched) {
490
+ debugWarn("matchError", "no route matched", { pathname });
491
+ return null;
492
+ }
493
+
494
+ const manifestEntry = await loadManifest(
495
+ matched.entry,
496
+ matched.routeKey,
497
+ pathname,
498
+ undefined,
499
+ false,
500
+ );
501
+
502
+ const findNearestErrorBoundary = (entry: EntryData | null) =>
503
+ findErrorBoundary(entry, defaultErrorBoundary);
504
+
505
+ const fallback = findNearestErrorBoundary(manifestEntry);
506
+ const useDefaultFallback = !fallback;
507
+
508
+ const errorInfo = createErrorInfo(
509
+ error,
510
+ manifestEntry.shortCode || "unknown",
511
+ segmentType,
512
+ );
513
+
514
+ let entryWithBoundary: EntryData | null = null;
515
+ let current: EntryData | null = manifestEntry;
516
+ while (current) {
517
+ if (current.errorBoundary && current.errorBoundary.length > 0) {
518
+ entryWithBoundary = current;
519
+ break;
520
+ }
521
+
522
+ if (current.layout && current.layout.length > 0) {
523
+ for (const orphan of current.layout) {
524
+ if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
525
+ entryWithBoundary = orphan;
526
+ break;
527
+ }
528
+ }
529
+ if (entryWithBoundary) break;
530
+ }
531
+
532
+ current = current.parent;
533
+ }
534
+
535
+ let boundaryEntry: EntryData;
536
+ let outletEntry: EntryData;
537
+
538
+ if (entryWithBoundary) {
539
+ boundaryEntry = entryWithBoundary;
540
+
541
+ outletEntry = manifestEntry;
542
+ current = manifestEntry;
543
+
544
+ while (current) {
545
+ if (current.parent === boundaryEntry) {
546
+ outletEntry = current;
547
+ break;
548
+ }
549
+
550
+ if (current.parent && current.parent.layout) {
551
+ if (current.parent.layout.includes(boundaryEntry)) {
552
+ outletEntry = current;
553
+ break;
554
+ }
555
+ }
556
+
557
+ current = current.parent;
558
+ }
559
+ } else {
560
+ let rootEntry = manifestEntry;
561
+ while (rootEntry.parent) {
562
+ rootEntry = rootEntry.parent;
563
+ }
564
+ boundaryEntry = rootEntry;
565
+ outletEntry = rootEntry;
566
+ }
567
+
568
+ const matchedIds: string[] = [];
569
+
570
+ current = boundaryEntry;
571
+ const stack: {
572
+ shortCode: string;
573
+ loaderEntries: LoaderEntry[];
574
+ }[] = [];
575
+ while (current) {
576
+ if (current.shortCode) {
577
+ stack.push({
578
+ shortCode: current.shortCode,
579
+ loaderEntries: current.loader || [],
580
+ });
581
+ }
582
+ current = current.parent;
583
+ }
584
+ for (const item of stack.reverse()) {
585
+ matchedIds.push(item.shortCode);
586
+ for (let i = 0; i < item.loaderEntries.length; i++) {
587
+ const loaderId = item.loaderEntries[i].loader?.$$id || "unknown";
588
+ matchedIds.push(`${item.shortCode}D${i}.${loaderId}`);
589
+ }
590
+ }
591
+
592
+ const reqCtx = getRequestContext();
593
+ if (reqCtx) {
594
+ reqCtx._setStatus(500);
595
+ }
596
+
597
+ const effectiveFallback = fallback || DefaultErrorFallback;
598
+ const errorSegment = createErrorSegment(
599
+ errorInfo,
600
+ effectiveFallback,
601
+ outletEntry,
602
+ matched.params,
603
+ );
604
+
605
+ if (useDefaultFallback) {
606
+ debugLog("matchError", "using default error boundary");
607
+ }
608
+
609
+ debugLog("matchError", "resolved boundary", {
610
+ boundarySegmentId: boundaryEntry.shortCode,
611
+ outletSegmentId: outletEntry.shortCode,
612
+ });
613
+
614
+ return {
615
+ segments: [errorSegment],
616
+ matched: matchedIds,
617
+ diff: [errorSegment.id],
618
+ params: matched.params,
619
+ };
620
+ }
@@ -45,7 +45,7 @@
45
45
  * - request, url, pathname: The incoming HTTP request
46
46
  *
47
47
  * Environment:
48
- * - env, bindings: Server environment (Cloudflare bindings, etc.)
48
+ * - env: Server environment (Cloudflare bindings, etc.)
49
49
  *
50
50
  * Client State (from RSC request headers):
51
51
  * - clientSegmentIds: Segments the client currently has
@@ -140,7 +140,6 @@ export interface MatchContext<TEnv = any> {
140
140
 
141
141
  // Environment
142
142
  env: TEnv;
143
- bindings: TEnv;
144
143
 
145
144
  // Client state
146
145
  clientSegmentIds: string[];
@@ -163,7 +162,7 @@ export interface MatchContext<TEnv = any> {
163
162
  handlerContext: HandlerContext<any, TEnv>;
164
163
  loaderPromises: Map<string, Promise<any>>;
165
164
 
166
- // Route map for server-side ctx.href() resolution
165
+ // Route map for server-side ctx.reverse() resolution
167
166
  routeMap: Record<string, string>;
168
167
 
169
168
  // Metrics
@@ -211,6 +210,9 @@ export interface MatchPipelineState {
211
210
  // Whether cache should be revalidated (SWR)
212
211
  shouldRevalidate?: boolean;
213
212
 
213
+ // Source of cache hit ("runtime" or "prerender")
214
+ cacheSource?: "runtime" | "prerender";
215
+
214
216
  // Resolved segments from pipeline
215
217
  segments: ResolvedSegment[];
216
218
  matchedIds: string[];