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

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 (316) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +5091 -941
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +61 -52
  7. package/skills/breadcrumbs/SKILL.md +250 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +167 -0
  14. package/skills/handler-use/SKILL.md +362 -0
  15. package/skills/hooks/SKILL.md +340 -72
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/intercept/SKILL.md +151 -8
  18. package/skills/layout/SKILL.md +122 -3
  19. package/skills/links/SKILL.md +92 -31
  20. package/skills/loader/SKILL.md +404 -44
  21. package/skills/middleware/SKILL.md +205 -37
  22. package/skills/migrate-nextjs/SKILL.md +560 -0
  23. package/skills/migrate-react-router/SKILL.md +765 -0
  24. package/skills/mime-routes/SKILL.md +128 -0
  25. package/skills/parallel/SKILL.md +263 -1
  26. package/skills/prerender/SKILL.md +685 -0
  27. package/skills/rango/SKILL.md +87 -16
  28. package/skills/response-routes/SKILL.md +411 -0
  29. package/skills/route/SKILL.md +281 -14
  30. package/skills/router-setup/SKILL.md +210 -32
  31. package/skills/tailwind/SKILL.md +129 -0
  32. package/skills/theme/SKILL.md +9 -8
  33. package/skills/typesafety/SKILL.md +328 -89
  34. package/skills/use-cache/SKILL.md +324 -0
  35. package/src/__internal.ts +102 -4
  36. package/src/bin/rango.ts +321 -0
  37. package/src/browser/action-coordinator.ts +97 -0
  38. package/src/browser/action-response-classifier.ts +99 -0
  39. package/src/browser/app-version.ts +14 -0
  40. package/src/browser/event-controller.ts +92 -64
  41. package/src/browser/history-state.ts +80 -0
  42. package/src/browser/intercept-utils.ts +52 -0
  43. package/src/browser/link-interceptor.ts +24 -4
  44. package/src/browser/logging.ts +55 -0
  45. package/src/browser/merge-segment-loaders.ts +20 -12
  46. package/src/browser/navigation-bridge.ts +317 -560
  47. package/src/browser/navigation-client.ts +206 -68
  48. package/src/browser/navigation-store.ts +73 -55
  49. package/src/browser/navigation-transaction.ts +297 -0
  50. package/src/browser/network-error-handler.ts +61 -0
  51. package/src/browser/partial-update.ts +343 -316
  52. package/src/browser/prefetch/cache.ts +216 -0
  53. package/src/browser/prefetch/fetch.ts +206 -0
  54. package/src/browser/prefetch/observer.ts +65 -0
  55. package/src/browser/prefetch/policy.ts +48 -0
  56. package/src/browser/prefetch/queue.ts +160 -0
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +112 -0
  59. package/src/browser/react/Link.tsx +253 -74
  60. package/src/browser/react/NavigationProvider.tsx +91 -11
  61. package/src/browser/react/context.ts +11 -0
  62. package/src/browser/react/filter-segment-order.ts +11 -0
  63. package/src/browser/react/index.ts +12 -12
  64. package/src/browser/react/location-state-shared.ts +95 -53
  65. package/src/browser/react/location-state.ts +60 -15
  66. package/src/browser/react/mount-context.ts +6 -1
  67. package/src/browser/react/nonce-context.ts +23 -0
  68. package/src/browser/react/shallow-equal.ts +27 -0
  69. package/src/browser/react/use-action.ts +29 -51
  70. package/src/browser/react/use-client-cache.ts +5 -3
  71. package/src/browser/react/use-handle.ts +30 -126
  72. package/src/browser/react/use-href.tsx +2 -2
  73. package/src/browser/react/use-link-status.ts +6 -5
  74. package/src/browser/react/use-navigation.ts +44 -65
  75. package/src/browser/react/use-params.ts +75 -0
  76. package/src/browser/react/use-pathname.ts +47 -0
  77. package/src/browser/react/use-router.ts +76 -0
  78. package/src/browser/react/use-search-params.ts +56 -0
  79. package/src/browser/react/use-segments.ts +80 -97
  80. package/src/browser/response-adapter.ts +73 -0
  81. package/src/browser/rsc-router.tsx +214 -58
  82. package/src/browser/scroll-restoration.ts +127 -52
  83. package/src/browser/segment-reconciler.ts +243 -0
  84. package/src/browser/segment-structure-assert.ts +16 -0
  85. package/src/browser/server-action-bridge.ts +510 -603
  86. package/src/browser/shallow.ts +6 -1
  87. package/src/browser/types.ts +141 -48
  88. package/src/browser/validate-redirect-origin.ts +29 -0
  89. package/src/build/generate-manifest.ts +235 -24
  90. package/src/build/generate-route-types.ts +39 -0
  91. package/src/build/index.ts +13 -0
  92. package/src/build/route-trie.ts +291 -0
  93. package/src/build/route-types/ast-helpers.ts +25 -0
  94. package/src/build/route-types/ast-route-extraction.ts +98 -0
  95. package/src/build/route-types/codegen.ts +102 -0
  96. package/src/build/route-types/include-resolution.ts +418 -0
  97. package/src/build/route-types/param-extraction.ts +48 -0
  98. package/src/build/route-types/per-module-writer.ts +128 -0
  99. package/src/build/route-types/router-processing.ts +618 -0
  100. package/src/build/route-types/scan-filter.ts +85 -0
  101. package/src/build/runtime-discovery.ts +231 -0
  102. package/src/cache/background-task.ts +34 -0
  103. package/src/cache/cache-key-utils.ts +44 -0
  104. package/src/cache/cache-policy.ts +125 -0
  105. package/src/cache/cache-runtime.ts +342 -0
  106. package/src/cache/cache-scope.ts +167 -309
  107. package/src/cache/cf/cf-cache-store.ts +571 -17
  108. package/src/cache/cf/index.ts +13 -3
  109. package/src/cache/document-cache.ts +116 -77
  110. package/src/cache/handle-capture.ts +81 -0
  111. package/src/cache/handle-snapshot.ts +41 -0
  112. package/src/cache/index.ts +1 -15
  113. package/src/cache/memory-segment-store.ts +191 -13
  114. package/src/cache/profile-registry.ts +73 -0
  115. package/src/cache/read-through-swr.ts +134 -0
  116. package/src/cache/segment-codec.ts +256 -0
  117. package/src/cache/taint.ts +153 -0
  118. package/src/cache/types.ts +72 -122
  119. package/src/client.rsc.tsx +3 -1
  120. package/src/client.tsx +135 -301
  121. package/src/component-utils.ts +4 -4
  122. package/src/components/DefaultDocument.tsx +5 -1
  123. package/src/context-var.ts +156 -0
  124. package/src/debug.ts +19 -9
  125. package/src/errors.ts +108 -2
  126. package/src/handle.ts +55 -29
  127. package/src/handles/MetaTags.tsx +73 -20
  128. package/src/handles/breadcrumbs.ts +66 -0
  129. package/src/handles/index.ts +1 -0
  130. package/src/handles/meta.ts +30 -13
  131. package/src/host/cookie-handler.ts +21 -15
  132. package/src/host/errors.ts +8 -8
  133. package/src/host/index.ts +4 -7
  134. package/src/host/pattern-matcher.ts +27 -27
  135. package/src/host/router.ts +61 -39
  136. package/src/host/testing.ts +8 -8
  137. package/src/host/types.ts +15 -7
  138. package/src/host/utils.ts +1 -1
  139. package/src/href-client.ts +119 -29
  140. package/src/index.rsc.ts +155 -19
  141. package/src/index.ts +251 -30
  142. package/src/internal-debug.ts +11 -0
  143. package/src/loader.rsc.ts +26 -157
  144. package/src/loader.ts +27 -10
  145. package/src/network-error-thrower.tsx +3 -1
  146. package/src/outlet-provider.tsx +45 -0
  147. package/src/prerender/param-hash.ts +37 -0
  148. package/src/prerender/store.ts +186 -0
  149. package/src/prerender.ts +524 -0
  150. package/src/reverse.ts +354 -0
  151. package/src/root-error-boundary.tsx +41 -29
  152. package/src/route-content-wrapper.tsx +7 -4
  153. package/src/route-definition/dsl-helpers.ts +1121 -0
  154. package/src/route-definition/helper-factories.ts +200 -0
  155. package/src/route-definition/helpers-types.ts +478 -0
  156. package/src/route-definition/index.ts +55 -0
  157. package/src/route-definition/redirect.ts +101 -0
  158. package/src/route-definition/resolve-handler-use.ts +149 -0
  159. package/src/route-definition.ts +1 -1428
  160. package/src/route-map-builder.ts +217 -123
  161. package/src/route-name.ts +53 -0
  162. package/src/route-types.ts +77 -8
  163. package/src/router/content-negotiation.ts +215 -0
  164. package/src/router/debug-manifest.ts +72 -0
  165. package/src/router/error-handling.ts +9 -9
  166. package/src/router/find-match.ts +160 -0
  167. package/src/router/handler-context.ts +438 -86
  168. package/src/router/intercept-resolution.ts +402 -0
  169. package/src/router/lazy-includes.ts +237 -0
  170. package/src/router/loader-resolution.ts +356 -128
  171. package/src/router/logging.ts +251 -0
  172. package/src/router/manifest.ts +163 -35
  173. package/src/router/match-api.ts +555 -0
  174. package/src/router/match-context.ts +5 -3
  175. package/src/router/match-handlers.ts +440 -0
  176. package/src/router/match-middleware/background-revalidation.ts +108 -93
  177. package/src/router/match-middleware/cache-lookup.ts +460 -10
  178. package/src/router/match-middleware/cache-store.ts +98 -26
  179. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  180. package/src/router/match-middleware/segment-resolution.ts +80 -6
  181. package/src/router/match-pipelines.ts +10 -45
  182. package/src/router/match-result.ts +135 -35
  183. package/src/router/metrics.ts +240 -15
  184. package/src/router/middleware-cookies.ts +55 -0
  185. package/src/router/middleware-types.ts +220 -0
  186. package/src/router/middleware.ts +324 -369
  187. package/src/router/navigation-snapshot.ts +182 -0
  188. package/src/router/pattern-matching.ts +211 -43
  189. package/src/router/prerender-match.ts +502 -0
  190. package/src/router/preview-match.ts +98 -0
  191. package/src/router/request-classification.ts +310 -0
  192. package/src/router/revalidation.ts +137 -38
  193. package/src/router/route-snapshot.ts +245 -0
  194. package/src/router/router-context.ts +41 -21
  195. package/src/router/router-interfaces.ts +484 -0
  196. package/src/router/router-options.ts +618 -0
  197. package/src/router/router-registry.ts +24 -0
  198. package/src/router/segment-resolution/fresh.ts +748 -0
  199. package/src/router/segment-resolution/helpers.ts +268 -0
  200. package/src/router/segment-resolution/loader-cache.ts +199 -0
  201. package/src/router/segment-resolution/revalidation.ts +1379 -0
  202. package/src/router/segment-resolution/static-store.ts +67 -0
  203. package/src/router/segment-resolution.ts +21 -0
  204. package/src/router/segment-wrappers.ts +291 -0
  205. package/src/router/telemetry-otel.ts +299 -0
  206. package/src/router/telemetry.ts +300 -0
  207. package/src/router/timeout.ts +148 -0
  208. package/src/router/trie-matching.ts +239 -0
  209. package/src/router/types.ts +78 -3
  210. package/src/router.ts +740 -4252
  211. package/src/rsc/handler-context.ts +45 -0
  212. package/src/rsc/handler.ts +907 -797
  213. package/src/rsc/helpers.ts +140 -6
  214. package/src/rsc/index.ts +0 -20
  215. package/src/rsc/loader-fetch.ts +229 -0
  216. package/src/rsc/manifest-init.ts +90 -0
  217. package/src/rsc/nonce.ts +14 -0
  218. package/src/rsc/origin-guard.ts +141 -0
  219. package/src/rsc/progressive-enhancement.ts +393 -0
  220. package/src/rsc/response-error.ts +37 -0
  221. package/src/rsc/response-route-handler.ts +347 -0
  222. package/src/rsc/rsc-rendering.ts +246 -0
  223. package/src/rsc/runtime-warnings.ts +42 -0
  224. package/src/rsc/server-action.ts +358 -0
  225. package/src/rsc/ssr-setup.ts +128 -0
  226. package/src/rsc/types.ts +46 -11
  227. package/src/search-params.ts +230 -0
  228. package/src/segment-content-promise.ts +67 -0
  229. package/src/segment-loader-promise.ts +122 -0
  230. package/src/segment-system.tsx +134 -36
  231. package/src/server/context.ts +341 -61
  232. package/src/server/cookie-store.ts +190 -0
  233. package/src/server/fetchable-loader-store.ts +37 -0
  234. package/src/server/handle-store.ts +113 -15
  235. package/src/server/loader-registry.ts +24 -64
  236. package/src/server/request-context.ts +607 -81
  237. package/src/server.ts +35 -130
  238. package/src/ssr/index.tsx +103 -30
  239. package/src/static-handler.ts +126 -0
  240. package/src/theme/ThemeProvider.tsx +21 -15
  241. package/src/theme/ThemeScript.tsx +5 -5
  242. package/src/theme/constants.ts +5 -2
  243. package/src/theme/index.ts +4 -14
  244. package/src/theme/theme-context.ts +4 -30
  245. package/src/theme/theme-script.ts +21 -18
  246. package/src/types/boundaries.ts +158 -0
  247. package/src/types/cache-types.ts +198 -0
  248. package/src/types/error-types.ts +192 -0
  249. package/src/types/global-namespace.ts +100 -0
  250. package/src/types/handler-context.ts +791 -0
  251. package/src/types/index.ts +88 -0
  252. package/src/types/loader-types.ts +210 -0
  253. package/src/types/route-config.ts +170 -0
  254. package/src/types/route-entry.ts +120 -0
  255. package/src/types/segments.ts +150 -0
  256. package/src/types.ts +1 -1623
  257. package/src/urls/include-helper.ts +207 -0
  258. package/src/urls/index.ts +53 -0
  259. package/src/urls/path-helper-types.ts +372 -0
  260. package/src/urls/path-helper.ts +364 -0
  261. package/src/urls/pattern-types.ts +107 -0
  262. package/src/urls/response-types.ts +116 -0
  263. package/src/urls/type-extraction.ts +372 -0
  264. package/src/urls/urls-function.ts +98 -0
  265. package/src/urls.ts +1 -802
  266. package/src/use-loader.tsx +161 -81
  267. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  268. package/src/vite/discovery/discover-routers.ts +348 -0
  269. package/src/vite/discovery/prerender-collection.ts +439 -0
  270. package/src/vite/discovery/route-types-writer.ts +258 -0
  271. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  272. package/src/vite/discovery/state.ts +117 -0
  273. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  274. package/src/vite/index.ts +15 -1133
  275. package/src/vite/plugin-types.ts +103 -0
  276. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  277. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  278. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  279. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  280. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  281. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  282. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  283. package/src/vite/plugins/expose-id-utils.ts +299 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -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 +786 -0
  290. package/src/vite/plugins/performance-tracks.ts +88 -0
  291. package/src/vite/plugins/refresh-cmd.ts +127 -0
  292. package/src/vite/plugins/use-cache-transform.ts +323 -0
  293. package/src/vite/plugins/version-injector.ts +83 -0
  294. package/src/vite/plugins/version-plugin.ts +266 -0
  295. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +462 -0
  298. package/src/vite/router-discovery.ts +977 -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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  304. package/src/vite/utils/prerender-utils.ts +221 -0
  305. package/src/vite/utils/shared-utils.ts +170 -0
  306. package/CLAUDE.md +0 -43
  307. package/src/browser/lru-cache.ts +0 -69
  308. package/src/browser/request-controller.ts +0 -164
  309. package/src/cache/memory-store.ts +0 -253
  310. package/src/href-context.ts +0 -33
  311. package/src/href.ts +0 -255
  312. package/src/server/route-manifest-cache.ts +0 -173
  313. package/src/vite/expose-handle-id.ts +0 -209
  314. package/src/vite/expose-loader-id.ts +0 -426
  315. package/src/vite/expose-location-state-id.ts +0 -177
  316. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,555 @@
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 type { DefaultRouteName } from "../types/global-namespace.js";
40
+ import { debugLog, debugWarn } from "./logging.js";
41
+ import {
42
+ resolveRoute,
43
+ ensureFullRouteSnapshot,
44
+ type RouteSnapshot,
45
+ } from "./route-snapshot.js";
46
+ import { resolveNavigation } from "./navigation-snapshot.js";
47
+
48
+ /**
49
+ * Create match context for full requests (document/SSR).
50
+ */
51
+ export async function createMatchContextForFull<TEnv>(
52
+ request: Request,
53
+ env: TEnv,
54
+ deps: MatchApiDeps<TEnv>,
55
+ findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
56
+ ): Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }> {
57
+ const url = new URL(request.url);
58
+ const pathname = url.pathname;
59
+
60
+ const metricsStore = deps.getMetricsStore();
61
+
62
+ // Full renders always resolve fresh with isSSR: true because loadManifest
63
+ // keys its cache on isSSR and stamps Store.isSSR for downstream behavior.
64
+ const result = await resolveRoute<TEnv>(pathname, {
65
+ findMatch: (p) => deps.findMatch(p, metricsStore),
66
+ metricsStore,
67
+ isSSR: true,
68
+ });
69
+
70
+ if (!result) {
71
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
72
+ cause: { pathname, method: request.method },
73
+ });
74
+ }
75
+
76
+ if (result.type === "redirect") {
77
+ return {
78
+ type: "redirect",
79
+ redirectUrl: result.redirectTo + url.search,
80
+ };
81
+ }
82
+
83
+ const snapshot = result.snapshot;
84
+
85
+ const { matched } = snapshot;
86
+
87
+ // Backward compat: downstream middleware reads matched.pt
88
+ if (snapshot.isPassthrough) {
89
+ matched.pt = true;
90
+ }
91
+
92
+ // Clean URL without internal _rsc* params for userland access
93
+ const cleanUrl = stripInternalParams(url);
94
+
95
+ const handlerContext = createHandlerContext(
96
+ matched.params,
97
+ request,
98
+ cleanUrl.searchParams,
99
+ pathname,
100
+ cleanUrl,
101
+ env,
102
+ deps.getRouteMap(),
103
+ matched.routeKey,
104
+ matched.responseType,
105
+ matched.pt === true,
106
+ );
107
+
108
+ const loaderPromises = new Map<string, Promise<any>>();
109
+ setupLoaderAccess(handlerContext, loaderPromises);
110
+
111
+ const Store = getContext().getOrCreateStore(matched.routeKey);
112
+ Store.run = <T>(fn: () => T | Promise<T>) =>
113
+ getContext().runWithStore(
114
+ Store,
115
+ Store.namespace || "#router",
116
+ Store.parent,
117
+ fn,
118
+ );
119
+ if (metricsStore) {
120
+ Store.metrics = metricsStore;
121
+ }
122
+
123
+ return {
124
+ request,
125
+ url: cleanUrl,
126
+ pathname,
127
+ env,
128
+ clientSegmentIds: [],
129
+ clientSegmentSet: new Set(),
130
+ stale: false,
131
+ prevUrl: cleanUrl,
132
+ prevParams: {},
133
+ prevMatch: null,
134
+ matched,
135
+ manifestEntry: snapshot.manifestEntry,
136
+ entries: snapshot.entries,
137
+ routeKey: matched.routeKey,
138
+ localRouteName: snapshot.localRouteName,
139
+ handlerContext,
140
+ loaderPromises,
141
+ routeMap: deps.getRouteMap(),
142
+ metricsStore,
143
+ Store,
144
+ interceptContextMatch: null,
145
+ interceptSelectorContext: {
146
+ from: cleanUrl,
147
+ to: cleanUrl,
148
+ params: matched.params,
149
+ request,
150
+ env,
151
+ segments: { path: [], ids: [] },
152
+ toRouteName:
153
+ matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
154
+ ? (matched.routeKey as DefaultRouteName)
155
+ : undefined,
156
+ },
157
+ isSameRouteNavigation: false,
158
+ interceptResult: null,
159
+ cacheScope: snapshot.cacheScope,
160
+ isIntercept: false,
161
+ actionContext: undefined,
162
+ isAction: false,
163
+ routeMiddleware: snapshot.routeMiddleware,
164
+ isFullMatch: true,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Create match context for partial requests (navigation/actions).
170
+ */
171
+ export async function createMatchContextForPartial<TEnv>(
172
+ request: Request,
173
+ env: TEnv,
174
+ deps: MatchApiDeps<TEnv>,
175
+ findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
176
+ actionContext?: ActionContext,
177
+ ): Promise<MatchContext<TEnv> | null> {
178
+ const url = new URL(request.url);
179
+ const pathname = url.pathname;
180
+
181
+ const metricsStore = deps.getMetricsStore();
182
+
183
+ const isHmr = !!request.headers.get("X-RSC-HMR");
184
+
185
+ // HMR: clear manifest cache so stale handler references are discarded
186
+ if (isHmr) {
187
+ clearManifestCache();
188
+ }
189
+
190
+ // Reuse the classified snapshot when available and not invalidated by HMR.
191
+ // classifyRequest already called resolveRoute(lite) with isSSR=false, which
192
+ // matches the partial path. On HMR, discard to pick up manifest changes.
193
+ const classifiedRoute = isHmr
194
+ ? undefined
195
+ : getRequestContext()?._classifiedRoute;
196
+
197
+ // Time route matching. On the reuse path, only nav findMatch calls are new
198
+ // (current-route findMatch and manifest-loading were already timed during
199
+ // classifyRequest via its metricsStore). On the fresh path, all findMatch
200
+ // calls are measured together.
201
+ const routeMatchStart = metricsStore ? performance.now() : 0;
202
+
203
+ let snapshot: RouteSnapshot<TEnv>;
204
+ if (classifiedRoute && classifiedRoute.manifestEntry) {
205
+ snapshot = ensureFullRouteSnapshot(classifiedRoute);
206
+ } else {
207
+ const result = await resolveRoute<TEnv>(pathname, {
208
+ findMatch: (p) => deps.findMatch(p, metricsStore),
209
+ metricsStore,
210
+ isSSR: false,
211
+ skipRouteMatchMetric: true,
212
+ });
213
+
214
+ if (!result) {
215
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
216
+ cause: { pathname, method: request.method },
217
+ });
218
+ }
219
+
220
+ if (result.type === "redirect") {
221
+ return null;
222
+ }
223
+
224
+ snapshot = result.snapshot;
225
+ }
226
+
227
+ const { matched } = snapshot;
228
+
229
+ // Backward compat: downstream middleware reads matched.pt
230
+ if (snapshot.isPassthrough) {
231
+ matched.pt = true;
232
+ }
233
+
234
+ // Navigation state (prev + intercept-source findMatch calls)
235
+ const nav = resolveNavigation(request, url, matched.routeKey, {
236
+ findMatch: deps.findMatch,
237
+ });
238
+ if (!nav) {
239
+ return null;
240
+ }
241
+
242
+ // Push route-matching metric. On the fresh path this covers all findMatch
243
+ // calls (current + prev + intercept-source). On the reuse path, current-route
244
+ // findMatch was already timed during classification, so this only covers
245
+ // the nav lookups (prev + intercept-source).
246
+ if (metricsStore) {
247
+ const isReuse = !!classifiedRoute;
248
+ metricsStore.metrics.push({
249
+ label: isReuse ? "route-matching:nav" : "route-matching",
250
+ duration: performance.now() - routeMatchStart,
251
+ startTime: routeMatchStart - metricsStore.requestStart,
252
+ });
253
+ }
254
+
255
+ if (nav.prevMatch && nav.prevMatch.entry !== matched.entry && !matched.pr) {
256
+ debugLog("matchPartial", "route group changed", {
257
+ from: nav.prevMatch.routeKey,
258
+ to: matched.routeKey,
259
+ });
260
+ }
261
+
262
+ // Clean URL without internal _rsc* params for userland access
263
+ const cleanUrl = stripInternalParams(url);
264
+
265
+ const handlerContext = createHandlerContext(
266
+ matched.params,
267
+ request,
268
+ cleanUrl.searchParams,
269
+ pathname,
270
+ cleanUrl,
271
+ env,
272
+ deps.getRouteMap(),
273
+ matched.routeKey,
274
+ matched.responseType,
275
+ matched.pt === true,
276
+ );
277
+
278
+ debugLog("matchPartial", "client segments", {
279
+ segments: Array.from(nav.clientSegmentSet),
280
+ });
281
+
282
+ const loaderPromises = new Map<string, Promise<any>>();
283
+ setupLoaderAccess(handlerContext, loaderPromises);
284
+
285
+ const Store = getContext().getOrCreateStore(matched.routeKey);
286
+ Store.run = <T>(fn: () => T | Promise<T>) =>
287
+ getContext().runWithStore(
288
+ Store,
289
+ Store.namespace || "#router",
290
+ Store.parent,
291
+ fn,
292
+ );
293
+ if (metricsStore) {
294
+ Store.metrics = metricsStore;
295
+ }
296
+
297
+ if (nav.hasInterceptSource) {
298
+ debugLog("matchPartial.intercept", "intercept context detected", {
299
+ currentUrl: pathname,
300
+ interceptSource: nav.interceptContextUrl.href,
301
+ contextRoute: nav.interceptContextMatch?.routeKey,
302
+ currentRoute: matched.routeKey,
303
+ sameRouteNavigation: nav.isSameRouteNavigation,
304
+ });
305
+ }
306
+
307
+ // Store previous route key on the request context for revalidation
308
+ // fromRouteName. Uses effectiveFromMatch so intercept-source navigations
309
+ // see the intercept origin route, not the plain previous URL route.
310
+ setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
311
+
312
+ const interceptSelectorContext: InterceptSelectorContext = {
313
+ from: nav.effectiveFromUrl,
314
+ to: cleanUrl,
315
+ params: matched.params,
316
+ request,
317
+ env,
318
+ segments: {
319
+ path: nav.effectiveFromUrl.pathname.split("/").filter(Boolean),
320
+ ids: nav.filteredSegmentIds,
321
+ },
322
+ fromRouteName:
323
+ nav.effectiveFromMatch?.routeKey &&
324
+ !isAutoGeneratedRouteName(nav.effectiveFromMatch.routeKey)
325
+ ? (nav.effectiveFromMatch.routeKey as DefaultRouteName)
326
+ : undefined,
327
+ toRouteName:
328
+ matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
329
+ ? (matched.routeKey as DefaultRouteName)
330
+ : undefined,
331
+ };
332
+ const isAction = !!actionContext;
333
+
334
+ const clientHasInterceptSegments = [...nav.clientSegmentSet].some((id) =>
335
+ id.includes(".@"),
336
+ );
337
+ const skipInterceptForAction = isAction && !clientHasInterceptSegments;
338
+ const interceptResult =
339
+ nav.isSameRouteNavigation || skipInterceptForAction
340
+ ? null
341
+ : findInterceptForRoute(
342
+ matched.routeKey,
343
+ snapshot.manifestEntry.parent,
344
+ interceptSelectorContext,
345
+ isAction,
346
+ ) ||
347
+ (snapshot.localRouteName !== matched.routeKey
348
+ ? findInterceptForRoute(
349
+ snapshot.localRouteName,
350
+ snapshot.manifestEntry.parent,
351
+ interceptSelectorContext,
352
+ isAction,
353
+ )
354
+ : null);
355
+
356
+ // Make a mutable copy of clientSegmentSet so we can delete entries
357
+ // for same-route navigation forcing
358
+ const clientSegmentSet = new Set(nav.clientSegmentSet);
359
+
360
+ if (
361
+ nav.isSameRouteNavigation &&
362
+ snapshot.manifestEntry.type === "route" &&
363
+ nav.hasInterceptSource
364
+ ) {
365
+ debugLog("matchPartial.intercept", "forcing route segment render", {
366
+ segmentId: snapshot.manifestEntry.shortCode,
367
+ });
368
+ clientSegmentSet.delete(snapshot.manifestEntry.shortCode);
369
+ }
370
+
371
+ const isIntercept = !!interceptResult;
372
+
373
+ return {
374
+ request,
375
+ url: cleanUrl,
376
+ pathname,
377
+ env,
378
+ clientSegmentIds: nav.clientSegmentIds,
379
+ clientSegmentSet,
380
+ stale: nav.stale,
381
+ prevUrl: nav.prevUrl,
382
+ prevParams: nav.prevParams,
383
+ prevMatch: nav.prevMatch,
384
+ matched,
385
+ manifestEntry: snapshot.manifestEntry,
386
+ entries: snapshot.entries,
387
+ routeKey: matched.routeKey,
388
+ localRouteName: snapshot.localRouteName,
389
+ handlerContext,
390
+ loaderPromises,
391
+ routeMap: deps.getRouteMap(),
392
+ metricsStore,
393
+ Store,
394
+ interceptContextMatch: nav.interceptContextMatch,
395
+ interceptSelectorContext,
396
+ isSameRouteNavigation: nav.isSameRouteNavigation,
397
+ interceptResult,
398
+ cacheScope: snapshot.cacheScope,
399
+ isIntercept,
400
+ actionContext,
401
+ isAction,
402
+ routeMiddleware: snapshot.routeMiddleware,
403
+ isFullMatch: false,
404
+ };
405
+ }
406
+
407
+ /**
408
+ * Match an error to the nearest error boundary and return error segments.
409
+ */
410
+ export async function matchError<TEnv>(
411
+ request: Request,
412
+ _context: TEnv,
413
+ error: unknown,
414
+ deps: MatchApiDeps<TEnv>,
415
+ defaultErrorBoundary: ReactNode | ErrorBoundaryHandler | undefined,
416
+ segmentType: ErrorInfo["segmentType"] = "route",
417
+ ): Promise<MatchResult | null> {
418
+ const url = new URL(request.url);
419
+ const pathname = url.pathname;
420
+
421
+ debugLog("matchError", "matching error", { pathname });
422
+
423
+ const matched = deps.findMatch(pathname);
424
+ if (!matched) {
425
+ debugWarn("matchError", "no route matched", { pathname });
426
+ return null;
427
+ }
428
+
429
+ const manifestEntry = await loadManifest(
430
+ matched.entry,
431
+ matched.routeKey,
432
+ pathname,
433
+ undefined,
434
+ false,
435
+ );
436
+
437
+ const findNearestErrorBoundary = (entry: EntryData | null) =>
438
+ findErrorBoundary(entry, defaultErrorBoundary);
439
+
440
+ const fallback = findNearestErrorBoundary(manifestEntry);
441
+ const useDefaultFallback = !fallback;
442
+
443
+ const errorInfo = createErrorInfo(
444
+ error,
445
+ manifestEntry.shortCode || "unknown",
446
+ segmentType,
447
+ );
448
+
449
+ let entryWithBoundary: EntryData | null = null;
450
+ let current: EntryData | null = manifestEntry;
451
+ while (current) {
452
+ if (current.errorBoundary && current.errorBoundary.length > 0) {
453
+ entryWithBoundary = current;
454
+ break;
455
+ }
456
+
457
+ if (current.layout && current.layout.length > 0) {
458
+ for (const orphan of current.layout) {
459
+ if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
460
+ entryWithBoundary = orphan;
461
+ break;
462
+ }
463
+ }
464
+ if (entryWithBoundary) break;
465
+ }
466
+
467
+ current = current.parent;
468
+ }
469
+
470
+ let boundaryEntry: EntryData;
471
+ let outletEntry: EntryData;
472
+
473
+ if (entryWithBoundary) {
474
+ boundaryEntry = entryWithBoundary;
475
+
476
+ outletEntry = manifestEntry;
477
+ current = manifestEntry;
478
+
479
+ while (current) {
480
+ if (current.parent === boundaryEntry) {
481
+ outletEntry = current;
482
+ break;
483
+ }
484
+
485
+ if (current.parent && current.parent.layout) {
486
+ if (current.parent.layout.includes(boundaryEntry)) {
487
+ outletEntry = current;
488
+ break;
489
+ }
490
+ }
491
+
492
+ current = current.parent;
493
+ }
494
+ } else {
495
+ let rootEntry = manifestEntry;
496
+ while (rootEntry.parent) {
497
+ rootEntry = rootEntry.parent;
498
+ }
499
+ boundaryEntry = rootEntry;
500
+ outletEntry = rootEntry;
501
+ }
502
+
503
+ const matchedIds: string[] = [];
504
+
505
+ current = boundaryEntry;
506
+ const stack: {
507
+ shortCode: string;
508
+ loaderEntries: LoaderEntry[];
509
+ }[] = [];
510
+ while (current) {
511
+ if (current.shortCode) {
512
+ stack.push({
513
+ shortCode: current.shortCode,
514
+ loaderEntries: current.loader || [],
515
+ });
516
+ }
517
+ current = current.parent;
518
+ }
519
+ for (const item of stack.reverse()) {
520
+ matchedIds.push(item.shortCode);
521
+ for (let i = 0; i < item.loaderEntries.length; i++) {
522
+ const loaderId = item.loaderEntries[i].loader?.$$id || "unknown";
523
+ matchedIds.push(`${item.shortCode}D${i}.${loaderId}`);
524
+ }
525
+ }
526
+
527
+ const reqCtx = getRequestContext();
528
+ if (reqCtx) {
529
+ reqCtx._setStatus(500);
530
+ }
531
+
532
+ const effectiveFallback = fallback || DefaultErrorFallback;
533
+ const errorSegment = createErrorSegment(
534
+ errorInfo,
535
+ effectiveFallback,
536
+ outletEntry,
537
+ matched.params,
538
+ );
539
+
540
+ if (useDefaultFallback) {
541
+ debugLog("matchError", "using default error boundary");
542
+ }
543
+
544
+ debugLog("matchError", "resolved boundary", {
545
+ boundarySegmentId: boundaryEntry.shortCode,
546
+ outletSegmentId: outletEntry.shortCode,
547
+ });
548
+
549
+ return {
550
+ segments: [errorSegment],
551
+ matched: matchedIds,
552
+ diff: [errorSegment.id],
553
+ params: matched.params,
554
+ };
555
+ }
@@ -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[];