@rangojs/router 0.17.0 → 0.18.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 (152) hide show
  1. package/README.md +3 -2
  2. package/dist/types/browser/cookie-name.d.ts +6 -0
  3. package/dist/types/browser/history-state.d.ts +16 -0
  4. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  5. package/dist/types/cache/cache-policy.d.ts +17 -0
  6. package/dist/types/cache/cache-scope.d.ts +98 -14
  7. package/dist/types/cache/cache-tag.d.ts +92 -7
  8. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  9. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  10. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  11. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  12. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  13. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  14. package/dist/types/cache/handle-snapshot.d.ts +4 -3
  15. package/dist/types/cache/index.d.ts +1 -0
  16. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  17. package/dist/types/cache/shell-memo.d.ts +178 -0
  18. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  19. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  20. package/dist/types/cache/types.d.ts +207 -43
  21. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  22. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  23. package/dist/types/router/instrument.d.ts +8 -0
  24. package/dist/types/router/middleware-types.d.ts +6 -1
  25. package/dist/types/router/segment-resolution/loader-cache.d.ts +40 -11
  26. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +21 -10
  27. package/dist/types/router/segment-resolution/mask-nested.d.ts +65 -24
  28. package/dist/types/rsc/full-payload.d.ts +14 -0
  29. package/dist/types/rsc/handler.d.ts +1 -1
  30. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  31. package/dist/types/rsc/shell-capture-constants.d.ts +53 -6
  32. package/dist/types/rsc/shell-capture.d.ts +146 -15
  33. package/dist/types/rsc/shell-serve.d.ts +140 -17
  34. package/dist/types/rsc/types.d.ts +7 -0
  35. package/dist/types/server/context.d.ts +109 -15
  36. package/dist/types/server/cookie-store.d.ts +21 -0
  37. package/dist/types/server/request-context.d.ts +159 -33
  38. package/dist/types/ssr/index.d.ts +9 -0
  39. package/dist/types/ssr/ssr-root.d.ts +9 -8
  40. package/dist/types/testing/flight.entry.d.ts +7 -0
  41. package/dist/types/testing/run-loader.d.ts +18 -3
  42. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  43. package/dist/types/testing/shell-status.d.ts +23 -10
  44. package/dist/types/types/handler-context.d.ts +14 -1
  45. package/dist/types/urls/pattern-types.d.ts +22 -18
  46. package/dist/types/vite/router-discovery.d.ts +29 -2
  47. package/dist/vite/index.js +46 -9
  48. package/package.json +1 -1
  49. package/skills/breadcrumbs/SKILL.md +17 -7
  50. package/skills/cache-guide/SKILL.md +123 -51
  51. package/skills/caching/SKILL.md +253 -23
  52. package/skills/catalog.json +1 -1
  53. package/skills/client-urls/SKILL.md +11 -6
  54. package/skills/cloudflare/SKILL.md +15 -0
  55. package/skills/comparison/references/framework-comparison.md +44 -32
  56. package/skills/defer-hydration/SKILL.md +50 -30
  57. package/skills/deployment-caching/SKILL.md +5 -2
  58. package/skills/document-cache/SKILL.md +8 -2
  59. package/skills/handler-use/SKILL.md +2 -0
  60. package/skills/hooks/handle-and-actions.md +3 -1
  61. package/skills/hooks/state.md +3 -1
  62. package/skills/loader/SKILL.md +161 -39
  63. package/skills/middleware/SKILL.md +16 -9
  64. package/skills/migrate-nextjs/SKILL.md +67 -36
  65. package/skills/migrate-react-router/SKILL.md +2 -1
  66. package/skills/migrate-react-router/component-migration.md +3 -1
  67. package/skills/migrate-react-router/data-and-actions.md +3 -1
  68. package/skills/migrate-react-router/route-mapping.md +7 -4
  69. package/skills/observability/SKILL.md +64 -0
  70. package/skills/parallel/SKILL.md +13 -8
  71. package/skills/ppr/SKILL.md +655 -363
  72. package/skills/prerender/SKILL.md +27 -14
  73. package/skills/rango/SKILL.md +52 -33
  74. package/skills/route/SKILL.md +12 -7
  75. package/skills/router-setup/SKILL.md +4 -2
  76. package/skills/scripts/SKILL.md +8 -1
  77. package/skills/server-actions/SKILL.md +11 -0
  78. package/skills/shell-manifest/SKILL.md +21 -12
  79. package/skills/testing/SKILL.md +25 -21
  80. package/skills/testing/cache-prerender.md +15 -14
  81. package/skills/testing/loader.md +18 -1
  82. package/skills/testing/serve-shell-request.md +188 -0
  83. package/skills/theme/SKILL.md +52 -4
  84. package/skills/typesafety/env-and-bindings.md +10 -2
  85. package/skills/use-cache/SKILL.md +39 -16
  86. package/skills/vercel/SKILL.md +14 -2
  87. package/skills/view-transitions/SKILL.md +1 -1
  88. package/src/browser/cookie-name.ts +23 -0
  89. package/src/browser/history-state.ts +23 -0
  90. package/src/browser/rsc-router.tsx +5 -0
  91. package/src/cache/cache-key-utils.ts +22 -2
  92. package/src/cache/cache-policy.ts +22 -0
  93. package/src/cache/cache-runtime.ts +34 -7
  94. package/src/cache/cache-scope.ts +345 -33
  95. package/src/cache/cache-tag.ts +375 -11
  96. package/src/cache/cf/cf-base64.ts +36 -4
  97. package/src/cache/cf/cf-cache-constants.ts +12 -0
  98. package/src/cache/cf/cf-cache-store.ts +990 -348
  99. package/src/cache/cf/cf-cache-types.ts +32 -4
  100. package/src/cache/cf/cf-shell-frame.ts +258 -0
  101. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  102. package/src/cache/handle-snapshot.ts +4 -3
  103. package/src/cache/index.ts +2 -0
  104. package/src/cache/isolate-tag-memo.ts +308 -0
  105. package/src/cache/shell-memo.ts +341 -0
  106. package/src/cache/shell-snapshot.ts +217 -27
  107. package/src/cache/tag-invalidation.ts +104 -19
  108. package/src/cache/types.ts +211 -43
  109. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  110. package/src/prerender/build-shell-capture.ts +65 -11
  111. package/src/route-definition/dsl-helpers.ts +4 -3
  112. package/src/router/handler-context.ts +38 -4
  113. package/src/router/instrument.ts +54 -2
  114. package/src/router/intercept-resolution.ts +19 -7
  115. package/src/router/loader-resolution.ts +63 -32
  116. package/src/router/match-api.ts +28 -9
  117. package/src/router/match-handlers.ts +5 -0
  118. package/src/router/match-middleware/cache-lookup.ts +17 -0
  119. package/src/router/match-middleware/cache-store.ts +31 -11
  120. package/src/router/middleware-types.ts +6 -1
  121. package/src/router/middleware.ts +23 -4
  122. package/src/router/prerender-match.ts +6 -0
  123. package/src/router/segment-resolution/fresh.ts +18 -9
  124. package/src/router/segment-resolution/helpers.ts +4 -2
  125. package/src/router/segment-resolution/loader-cache.ts +227 -56
  126. package/src/router/segment-resolution/loader-snapshot.ts +203 -51
  127. package/src/router/segment-resolution/mask-nested.ts +136 -42
  128. package/src/router/segment-resolution/revalidation.ts +27 -10
  129. package/src/rsc/full-payload.ts +25 -1
  130. package/src/rsc/handler.ts +14 -2
  131. package/src/rsc/progressive-enhancement.ts +3 -2
  132. package/src/rsc/response-cache-serve.ts +9 -6
  133. package/src/rsc/rsc-rendering.ts +601 -181
  134. package/src/rsc/shell-build-manifest.ts +27 -9
  135. package/src/rsc/shell-capture-constants.ts +60 -6
  136. package/src/rsc/shell-capture.ts +1001 -250
  137. package/src/rsc/shell-serve.ts +316 -34
  138. package/src/rsc/types.ts +7 -0
  139. package/src/server/context.ts +219 -28
  140. package/src/server/cookie-store.ts +87 -62
  141. package/src/server/request-context.ts +263 -38
  142. package/src/ssr/index.tsx +96 -37
  143. package/src/ssr/ssr-root.tsx +9 -8
  144. package/src/testing/flight.entry.ts +14 -0
  145. package/src/testing/run-loader.ts +63 -5
  146. package/src/testing/serve-shell-request.ts +378 -0
  147. package/src/testing/shell-status.ts +34 -12
  148. package/src/theme/ThemeProvider.tsx +7 -6
  149. package/src/types/handler-context.ts +14 -1
  150. package/src/urls/pattern-types.ts +22 -18
  151. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  152. package/src/vite/router-discovery.ts +14 -4
@@ -13,6 +13,7 @@ import type { RscPayload } from "./types.js";
13
13
  import type { HandlerContext } from "./handler-context.js";
14
14
  import type { RequestContext } from "../server/request-context.js";
15
15
  import type { HandleStore } from "../server/handle-store.js";
16
+ import type { Theme } from "../theme/types.js";
16
17
  import { gateTransitions } from "./transition-gate.js";
17
18
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
18
19
 
@@ -71,7 +72,30 @@ export function buildFullPayload(
71
72
  // entry knows whether to wrap hydration in React.StrictMode. Partial
72
73
  // (navigation) payloads omit it by design; StrictMode is decided once.
73
74
  strictMode: ctx.router.strictMode,
74
- initialTheme: reqCtx.theme,
75
+ initialTheme: payloadInitialTheme(reqCtx),
75
76
  },
76
77
  };
77
78
  }
79
+
80
+ /**
81
+ * The theme a document payload carries as `metadata.initialTheme` (the
82
+ * ThemeProvider's first state), read with no identity guard: it is the
83
+ * router's own read, not handler code (#971).
84
+ *
85
+ * A shell capture carries the no-cookie default, not the capturing visitor's
86
+ * theme. The shell and its ShellCacheEntry.initialTheme serve every visitor,
87
+ * and ThemeProvider re-syncs only from an explicitly stored theme, so a
88
+ * visitor with none kept the capturer's `useTheme().theme` for the page's
89
+ * lifetime. A visitor with a stored theme gets it pre-paint (theme script)
90
+ * and in the provider after mount.
91
+ */
92
+ export function payloadInitialTheme(
93
+ reqCtx: Pick<
94
+ RequestContext<any>,
95
+ "_themeConfig" | "_shellCaptureRun" | "_readTheme"
96
+ >,
97
+ ): Theme | undefined {
98
+ const config = reqCtx._themeConfig;
99
+ if (!config) return undefined;
100
+ return reqCtx._shellCaptureRun ? config.defaultTheme : reqCtx._readTheme();
101
+ }
@@ -31,6 +31,7 @@ import {
31
31
  } from "./helpers.js";
32
32
  import { renderRscFlightStage, renderRscResponse } from "./render-pipeline.js";
33
33
  import { guardOutgoingRedirect } from "./redirect-guard.js";
34
+ import { payloadInitialTheme } from "./full-payload.js";
34
35
  import {
35
36
  resolveSoftRedirectUrl,
36
37
  resolveExternalRedirect,
@@ -85,6 +86,7 @@ import {
85
86
  ORIGIN_CHECK_PHASE_BY_MODE,
86
87
  } from "./origin-guard.js";
87
88
  import { handleRscRendering } from "./rsc-rendering.js";
89
+ import { withoutShellMissMarker } from "./shell-serve.js";
88
90
  import {
89
91
  withTimeout,
90
92
  isTimeoutEnabled,
@@ -436,10 +438,16 @@ export function createRSCHandler<
436
438
  };
437
439
 
438
440
  return async function handler(
439
- request: Request,
441
+ incomingRequest: Request,
440
442
  input: RouterRequestInput<TEnv> = {},
441
443
  ): Promise<Response> {
442
444
  const handlerStart = performance.now();
445
+ // A degraded PPR HIT's reload carries the forced-MISS marker: keep only
446
+ // the flag (set on the request context below) and drop the marker before
447
+ // anything reads the request, so middleware, handlers, loaders, cache
448
+ // keys and the SSR search seed see the URL the visitor asked for.
449
+ const unmarkedRequest = withoutShellMissMarker(incomingRequest);
450
+ const request = unmarkedRequest ?? incomingRequest;
443
451
  // Create the metrics store at handler start so handler:total has startTime=0
444
452
  // and all metrics are relative to the request entry point.
445
453
  const earlyMetricsStore = router.debugPerformance
@@ -566,6 +574,7 @@ export function createRSCHandler<
566
574
  stateCookieName: router.resolvedStateCookieName,
567
575
  version,
568
576
  });
577
+ if (unmarkedRequest) requestContext._shellForcedMiss = true;
569
578
  // Gate on the SAME enabled-semantics withTimeout uses (isTimeoutEnabled):
570
579
  // a `renderStartMs: 0` / negative opt-out disables the timeout, so the
571
580
  // driver's cursor bookkeeping (which only the timeout reads) must be off too.
@@ -662,6 +671,9 @@ export function createRSCHandler<
662
671
  // the host; it never reads or wraps response.body. A downstream throw
663
672
  // skips it entirely (no response exists to hand off).
664
673
  return observePhase(PHASES.response, (responseSpan) => {
674
+ // Header and cookie writes from here on (a streaming loader or
675
+ // render) no longer reach the response.
676
+ requestContext._responseSent = true;
665
677
  let response: Response;
666
678
  if (hasMiddleware) {
667
679
  if (
@@ -1355,7 +1367,7 @@ export function createRSCHandler<
1355
1367
  themeConfig: router.themeConfig,
1356
1368
  warmupEnabled: router.warmupEnabled,
1357
1369
  strictMode: router.strictMode,
1358
- initialTheme: getRequestContext().theme,
1370
+ initialTheme: payloadInitialTheme(getRequestContext()),
1359
1371
  },
1360
1372
  };
1361
1373
 
@@ -15,6 +15,7 @@ import type { MiddlewareFn } from "../router/middleware.js";
15
15
  import { executeMiddleware } from "../router/middleware.js";
16
16
  import { observePhase, PHASES } from "../router/instrument.js";
17
17
  import { gateTransitions } from "./transition-gate.js";
18
+ import { payloadInitialTheme } from "./full-payload.js";
18
19
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
19
20
  import type { RscPayload, ReactFormState } from "./types.js";
20
21
  import {
@@ -444,7 +445,7 @@ function renderPeResponse<TEnv>(
444
445
  themeConfig: ctx.router.themeConfig,
445
446
  warmupEnabled: ctx.router.warmupEnabled,
446
447
  strictMode: ctx.router.strictMode,
447
- initialTheme: getRequestContext().theme,
448
+ initialTheme: payloadInitialTheme(getRequestContext()),
448
449
  },
449
450
  };
450
451
 
@@ -646,7 +647,7 @@ function renderPeErrorResponse<TEnv>(
646
647
  themeConfig: ctx.router.themeConfig,
647
648
  warmupEnabled: ctx.router.warmupEnabled,
648
649
  strictMode: ctx.router.strictMode,
649
- initialTheme: getRequestContext().theme,
650
+ initialTheme: payloadInitialTheme(getRequestContext()),
650
651
  },
651
652
  };
652
653
 
@@ -105,7 +105,10 @@ export async function serveResponseRouteWithCache(
105
105
  // the host-namespacing and search-normalization rules cannot drift.
106
106
  let cacheKey = `response:${responseType}:${cacheKeyBase(url.host, url.pathname, url.searchParams, undefined, reqCtx._searchParamsFilter)}`;
107
107
 
108
- // Priority 1: route-level key() (full override). Priority 2: store-level
108
+ // Priority 1: route-level key() (full override): the key() results of every
109
+ // cache() enclosing the route, composed like the segment record key
110
+ // (CacheScope.resolveKeyFrom, #970), so a nested cache() never shares an
111
+ // entry across the enclosing partitions. Priority 2: store-level
109
112
  // keyGenerator (modifies the default key).
110
113
  //
111
114
  // A CONFIGURED key()/keyGenerator that THROWS must DEGRADE TO A MISS, not fall
@@ -114,13 +117,13 @@ export async function serveResponseRouteWithCache(
114
117
  // would cache PERSONALIZED output under it and serve it cross-user (cache
115
118
  // poisoning). Mirrors the segment-cache behavior (cache-scope.ts lookupRoute):
116
119
  // a throwing key degrades to a cache miss, never a collision onto the default
117
- // slot. The no-key default path is left untouched (the broad key is correct
118
- // when no key is configured).
120
+ // slot, and so does a key() with no ambient request context to run in
121
+ // (resolveKeyFrom rejects). The no-key default path is left untouched (the
122
+ // broad key is correct when no key is configured).
119
123
  let keyResolutionFailed = false;
120
- if (cacheScope.config !== false && cacheScope.config.key) {
124
+ if (cacheScope.keyFns.length > 0) {
121
125
  try {
122
- const customKey = await cacheScope.config.key(reqCtx);
123
- cacheKey = `response:${customKey}`;
126
+ cacheKey = `response:${await cacheScope.resolveKeyFrom(cacheKey)}`;
124
127
  } catch (error) {
125
128
  keyResolutionFailed = true;
126
129
  reportCacheError(