@rangojs/router 0.17.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.
- package/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +9 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +47 -3
- package/src/rsc/handler.ts +24 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
package/src/rsc/full-payload.ts
CHANGED
|
@@ -13,7 +13,10 @@ 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 {
|
|
16
|
+
import type { Theme } from "../theme/types.js";
|
|
17
|
+
import { documentCacheStoresRender } from "../cache/document-cache.js";
|
|
18
|
+
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
19
|
+
import { publicRouteName } from "../route-name.js";
|
|
17
20
|
import { resolvedHandleStream } from "../handles/deferred-resolution.js";
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -38,11 +41,12 @@ export function buildFullPayload(
|
|
|
38
41
|
pathname: url.pathname,
|
|
39
42
|
routerId: ctx.router.id,
|
|
40
43
|
basename: ctx.router.basename,
|
|
41
|
-
segments:
|
|
44
|
+
segments: attachTransitionWhen(m.segments, reqCtx),
|
|
42
45
|
matched: m.matched,
|
|
43
46
|
diff: m.diff,
|
|
44
47
|
resolvedIds: m.resolvedIds,
|
|
45
48
|
params: m.params,
|
|
49
|
+
routeName: publicRouteName(m.routeName),
|
|
46
50
|
isPartial: false,
|
|
47
51
|
interceptTargets: m.interceptTargets,
|
|
48
52
|
rootLayout: ctx.router.rootLayout,
|
|
@@ -71,7 +75,47 @@ export function buildFullPayload(
|
|
|
71
75
|
// entry knows whether to wrap hydration in React.StrictMode. Partial
|
|
72
76
|
// (navigation) payloads omit it by design; StrictMode is decided once.
|
|
73
77
|
strictMode: ctx.router.strictMode,
|
|
74
|
-
initialTheme: reqCtx
|
|
78
|
+
initialTheme: payloadInitialTheme(reqCtx),
|
|
75
79
|
},
|
|
76
80
|
};
|
|
77
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The theme a document payload carries as `metadata.initialTheme` (the
|
|
85
|
+
* ThemeProvider's first state), read with no identity guard: it is the
|
|
86
|
+
* router's own read, not handler code (#971).
|
|
87
|
+
*
|
|
88
|
+
* A render other visitors may be served carries the no-cookie default, not
|
|
89
|
+
* the rendering visitor's theme: a shell capture, and a render the document
|
|
90
|
+
* cache runs whose response opted in before next() (documentCacheStoresRender,
|
|
91
|
+
* #978). The shell, its ShellCacheEntry.initialTheme and the stored document
|
|
92
|
+
* serve every visitor, and ThemeProvider re-syncs only from an explicitly
|
|
93
|
+
* stored theme, so a visitor with none kept the first visitor's
|
|
94
|
+
* `useTheme().theme` for the page's lifetime. A visitor with a stored theme
|
|
95
|
+
* gets it pre-paint (theme script) and in the provider after mount.
|
|
96
|
+
*
|
|
97
|
+
* Any other render carries the visitor's theme (the unmatched-route 404 reads
|
|
98
|
+
* it without this function, rsc/handler.ts); when that differs from the
|
|
99
|
+
* default it marks `_payloadVisitorTheme`, and the document cache does not
|
|
100
|
+
* store the response.
|
|
101
|
+
*/
|
|
102
|
+
export function payloadInitialTheme(
|
|
103
|
+
reqCtx: Pick<
|
|
104
|
+
RequestContext<any>,
|
|
105
|
+
| "_themeConfig"
|
|
106
|
+
| "_shellCaptureRun"
|
|
107
|
+
| "_readTheme"
|
|
108
|
+
| "_documentCacheRender"
|
|
109
|
+
| "_payloadVisitorTheme"
|
|
110
|
+
| "res"
|
|
111
|
+
>,
|
|
112
|
+
): Theme | undefined {
|
|
113
|
+
const config = reqCtx._themeConfig;
|
|
114
|
+
if (!config) return undefined;
|
|
115
|
+
if (reqCtx._shellCaptureRun || documentCacheStoresRender(reqCtx)) {
|
|
116
|
+
return config.defaultTheme;
|
|
117
|
+
}
|
|
118
|
+
const theme = reqCtx._readTheme();
|
|
119
|
+
if (theme !== config.defaultTheme) reqCtx._payloadVisitorTheme = true;
|
|
120
|
+
return theme;
|
|
121
|
+
}
|
package/src/rsc/handler.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* and progressive enhancement (no-JS form submissions).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
10
11
|
import { isRouteNotFoundError } from "../errors.js";
|
|
11
12
|
import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
|
|
12
13
|
import {
|
|
@@ -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
|
-
|
|
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 (
|
|
@@ -913,8 +925,10 @@ export function createRSCHandler<
|
|
|
913
925
|
originPhase,
|
|
914
926
|
);
|
|
915
927
|
if (originResult) {
|
|
928
|
+
const origin = requestHeaders(request).get("origin");
|
|
929
|
+
const host = requestHeaders(request).get("host");
|
|
916
930
|
const originError = new Error(
|
|
917
|
-
`Origin check rejected: ${
|
|
931
|
+
`Origin check rejected: ${origin ?? "none"} vs ${host ?? "none"}`,
|
|
918
932
|
);
|
|
919
933
|
originError.name = "OriginCheckError";
|
|
920
934
|
|
|
@@ -923,11 +937,7 @@ export function createRSCHandler<
|
|
|
923
937
|
url,
|
|
924
938
|
env,
|
|
925
939
|
handledByBoundary: false,
|
|
926
|
-
metadata: {
|
|
927
|
-
phase: originPhase,
|
|
928
|
-
origin: request.headers.get("origin"),
|
|
929
|
-
host: request.headers.get("host"),
|
|
930
|
-
},
|
|
940
|
+
metadata: { phase: originPhase, origin, host },
|
|
931
941
|
});
|
|
932
942
|
|
|
933
943
|
if (router.telemetry) {
|
|
@@ -938,8 +948,8 @@ export function createRSCHandler<
|
|
|
938
948
|
method: request.method,
|
|
939
949
|
pathname: url.pathname,
|
|
940
950
|
phase: originPhase,
|
|
941
|
-
origin
|
|
942
|
-
host
|
|
951
|
+
origin,
|
|
952
|
+
host,
|
|
943
953
|
});
|
|
944
954
|
}
|
|
945
955
|
|
|
@@ -1355,7 +1365,11 @@ export function createRSCHandler<
|
|
|
1355
1365
|
themeConfig: router.themeConfig,
|
|
1356
1366
|
warmupEnabled: router.warmupEnabled,
|
|
1357
1367
|
strictMode: router.strictMode,
|
|
1358
|
-
|
|
1368
|
+
// The visitor's theme, not payloadInitialTheme (full-payload.ts):
|
|
1369
|
+
// that picks the default when middleware opted the URL into the
|
|
1370
|
+
// document cache before next(), but a 404 is never stored or
|
|
1371
|
+
// captured (#978).
|
|
1372
|
+
initialTheme: getRequestContext()._readTheme(),
|
|
1359
1373
|
},
|
|
1360
1374
|
};
|
|
1361
1375
|
|
package/src/rsc/loader-fetch.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* has already extracted params from it.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
14
15
|
import { getLoaderLazy } from "../server/loader-registry.js";
|
|
15
16
|
import { isDataNotFoundError } from "../errors.js";
|
|
16
17
|
import { executeLoaderMiddleware } from "../router/middleware.js";
|
|
@@ -144,7 +145,7 @@ export async function handleLoaderFetch<TEnv>(
|
|
|
144
145
|
|
|
145
146
|
if (isBodyMethod) {
|
|
146
147
|
try {
|
|
147
|
-
const contentType = request.
|
|
148
|
+
const contentType = requestHeaders(request).get("content-type") || "";
|
|
148
149
|
if (contentType.includes("multipart/form-data")) {
|
|
149
150
|
// FormData body — sent by load() when body is a FormData instance.
|
|
150
151
|
// Preserves File objects and binary data.
|
package/src/rsc/manifest-init.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
setRouterPrecomputedEntries,
|
|
15
15
|
} from "../route-map-builder.js";
|
|
16
16
|
import { mergeFullManifests } from "../build/merge-full-manifests.js";
|
|
17
|
+
import { isTransitionWhenValidationEnabled } from "../transition-when-ref.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Build a fresh route trie from the router's URL-pattern mounts and store it in
|
|
@@ -49,6 +50,9 @@ export async function buildRouterTrieFromUrlpatterns(
|
|
|
49
50
|
await generateManifestFull(mount.patterns, mount.mountIndex, {
|
|
50
51
|
routerId: router.id,
|
|
51
52
|
...(router.basename ? { urlPrefix: router.basename } : {}),
|
|
53
|
+
// Under the Vite plugin the realm validates like discovery does, so
|
|
54
|
+
// a Cloudflare dev rebuild fails on an invalid when too.
|
|
55
|
+
validateTransitionWhen: isTransitionWhenValidationEnabled(),
|
|
52
56
|
}),
|
|
53
57
|
);
|
|
54
58
|
}
|
package/src/rsc/origin-guard.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* navigations, bookmarks, and non-browser clients don't send Origin.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
12
13
|
import type { RequestPlan } from "../router/request-classification.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -69,10 +70,11 @@ export type OriginCheckConfig<TEnv = any> =
|
|
|
69
70
|
* Returns true to allow, false to reject.
|
|
70
71
|
*/
|
|
71
72
|
export function defaultOriginCheck(request: Request, url: URL): boolean {
|
|
72
|
-
|
|
73
|
+
const headers = requestHeaders(request);
|
|
74
|
+
let requestOrigin = headers.get("origin");
|
|
73
75
|
|
|
74
76
|
if (!requestOrigin) {
|
|
75
|
-
const referer =
|
|
77
|
+
const referer = headers.get("referer");
|
|
76
78
|
if (referer) {
|
|
77
79
|
try {
|
|
78
80
|
requestOrigin = new URL(referer).origin;
|
|
@@ -92,7 +94,7 @@ export function defaultOriginCheck(request: Request, url: URL): boolean {
|
|
|
92
94
|
// :authority), so a missing Host here is anomalous. Fail closed rather than
|
|
93
95
|
// fall back to url.host (derived from the request line) when the trusted Host
|
|
94
96
|
// cannot be established.
|
|
95
|
-
const expectedHost =
|
|
97
|
+
const expectedHost = headers.get("host");
|
|
96
98
|
if (!expectedHost) return false;
|
|
97
99
|
|
|
98
100
|
const expectedOrigin = `${url.protocol}//${expectedHost}`;
|
|
@@ -102,11 +104,12 @@ export function defaultOriginCheck(request: Request, url: URL): boolean {
|
|
|
102
104
|
|
|
103
105
|
function createForbiddenResponse(request: Request): Response {
|
|
104
106
|
const isDev = process.env.NODE_ENV !== "production";
|
|
107
|
+
const headers = requestHeaders(request);
|
|
105
108
|
const body = isDev
|
|
106
109
|
? "Forbidden: Origin mismatch. The request origin does not match the server host. " +
|
|
107
110
|
`Set originCheck: false in createRouter() to disable this check. ` +
|
|
108
|
-
`(Origin: ${
|
|
109
|
-
`Host: ${
|
|
111
|
+
`(Origin: ${headers.get("origin") ?? "none"}, ` +
|
|
112
|
+
`Host: ${headers.get("host") ?? "none"})`
|
|
110
113
|
: "Forbidden";
|
|
111
114
|
|
|
112
115
|
return new Response(body, {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* reference. We detect these and return HTML instead of RSC stream.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
9
10
|
import {
|
|
10
11
|
getRequestContext,
|
|
11
12
|
setRequestContextParams,
|
|
@@ -14,7 +15,8 @@ import { createSsrHtmlStage } from "./ssr-setup.js";
|
|
|
14
15
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
15
16
|
import { executeMiddleware } from "../router/middleware.js";
|
|
16
17
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
17
|
-
import {
|
|
18
|
+
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
19
|
+
import { payloadInitialTheme } from "./full-payload.js";
|
|
18
20
|
import { resolvedHandleStream } from "../handles/deferred-resolution.js";
|
|
19
21
|
import type { RscPayload, ReactFormState } from "./types.js";
|
|
20
22
|
import {
|
|
@@ -63,7 +65,7 @@ export async function handleProgressiveEnhancement<TEnv>(
|
|
|
63
65
|
nonce: string | undefined,
|
|
64
66
|
routeMwInfo?: PeRouteMiddlewareInfo,
|
|
65
67
|
): Promise<Response | null> {
|
|
66
|
-
const contentType = request.
|
|
68
|
+
const contentType = requestHeaders(request).get("content-type") || "";
|
|
67
69
|
const isFormSubmission =
|
|
68
70
|
contentType.includes("multipart/form-data") ||
|
|
69
71
|
contentType.includes("application/x-www-form-urlencoded");
|
|
@@ -311,7 +313,7 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
311
313
|
// custom headers) so loaders that read request headers/cookies behave
|
|
312
314
|
// identically under PE and the JS action path. Drop body-framing headers
|
|
313
315
|
// from the bodyless GET and force the HTML accept.
|
|
314
|
-
const headers = new Headers(request
|
|
316
|
+
const headers = new Headers(requestHeaders(request));
|
|
315
317
|
headers.delete("content-type");
|
|
316
318
|
headers.delete("content-length");
|
|
317
319
|
headers.delete("content-encoding");
|
|
@@ -325,15 +327,9 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
325
327
|
// JS/PE parity: this is an action's revalidation render, so mark it BEFORE
|
|
326
328
|
// matching — a stale `foregroundOnAction` cache entry must re-execute in the
|
|
327
329
|
// foreground during the re-render, exactly as the JS path's
|
|
328
|
-
// revalidateAfterAction does.
|
|
329
|
-
// while foregroundOnAction reads _inActionRevalidation there too, so all
|
|
330
|
-
// action metadata must be available before matching.
|
|
330
|
+
// revalidateAfterAction does.
|
|
331
331
|
const peReqCtx = getRequestContext();
|
|
332
332
|
peReqCtx._inActionRevalidation = true;
|
|
333
|
-
peReqCtx._gateActionId = directActionId ?? undefined;
|
|
334
|
-
peReqCtx._gateActionUrl = new URL(url);
|
|
335
|
-
peReqCtx._gateActionResult = actionResult;
|
|
336
|
-
peReqCtx._gateFormData = formData;
|
|
337
333
|
|
|
338
334
|
return runRoutine(
|
|
339
335
|
peRenderPlan({
|
|
@@ -426,11 +422,7 @@ function renderPeResponse<TEnv>(
|
|
|
426
422
|
pathname: url.pathname,
|
|
427
423
|
routerId: ctx.router.id,
|
|
428
424
|
basename: ctx.router.basename,
|
|
429
|
-
segments:
|
|
430
|
-
match.segments,
|
|
431
|
-
getRequestContext(),
|
|
432
|
-
ctx.router.onError,
|
|
433
|
-
),
|
|
425
|
+
segments: attachTransitionWhen(match.segments, getRequestContext()),
|
|
434
426
|
matched: match.matched,
|
|
435
427
|
diff: match.diff,
|
|
436
428
|
resolvedIds: match.resolvedIds,
|
|
@@ -444,7 +436,7 @@ function renderPeResponse<TEnv>(
|
|
|
444
436
|
themeConfig: ctx.router.themeConfig,
|
|
445
437
|
warmupEnabled: ctx.router.warmupEnabled,
|
|
446
438
|
strictMode: ctx.router.strictMode,
|
|
447
|
-
initialTheme: getRequestContext()
|
|
439
|
+
initialTheme: payloadInitialTheme(getRequestContext()),
|
|
448
440
|
},
|
|
449
441
|
};
|
|
450
442
|
|
|
@@ -602,16 +594,6 @@ async function matchPeErrorBoundary<TEnv>(
|
|
|
602
594
|
|
|
603
595
|
setRequestContextParams(errorResult.params, errorResult.routeName);
|
|
604
596
|
|
|
605
|
-
// Only the failing action id + URL are in scope here (no formData/actionResult
|
|
606
|
-
// thread into this helper). Expose the URL only when the action id is known:
|
|
607
|
-
// this helper also handles malformed form bodies before action detection, and
|
|
608
|
-
// those should not look like action-triggered renders to transition({ when }).
|
|
609
|
-
if (actionId != null) {
|
|
610
|
-
const peErrCtx = getRequestContext();
|
|
611
|
-
peErrCtx._gateActionId = actionId;
|
|
612
|
-
peErrCtx._gateActionUrl = new URL(url);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
597
|
return errorResult;
|
|
616
598
|
}
|
|
617
599
|
|
|
@@ -627,11 +609,7 @@ function renderPeErrorResponse<TEnv>(
|
|
|
627
609
|
pathname: url.pathname,
|
|
628
610
|
routerId: ctx.router.id,
|
|
629
611
|
basename: ctx.router.basename,
|
|
630
|
-
segments:
|
|
631
|
-
errorResult.segments,
|
|
632
|
-
getRequestContext(),
|
|
633
|
-
ctx.router.onError,
|
|
634
|
-
),
|
|
612
|
+
segments: attachTransitionWhen(errorResult.segments, getRequestContext()),
|
|
635
613
|
matched: errorResult.matched,
|
|
636
614
|
diff: errorResult.diff,
|
|
637
615
|
resolvedIds: errorResult.resolvedIds,
|
|
@@ -646,7 +624,7 @@ function renderPeErrorResponse<TEnv>(
|
|
|
646
624
|
themeConfig: ctx.router.themeConfig,
|
|
647
625
|
warmupEnabled: ctx.router.warmupEnabled,
|
|
648
626
|
strictMode: ctx.router.strictMode,
|
|
649
|
-
initialTheme: getRequestContext()
|
|
627
|
+
initialTheme: payloadInitialTheme(getRequestContext()),
|
|
650
628
|
},
|
|
651
629
|
};
|
|
652
630
|
|
|
@@ -3,24 +3,27 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Owns the single response-cache contract — cache-scope resolution from the
|
|
5
5
|
* matched entry tree, condition eval, key resolution (route key() > store
|
|
6
|
-
* keyGenerator > default), tag resolution,
|
|
7
|
-
* fresh-hit / SWR-revalidate /
|
|
6
|
+
* keyGenerator > default, CacheScope.resolveKeyFrom), tag resolution,
|
|
7
|
+
* pre-handler-callback timing, and the fresh-hit / SWR-revalidate /
|
|
8
|
+
* miss-write branches — for BOTH the production
|
|
8
9
|
* response-route handler (rsc/response-route-handler.ts) and the dispatch testing
|
|
9
10
|
* primitive (testing/dispatch.ts), so the two can never drift.
|
|
10
11
|
*
|
|
11
12
|
* Plugin-rsc hazard: cache-scope.ts pulls @vitejs/plugin-rsc (via segment-codec),
|
|
12
13
|
* which the non-Vite unit-test runner cannot resolve, and this module is on the
|
|
13
|
-
* testing barrel's EAGER graph (dispatch imports it). So `createCacheScope`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* testing barrel's EAGER graph (dispatch imports it). So `createCacheScope` is
|
|
15
|
+
* NOT imported here at runtime — it is INJECTED by the caller (production
|
|
16
|
+
* imports it statically; dispatch lazy-imports it only once a response route
|
|
17
|
+
* matches). The only runtime imports here are plugin-rsc-free
|
|
17
18
|
* (helpers' isCacheableStatus/finalizeResponse, traverseBack); cache-scope is a
|
|
18
19
|
* type-only import (erased at build).
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import type { CacheScope } from "../cache/cache-scope.js";
|
|
22
|
-
import
|
|
23
|
-
|
|
23
|
+
import {
|
|
24
|
+
_getRequestContext,
|
|
25
|
+
type RequestContext,
|
|
26
|
+
} from "../server/request-context.js";
|
|
24
27
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
25
28
|
import type { EntryCacheConfig, EntryData } from "../server/context.js";
|
|
26
29
|
import { traverseBack } from "../router/pattern-matching.js";
|
|
@@ -31,18 +34,19 @@ import {
|
|
|
31
34
|
} from "./helpers.js";
|
|
32
35
|
import { reportCacheError } from "../cache/cache-error.js";
|
|
33
36
|
import { cacheKeyBase } from "../cache/cache-key-utils.js";
|
|
37
|
+
import {
|
|
38
|
+
executionStart,
|
|
39
|
+
predatesInvalidation,
|
|
40
|
+
type ExecutionStart,
|
|
41
|
+
} from "../cache/tag-invalidation.js";
|
|
34
42
|
import { hasPerClientSignal } from "../browser/cookie-name.js";
|
|
35
43
|
|
|
36
|
-
/** Injected cache-scope
|
|
44
|
+
/** Injected cache-scope builder (kept off this module's runtime import graph). */
|
|
37
45
|
export interface CacheScopeDeps {
|
|
38
46
|
createCacheScope: (
|
|
39
47
|
config: EntryCacheConfig | undefined,
|
|
40
48
|
parent?: CacheScope | null,
|
|
41
49
|
) => CacheScope | null;
|
|
42
|
-
resolveCacheTags: (
|
|
43
|
-
config: PartialCacheOptions | false,
|
|
44
|
-
ctx: RequestContext | undefined,
|
|
45
|
-
) => string[] | undefined;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export interface ServeResponseRouteWithCacheArgs {
|
|
@@ -60,8 +64,12 @@ export interface ServeResponseRouteWithCacheArgs {
|
|
|
60
64
|
* applies (no scope, disabled, condition false, or store lacks get/putResponse)
|
|
61
65
|
* so the caller falls through to a plain `executeHandler()` run.
|
|
62
66
|
*
|
|
63
|
-
* Must run inside runWithRequestContext (reads the ambient
|
|
64
|
-
* the helpers and reqCtx.waitUntil for background writes).
|
|
67
|
+
* Must run inside runWithRequestContext for `reqCtx` (reads the ambient
|
|
68
|
+
* request context via the helpers and reqCtx.waitUntil for background writes).
|
|
69
|
+
* Outside it, the route runs uncached: the scope resolves `key()`, a store
|
|
70
|
+
* keyGenerator and `condition()` against the ambient context, and without
|
|
71
|
+
* it a keyGenerator was skipped and the entry landed on the broad default
|
|
72
|
+
* key.
|
|
65
73
|
*/
|
|
66
74
|
export async function serveResponseRouteWithCache(
|
|
67
75
|
args: ServeResponseRouteWithCacheArgs,
|
|
@@ -75,6 +83,7 @@ export async function serveResponseRouteWithCache(
|
|
|
75
83
|
if (method !== "GET" && method !== "HEAD") {
|
|
76
84
|
return undefined;
|
|
77
85
|
}
|
|
86
|
+
if (_getRequestContext() !== reqCtx) return undefined;
|
|
78
87
|
|
|
79
88
|
let cacheScope: CacheScope | null = null;
|
|
80
89
|
for (const entry of traverseBack(manifestEntry)) {
|
|
@@ -83,20 +92,12 @@ export async function serveResponseRouteWithCache(
|
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let conditionPassed = true;
|
|
90
|
-
if (cacheScope.config !== false && cacheScope.config.condition) {
|
|
91
|
-
try {
|
|
92
|
-
conditionPassed = !!cacheScope.config.condition(reqCtx);
|
|
93
|
-
} catch {
|
|
94
|
-
conditionPassed = false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
95
|
+
// Skip the response cache when a condition() of the scope, or of any
|
|
96
|
+
// cache() enclosing it (#974), refuses this request.
|
|
97
|
+
if (!cacheScope?.allowsCache("read")) return undefined;
|
|
97
98
|
|
|
98
99
|
const store = cacheScope.getStore() ?? reqCtx._cacheStore;
|
|
99
|
-
if (!
|
|
100
|
+
if (!store?.getResponse || !store?.putResponse) {
|
|
100
101
|
return undefined;
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -105,8 +106,12 @@ export async function serveResponseRouteWithCache(
|
|
|
105
106
|
// the host-namespacing and search-normalization rules cannot drift.
|
|
106
107
|
let cacheKey = `response:${responseType}:${cacheKeyBase(url.host, url.pathname, url.searchParams, undefined, reqCtx._searchParamsFilter)}`;
|
|
107
108
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
109
|
+
// Keyed like the segment record (CacheScope.resolveKeyFrom): with key()
|
|
110
|
+
// results on the cache() chain (a full override, namespaced, #975) or an
|
|
111
|
+
// enclosing scope on another store whose keyGenerator partitions (#974),
|
|
112
|
+
// `response:` plus the composed key, so a nested cache() never shares an
|
|
113
|
+
// entry across the enclosing partitions (#970); otherwise the store's
|
|
114
|
+
// keyGenerator result (it modifies the default key), else the default key.
|
|
110
115
|
//
|
|
111
116
|
// A CONFIGURED key()/keyGenerator that THROWS must DEGRADE TO A MISS, not fall
|
|
112
117
|
// back to the broad default key. The default key is intentionally broad; if the
|
|
@@ -114,46 +119,27 @@ export async function serveResponseRouteWithCache(
|
|
|
114
119
|
// would cache PERSONALIZED output under it and serve it cross-user (cache
|
|
115
120
|
// poisoning). Mirrors the segment-cache behavior (cache-scope.ts lookupRoute):
|
|
116
121
|
// a throwing key degrades to a cache miss, never a collision onto the default
|
|
117
|
-
// slot
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
} else if (store.keyGenerator) {
|
|
134
|
-
try {
|
|
135
|
-
cacheKey = await store.keyGenerator(reqCtx, cacheKey);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
keyResolutionFailed = true;
|
|
138
|
-
reportCacheError(
|
|
139
|
-
error,
|
|
140
|
-
"cache-read",
|
|
141
|
-
"[ResponseCache] keyGenerator failed",
|
|
142
|
-
reqCtx,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Degrade to a MISS: return undefined so the caller runs the route UNCACHED.
|
|
148
|
-
// This early-returns BEFORE _onResponseCallbacks is saved/cleared below, so the
|
|
149
|
-
// pre-handler onResponse callbacks are still intact for the uncached run.
|
|
150
|
-
if (keyResolutionFailed) {
|
|
122
|
+
// slot, and so does a key() with no ambient request context to run in
|
|
123
|
+
// (resolveKeyFrom rejects).
|
|
124
|
+
try {
|
|
125
|
+
cacheKey = await cacheScope.resolveKeyFrom(cacheKey, "response:");
|
|
126
|
+
} catch (error) {
|
|
127
|
+
reportCacheError(
|
|
128
|
+
error,
|
|
129
|
+
"cache-read",
|
|
130
|
+
"[ResponseCache] Key resolution failed",
|
|
131
|
+
reqCtx,
|
|
132
|
+
);
|
|
133
|
+
// Degrade to a MISS: the caller runs the route UNCACHED. This returns
|
|
134
|
+
// BEFORE _onResponseCallbacks is saved/cleared below, so the pre-handler
|
|
135
|
+
// onResponse callbacks are still intact for the uncached run.
|
|
151
136
|
return undefined;
|
|
152
137
|
}
|
|
153
138
|
|
|
154
|
-
// Resolve cache tags for this
|
|
155
|
-
// request context is available, so the
|
|
156
|
-
|
|
139
|
+
// Resolve cache tags for this entry (static or dynamic, the enclosing
|
|
140
|
+
// scopes' included, #974) while the request context is available, so the
|
|
141
|
+
// stored entry is tag-invalidatable.
|
|
142
|
+
const responseTags = cacheScope.resolveTags(reqCtx);
|
|
157
143
|
|
|
158
144
|
// Pre-handler callbacks (registered by app-level middleware before the cache
|
|
159
145
|
// block) are saved and the live array is cleared:
|
|
@@ -180,17 +166,26 @@ export async function serveResponseRouteWithCache(
|
|
|
180
166
|
const canServeCached = (response: Response): boolean =>
|
|
181
167
|
isCacheableStatus(response.status) && !hasPerClientSignal(response.headers);
|
|
182
168
|
|
|
183
|
-
|
|
169
|
+
// A handler run that started before one of the entry's tags was
|
|
170
|
+
// invalidated is not written (#977, predatesInvalidation).
|
|
171
|
+
const putFresh = async (
|
|
184
172
|
store2: SegmentCacheStore,
|
|
185
173
|
fresh: Response,
|
|
186
|
-
|
|
187
|
-
|
|
174
|
+
start: ExecutionStart,
|
|
175
|
+
): Promise<void> => {
|
|
176
|
+
if (await predatesInvalidation(store2, responseTags, start)) {
|
|
177
|
+
// Nothing reads the copy now: release it (a tee branch buffers).
|
|
178
|
+
await fresh.body?.cancel();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await store2.putResponse!(
|
|
188
182
|
cacheKey,
|
|
189
|
-
fresh
|
|
183
|
+
fresh,
|
|
190
184
|
cacheScope!.ttl,
|
|
191
185
|
cacheScope!.swr,
|
|
192
186
|
responseTags,
|
|
193
187
|
);
|
|
188
|
+
};
|
|
194
189
|
|
|
195
190
|
try {
|
|
196
191
|
const cached = await store.getResponse(cacheKey);
|
|
@@ -201,8 +196,9 @@ export async function serveResponseRouteWithCache(
|
|
|
201
196
|
// Stale hit (SWR): return cached, revalidate in background.
|
|
202
197
|
reqCtx.waitUntil(async () => {
|
|
203
198
|
try {
|
|
199
|
+
const start = executionStart();
|
|
204
200
|
const fresh = finalizeResponse(await executeHandler());
|
|
205
|
-
if (canStore(fresh)) await putFresh(store, fresh);
|
|
201
|
+
if (canStore(fresh)) await putFresh(store, fresh, start);
|
|
206
202
|
} catch (error) {
|
|
207
203
|
reportCacheError(
|
|
208
204
|
error,
|
|
@@ -224,6 +220,7 @@ export async function serveResponseRouteWithCache(
|
|
|
224
220
|
}
|
|
225
221
|
|
|
226
222
|
// Cache miss: execute the handler and cache the result when storeable.
|
|
223
|
+
const start = executionStart();
|
|
227
224
|
const response = finalizeResponse(await executeHandler());
|
|
228
225
|
if (canStore(response)) {
|
|
229
226
|
// Clone SYNCHRONOUSLY here, before returning. The original `response` is
|
|
@@ -238,13 +235,7 @@ export async function serveResponseRouteWithCache(
|
|
|
238
235
|
const toCache = response.clone();
|
|
239
236
|
reqCtx.waitUntil(async () => {
|
|
240
237
|
try {
|
|
241
|
-
await store
|
|
242
|
-
cacheKey,
|
|
243
|
-
toCache,
|
|
244
|
-
cacheScope!.ttl,
|
|
245
|
-
cacheScope!.swr,
|
|
246
|
-
responseTags,
|
|
247
|
-
);
|
|
238
|
+
await putFresh(store, toCache, start);
|
|
248
239
|
} catch (error) {
|
|
249
240
|
reportCacheError(
|
|
250
241
|
error,
|
|
@@ -11,7 +11,7 @@ import { getRequestContext } from "../server/request-context.js";
|
|
|
11
11
|
import { contextGet, isNonCacheable } from "../context-var.js";
|
|
12
12
|
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
13
13
|
import { RESPONSE_TYPE_MIME } from "../router/content-negotiation.js";
|
|
14
|
-
import { createCacheScope
|
|
14
|
+
import { createCacheScope } from "../cache/cache-scope.js";
|
|
15
15
|
import { serveResponseRouteWithCache } from "./response-cache-serve.js";
|
|
16
16
|
import { executeMiddleware } from "../router/middleware.js";
|
|
17
17
|
import {
|
|
@@ -220,7 +220,7 @@ export async function handleResponseRoute<TEnv>(
|
|
|
220
220
|
responseType: preview.responseType,
|
|
221
221
|
url,
|
|
222
222
|
executeHandler,
|
|
223
|
-
deps: { createCacheScope
|
|
223
|
+
deps: { createCacheScope },
|
|
224
224
|
});
|
|
225
225
|
if (cached !== undefined) return cached;
|
|
226
226
|
}
|