@rangojs/router 0.0.0-experimental.122 → 0.0.0-experimental.125

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 (260) hide show
  1. package/dist/bin/rango.js +10 -6
  2. package/dist/testing/vitest.js +82 -0
  3. package/dist/vite/index.js +55 -48
  4. package/package.json +61 -21
  5. package/skills/caching/SKILL.md +2 -1
  6. package/skills/hooks/SKILL.md +40 -29
  7. package/skills/host-router/SKILL.md +16 -2
  8. package/skills/intercept/SKILL.md +4 -2
  9. package/skills/layout/SKILL.md +11 -6
  10. package/skills/loader/SKILL.md +6 -2
  11. package/skills/middleware/SKILL.md +4 -2
  12. package/skills/migrate-nextjs/SKILL.md +3 -1
  13. package/skills/parallel/SKILL.md +9 -4
  14. package/skills/rango/SKILL.md +12 -0
  15. package/skills/route/SKILL.md +10 -2
  16. package/skills/testing/SKILL.md +129 -0
  17. package/skills/testing/bindings.md +89 -0
  18. package/skills/testing/cache-prerender.md +98 -0
  19. package/skills/testing/client-components.md +122 -0
  20. package/skills/testing/e2e-parity.md +125 -0
  21. package/skills/testing/flight.md +89 -0
  22. package/skills/testing/handles.md +129 -0
  23. package/skills/testing/loader.md +128 -0
  24. package/skills/testing/middleware.md +99 -0
  25. package/skills/testing/render-handler.md +118 -0
  26. package/skills/testing/response-routes.md +95 -0
  27. package/skills/testing/reverse-and-types.md +84 -0
  28. package/skills/testing/server-actions.md +107 -0
  29. package/skills/testing/server-tree.md +128 -0
  30. package/skills/testing/setup.md +120 -0
  31. package/src/__internal.ts +0 -65
  32. package/src/browser/action-coordinator.ts +1 -1
  33. package/src/browser/action-fence.ts +47 -0
  34. package/src/browser/cookie-name.ts +140 -0
  35. package/src/browser/event-controller.ts +1 -83
  36. package/src/browser/invalidate-client-cache.ts +52 -0
  37. package/src/browser/navigation-bridge.ts +14 -1
  38. package/src/browser/navigation-client.ts +14 -1
  39. package/src/browser/navigation-store-handle.ts +38 -0
  40. package/src/browser/navigation-store.ts +26 -51
  41. package/src/browser/navigation-transaction.ts +0 -32
  42. package/src/browser/partial-update.ts +1 -83
  43. package/src/browser/prefetch/cache.ts +6 -45
  44. package/src/browser/prefetch/fetch.ts +7 -0
  45. package/src/browser/prefetch/queue.ts +6 -3
  46. package/src/browser/rango-state.ts +157 -99
  47. package/src/browser/react/Link.tsx +0 -2
  48. package/src/browser/react/NavigationProvider.tsx +2 -1
  49. package/src/browser/react/ScrollRestoration.tsx +10 -6
  50. package/src/browser/react/filter-segment-order.ts +0 -2
  51. package/src/browser/react/index.ts +0 -51
  52. package/src/browser/react/location-state-shared.ts +0 -13
  53. package/src/browser/react/location-state.ts +0 -1
  54. package/src/browser/react/use-action.ts +6 -15
  55. package/src/browser/react/use-handle.ts +0 -5
  56. package/src/browser/react/use-link-status.ts +0 -4
  57. package/src/browser/react/use-navigation.ts +0 -3
  58. package/src/browser/react/use-params.ts +0 -2
  59. package/src/browser/react/use-search-params.ts +0 -5
  60. package/src/browser/react/use-segments.ts +0 -13
  61. package/src/browser/rsc-router.tsx +12 -4
  62. package/src/browser/server-action-bridge.ts +77 -15
  63. package/src/browser/types.ts +7 -2
  64. package/src/browser/validate-redirect-origin.ts +4 -5
  65. package/src/build/route-trie.ts +3 -0
  66. package/src/build/route-types/param-extraction.ts +6 -3
  67. package/src/build/route-types/router-processing.ts +0 -8
  68. package/src/cache/cache-policy.ts +0 -54
  69. package/src/cache/cache-runtime.ts +27 -24
  70. package/src/cache/cache-scope.ts +0 -27
  71. package/src/cache/cache-tag.ts +0 -37
  72. package/src/cache/cf/cf-cache-store.ts +94 -46
  73. package/src/cache/cf/index.ts +0 -24
  74. package/src/cache/document-cache.ts +11 -36
  75. package/src/cache/handle-snapshot.ts +0 -40
  76. package/src/cache/index.ts +0 -27
  77. package/src/cache/memory-segment-store.ts +2 -48
  78. package/src/cache/profile-registry.ts +7 -3
  79. package/src/cache/read-through-swr.ts +41 -11
  80. package/src/cache/segment-codec.ts +0 -16
  81. package/src/cache/types.ts +0 -98
  82. package/src/client.rsc.tsx +1 -22
  83. package/src/client.tsx +14 -38
  84. package/src/component-utils.ts +19 -0
  85. package/src/deps/ssr.ts +0 -1
  86. package/src/handle.ts +28 -18
  87. package/src/handles/MetaTags.tsx +0 -14
  88. package/src/handles/meta.ts +0 -39
  89. package/src/host/cookie-handler.ts +0 -36
  90. package/src/host/errors.ts +0 -24
  91. package/src/host/index.ts +6 -0
  92. package/src/host/pattern-matcher.ts +7 -50
  93. package/src/host/router.ts +1 -65
  94. package/src/host/testing.ts +40 -27
  95. package/src/host/types.ts +6 -2
  96. package/src/href-client.ts +0 -4
  97. package/src/index.rsc.ts +42 -3
  98. package/src/index.ts +31 -1
  99. package/src/internal-debug.ts +2 -4
  100. package/src/loader.rsc.ts +19 -9
  101. package/src/loader.ts +12 -4
  102. package/src/network-error-thrower.tsx +1 -6
  103. package/src/outlet-provider.tsx +1 -5
  104. package/src/prerender/param-hash.ts +10 -11
  105. package/src/prerender/store.ts +23 -30
  106. package/src/prerender.ts +58 -3
  107. package/src/root-error-boundary.tsx +1 -19
  108. package/src/route-content-wrapper.tsx +1 -44
  109. package/src/route-definition/dsl-helpers.ts +7 -19
  110. package/src/route-definition/helpers-types.ts +3 -3
  111. package/src/route-definition/redirect.ts +11 -1
  112. package/src/route-map-builder.ts +0 -16
  113. package/src/router/basename.ts +14 -0
  114. package/src/router/content-negotiation.ts +0 -13
  115. package/src/router/error-handling.ts +12 -16
  116. package/src/router/find-match.ts +4 -30
  117. package/src/router/intercept-resolution.ts +10 -1
  118. package/src/router/lazy-includes.ts +1 -57
  119. package/src/router/loader-resolution.ts +3 -2
  120. package/src/router/logging.ts +0 -6
  121. package/src/router/manifest.ts +1 -25
  122. package/src/router/match-api.ts +0 -20
  123. package/src/router/match-context.ts +0 -22
  124. package/src/router/match-handlers.ts +57 -58
  125. package/src/router/match-middleware/background-revalidation.ts +0 -7
  126. package/src/router/match-middleware/cache-lookup.ts +1 -54
  127. package/src/router/match-middleware/cache-store.ts +0 -31
  128. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  129. package/src/router/match-middleware/segment-resolution.ts +0 -21
  130. package/src/router/match-pipelines.ts +1 -42
  131. package/src/router/match-result.ts +1 -52
  132. package/src/router/metrics.ts +0 -34
  133. package/src/router/middleware-cookies.ts +0 -13
  134. package/src/router/middleware-types.ts +0 -115
  135. package/src/router/middleware.ts +7 -30
  136. package/src/router/navigation-snapshot.ts +0 -51
  137. package/src/router/params-util.ts +23 -0
  138. package/src/router/pattern-matching.ts +1 -33
  139. package/src/router/prerender-match.ts +33 -45
  140. package/src/router/request-classification.ts +1 -38
  141. package/src/router/revalidation.ts +5 -58
  142. package/src/router/router-context.ts +0 -26
  143. package/src/router/router-interfaces.ts +7 -0
  144. package/src/router/router-options.ts +30 -0
  145. package/src/router/segment-resolution/fresh.ts +25 -57
  146. package/src/router/segment-resolution/helpers.ts +34 -0
  147. package/src/router/segment-resolution/loader-cache.ts +10 -13
  148. package/src/router/segment-resolution/revalidation.ts +5 -42
  149. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  150. package/src/router/segment-resolution.ts +4 -1
  151. package/src/router/state-cookie-name.ts +33 -0
  152. package/src/router/telemetry-otel.ts +0 -20
  153. package/src/router/telemetry.ts +96 -19
  154. package/src/router/timeout.ts +0 -20
  155. package/src/router/trie-matching.ts +63 -40
  156. package/src/router/types.ts +1 -63
  157. package/src/router/url-params.ts +0 -5
  158. package/src/router.ts +40 -9
  159. package/src/rsc/handler.ts +14 -2
  160. package/src/rsc/helpers.ts +34 -0
  161. package/src/rsc/origin-guard.ts +0 -12
  162. package/src/rsc/progressive-enhancement.ts +4 -1
  163. package/src/rsc/rsc-rendering.ts +4 -7
  164. package/src/rsc/runtime-warnings.ts +14 -0
  165. package/src/rsc/server-action.ts +30 -28
  166. package/src/rsc/types.ts +2 -1
  167. package/src/runtime-env.ts +18 -0
  168. package/src/search-params.ts +0 -16
  169. package/src/segment-loader-promise.ts +14 -2
  170. package/src/segment-system.tsx +79 -88
  171. package/src/server/cookie-store.ts +52 -1
  172. package/src/server/handle-store.ts +7 -24
  173. package/src/server/loader-registry.ts +5 -24
  174. package/src/server/request-context.ts +74 -77
  175. package/src/ssr/index.tsx +14 -14
  176. package/src/static-handler.ts +10 -13
  177. package/src/testing/cache-status.ts +119 -0
  178. package/src/testing/collect-handle.ts +40 -0
  179. package/src/testing/dispatch.ts +581 -0
  180. package/src/testing/dom.entry.ts +22 -0
  181. package/src/testing/e2e/fixture.ts +188 -0
  182. package/src/testing/e2e/index.ts +127 -0
  183. package/src/testing/e2e/matchers.ts +35 -0
  184. package/src/testing/e2e/page-helpers.ts +272 -0
  185. package/src/testing/e2e/parity.ts +387 -0
  186. package/src/testing/e2e/server.ts +195 -0
  187. package/src/testing/flight-matchers.ts +97 -0
  188. package/src/testing/flight-normalize.ts +11 -0
  189. package/src/testing/flight-runtime.d.ts +57 -0
  190. package/src/testing/flight-tree.ts +682 -0
  191. package/src/testing/flight.entry.ts +52 -0
  192. package/src/testing/flight.ts +186 -0
  193. package/src/testing/generated-routes.ts +183 -0
  194. package/src/testing/index.ts +98 -0
  195. package/src/testing/internal/context.ts +348 -0
  196. package/src/testing/internal/flight-client-globals.ts +30 -0
  197. package/src/testing/internal/seed-vars.ts +54 -0
  198. package/src/testing/render-handler.ts +311 -0
  199. package/src/testing/render-route.tsx +504 -0
  200. package/src/testing/run-loader.ts +378 -0
  201. package/src/testing/run-middleware.ts +205 -0
  202. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  203. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  204. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  205. package/src/testing/vitest-stubs/version.ts +5 -0
  206. package/src/testing/vitest.ts +305 -0
  207. package/src/theme/ThemeProvider.tsx +0 -52
  208. package/src/theme/ThemeScript.tsx +0 -6
  209. package/src/theme/constants.ts +0 -12
  210. package/src/theme/index.ts +0 -7
  211. package/src/theme/theme-context.ts +1 -5
  212. package/src/theme/theme-script.ts +0 -14
  213. package/src/theme/use-theme.ts +0 -3
  214. package/src/types/boundaries.ts +0 -35
  215. package/src/types/error-types.ts +25 -89
  216. package/src/types/global-namespace.ts +15 -15
  217. package/src/types/handler-context.ts +16 -13
  218. package/src/types/index.ts +0 -10
  219. package/src/types/request-scope.ts +0 -19
  220. package/src/types/route-config.ts +6 -50
  221. package/src/types/route-entry.ts +0 -6
  222. package/src/types/segments.ts +0 -13
  223. package/src/urls/include-helper.ts +0 -4
  224. package/src/urls/index.ts +0 -6
  225. package/src/urls/path-helper-types.ts +2 -2
  226. package/src/urls/path-helper.ts +0 -54
  227. package/src/urls/urls-function.ts +0 -13
  228. package/src/use-loader.tsx +0 -186
  229. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  230. package/src/vite/discovery/discover-routers.ts +6 -7
  231. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  232. package/src/vite/plugin-types.ts +3 -1
  233. package/src/vite/plugins/cjs-to-esm.ts +0 -11
  234. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  235. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  236. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  237. package/src/vite/plugins/expose-action-id.ts +2 -73
  238. package/src/vite/plugins/expose-id-utils.ts +0 -55
  239. package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
  240. package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
  241. package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
  242. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  243. package/src/vite/plugins/expose-internal-ids.ts +10 -0
  244. package/src/vite/plugins/performance-tracks.ts +0 -3
  245. package/src/vite/plugins/use-cache-transform.ts +0 -36
  246. package/src/vite/plugins/version-injector.ts +0 -20
  247. package/src/vite/plugins/version-plugin.ts +1 -49
  248. package/src/vite/plugins/virtual-entries.ts +0 -15
  249. package/src/vite/rango.ts +1 -108
  250. package/src/vite/router-discovery.ts +2 -1
  251. package/src/vite/utils/ast-handler-extract.ts +0 -16
  252. package/src/vite/utils/bundle-analysis.ts +6 -13
  253. package/src/vite/utils/client-chunks.ts +0 -6
  254. package/src/vite/utils/forward-user-plugins.ts +0 -22
  255. package/src/vite/utils/manifest-utils.ts +0 -4
  256. package/src/vite/utils/package-resolution.ts +1 -73
  257. package/src/vite/utils/prerender-utils.ts +0 -35
  258. package/src/vite/utils/shared-utils.ts +3 -35
  259. package/src/browser/react/use-client-cache.ts +0 -58
  260. package/src/browser/shallow.ts +0 -40
@@ -168,8 +168,6 @@ export function withBackgroundRevalidation<TEnv>(
168
168
  requestCtx._handleStore = createHandleStore();
169
169
 
170
170
  try {
171
- // Create fresh handler context and loader promises to avoid
172
- // reusing memoized results from the foreground pass
173
171
  const freshHandlerContext = createHandlerContext(
174
172
  ctx.matched.params,
175
173
  ctx.request,
@@ -185,10 +183,6 @@ export function withBackgroundRevalidation<TEnv>(
185
183
  const freshLoaderPromises = new Map<string, Promise<any>>();
186
184
  setupLoaderAccess(freshHandlerContext, freshLoaderPromises);
187
185
 
188
- // Resolve all segments fresh (without revalidation logic)
189
- // to ensure complete components for caching.
190
- // Skip DSL loaders — they are never cached (cacheRoute filters them)
191
- // and are always resolved fresh on each request.
192
186
  const freshSegments = await ctx.Store.run(() =>
193
187
  resolveAllSegments(
194
188
  ctx.entries,
@@ -200,7 +194,6 @@ export function withBackgroundRevalidation<TEnv>(
200
194
  ),
201
195
  );
202
196
 
203
- // Also resolve intercept segments fresh if applicable
204
197
  let freshInterceptSegments: ResolvedSegment[] = [];
205
198
  if (ctx.interceptResult) {
206
199
  freshInterceptSegments = await ctx.Store.run(() =>
@@ -103,6 +103,7 @@ import {
103
103
  getRequestContext,
104
104
  _getRequestContext,
105
105
  } from "../../server/request-context.js";
106
+ import { paramsEqual } from "../params-util.js";
106
107
 
107
108
  // Lazily initialized prerender store singleton and dynamically imported deps.
108
109
  // Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
@@ -124,22 +125,6 @@ let _lazyGetRequestContext:
124
125
  | typeof import("../../server/request-context.js").getRequestContext
125
126
  | undefined;
126
127
 
127
- function paramsEqual(
128
- a: Record<string, string>,
129
- b: Record<string, string>,
130
- ): boolean {
131
- if (a === b) return true;
132
-
133
- const keysA = Object.keys(a);
134
- if (keysA.length !== Object.keys(b).length) return false;
135
-
136
- for (const key of keysA) {
137
- if (a[key] !== b[key]) return false;
138
- }
139
-
140
- return true;
141
- }
142
-
143
128
  async function ensurePrerenderDeps() {
144
129
  if (!_deserializeSegments) {
145
130
  const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
@@ -367,10 +352,6 @@ export function withCacheLookup<TEnv>(
367
352
  resolveLoadersOnly,
368
353
  } = getRouterContext<TEnv>();
369
354
 
370
- // Prerender lookup: check build-time cached data before runtime cache.
371
- // Prerender data is available regardless of runtime cache configuration.
372
- // Skip for HMR requests — the dev prerender endpoint reads from a stale
373
- // RouterRegistry snapshot; rendering fresh ensures edits are visible.
374
355
  const isHmr = !!ctx.request.headers.get("X-RSC-HMR");
375
356
  if (!ctx.isAction && !isHmr && ctx.matched.pr) {
376
357
  await ensurePrerenderDeps();
@@ -385,14 +366,6 @@ export function withCacheLookup<TEnv>(
385
366
  }
386
367
  }
387
368
 
388
- // Dev-mode static handler interception for non-Node.js runtimes.
389
- // __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
390
- // lacks a Node.js module runner (e.g. workerd, Deno workers). In those
391
- // runtimes, handlers that depend on Node APIs like node:fs can't run
392
- // in-process. We redirect them to the /__rsc_prerender endpoint which
393
- // resolves segments in a Node.js temp server, same as prerender routes.
394
- // In Node.js dev mode this variable is undefined -- handlers run
395
- // in-process where Node APIs work, so no interception is needed.
396
369
  if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
397
370
  const hasStatic = ctx.entries.some(
398
371
  (e) =>
@@ -415,9 +388,7 @@ export function withCacheLookup<TEnv>(
415
388
  }
416
389
  }
417
390
 
418
- // Skip cache during actions
419
391
  if (ctx.isAction || !ctx.cacheScope?.enabled) {
420
- // Cache miss - pass through to segment resolution
421
392
  yield* source;
422
393
  if (ms) {
423
394
  ms.metrics.push({
@@ -429,7 +400,6 @@ export function withCacheLookup<TEnv>(
429
400
  return;
430
401
  }
431
402
 
432
- // Lookup cache
433
403
  const cacheResult = await ctx.cacheScope.lookupRoute(
434
404
  ctx.pathname,
435
405
  ctx.matched.params,
@@ -437,7 +407,6 @@ export function withCacheLookup<TEnv>(
437
407
  );
438
408
 
439
409
  if (!cacheResult) {
440
- // Cache miss - pass through to segment resolution
441
410
  yield* source;
442
411
  if (ms) {
443
412
  ms.metrics.push({
@@ -449,16 +418,12 @@ export function withCacheLookup<TEnv>(
449
418
  return;
450
419
  }
451
420
 
452
- // Cache HIT
453
421
  state.cacheHit = true;
454
422
  state.cacheSource = "runtime";
455
423
  state.shouldRevalidate = cacheResult.shouldRevalidate;
456
424
  state.cachedSegments = cacheResult.segments;
457
425
  state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
458
426
 
459
- // Apply revalidation to cached segments.
460
- // For full matches or empty client segment sets, this map is unnecessary:
461
- // we never run segment-level revalidation and can stream segments directly.
462
427
  const canCheckSegmentRevalidation =
463
428
  !ctx.isFullMatch &&
464
429
  ctx.clientSegmentSet.size > 0 &&
@@ -468,7 +433,6 @@ export function withCacheLookup<TEnv>(
468
433
  : undefined;
469
434
 
470
435
  for (const segment of cacheResult.segments) {
471
- // Skip segments client doesn't have - they need their component
472
436
  if (!ctx.clientSegmentSet.has(segment.id)) {
473
437
  if (isTraceActive()) {
474
438
  pushRevalidationTraceEntry({
@@ -485,19 +449,13 @@ export function withCacheLookup<TEnv>(
485
449
  continue;
486
450
  }
487
451
 
488
- // Skip intercept segments - they're handled separately
489
452
  if (segment.namespace?.startsWith("intercept:")) {
490
453
  yield segment;
491
454
  continue;
492
455
  }
493
456
 
494
- // Look up revalidation rules for this segment
495
457
  const entryInfo = entryRevalidateMap?.get(segment.id);
496
458
 
497
- // Even without explicit revalidation rules, route segments and their
498
- // children must re-render when params or search params change — the
499
- // handler reads ctx.params/ctx.searchParams so different values produce
500
- // different content. Matches evaluateRevalidation's default logic.
501
459
  const searchChanged = ctx.prevUrl.search !== ctx.url.search;
502
460
  const routeParamsChanged = !paramsEqual(
503
461
  ctx.matched.params,
@@ -511,7 +469,6 @@ export function withCacheLookup<TEnv>(
511
469
 
512
470
  if (!entryInfo || entryInfo.revalidate.length === 0) {
513
471
  if (shouldDefaultRevalidate) {
514
- // Params or search params changed — must re-render even without custom rules
515
472
  if (isTraceActive()) {
516
473
  pushRevalidationTraceEntry({
517
474
  segmentId: segment.id,
@@ -528,7 +485,6 @@ export function withCacheLookup<TEnv>(
528
485
  yield segment;
529
486
  continue;
530
487
  }
531
- // No revalidation rules, use default behavior (skip if client has)
532
488
  if (isTraceActive()) {
533
489
  pushRevalidationTraceEntry({
534
490
  segmentId: segment.id,
@@ -546,7 +502,6 @@ export function withCacheLookup<TEnv>(
546
502
  continue;
547
503
  }
548
504
 
549
- // Evaluate revalidation rules
550
505
  const shouldRevalidate = await evaluateRevalidation({
551
506
  segment,
552
507
  prevParams: ctx.prevParams,
@@ -580,7 +535,6 @@ export function withCacheLookup<TEnv>(
580
535
  }
581
536
 
582
537
  if (!shouldRevalidate) {
583
- // Client has it, no revalidation needed
584
538
  segment.component = null;
585
539
  segment.loading = undefined;
586
540
  }
@@ -588,7 +542,6 @@ export function withCacheLookup<TEnv>(
588
542
  yield segment;
589
543
  }
590
544
 
591
- // Set streaming flag (once) and resolve render barrier.
592
545
  const barrierReqCtx = _getRequestContext();
593
546
  if (barrierReqCtx) {
594
547
  if (barrierReqCtx._treeHasStreaming === undefined) {
@@ -597,22 +550,17 @@ export function withCacheLookup<TEnv>(
597
550
  barrierReqCtx._resolveRenderBarrier(cacheResult.segments);
598
551
  }
599
552
 
600
- // Resolve loaders fresh (loaders are NOT cached by default)
601
- // This ensures fresh data even on cache hit
602
553
  const Store = ctx.Store;
603
554
  const loaderStart = performance.now();
604
555
 
605
556
  if (ctx.isFullMatch) {
606
- // Full match (document request) - simple loader resolution without revalidation
607
557
  if (resolveLoadersOnly) {
608
558
  const loaderSegments = await Store.run(() =>
609
559
  resolveLoadersOnly(ctx.entries, ctx.handlerContext),
610
560
  );
611
561
 
612
- // Update state - full match doesn't track matchedIds separately
613
562
  state.matchedIds = state.cachedMatchedIds!;
614
563
 
615
- // Yield fresh loader segments
616
564
  for (const segment of loaderSegments) {
617
565
  yield segment;
618
566
  }
@@ -620,7 +568,6 @@ export function withCacheLookup<TEnv>(
620
568
  state.matchedIds = state.cachedMatchedIds!;
621
569
  }
622
570
  } else {
623
- // Partial match (navigation) - loader resolution with revalidation
624
571
  if (resolveLoadersOnlyWithRevalidation) {
625
572
  const loaderResult = await Store.run(() =>
626
573
  resolveLoadersOnlyWithRevalidation(
@@ -123,21 +123,14 @@ export function withCacheStore<TEnv>(
123
123
  ): AsyncGenerator<ResolvedSegment> {
124
124
  const ms = ctx.metricsStore;
125
125
 
126
- // Collect all segments while passing them through
127
126
  const allSegments: ResolvedSegment[] = [];
128
127
  for await (const segment of source) {
129
128
  allSegments.push(segment);
130
129
  yield segment;
131
130
  }
132
131
 
133
- // Measure own work only (after source iteration completes)
134
132
  const ownStart = performance.now();
135
133
 
136
- // Skip caching if:
137
- // 1. Cache miss but cache scope is disabled
138
- // 2. This is an action (actions don't cache)
139
- // 3. Cache was already hit (no need to re-cache)
140
- // 4. Non-GET request (only cache GET requests)
141
134
  if (
142
135
  !ctx.cacheScope?.enabled ||
143
136
  ctx.isAction ||
@@ -162,13 +155,8 @@ export function withCacheStore<TEnv>(
162
155
  createHandleStore,
163
156
  } = getRouterContext<TEnv>();
164
157
 
165
- // Combine main segments with intercept segments
166
158
  const allSegmentsToCache = [...allSegments, ...state.interceptSegments];
167
159
 
168
- // Check if any non-loader segments have null components from revalidation
169
- // skip (client already had them). Segments where the handler intentionally
170
- // returned null are not revalidation skips — re-rendering them will still
171
- // produce null, so proactive caching would be wasted work.
172
160
  const hasNullComponents = allSegmentsToCache.some(
173
161
  (s) =>
174
162
  s.component === null &&
@@ -184,10 +172,7 @@ export function withCacheStore<TEnv>(
184
172
  ? getOrCreateRequestId(ctx.request)
185
173
  : undefined;
186
174
 
187
- // Register onResponse callback to skip caching for non-200 responses
188
- // Note: error/notFound status codes are set elsewhere (not caching-specific)
189
175
  requestCtx.onResponse((response) => {
190
- // Only cache successful responses
191
176
  if (response.status !== 200) {
192
177
  debugLog("cacheStore", "skipping cache for non-200 response", {
193
178
  status: response.status,
@@ -197,10 +182,7 @@ export function withCacheStore<TEnv>(
197
182
  }
198
183
 
199
184
  if (hasNullComponents) {
200
- // Proactive caching: render all segments fresh in background
201
- // This ensures cache has complete components for future requests
202
185
  requestCtx.waitUntil(async () => {
203
- // Prevent background metrics from polluting foreground timeline.
204
186
  const savedMetrics = ctx.Store.metrics;
205
187
  ctx.Store.metrics = undefined;
206
188
 
@@ -208,15 +190,9 @@ export function withCacheStore<TEnv>(
208
190
  debugLog("cacheStore", "proactive caching started", {
209
191
  pathname: ctx.pathname,
210
192
  });
211
- // Swap to a fresh HandleStore so handle.push() calls from
212
- // proactive resolution are captured (not silenced). The original
213
- // store's stream is already sent by waitUntil time.
214
- // cacheRoute reads from requestCtx._handleStore, so this ensures
215
- // complete handle data (e.g. breadcrumbs) is cached.
216
193
  const originalHandleStore = requestCtx._handleStore;
217
194
  requestCtx._handleStore = createHandleStore();
218
195
  try {
219
- // Create fresh context for proactive caching
220
196
  const proactiveHandlerContext = createHandlerContext(
221
197
  ctx.matched.params,
222
198
  ctx.request,
@@ -231,12 +207,8 @@ export function withCacheStore<TEnv>(
231
207
  );
232
208
  const proactiveLoaderPromises = new Map<string, Promise<any>>();
233
209
 
234
- // Use normal loader access so handle data is captured
235
210
  setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
236
211
 
237
- // Re-resolve ALL segments without revalidation.
238
- // Skip DSL loaders — they are never cached (cacheRoute filters them)
239
- // and are always resolved fresh on each request.
240
212
  const Store = ctx.Store;
241
213
  const freshSegments = await Store.run(() =>
242
214
  resolveAllSegments(
@@ -249,7 +221,6 @@ export function withCacheStore<TEnv>(
249
221
  ),
250
222
  );
251
223
 
252
- // Also resolve intercept segments fresh if applicable
253
224
  let freshInterceptSegments: ResolvedSegment[] = [];
254
225
  if (ctx.interceptResult) {
255
226
  freshInterceptSegments = await Store.run(() =>
@@ -301,8 +272,6 @@ export function withCacheStore<TEnv>(
301
272
  }
302
273
  });
303
274
  } else {
304
- // All segments have components - cache directly
305
- // Schedule caching in waitUntil since cacheRoute is now async (key resolution)
306
275
  if (INTERNAL_RANGO_DEBUG) {
307
276
  console.log(
308
277
  `[RSC CacheStore][req:${reqId}] Direct cache path: scheduling cacheRoute for ${ctx.pathname} (${allSegmentsToCache.length} segments, hasNullComponents=${hasNullComponents})`,
@@ -125,17 +125,14 @@ export function withInterceptResolution<TEnv>(
125
125
  ): AsyncGenerator<ResolvedSegment> {
126
126
  const ms = ctx.metricsStore;
127
127
 
128
- // First, yield all segments from the source (main segment resolution or cache)
129
128
  const segments: ResolvedSegment[] = [];
130
129
  for await (const segment of source) {
131
130
  segments.push(segment);
132
131
  yield segment;
133
132
  }
134
133
 
135
- // Measure own work only (after source iteration completes)
136
134
  const ownStart = performance.now();
137
135
 
138
- // Skip intercept resolution for full match (document requests don't have intercepts)
139
136
  if (ctx.isFullMatch) {
140
137
  if (ms) {
141
138
  ms.metrics.push({
@@ -147,18 +144,12 @@ export function withInterceptResolution<TEnv>(
147
144
  return;
148
145
  }
149
146
 
150
- // Skip intercept resolution if:
151
- // 1. No intercept result
152
- // 2. Already have intercept segments (from cache hit with intercept key)
153
- // 3. Cache hit with intercept key
154
147
  const skipInterceptResolution =
155
148
  !ctx.interceptResult ||
156
149
  state.interceptSegments.length > 0 ||
157
150
  (state.cacheHit && ctx.isIntercept);
158
151
 
159
152
  if (skipInterceptResolution) {
160
- // For cache hit with intercept, extract intercept segments from cached data for slots
161
- // and re-resolve loaders for fresh data
162
153
  if (ctx.interceptResult && state.cacheHit && ctx.isIntercept) {
163
154
  await handleCacheHitIntercept(ctx, state, segments);
164
155
  }
@@ -172,7 +163,6 @@ export function withInterceptResolution<TEnv>(
172
163
  return;
173
164
  }
174
165
 
175
- // Resolve intercept segments
176
166
  const { resolveInterceptEntry } = getRouterContext<TEnv>();
177
167
 
178
168
  const slotName = ctx.interceptResult!.intercept.slotName;
@@ -181,7 +171,6 @@ export function withInterceptResolution<TEnv>(
181
171
  slotName,
182
172
  });
183
173
 
184
- // Resolve intercept entry (middleware, loaders, handler)
185
174
  const Store = ctx.Store;
186
175
  const interceptSegments = await Store.run(() =>
187
176
  resolveInterceptEntry(
@@ -203,14 +192,12 @@ export function withInterceptResolution<TEnv>(
203
192
  ),
204
193
  );
205
194
 
206
- // Update state
207
195
  state.interceptSegments = interceptSegments;
208
196
  state.slots[slotName] = {
209
197
  active: true,
210
198
  segments: interceptSegments,
211
199
  };
212
200
 
213
- // Yield intercept segments
214
201
  for (const segment of interceptSegments) {
215
202
  yield segment;
216
203
  }
@@ -225,11 +212,6 @@ export function withInterceptResolution<TEnv>(
225
212
  };
226
213
  }
227
214
 
228
- /**
229
- * Handle cache hit with intercept scenario
230
- *
231
- * Extract intercept segments from cached data and re-resolve loaders for fresh data.
232
- */
233
215
  async function handleCacheHitIntercept<TEnv>(
234
216
  ctx: MatchContext<TEnv>,
235
217
  state: MatchPipelineState,
@@ -241,14 +223,11 @@ async function handleCacheHitIntercept<TEnv>(
241
223
 
242
224
  const slotName = ctx.interceptResult.intercept.slotName;
243
225
 
244
- // Find intercept segments from cached segments (namespace starts with "intercept:")
245
226
  const interceptSegments = segments.filter((s) =>
246
227
  s.namespace?.startsWith("intercept:"),
247
228
  );
248
229
  state.interceptSegments = interceptSegments;
249
230
 
250
- // Re-resolve intercept loaders for fresh data on cache hit
251
- // This keeps cached component/layout but fetches fresh loader data
252
231
  if (resolveInterceptLoadersOnly) {
253
232
  const Store = ctx.Store;
254
233
  const freshLoaderResult = await Store.run(() =>
@@ -271,7 +250,6 @@ async function handleCacheHitIntercept<TEnv>(
271
250
  ),
272
251
  );
273
252
 
274
- // Update intercept segment's loaderDataPromise with fresh data
275
253
  if (freshLoaderResult) {
276
254
  const interceptMainSegment = interceptSegments.find(
277
255
  (s) => s.type === "parallel" && s.slot,
@@ -93,12 +93,6 @@ import type { MatchContext, MatchPipelineState } from "../match-context.js";
93
93
  import { getRouterContext } from "../router-context.js";
94
94
  import type { GeneratorMiddleware } from "./cache-lookup.js";
95
95
 
96
- /**
97
- * Check whether any entry in the tree uses loading() (streaming).
98
- * Matches the router's streaming semantics in fresh.ts: streaming is
99
- * enabled when `loading` is defined AND not `false`. `loading: false`
100
- * explicitly disables streaming; `undefined` means no loading at all.
101
- */
102
96
  export function treeHasStreaming(entries: EntryData[]): boolean {
103
97
  for (const entry of entries) {
104
98
  if (
@@ -130,12 +124,6 @@ export function treeHasStreaming(entries: EntryData[]): boolean {
130
124
  return false;
131
125
  }
132
126
 
133
- /**
134
- * Creates segment resolution middleware
135
- *
136
- * Only runs on cache miss (state.cacheHit === false).
137
- * Uses resolveAllSegmentsWithRevalidation from RouterContext to resolve segments.
138
- */
139
127
  export function withSegmentResolution<TEnv>(
140
128
  ctx: MatchContext<TEnv>,
141
129
  state: MatchPipelineState,
@@ -145,17 +133,12 @@ export function withSegmentResolution<TEnv>(
145
133
  ): AsyncGenerator<ResolvedSegment> {
146
134
  const ms = ctx.metricsStore;
147
135
 
148
- // IMPORTANT: Always iterate source first to give cache-lookup a chance
149
- // to run and set state.cacheHit. Without this, cache-lookup never executes!
150
136
  for await (const segment of source) {
151
137
  yield segment;
152
138
  }
153
139
 
154
- // Measure own work only (after source iteration completes)
155
140
  const ownStart = performance.now();
156
141
 
157
- // If cache hit, segments were already yielded by cache lookup
158
- // (render barrier is resolved on the cache-hit path)
159
142
  if (state.cacheHit) {
160
143
  if (ms) {
161
144
  ms.metrics.push({
@@ -178,7 +161,6 @@ export function withSegmentResolution<TEnv>(
178
161
  const Store = ctx.Store;
179
162
 
180
163
  if (ctx.isFullMatch) {
181
- // Full match (document request) - simple resolution without revalidation
182
164
  const segments = await Store.run(() =>
183
165
  resolveAllSegments(
184
166
  ctx.entries,
@@ -189,15 +171,12 @@ export function withSegmentResolution<TEnv>(
189
171
  ),
190
172
  );
191
173
 
192
- // Update state with resolved segments
193
174
  state.segments = segments;
194
175
  state.matchedIds = segments.map((s: { id: string }) => s.id);
195
176
 
196
177
  if (reqCtx) {
197
178
  reqCtx._resolveRenderBarrier(segments);
198
179
  }
199
-
200
- // Yield all resolved segments
201
180
  for (const segment of segments) {
202
181
  yield segment;
203
182
  }
@@ -106,16 +106,6 @@ import {
106
106
  withSegmentResolution,
107
107
  } from "./match-middleware/index.js";
108
108
 
109
- /**
110
- * Compose multiple async generator middleware into a single middleware
111
- *
112
- * Middleware are applied in reverse order (rightmost runs first, innermost).
113
- * For the pipeline:
114
- * compose(A, B, C)(source)
115
- *
116
- * The flow is: source -> C -> B -> A -> output
117
- * Where C is the innermost (runs first on input) and A is outermost (runs last).
118
- */
119
109
  export function compose<T>(
120
110
  ...middleware: GeneratorMiddleware<T>[]
121
111
  ): GeneratorMiddleware<T> {
@@ -126,54 +116,23 @@ export function compose<T>(
126
116
  return middleware[0];
127
117
  }
128
118
  return (source) => {
129
- // Apply middleware in reverse order (rightmost first)
130
119
  return middleware.reduceRight((prev, fn) => fn(prev), source);
131
120
  };
132
121
  }
133
122
 
134
- /**
135
- * Create an empty async generator (source for pipeline)
136
- */
137
- export async function* empty<T>(): AsyncGenerator<T> {
138
- // Yields nothing - used as the initial source for the pipeline
139
- }
123
+ export async function* empty<T>(): AsyncGenerator<T> {}
140
124
 
141
- /**
142
- * Create the match partial pipeline
143
- *
144
- * Pipeline order (innermost to outermost):
145
- * 1. cache-lookup - Check cache first, yield cached segments if hit
146
- * 2. segment-resolution - Resolve segments if cache miss
147
- * 3. intercept-resolution - Resolve intercept segments
148
- * 4. cache-store - Store segments in cache
149
- * 5. background-revalidation - Trigger SWR if cache was stale
150
- *
151
- * Data flow:
152
- * - empty() produces no segments
153
- * - cache-lookup either yields cached segments OR passes through to segment-resolution
154
- * - segment-resolution resolves fresh segments on cache miss
155
- * - intercept-resolution adds intercept segments
156
- * - cache-store observes and caches segments
157
- * - background-revalidation triggers SWR revalidation if needed
158
- */
159
125
  export function createMatchPartialPipeline<TEnv>(
160
126
  ctx: MatchContext<TEnv>,
161
127
  state: MatchPipelineState,
162
128
  ): AsyncGenerator<ResolvedSegment> {
163
- // Build the middleware chain
164
129
  const pipeline = compose<ResolvedSegment>(
165
- // Outermost - observes segments and triggers background revalidation
166
130
  withBackgroundRevalidation(ctx, state),
167
- // Observes and stores segments in cache
168
131
  withCacheStore(ctx, state),
169
- // Adds intercept segments after main segments
170
132
  withInterceptResolution(ctx, state),
171
- // Resolves segments on cache miss
172
133
  withSegmentResolution(ctx, state),
173
- // Innermost - checks cache first
174
134
  withCacheLookup(ctx, state),
175
135
  );
176
136
 
177
- // Start with empty source - cache lookup or segment resolution will produce segments
178
137
  return pipeline(empty());
179
138
  }
@@ -112,9 +112,6 @@ import type { MatchContext, MatchPipelineState } from "./match-context.js";
112
112
  import { debugLog } from "./logging.js";
113
113
  import { appendMetric } from "./metrics.js";
114
114
 
115
- /**
116
- * Collect all segments from an async generator
117
- */
118
115
  export async function collectSegments(
119
116
  generator: AsyncGenerator<ResolvedSegment>,
120
117
  ): Promise<ResolvedSegment[]> {
@@ -159,8 +156,6 @@ function deduplicateLoaderSegments(
159
156
  }
160
157
  }
161
158
 
162
- // An inherited loader is needed when it shares a namespace with a
163
- // loading-bearing segment (its data sits behind that LoaderBoundary).
164
159
  const loadersWithLoading = new Set<string>();
165
160
  for (const ns of namespacesWithLoading) {
166
161
  for (const id of loaderIdsByNamespace.get(ns) ?? []) {
@@ -195,9 +190,6 @@ function deduplicateLoaderSegments(
195
190
  return { segments: result, removedIds };
196
191
  }
197
192
 
198
- /**
199
- * Build the final MatchResult from collected segments and context
200
- */
201
193
  export function buildMatchResult<TEnv>(
202
194
  allSegments: ResolvedSegment[],
203
195
  ctx: MatchContext<TEnv>,
@@ -211,11 +203,6 @@ export function buildMatchResult<TEnv>(
211
203
  let segmentsToRender: ResolvedSegment[];
212
204
 
213
205
  if (ctx.isFullMatch) {
214
- // Full match (document request) - all segments are rendered
215
- // Deduplicate by segment ID (defense-in-depth). The primary dedup is in
216
- // resolveAllSegments, but this guards against any path that bypasses it.
217
- // include() scopes can produce entries that resolve the same shared layout,
218
- // and duplicate IDs change the client's React tree depth causing remounts.
219
206
  const seen = new Set<string>();
220
207
  segmentsToRender = [];
221
208
  for (const s of allSegments) {
@@ -226,24 +213,14 @@ export function buildMatchResult<TEnv>(
226
213
  }
227
214
  allIds = segmentsToRender.map((s) => s.id);
228
215
  } else {
229
- // Partial match (navigation) - filter and handle intercepts
230
- // When intercepting, tell browser to keep its current segments + add modal
231
- // This prevents the browser from discarding the current page content
232
- // If client sent empty segments (HMR recovery), use segment IDs from allSegments
233
216
  allIds = ctx.interceptResult
234
217
  ? ctx.clientSegmentIds.length > 0
235
218
  ? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
236
- : allSegments.map((s) => s.id) // Use actual segments, not matchedIds
219
+ : allSegments.map((s) => s.id)
237
220
  : [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
238
221
 
239
- // Deduplicate allIds (defense-in-depth for partial match path)
240
222
  allIds = [...new Set(allIds)];
241
223
 
242
- // Filter out null-component segments only when the client already has
243
- // them cached (revalidation skip). If the client doesn't have the segment,
244
- // it must be included even with null component — it's structurally required
245
- // as a parent node for child layouts/parallels to reconcile against.
246
- // Loader segments are always included as they carry data.
247
224
  const clientIdSet = new Set(ctx.clientSegmentIds);
248
225
  segmentsToRender = allSegments.filter(
249
226
  (s) =>
@@ -256,34 +233,13 @@ export function buildMatchResult<TEnv>(
256
233
  logPrefix,
257
234
  );
258
235
 
259
- debugLog(logPrefix, "all segments", {
260
- segments: allSegments.map((s) => ({
261
- id: s.id,
262
- type: s.type,
263
- hasComponent: s.component !== null,
264
- })),
265
- });
266
- debugLog(logPrefix, "segments to render", {
267
- segmentIds: dedupedSegments.map((s) => s.id),
268
- });
269
-
270
- // Remove deduped loader IDs from matched so the client doesn't treat
271
- // them as missing segments and trigger a fallback refetch.
272
236
  const matchedIds =
273
237
  removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
274
238
 
275
- // resolvedIds: every segment whose handler actually ran this request.
276
- // For full-match every segment is fresh; for partial-match we filter by
277
- // the internal `_handlerRan` flag set in revalidation.ts. Drives the
278
- // client's handle-bucket cleanup — a slot that re-resolved and pushed
279
- // nothing must have its previous handle data cleared, but `diff` won't
280
- // carry it because the segment payload skips null-component cached
281
- // segments to save bytes.
282
239
  const resolvedIds = ctx.isFullMatch
283
240
  ? allSegments.map((s) => s.id)
284
241
  : allSegments.filter((s) => s._handlerRan).map((s) => s.id);
285
242
 
286
- // Strip internal-only fields from the segments going on the wire.
287
243
  const cleanedSegments = dedupedSegments.map((s) => {
288
244
  if (s._handlerRan === undefined) return s;
289
245
  const { _handlerRan: _drop, ...rest } = s;
@@ -303,12 +259,6 @@ export function buildMatchResult<TEnv>(
303
259
  };
304
260
  }
305
261
 
306
- /**
307
- * Collect segments from pipeline and build MatchResult
308
- *
309
- * This is the main entry point for building the final result after
310
- * the pipeline has processed all segments.
311
- */
312
262
  export async function collectMatchResult<TEnv>(
313
263
  pipeline: AsyncGenerator<ResolvedSegment>,
314
264
  ctx: MatchContext<TEnv>,
@@ -318,7 +268,6 @@ export async function collectMatchResult<TEnv>(
318
268
 
319
269
  const buildStart = performance.now();
320
270
 
321
- // Update state with collected segments if not already set
322
271
  if (state.segments.length === 0) {
323
272
  state.segments = allSegments;
324
273
  }