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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +883 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4655 -747
  5. package/package.json +78 -50
  6. package/skills/cache-guide/SKILL.md +262 -0
  7. package/skills/caching/SKILL.md +54 -25
  8. package/skills/composability/SKILL.md +172 -0
  9. package/skills/debug-manifest/SKILL.md +12 -8
  10. package/skills/document-cache/SKILL.md +23 -21
  11. package/skills/fonts/SKILL.md +167 -0
  12. package/skills/hooks/SKILL.md +390 -63
  13. package/skills/host-router/SKILL.md +218 -0
  14. package/skills/intercept/SKILL.md +133 -10
  15. package/skills/layout/SKILL.md +102 -5
  16. package/skills/links/SKILL.md +239 -0
  17. package/skills/loader/SKILL.md +366 -29
  18. package/skills/middleware/SKILL.md +173 -36
  19. package/skills/mime-routes/SKILL.md +128 -0
  20. package/skills/parallel/SKILL.md +80 -3
  21. package/skills/prerender/SKILL.md +643 -0
  22. package/skills/rango/SKILL.md +86 -16
  23. package/skills/response-routes/SKILL.md +411 -0
  24. package/skills/route/SKILL.md +227 -14
  25. package/skills/router-setup/SKILL.md +225 -32
  26. package/skills/tailwind/SKILL.md +129 -0
  27. package/skills/theme/SKILL.md +12 -11
  28. package/skills/typesafety/SKILL.md +401 -75
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +10 -4
  31. package/src/bin/rango.ts +321 -0
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +20 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +201 -553
  41. package/src/browser/navigation-client.ts +124 -71
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +267 -317
  46. package/src/browser/prefetch/cache.ts +146 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +42 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +173 -73
  53. package/src/browser/react/NavigationProvider.tsx +138 -27
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +37 -0
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +49 -65
  65. package/src/browser/react/use-href.tsx +20 -188
  66. package/src/browser/react/use-link-status.ts +6 -5
  67. package/src/browser/react/use-mount.ts +31 -0
  68. package/src/browser/react/use-navigation.ts +27 -78
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +111 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +83 -0
  79. package/src/browser/server-action-bridge.ts +504 -584
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +92 -57
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +438 -0
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +35 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +10 -15
  114. package/src/client.tsx +114 -135
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +34 -19
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +165 -0
  124. package/src/host/errors.ts +97 -0
  125. package/src/host/index.ts +53 -0
  126. package/src/host/pattern-matcher.ts +214 -0
  127. package/src/host/router.ts +352 -0
  128. package/src/host/testing.ts +79 -0
  129. package/src/host/types.ts +146 -0
  130. package/src/host/utils.ts +25 -0
  131. package/src/href-client.ts +135 -49
  132. package/src/index.rsc.ts +182 -17
  133. package/src/index.ts +238 -24
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +27 -142
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +37 -0
  140. package/src/prerender/store.ts +185 -0
  141. package/src/prerender.ts +463 -0
  142. package/src/reverse.ts +330 -0
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +9 -11
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1388
  151. package/src/route-map-builder.ts +241 -112
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +70 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +371 -81
  159. package/src/router/intercept-resolution.ts +395 -0
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +155 -32
  164. package/src/router/match-api.ts +620 -0
  165. package/src/router/match-context.ts +5 -3
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +382 -9
  169. package/src/router/match-middleware/cache-store.ts +51 -22
  170. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  171. package/src/router/match-middleware/segment-resolution.ts +24 -6
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -29
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +321 -30
  179. package/src/router/prerender-match.ts +400 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1241 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -0
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +239 -0
  197. package/src/router/types.ts +77 -3
  198. package/src/router.ts +688 -3656
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +786 -760
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +5 -25
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +235 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +40 -14
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +57 -61
  217. package/src/server/context.ts +202 -51
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +422 -70
  223. package/src/server.ts +36 -120
  224. package/src/ssr/index.tsx +157 -26
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1577
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -726
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -782
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -3
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/href.ts +0 -255
  294. package/src/vite/expose-handle-id.ts +0 -209
  295. package/src/vite/expose-loader-id.ts +0 -357
  296. package/src/vite/expose-location-state-id.ts +0 -177
  297. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -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 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[];