@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,266 @@
1
+ /**
2
+ * Match Context for Router Pipeline
3
+ *
4
+ * Encapsulates all state needed by the match pipeline middleware.
5
+ * Created once at the start of match()/matchPartial() and passed through the pipeline.
6
+ *
7
+ * DATA FLOW ARCHITECTURE
8
+ * ======================
9
+ *
10
+ * The router uses two complementary data structures:
11
+ *
12
+ * MatchContext (ctx) - Immutable request state
13
+ * MatchPipelineState (state) - Mutable pipeline state
14
+ *
15
+ *
16
+ * Request
17
+ * |
18
+ * v
19
+ * +-------------------+
20
+ * | Create Context | ctx = immutable snapshot of request
21
+ * +-------------------+
22
+ * |
23
+ * v
24
+ * +-------------------+
25
+ * | Create State | state = mutable accumulator
26
+ * +-------------------+
27
+ * |
28
+ * +---> [Pipeline Middleware]
29
+ * | |
30
+ * | ctx: read-only
31
+ * | state: read/write
32
+ * | |
33
+ * +<----------+
34
+ * |
35
+ * v
36
+ * +-------------------+
37
+ * | Build Result | Merge ctx + state into MatchResult
38
+ * +-------------------+
39
+ *
40
+ *
41
+ * MATCHCONTEXT FIELDS
42
+ * ===================
43
+ *
44
+ * Request Info:
45
+ * - request, url, pathname: The incoming HTTP request
46
+ *
47
+ * Environment:
48
+ * - env: Server environment (Cloudflare bindings, etc.)
49
+ *
50
+ * Client State (from RSC request headers):
51
+ * - clientSegmentIds: Segments the client currently has
52
+ * - clientSegmentSet: Set version for O(1) lookup
53
+ * - stale: Whether client considers its cache stale
54
+ *
55
+ * Navigation State:
56
+ * - prevUrl, prevParams, prevMatch: Previous navigation for comparison
57
+ *
58
+ * Current Match:
59
+ * - matched: Route match result (params, route key)
60
+ * - manifestEntry: Resolved manifest data
61
+ * - entries: All route entries (layouts, loaders, etc.)
62
+ * - routeKey, localRouteName: Route identifiers
63
+ *
64
+ * Handler Context:
65
+ * - handlerContext: Context passed to loaders
66
+ * - loaderPromises: Memoized loader promises
67
+ *
68
+ * Intercepts:
69
+ * - interceptResult: Detected intercept (if soft navigation)
70
+ * - interceptSelectorContext: Context for intercept matching
71
+ *
72
+ * Cache:
73
+ * - cacheScope: Cache configuration and methods
74
+ * - isIntercept: Whether this is an intercept request
75
+ *
76
+ * Flags:
77
+ * - isAction: POST/mutation request
78
+ * - isFullMatch: Document request vs navigation
79
+ *
80
+ *
81
+ * MATCHPIPELINESTATE FIELDS
82
+ * =========================
83
+ *
84
+ * State flags (set by middleware, read by others):
85
+ * - cacheHit: Cache lookup succeeded
86
+ * - shouldRevalidate: SWR revalidation needed
87
+ *
88
+ * Segment accumulation:
89
+ * - segments: Resolved segments from pipeline
90
+ * - matchedIds: All segment IDs in match order
91
+ * - cachedSegments: Segments from cache (if hit)
92
+ *
93
+ * Intercept data:
94
+ * - interceptSegments: Segments for modal slots
95
+ * - slots: Named slot data for client
96
+ *
97
+ *
98
+ * IMMUTABILITY CONTRACT
99
+ * =====================
100
+ *
101
+ * MatchContext is treated as immutable after creation.
102
+ * Middleware should NEVER modify ctx properties.
103
+ *
104
+ * MatchPipelineState is explicitly mutable.
105
+ * Middleware communicate by setting state flags.
106
+ *
107
+ * This separation ensures:
108
+ * - Request data is consistent across all middleware
109
+ * - Pipeline state changes are explicit and trackable
110
+ * - No hidden side effects in request handling
111
+ */
112
+ import type { CacheScope } from "../cache/cache-scope.js";
113
+ import type {
114
+ EntryData,
115
+ InterceptSelectorContext,
116
+ MetricsStore,
117
+ } from "../server/context.js";
118
+ import type { HandlerContext, ResolvedSegment } from "../types.js";
119
+ import type { RouteMatchResult } from "./pattern-matching.js";
120
+ import type { InterceptResult } from "./router-context.js";
121
+
122
+ /**
123
+ * Action context passed to matchPartial
124
+ */
125
+ export interface ActionContext {
126
+ actionId?: string;
127
+ actionUrl?: URL;
128
+ actionResult?: any;
129
+ formData?: FormData;
130
+ }
131
+
132
+ /**
133
+ * Match context containing all state for the match pipeline
134
+ */
135
+ export interface MatchContext<TEnv = any> {
136
+ // Request info
137
+ request: Request;
138
+ url: URL;
139
+ pathname: string;
140
+
141
+ // Environment
142
+ env: TEnv;
143
+
144
+ // Client state
145
+ clientSegmentIds: string[];
146
+ clientSegmentSet: Set<string>;
147
+ stale: boolean;
148
+
149
+ // Previous navigation state
150
+ prevUrl: URL;
151
+ prevParams: Record<string, string>;
152
+ prevMatch: RouteMatchResult | null;
153
+
154
+ // Current route match
155
+ matched: RouteMatchResult;
156
+ manifestEntry: EntryData;
157
+ entries: EntryData[];
158
+ routeKey: string;
159
+ localRouteName: string;
160
+
161
+ // Handler context (for loaders)
162
+ handlerContext: HandlerContext<any, TEnv>;
163
+ loaderPromises: Map<string, Promise<any>>;
164
+
165
+ // Route map for server-side ctx.reverse() resolution
166
+ routeMap: Record<string, string>;
167
+
168
+ // Metrics
169
+ metricsStore: MetricsStore | undefined;
170
+
171
+ // Store for running within context
172
+ Store: any;
173
+
174
+ // Intercept detection
175
+ interceptContextMatch: RouteMatchResult | null;
176
+ interceptSelectorContext: InterceptSelectorContext;
177
+ isSameRouteNavigation: boolean;
178
+ interceptResult: InterceptResult | null;
179
+
180
+ // Cache
181
+ cacheScope: CacheScope | null;
182
+ isIntercept: boolean;
183
+
184
+ // Action context (if this is an action)
185
+ actionContext?: ActionContext;
186
+ isAction: boolean;
187
+
188
+ // Route middleware
189
+ routeMiddleware: Array<{
190
+ handler: any;
191
+ params: Record<string, string>;
192
+ }>;
193
+
194
+ // Full match flag (document requests vs partial/navigation requests)
195
+ // When true, uses simpler resolution without revalidation logic
196
+ isFullMatch: boolean;
197
+ }
198
+
199
+ /**
200
+ * Mutable state that flows through the pipeline
201
+ */
202
+ export interface MatchPipelineState {
203
+ // Whether cache was hit
204
+ cacheHit: boolean;
205
+
206
+ // Cached segments (if cache hit)
207
+ cachedSegments?: ResolvedSegment[];
208
+ cachedMatchedIds?: string[];
209
+
210
+ // Whether cache should be revalidated (SWR)
211
+ shouldRevalidate?: boolean;
212
+
213
+ // Source of cache hit ("runtime" or "prerender")
214
+ cacheSource?: "runtime" | "prerender";
215
+
216
+ // Resolved segments from pipeline
217
+ segments: ResolvedSegment[];
218
+ matchedIds: string[];
219
+
220
+ // Intercept segments
221
+ interceptSegments: ResolvedSegment[];
222
+
223
+ // Slots state
224
+ slots: Record<
225
+ string,
226
+ {
227
+ active: boolean;
228
+ segments: ResolvedSegment[];
229
+ }
230
+ >;
231
+ }
232
+
233
+ /**
234
+ * Create initial pipeline state
235
+ */
236
+ export function createPipelineState(): MatchPipelineState {
237
+ return {
238
+ cacheHit: false,
239
+ segments: [],
240
+ matchedIds: [],
241
+ interceptSegments: [],
242
+ slots: {},
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Input parameters for createMatchContext
248
+ */
249
+ export interface CreateMatchContextInput<TEnv = any> {
250
+ request: Request;
251
+ env: TEnv;
252
+ actionContext?: ActionContext;
253
+ }
254
+
255
+ /**
256
+ * Result from createMatchContext - either a context or null (fall back to full match)
257
+ */
258
+ export type CreateMatchContextResult<TEnv = any> =
259
+ | { type: "context"; ctx: MatchContext<TEnv> }
260
+ | { type: "fallback"; reason: string }
261
+ | { type: "error"; error: Error };
262
+
263
+ // Note: createMatchContext() will be implemented in Step J10 when we wire everything together.
264
+ // It requires access to RouterContext (findMatch, loadManifest, etc.) which are closure
265
+ // functions from createRouter(). The implementation will live in router.ts initially
266
+ // and call getRouterContext() to access these dependencies.
@@ -0,0 +1,440 @@
1
+ import type { ReactNode } from "react";
2
+ import { sanitizeError } from "../errors";
3
+ import type { ErrorInfo, ErrorPhase, MatchResult } from "../types";
4
+ import type {
5
+ EntryData,
6
+ InterceptEntry,
7
+ InterceptSelectorContext,
8
+ } from "../server/context";
9
+ import type { MatchApiDeps } from "./types.js";
10
+ import type { RouterContext } from "./router-context.js";
11
+ import { runWithRouterContext } from "./router-context.js";
12
+ import {
13
+ type ActionContext,
14
+ type MatchContext,
15
+ createPipelineState,
16
+ } from "./match-context.js";
17
+ import { createMatchPartialPipeline } from "./match-pipelines.js";
18
+ import { collectMatchResult } from "./match-result.js";
19
+ import {
20
+ createMatchContextForFull as _createMatchContextForFull,
21
+ createMatchContextForPartial as _createMatchContextForPartial,
22
+ matchError as _matchError,
23
+ } from "./match-api.js";
24
+ import { previewMatch as _previewMatch } from "./preview-match.js";
25
+ import {
26
+ runWithRouterLogContext,
27
+ withRouterLogScope,
28
+ isRouterDebugEnabled,
29
+ startRevalidationTrace,
30
+ flushRevalidationTrace,
31
+ } from "./logging.js";
32
+ import type { ErrorBoundaryHandler, NotFoundBoundaryHandler } from "../types";
33
+ import type { MiddlewareFn } from "./middleware.js";
34
+ import {
35
+ type TelemetrySink,
36
+ safeEmit,
37
+ resolveSink,
38
+ getRequestId,
39
+ } from "./telemetry.js";
40
+
41
+ export interface MatchHandlerDeps<TEnv = any> {
42
+ buildRouterContext: () => RouterContext<TEnv>;
43
+ callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
44
+ matchApiDeps: MatchApiDeps<TEnv>;
45
+ defaultErrorBoundary: ReactNode | ErrorBoundaryHandler | undefined;
46
+ findMatch: (pathname: string, ms?: any) => any;
47
+ findInterceptForRoute: (
48
+ routeKey: string,
49
+ parentEntry: EntryData | null,
50
+ selectorContext: InterceptSelectorContext | null,
51
+ isAction: boolean,
52
+ ) => { intercept: InterceptEntry; entry: EntryData } | null;
53
+ telemetry?: TelemetrySink;
54
+ }
55
+
56
+ export interface MatchHandlers<TEnv = any> {
57
+ match: (request: Request, env: TEnv) => Promise<MatchResult>;
58
+ matchPartial: (
59
+ request: Request,
60
+ context: TEnv,
61
+ actionContext?: ActionContext,
62
+ ) => Promise<MatchResult | null>;
63
+ matchError: (
64
+ request: Request,
65
+ _context: TEnv,
66
+ error: unknown,
67
+ segmentType?: ErrorInfo["segmentType"],
68
+ ) => Promise<MatchResult | null>;
69
+ previewMatch: (
70
+ request: Request,
71
+ _context: TEnv,
72
+ ) => Promise<{
73
+ routeMiddleware?: Array<{
74
+ handler: MiddlewareFn;
75
+ params: Record<string, string>;
76
+ }>;
77
+ responseType?: string;
78
+ handler?: Function;
79
+ params?: Record<string, string>;
80
+ negotiated?: boolean;
81
+ manifestEntry?: EntryData;
82
+ } | null>;
83
+ createMatchContextForFull: (
84
+ request: Request,
85
+ env: TEnv,
86
+ ) => Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }>;
87
+ createMatchContextForPartial: (
88
+ request: Request,
89
+ env: TEnv,
90
+ actionContext?: {
91
+ actionId?: string;
92
+ actionUrl?: URL;
93
+ actionResult?: any;
94
+ formData?: FormData;
95
+ },
96
+ ) => Promise<MatchContext<TEnv> | null>;
97
+ }
98
+
99
+ /**
100
+ * Create match handler functions bound to router closure state.
101
+ * These are the main request-handling entry points for SSR, navigation,
102
+ * error recovery, and preview matching.
103
+ */
104
+ export function createMatchHandlers<TEnv = any>(
105
+ deps: MatchHandlerDeps<TEnv>,
106
+ ): MatchHandlers<TEnv> {
107
+ const {
108
+ buildRouterContext,
109
+ callOnError,
110
+ matchApiDeps,
111
+ defaultErrorBoundary,
112
+ findInterceptForRoute,
113
+ } = deps;
114
+ const hasTelemetry = !!deps.telemetry;
115
+ const telemetry = resolveSink(deps.telemetry);
116
+
117
+ async function createMatchContextForFull(
118
+ request: Request,
119
+ env: TEnv,
120
+ ): Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }> {
121
+ return _createMatchContextForFull(
122
+ request,
123
+ env,
124
+ matchApiDeps,
125
+ findInterceptForRoute,
126
+ );
127
+ }
128
+
129
+ async function createMatchContextForPartial(
130
+ request: Request,
131
+ env: TEnv,
132
+ actionContext?: {
133
+ actionId?: string;
134
+ actionUrl?: URL;
135
+ actionResult?: any;
136
+ formData?: FormData;
137
+ },
138
+ ): Promise<MatchContext<TEnv> | null> {
139
+ return _createMatchContextForPartial(
140
+ request,
141
+ env,
142
+ matchApiDeps,
143
+ findInterceptForRoute,
144
+ actionContext,
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Match request and return segments (document/SSR requests)
150
+ *
151
+ * Uses generator middleware pipeline for clean separation of concerns:
152
+ * - cache-lookup: Check cache first
153
+ * - segment-resolution: Resolve segments on cache miss
154
+ * - cache-store: Store results in cache
155
+ * - background-revalidation: SWR revalidation
156
+ */
157
+ async function match(request: Request, env: TEnv): Promise<MatchResult> {
158
+ const requestId = hasTelemetry ? getRequestId(request) : undefined;
159
+ return runWithRouterLogContext({ request, transaction: "match" }, () => {
160
+ const routerCtx = buildRouterContext();
161
+ routerCtx.requestId = requestId;
162
+ return runWithRouterContext(routerCtx, async () =>
163
+ withRouterLogScope("match", async () => {
164
+ const matchStart = performance.now();
165
+ const pathname = new URL(request.url).pathname;
166
+ if (hasTelemetry) {
167
+ safeEmit(telemetry, {
168
+ type: "request.start",
169
+ timestamp: matchStart,
170
+ requestId,
171
+ method: request.method,
172
+ pathname,
173
+ transaction: "match",
174
+ isPartial: false,
175
+ });
176
+ }
177
+
178
+ const result = await createMatchContextForFull(request, env);
179
+
180
+ // Handle redirect case
181
+ if ("type" in result && result.type === "redirect") {
182
+ if (hasTelemetry) {
183
+ safeEmit(telemetry, {
184
+ type: "request.end",
185
+ timestamp: performance.now(),
186
+ requestId,
187
+ method: request.method,
188
+ pathname,
189
+ transaction: "match",
190
+ durationMs: performance.now() - matchStart,
191
+ segmentCount: 0,
192
+ cacheHit: false,
193
+ });
194
+ }
195
+ return {
196
+ segments: [],
197
+ matched: [],
198
+ diff: [],
199
+ params: {},
200
+ redirect: result.redirectUrl,
201
+ };
202
+ }
203
+
204
+ const ctx = result as MatchContext<TEnv>;
205
+
206
+ try {
207
+ const state = createPipelineState();
208
+ const pipeline = createMatchPartialPipeline(ctx, state);
209
+ const matchResult = await collectMatchResult(pipeline, ctx, state);
210
+ if (hasTelemetry) {
211
+ safeEmit(telemetry, {
212
+ type: "cache.decision",
213
+ timestamp: performance.now(),
214
+ requestId,
215
+ pathname,
216
+ routeKey: ctx.routeKey,
217
+ hit: state.cacheHit,
218
+ shouldRevalidate: !!state.shouldRevalidate,
219
+ source: state.cacheSource,
220
+ });
221
+ safeEmit(telemetry, {
222
+ type: "request.end",
223
+ timestamp: performance.now(),
224
+ requestId,
225
+ method: request.method,
226
+ pathname,
227
+ transaction: "match",
228
+ durationMs: performance.now() - matchStart,
229
+ segmentCount: matchResult.segments.length,
230
+ cacheHit: state.cacheHit,
231
+ });
232
+ }
233
+ return matchResult;
234
+ } catch (error) {
235
+ if (hasTelemetry) {
236
+ const errorObj =
237
+ error instanceof Error ? error : new Error(String(error));
238
+ safeEmit(telemetry, {
239
+ type: "request.error",
240
+ timestamp: performance.now(),
241
+ requestId,
242
+ method: request.method,
243
+ pathname,
244
+ transaction: "match",
245
+ error: errorObj,
246
+ phase: error instanceof Response ? "redirect" : "routing",
247
+ durationMs: performance.now() - matchStart,
248
+ });
249
+ }
250
+ if (error instanceof Response) throw error;
251
+ // Report unhandled errors during full match pipeline
252
+ callOnError(error, "routing", {
253
+ request,
254
+ url: ctx.url,
255
+ env,
256
+ isPartial: false,
257
+ handledByBoundary: false,
258
+ });
259
+ throw sanitizeError(error);
260
+ }
261
+ }),
262
+ );
263
+ });
264
+ }
265
+
266
+ async function matchError(
267
+ request: Request,
268
+ _context: TEnv,
269
+ error: unknown,
270
+ segmentType: ErrorInfo["segmentType"] = "route",
271
+ ): Promise<MatchResult | null> {
272
+ return runWithRouterLogContext({ request, transaction: "matchError" }, () =>
273
+ withRouterLogScope("matchError", () =>
274
+ _matchError(
275
+ request,
276
+ _context,
277
+ error,
278
+ matchApiDeps,
279
+ defaultErrorBoundary,
280
+ segmentType,
281
+ ),
282
+ ),
283
+ );
284
+ }
285
+
286
+ /**
287
+ * Match partial request with revalidation
288
+ *
289
+ * Uses generator middleware pipeline for clean separation of concerns:
290
+ * - cache-lookup: Check cache first
291
+ * - segment-resolution: Resolve segments on cache miss
292
+ * - intercept-resolution: Handle intercept routes
293
+ * - cache-store: Store results in cache
294
+ * - background-revalidation: SWR revalidation
295
+ */
296
+ async function matchPartial(
297
+ request: Request,
298
+ context: TEnv,
299
+ actionContext?: ActionContext,
300
+ ): Promise<MatchResult | null> {
301
+ const partialRequestId = hasTelemetry ? getRequestId(request) : undefined;
302
+ return runWithRouterLogContext(
303
+ { request, transaction: "matchPartial" },
304
+ () => {
305
+ const routerCtx = buildRouterContext();
306
+ routerCtx.requestId = partialRequestId;
307
+ return runWithRouterContext(routerCtx, async () =>
308
+ withRouterLogScope("matchPartial", async () => {
309
+ const matchStart = performance.now();
310
+ const pathname = new URL(request.url).pathname;
311
+ if (hasTelemetry) {
312
+ safeEmit(telemetry, {
313
+ type: "request.start",
314
+ timestamp: matchStart,
315
+ requestId: partialRequestId,
316
+ method: request.method,
317
+ pathname,
318
+ transaction: "matchPartial",
319
+ isPartial: true,
320
+ });
321
+ }
322
+
323
+ const ctx = await createMatchContextForPartial(
324
+ request,
325
+ context,
326
+ actionContext,
327
+ );
328
+ if (!ctx) {
329
+ if (hasTelemetry) {
330
+ safeEmit(telemetry, {
331
+ type: "request.end",
332
+ timestamp: performance.now(),
333
+ requestId: partialRequestId,
334
+ method: request.method,
335
+ pathname,
336
+ transaction: "matchPartial",
337
+ durationMs: performance.now() - matchStart,
338
+ segmentCount: 0,
339
+ cacheHit: false,
340
+ });
341
+ }
342
+ return null;
343
+ }
344
+
345
+ if (isRouterDebugEnabled()) {
346
+ startRevalidationTrace({
347
+ method: request.method,
348
+ prevUrl: ctx.prevUrl.href,
349
+ nextUrl: ctx.url.href,
350
+ routeKey: ctx.routeKey,
351
+ isAction: !!actionContext,
352
+ stale: ctx.stale || undefined,
353
+ });
354
+ }
355
+
356
+ try {
357
+ const state = createPipelineState();
358
+ const pipeline = createMatchPartialPipeline(ctx, state);
359
+ const matchResult = await collectMatchResult(
360
+ pipeline,
361
+ ctx,
362
+ state,
363
+ );
364
+ flushRevalidationTrace();
365
+ if (hasTelemetry) {
366
+ safeEmit(telemetry, {
367
+ type: "cache.decision",
368
+ timestamp: performance.now(),
369
+ requestId: partialRequestId,
370
+ pathname,
371
+ routeKey: ctx.routeKey,
372
+ hit: state.cacheHit,
373
+ shouldRevalidate: !!state.shouldRevalidate,
374
+ source: state.cacheSource,
375
+ });
376
+ safeEmit(telemetry, {
377
+ type: "request.end",
378
+ timestamp: performance.now(),
379
+ requestId: partialRequestId,
380
+ method: request.method,
381
+ pathname,
382
+ transaction: "matchPartial",
383
+ durationMs: performance.now() - matchStart,
384
+ segmentCount: matchResult.segments.length,
385
+ cacheHit: state.cacheHit,
386
+ });
387
+ }
388
+ return matchResult;
389
+ } catch (error) {
390
+ flushRevalidationTrace();
391
+ if (hasTelemetry) {
392
+ const errorObj =
393
+ error instanceof Error ? error : new Error(String(error));
394
+ const phase = actionContext ? "action" : "revalidation";
395
+ safeEmit(telemetry, {
396
+ type: "request.error",
397
+ timestamp: performance.now(),
398
+ requestId: partialRequestId,
399
+ method: request.method,
400
+ pathname,
401
+ transaction: "matchPartial",
402
+ error: errorObj,
403
+ phase: error instanceof Response ? "redirect" : phase,
404
+ durationMs: performance.now() - matchStart,
405
+ });
406
+ }
407
+ if (error instanceof Response) throw error;
408
+ // Report unhandled errors during partial match pipeline
409
+ callOnError(error, actionContext ? "action" : "revalidation", {
410
+ request,
411
+ url: ctx.url,
412
+ env: context,
413
+ actionId: actionContext?.actionId,
414
+ isPartial: true,
415
+ handledByBoundary: false,
416
+ });
417
+ throw sanitizeError(error);
418
+ }
419
+ }),
420
+ );
421
+ },
422
+ );
423
+ }
424
+
425
+ async function previewMatch(
426
+ request: Request,
427
+ _context: TEnv,
428
+ ): ReturnType<typeof _previewMatch> {
429
+ return _previewMatch(request, _context, { findMatch: deps.findMatch });
430
+ }
431
+
432
+ return {
433
+ match: match,
434
+ matchPartial: matchPartial,
435
+ matchError: matchError,
436
+ previewMatch: previewMatch,
437
+ createMatchContextForFull: createMatchContextForFull,
438
+ createMatchContextForPartial: createMatchContextForPartial,
439
+ };
440
+ }