@rangojs/router 0.18.0 → 0.19.0

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 (244) hide show
  1. package/dist/types/browser/event-controller.d.ts +3 -3
  2. package/dist/types/browser/history-state.d.ts +2 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  4. package/dist/types/browser/partial-update.d.ts +16 -1
  5. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  6. package/dist/types/browser/react/Link.d.ts +19 -9
  7. package/dist/types/browser/react/context.d.ts +2 -2
  8. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  9. package/dist/types/browser/react/location-state.d.ts +1 -1
  10. package/dist/types/browser/transition-when.d.ts +80 -0
  11. package/dist/types/browser/types.d.ts +104 -9
  12. package/dist/types/build/generate-manifest.d.ts +8 -0
  13. package/dist/types/cache/background-task.d.ts +24 -2
  14. package/dist/types/cache/cache-exec-scope.d.ts +31 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +44 -13
  16. package/dist/types/cache/cache-policy.d.ts +1 -1
  17. package/dist/types/cache/cache-runtime.d.ts +7 -0
  18. package/dist/types/cache/cache-scope.d.ts +117 -35
  19. package/dist/types/cache/cache-tag.d.ts +24 -4
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
  21. package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
  22. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
  23. package/dist/types/cache/document-cache.d.ts +10 -0
  24. package/dist/types/cache/handle-snapshot.d.ts +40 -8
  25. package/dist/types/cache/invalidation-order.d.ts +23 -0
  26. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +88 -141
  28. package/dist/types/cache/tag-invalidation.d.ts +38 -14
  29. package/dist/types/cache/types.d.ts +68 -61
  30. package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
  31. package/dist/types/client-urls/navigation.d.ts +28 -2
  32. package/dist/types/client-urls/server-projection.d.ts +5 -2
  33. package/dist/types/client-urls/types.d.ts +5 -5
  34. package/dist/types/client.d.ts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  38. package/dist/types/route-name.d.ts +7 -0
  39. package/dist/types/router/error-handling.d.ts +1 -1
  40. package/dist/types/router/intercept-resolution.d.ts +2 -0
  41. package/dist/types/router/match-context.d.ts +5 -0
  42. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  43. package/dist/types/router/router-options.d.ts +4 -1
  44. package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
  45. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  46. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
  47. package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
  48. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  49. package/dist/types/router/transition-when.d.ts +24 -10
  50. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  51. package/dist/types/rsc/full-payload.d.ts +14 -7
  52. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  53. package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
  54. package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
  55. package/dist/types/rsc/shell-capture.d.ts +78 -81
  56. package/dist/types/rsc/shell-serve.d.ts +19 -48
  57. package/dist/types/rsc/types.d.ts +2 -0
  58. package/dist/types/segment-system.d.ts +7 -0
  59. package/dist/types/server/context.d.ts +89 -61
  60. package/dist/types/server/cookie-store.d.ts +30 -18
  61. package/dist/types/server/handle-store.d.ts +52 -1
  62. package/dist/types/server/request-context.d.ts +66 -45
  63. package/dist/types/server/request-headers.d.ts +44 -0
  64. package/dist/types/server.d.ts +1 -0
  65. package/dist/types/testing/e2e/index.d.ts +1 -1
  66. package/dist/types/testing/index.d.ts +3 -2
  67. package/dist/types/testing/internal/action-id.d.ts +7 -0
  68. package/dist/types/testing/location-state-key.d.ts +24 -0
  69. package/dist/types/testing/render-route.d.ts +17 -1
  70. package/dist/types/testing/run-transition-when.d.ts +44 -69
  71. package/dist/types/testing/serve-shell-request.d.ts +7 -6
  72. package/dist/types/testing/shell-status.d.ts +34 -22
  73. package/dist/types/transition-when-ref.d.ts +79 -0
  74. package/dist/types/types/cache-types.d.ts +15 -3
  75. package/dist/types/types/index.d.ts +1 -1
  76. package/dist/types/types/segments.d.ts +86 -71
  77. package/dist/types/urls/path-helper-types.d.ts +3 -4
  78. package/dist/types/urls/pattern-types.d.ts +3 -4
  79. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  80. package/dist/vite/index.js +960 -97
  81. package/package.json +1 -1
  82. package/skills/cache-guide/SKILL.md +76 -49
  83. package/skills/caching/SKILL.md +167 -69
  84. package/skills/client-urls/SKILL.md +1 -1
  85. package/skills/comparison/references/framework-comparison.md +8 -5
  86. package/skills/document-cache/SKILL.md +24 -0
  87. package/skills/hooks/SKILL.md +3 -1
  88. package/skills/hooks/navigation.md +23 -2
  89. package/skills/hooks/state.md +98 -6
  90. package/skills/intercept/SKILL.md +29 -12
  91. package/skills/loader/SKILL.md +52 -21
  92. package/skills/middleware/SKILL.md +18 -0
  93. package/skills/migrate-nextjs/SKILL.md +3 -3
  94. package/skills/observability/SKILL.md +6 -5
  95. package/skills/parallel/SKILL.md +1 -1
  96. package/skills/ppr/SKILL.md +129 -77
  97. package/skills/prerender/SKILL.md +3 -3
  98. package/skills/rango/SKILL.md +25 -6
  99. package/skills/route/SKILL.md +2 -0
  100. package/skills/testing/SKILL.md +27 -25
  101. package/skills/testing/cache-prerender.md +9 -9
  102. package/skills/testing/client-components.md +45 -3
  103. package/skills/testing/loader.md +3 -2
  104. package/skills/testing/middleware.md +1 -0
  105. package/skills/testing/predicates.md +49 -49
  106. package/skills/testing/render-handler.md +1 -0
  107. package/skills/testing/serve-shell-request.md +14 -0
  108. package/skills/testing/server-actions.md +9 -3
  109. package/skills/theme/SKILL.md +8 -4
  110. package/skills/typesafety/env-and-bindings.md +8 -0
  111. package/skills/use-cache/SKILL.md +64 -19
  112. package/skills/view-transitions/SKILL.md +150 -29
  113. package/src/__augment-tests__/augmented.check.ts +23 -0
  114. package/src/browser/event-controller.ts +4 -4
  115. package/src/browser/history-state.ts +52 -1
  116. package/src/browser/link-interceptor.ts +4 -0
  117. package/src/browser/navigation-bridge.ts +77 -18
  118. package/src/browser/navigation-client.ts +4 -3
  119. package/src/browser/navigation-store.ts +43 -1
  120. package/src/browser/navigation-transaction.ts +31 -11
  121. package/src/browser/partial-update.ts +146 -35
  122. package/src/browser/prefetch/cache.ts +6 -6
  123. package/src/browser/prefetch/fetch.ts +2 -2
  124. package/src/browser/react/Link.tsx +38 -33
  125. package/src/browser/react/NavigationProvider.tsx +2 -2
  126. package/src/browser/react/context.ts +2 -2
  127. package/src/browser/react/location-state-shared.ts +241 -49
  128. package/src/browser/react/location-state.ts +5 -2
  129. package/src/browser/rsc-router.tsx +4 -0
  130. package/src/browser/server-action-bridge.ts +84 -15
  131. package/src/browser/transition-when.ts +205 -0
  132. package/src/browser/types.ts +124 -10
  133. package/src/build/generate-manifest.ts +77 -1
  134. package/src/cache/background-task.ts +55 -2
  135. package/src/cache/cache-exec-scope.ts +48 -1
  136. package/src/cache/cache-key-utils.ts +56 -16
  137. package/src/cache/cache-policy.ts +9 -4
  138. package/src/cache/cache-runtime.ts +197 -144
  139. package/src/cache/cache-scope.ts +304 -112
  140. package/src/cache/cache-tag.ts +105 -13
  141. package/src/cache/cf/cf-cache-store.ts +152 -76
  142. package/src/cache/cf/cf-shell-frame.ts +2 -2
  143. package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
  144. package/src/cache/document-cache.ts +53 -7
  145. package/src/cache/handle-snapshot.ts +52 -9
  146. package/src/cache/invalidation-order.ts +84 -0
  147. package/src/cache/request-tag-mask.ts +147 -0
  148. package/src/cache/shell-snapshot.ts +130 -348
  149. package/src/cache/tag-invalidation.ts +57 -51
  150. package/src/cache/types.ts +71 -67
  151. package/src/cache/vercel/vercel-cache-store.ts +61 -58
  152. package/src/client-urls/client-root.tsx +8 -4
  153. package/src/client-urls/client-urls.ts +8 -6
  154. package/src/client-urls/navigation.ts +55 -4
  155. package/src/client-urls/server-projection.ts +31 -10
  156. package/src/client-urls/types.ts +12 -5
  157. package/src/client.tsx +2 -0
  158. package/src/host/cookie-handler.ts +2 -1
  159. package/src/index.rsc.ts +3 -0
  160. package/src/index.ts +3 -0
  161. package/src/loader-redirect.tsx +7 -3
  162. package/src/prerender/build-shell-capture.ts +155 -146
  163. package/src/route-definition/dsl-helpers.ts +2 -0
  164. package/src/route-definition/helpers-types.ts +11 -11
  165. package/src/route-name.ts +12 -0
  166. package/src/router/content-negotiation.ts +4 -1
  167. package/src/router/error-handling.ts +5 -2
  168. package/src/router/handler-context.ts +10 -7
  169. package/src/router/intercept-resolution.ts +11 -1
  170. package/src/router/loader-resolution.ts +28 -3
  171. package/src/router/logging.ts +5 -3
  172. package/src/router/match-api.ts +62 -34
  173. package/src/router/match-context.ts +5 -0
  174. package/src/router/match-handlers.ts +9 -29
  175. package/src/router/match-middleware/background-revalidation.ts +3 -5
  176. package/src/router/match-middleware/cache-lookup.ts +35 -19
  177. package/src/router/match-middleware/cache-store.ts +15 -9
  178. package/src/router/match-result.ts +1 -0
  179. package/src/router/middleware.ts +2 -5
  180. package/src/router/navigation-snapshot.ts +6 -5
  181. package/src/router/prerender-match.ts +4 -0
  182. package/src/router/request-classification.ts +7 -4
  183. package/src/router/router-options.ts +4 -1
  184. package/src/router/segment-resolution/fresh.ts +0 -4
  185. package/src/router/segment-resolution/loader-cache.ts +150 -25
  186. package/src/router/segment-resolution/loader-mask.ts +3 -4
  187. package/src/router/segment-resolution/loader-snapshot.ts +15 -78
  188. package/src/router/segment-resolution/mask-nested.ts +47 -22
  189. package/src/router/segment-resolution/revalidation.ts +0 -3
  190. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  191. package/src/router/telemetry.ts +6 -3
  192. package/src/router/transition-when.ts +34 -52
  193. package/src/router.ts +2 -1
  194. package/src/rsc/attach-transition-when.ts +47 -0
  195. package/src/rsc/full-payload.ts +30 -10
  196. package/src/rsc/handler.ts +12 -10
  197. package/src/rsc/loader-fetch.ts +2 -1
  198. package/src/rsc/manifest-init.ts +4 -0
  199. package/src/rsc/origin-guard.ts +8 -5
  200. package/src/rsc/progressive-enhancement.ts +7 -30
  201. package/src/rsc/response-cache-serve.ts +68 -80
  202. package/src/rsc/response-route-handler.ts +2 -2
  203. package/src/rsc/rsc-rendering.ts +206 -252
  204. package/src/rsc/server-action.ts +8 -20
  205. package/src/rsc/shell-build-manifest.ts +22 -20
  206. package/src/rsc/shell-capture-constants.ts +136 -20
  207. package/src/rsc/shell-capture.ts +352 -589
  208. package/src/rsc/shell-serve.ts +73 -109
  209. package/src/rsc/ssr-setup.ts +3 -2
  210. package/src/rsc/types.ts +2 -0
  211. package/src/segment-system.tsx +40 -7
  212. package/src/server/context.ts +189 -124
  213. package/src/server/cookie-store.ts +153 -127
  214. package/src/server/handle-store.ts +245 -27
  215. package/src/server/request-context.ts +123 -84
  216. package/src/server/request-headers.ts +134 -0
  217. package/src/server.ts +4 -0
  218. package/src/testing/dispatch.ts +4 -7
  219. package/src/testing/e2e/index.ts +1 -0
  220. package/src/testing/index.ts +5 -0
  221. package/src/testing/internal/action-id.ts +25 -0
  222. package/src/testing/internal/context.ts +1 -1
  223. package/src/testing/location-state-key.ts +45 -0
  224. package/src/testing/render-handler.ts +1 -1
  225. package/src/testing/render-route.tsx +109 -16
  226. package/src/testing/run-client-revalidate.ts +3 -19
  227. package/src/testing/run-loader.ts +5 -1
  228. package/src/testing/run-transition-when.ts +109 -172
  229. package/src/testing/serve-shell-request.ts +10 -7
  230. package/src/testing/shell-status.ts +60 -56
  231. package/src/transition-when-ref.ts +220 -0
  232. package/src/types/cache-types.ts +15 -3
  233. package/src/types/index.ts +3 -0
  234. package/src/types/segments.ts +94 -91
  235. package/src/urls/path-helper-types.ts +3 -4
  236. package/src/urls/pattern-types.ts +3 -4
  237. package/src/vite/discovery/discover-routers.ts +3 -0
  238. package/src/vite/discovery/shell-prerender-phase.ts +0 -2
  239. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  240. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  241. package/src/vite/rango.ts +2 -0
  242. package/src/vite/router-discovery.ts +75 -5
  243. package/dist/types/rsc/transition-gate.d.ts +0 -23
  244. package/src/rsc/transition-gate.ts +0 -86
package/src/vite/rango.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  exposeRouterId,
11
11
  } from "./plugins/expose-internal-ids.js";
12
12
  import { useCacheTransform } from "./plugins/use-cache-transform.js";
13
+ import { transitionWhenHoistPlugin } from "./plugins/transition-when-hoist.js";
13
14
  import { clientRefDedup } from "./plugins/client-ref-dedup.js";
14
15
  import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
15
16
  import {
@@ -563,6 +564,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
563
564
  });
564
565
 
565
566
  plugins.push(createLoaderDirectiveGuardPlugin());
567
+ plugins.push(transitionWhenHoistPlugin());
566
568
  plugins.push(createLoaderScanStubPlugin());
567
569
  plugins.push(exposeActionId());
568
570
  plugins.push(
@@ -65,6 +65,8 @@ import {
65
65
  } from "./discovery/client-urls-projection.js";
66
66
  import { runShellPrerenderPhase } from "./discovery/shell-prerender-phase.js";
67
67
  import { describeDiscoveryFailure } from "./discovery/discovery-errors.js";
68
+ import { findTransitionWhenError } from "../transition-when-ref.js";
69
+ import { transitionWhenHoistPlugin } from "./plugins/transition-when-hoist.js";
68
70
  import {
69
71
  createDevPrerenderCache,
70
72
  devPrerenderCacheKey,
@@ -286,6 +288,9 @@ export async function createTempRscServer(
286
288
  : []),
287
289
  createVirtualStubPlugin(),
288
290
  createCloudflareProtocolStubPlugin(),
291
+ // Inline transition({ when }) literals become client references here
292
+ // too, or discovery would see (and reject) the plain function.
293
+ transitionWhenHoistPlugin(),
289
294
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
290
295
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
291
296
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
@@ -443,6 +448,37 @@ export function createRouterDiscoveryPlugin(
443
448
  let viteCommand: "serve" | "build" = "build";
444
449
  let viteMode = "production";
445
450
 
451
+ // Dev: an invalid transition({ when }) is a definition error the user must
452
+ // see in the browser too, as the Vite error overlay. The overlay does not
453
+ // survive on its own: a reload clears it, and plugin-rsc closes every
454
+ // overlay on each "rsc:update" (the edit's own, the gen-file write's), each
455
+ // followed by the client's RSC refetch. So until a discovery succeeds the
456
+ // pending error is re-sent to every client that connects and after every
457
+ // request the dev server answers (configureServer).
458
+ let transitionWhenOverlay: {
459
+ message: string;
460
+ stack: string;
461
+ plugin: string;
462
+ } | null = null;
463
+ const sendTransitionWhenOverlay = (): void => {
464
+ if (!transitionWhenOverlay) return;
465
+ (s.devServer?.environments as any)?.client?.hot?.send({
466
+ type: "error",
467
+ err: transitionWhenOverlay,
468
+ });
469
+ };
470
+ const showTransitionWhenError = (err: unknown): Error | undefined => {
471
+ const whenError = findTransitionWhenError(err);
472
+ if (!whenError) return undefined;
473
+ transitionWhenOverlay = {
474
+ message: whenError.message,
475
+ stack: whenError.stack ?? "",
476
+ plugin: "@rangojs/router",
477
+ };
478
+ sendTransitionWhenOverlay();
479
+ return whenError;
480
+ };
481
+
446
482
  return {
447
483
  name: "@rangojs/router:discovery",
448
484
 
@@ -525,6 +561,32 @@ export function createRouterDiscoveryPlugin(
525
561
  if ((globalThis as any).__rscRouterDiscoveryActive) return;
526
562
  s.devServer = server;
527
563
 
564
+ (server.environments as any)?.client?.hot?.on?.(
565
+ "vite:client:connect",
566
+ (_payload: unknown, client: any) => {
567
+ if (transitionWhenOverlay) {
568
+ client?.send?.({ type: "error", err: transitionWhenOverlay });
569
+ }
570
+ },
571
+ );
572
+ // The http server's "request" event, not a middleware: plugin-rsc
573
+ // answers page and RSC requests before a middleware added here runs.
574
+ let overlayResendQueued = false;
575
+ server.httpServer?.on("request", (req, res) => {
576
+ // Documents and RSC fetches only (the refetch that follows each
577
+ // "rsc:update"), one send per burst.
578
+ const isPage =
579
+ req.url?.includes("_rsc") === true ||
580
+ req.headers.accept?.includes("text/html") === true;
581
+ if (transitionWhenOverlay && isPage && !overlayResendQueued) {
582
+ overlayResendQueued = true;
583
+ res.once("close", () => {
584
+ overlayResendQueued = false;
585
+ sendTransitionWhenOverlay();
586
+ });
587
+ }
588
+ });
589
+
528
590
  let workerReadyEpoch: number | undefined;
529
591
  const publishDevDiscoveryReady = (epoch: number) => {
530
592
  if (epoch <= (workerReadyEpoch ?? -1)) return;
@@ -870,6 +932,7 @@ export function createRouterDiscoveryPlugin(
870
932
  } else {
871
933
  console.error(report.message);
872
934
  }
935
+ showTransitionWhenError(err);
873
936
  };
874
937
 
875
938
  const discover = async () => {
@@ -1483,9 +1546,7 @@ export function createRouterDiscoveryPlugin(
1483
1546
  const body = JSON.stringify({
1484
1547
  entry: result.entry,
1485
1548
  ttl,
1486
- swr,
1487
1549
  tags: result.tags,
1488
- routeName,
1489
1550
  });
1490
1551
  devPrerenderCache.set(routerInstance, cacheKey, body);
1491
1552
  res.setHeader("content-type", "application/json");
@@ -1629,6 +1690,7 @@ export function createRouterDiscoveryPlugin(
1629
1690
  );
1630
1691
  s.lastDiscoveryError = null;
1631
1692
  }
1693
+ transitionWhenOverlay = null;
1632
1694
  // Cloudflare dev: on a successful cycle drop the workerd runner's
1633
1695
  // cached worker-entry chain so the next request re-evaluates
1634
1696
  // createRouter() with the new routes. Fired here in the work path
@@ -1656,9 +1718,17 @@ export function createRouterDiscoveryPlugin(
1656
1718
  message: err?.message ?? String(err),
1657
1719
  at: Date.now(),
1658
1720
  };
1659
- console.warn(
1660
- `[rango] Runtime re-discovery failed: ${err.message}`,
1661
- );
1721
+ // An invalid transition({ when }) is a definition error the
1722
+ // user must see: terminal error plus the browser's Vite overlay,
1723
+ // not a warning while the last-good route tree keeps serving.
1724
+ const whenError = showTransitionWhenError(err);
1725
+ if (whenError) {
1726
+ console.error(whenError.message);
1727
+ } else {
1728
+ console.warn(
1729
+ `[rango] Runtime re-discovery failed: ${err.message}`,
1730
+ );
1731
+ }
1662
1732
  debugDiscovery?.(
1663
1733
  "hmr: lastDiscoveryError set (%s) — manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
1664
1734
  err?.message,
@@ -1,23 +0,0 @@
1
- import type { MatchResult } from "../types.js";
2
- import type { getRequestContext } from "../server/request-context.js";
3
- import type { OnErrorCallback } from "../types/error-types.js";
4
- /**
5
- * Apply transition({ when }) gates to a payload's segments.
6
- *
7
- * PPR predicates were evaluated from the manifest before cache lookup and route
8
- * handlers; their request-specific decisions are projected onto a copy so they
9
- * never mutate reusable cache/prerender/shell records. Ordinary-route predicates
10
- * were collected during fresh resolution and are evaluated here after handlers.
11
- * Both paths drop the segment transition when the predicate does not hold.
12
- *
13
- * Mutating the segments here is safe: the segment cache stores a serialized copy
14
- * (segment-codec), written during match() BEFORE this gate runs, so dropping a
15
- * transition never corrupts a cache entry. On an ordinary route, a cache hit
16
- * skips resolution, collects no predicate, and replays the cached transition
17
- * as-is (it was serialized before the gate). PPR routes are the exception: their
18
- * manifest predicates were evaluated before lookup and apply to replayed data.
19
- *
20
- * Returns the same array for the ordinary post-handler path. A PPR drop returns a
21
- * copy containing only the request-specific transition changes.
22
- */
23
- export declare function gateTransitions(segments: MatchResult["segments"], ctx: ReturnType<typeof getRequestContext>, onError?: OnErrorCallback): MatchResult["segments"];
@@ -1,86 +0,0 @@
1
- import type { MatchResult } from "../types.js";
2
- import type { getRequestContext } from "../server/request-context.js";
3
- import { invokeOnError } from "../router/error-handling.js";
4
- import type { OnErrorCallback } from "../types/error-types.js";
5
- import { createTransitionWhenContext } from "../router/transition-when.js";
6
-
7
- /**
8
- * Apply transition({ when }) gates to a payload's segments.
9
- *
10
- * PPR predicates were evaluated from the manifest before cache lookup and route
11
- * handlers; their request-specific decisions are projected onto a copy so they
12
- * never mutate reusable cache/prerender/shell records. Ordinary-route predicates
13
- * were collected during fresh resolution and are evaluated here after handlers.
14
- * Both paths drop the segment transition when the predicate does not hold.
15
- *
16
- * Mutating the segments here is safe: the segment cache stores a serialized copy
17
- * (segment-codec), written during match() BEFORE this gate runs, so dropping a
18
- * transition never corrupts a cache entry. On an ordinary route, a cache hit
19
- * skips resolution, collects no predicate, and replays the cached transition
20
- * as-is (it was serialized before the gate). PPR routes are the exception: their
21
- * manifest predicates were evaluated before lookup and apply to replayed data.
22
- *
23
- * Returns the same array for the ordinary post-handler path. A PPR drop returns a
24
- * copy containing only the request-specific transition changes.
25
- */
26
- export function gateTransitions(
27
- segments: MatchResult["segments"],
28
- ctx: ReturnType<typeof getRequestContext>,
29
- onError?: OnErrorCallback,
30
- ): MatchResult["segments"] {
31
- const pprDecisions = ctx._pprTransitionDecisions;
32
- let gatedSegments = segments;
33
-
34
- const dropTransition = (id: string): void => {
35
- const index = gatedSegments.findIndex((segment) => segment.id === id);
36
- if (index === -1) return;
37
- if (!pprDecisions) {
38
- gatedSegments[index].transition = undefined;
39
- return;
40
- }
41
- if (gatedSegments === segments) gatedSegments = [...segments];
42
- gatedSegments[index] = {
43
- ...gatedSegments[index],
44
- transition: undefined,
45
- };
46
- };
47
-
48
- if (pprDecisions) {
49
- for (const [id, keep] of pprDecisions) {
50
- if (!keep) dropTransition(id);
51
- }
52
- }
53
- const predicates = ctx._transitionWhen;
54
- if (predicates) {
55
- for (const { id, when } of predicates) {
56
- try {
57
- // Assemble the ShouldRevalidateFn-shaped predicate context from the
58
- // request context. Source fields (currentUrl/currentParams/fromRouteName)
59
- // were stashed at match time from the navigation snapshot; action fields
60
- // at the action-bearing gate call sites. nextUrl/nextParams/toRouteName/
61
- // method/get/env come straight off ctx (setRequestContextParams ran
62
- // before the gate). Source/action fields are undefined when absent —
63
- // never fabricated (see TransitionWhenContext).
64
- if (when(createTransitionWhenContext(ctx)) !== false) continue;
65
- } catch (error) {
66
- // A throwing predicate must not fail the response: report it and treat
67
- // the transition as gated off (do not hold). invokeOnError no-ops when
68
- // onError is undefined.
69
- invokeOnError(
70
- onError,
71
- error,
72
- "rendering",
73
- {
74
- request: ctx.request,
75
- url: ctx.url,
76
- params: ctx.params,
77
- segmentId: id,
78
- },
79
- "RSC",
80
- );
81
- }
82
- dropTransition(id);
83
- }
84
- }
85
- return gatedSegments;
86
- }