@rangojs/router 0.0.0-experimental.0f44aca1

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