@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
|
@@ -8,14 +8,20 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { CookieOptions } from "../router/middleware-types.js";
|
|
11
|
-
import {
|
|
11
|
+
import type { Theme } from "../theme/types.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
getRequestContext,
|
|
14
|
+
_getRequestContext,
|
|
15
|
+
type RequestContext,
|
|
16
|
+
} from "./request-context.js";
|
|
17
|
+
import {
|
|
18
|
+
guardIdentityRead,
|
|
19
|
+
recordLoaderIdentityRead,
|
|
20
|
+
refuseInCacheScope,
|
|
21
|
+
requestReadCaptureFix,
|
|
22
|
+
type IdentityReadWording,
|
|
16
23
|
} from "./context.js";
|
|
17
|
-
import {
|
|
18
|
-
import { PPR_LANE_HINT } from "../rsc/shell-capture-constants.js";
|
|
24
|
+
import { requestHeaders, shadowRequestHeaders } from "./request-headers.js";
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
27
|
* A single cookie entry returned by get() and getAll().
|
|
@@ -66,8 +72,7 @@ export interface CookieStore {
|
|
|
66
72
|
*/
|
|
67
73
|
export function cookies(): CookieStore {
|
|
68
74
|
const ctx = getRequestContext();
|
|
69
|
-
|
|
70
|
-
assertNotInsideShellCapture(ctx, "cookies");
|
|
75
|
+
guardIdentityRead(ctx, "cookies()", COOKIES_READ);
|
|
71
76
|
return createCookieStore(ctx);
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -91,120 +96,165 @@ export interface ReadonlyHeaders {
|
|
|
91
96
|
type HeadersIterator<T> = IterableIterator<T>;
|
|
92
97
|
|
|
93
98
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* they would be frozen into the shared cache entry and served to the wrong
|
|
103
|
-
* users. This is the same hazard for both scopes: a `cache()` boundary caches
|
|
104
|
-
* everything except loaders (it is the document-level "PPR shell"), so a read
|
|
105
|
-
* here is baked into the shell exactly like a `"use cache"` return value is
|
|
106
|
-
* baked into its cache entry.
|
|
107
|
-
*
|
|
108
|
-
* Both checks are scoped so loaders stay exempt — loaders are the dynamic
|
|
109
|
-
* "holes" of a cached document and always run fresh. `isInsideCacheScope()`
|
|
110
|
-
* returns false inside loaders by construction; `isInsideCacheExecScope()`
|
|
111
|
-
* follows the cached body's own async chain, so a loader running in PARALLEL
|
|
112
|
-
* with a slow "use cache" fetch on the same request reads cookies() freely.
|
|
113
|
-
* (Scar: the previous INSIDE_CACHE_EXEC stamp on the shared RequestContext
|
|
114
|
-
* made exactly that parallel read throw for the cached fetch's whole
|
|
115
|
-
* execution window — see cache-exec-scope.ts.)
|
|
99
|
+
* The cache-scope wording of cookies(), headers() and the response
|
|
100
|
+
* directives. Request-scoped data read in a cached scope is not reflected in
|
|
101
|
+
* its key, so it would be frozen into the shared entry and served to the
|
|
102
|
+
* wrong users; a "use cache" scope follows the cached body's own async chain
|
|
103
|
+
* (cache-exec-scope.ts), so a loader running in PARALLEL with a slow cached
|
|
104
|
+
* fetch on the same request reads cookies() freely. (Scar: an
|
|
105
|
+
* INSIDE_CACHE_EXEC stamp on the shared RequestContext made that parallel
|
|
106
|
+
* read throw for the fetch's whole execution window.)
|
|
116
107
|
*/
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
108
|
+
const REQUEST_READ_CACHE_FIX: Pick<
|
|
109
|
+
IdentityReadWording["fix"],
|
|
110
|
+
"useCache" | "cacheScope"
|
|
111
|
+
> = {
|
|
112
|
+
useCache:
|
|
113
|
+
"Request-scoped data (cookies, headers) varies per request but is not " +
|
|
114
|
+
"reflected in the cache key, so cached results would be served to the " +
|
|
115
|
+
"wrong users. Extract the value before the cached function and pass it " +
|
|
116
|
+
"as an argument:\n\n" +
|
|
117
|
+
' const locale = cookies().get("locale")?.value ?? "en";\n' +
|
|
118
|
+
" const data = await getCachedData(locale); // locale is now in the cache key",
|
|
119
|
+
cacheScope:
|
|
120
|
+
"A cache() scope caches everything except loaders, so request-scoped " +
|
|
121
|
+
"data (cookies, headers) read here would be frozen into the shared " +
|
|
122
|
+
"cached shell and served to other users. Read it inside a loader " +
|
|
123
|
+
"instead — loaders always run fresh on every request, even on a cache hit:\n\n" +
|
|
124
|
+
' loader("user", () => getUser(cookies().get("session")?.value));',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* cookies() and headers() record the read on the returned view's read
|
|
129
|
+
* methods (createCookieStore, the headers() proxy), so a write alone is not
|
|
130
|
+
* one (#972).
|
|
131
|
+
*/
|
|
132
|
+
function requestReadWording(what: string): IdentityReadWording {
|
|
133
|
+
return {
|
|
134
|
+
verb: "called",
|
|
135
|
+
fix: { ...REQUEST_READ_CACHE_FIX, ...requestReadCaptureFix(what) },
|
|
136
|
+
record: false,
|
|
137
|
+
};
|
|
139
138
|
}
|
|
140
139
|
|
|
140
|
+
const COOKIES_READ: IdentityReadWording = requestReadWording("cookies");
|
|
141
|
+
const HEADERS_READ: IdentityReadWording = requestReadWording("headers");
|
|
142
|
+
|
|
141
143
|
/**
|
|
142
|
-
*
|
|
143
|
-
* (`
|
|
144
|
-
*
|
|
145
|
-
* hitting the URL, so a request-scoped read here would bake one user's
|
|
146
|
-
* cookies/headers into markup served to others — same hazard as the cache
|
|
147
|
-
* scopes above, at the document tier. DSL segment loaders need no exemption:
|
|
148
|
-
* the live lane is masked (never executed) during capture, and the bake lane
|
|
149
|
-
* is exactly what this guard exists for.
|
|
150
|
-
*
|
|
151
|
-
* HANDLER-INVOKED loader bodies (`await ctx.use(Loader)` from a handler) are
|
|
152
|
-
* EXEMPT — the consumption-lane rule: handler consumption yields a BAKED
|
|
153
|
-
* shared copy in every artifact tier, and the cache-purity guards above
|
|
154
|
-
* already permit identity reads there (cache()/"use cache" bake the same
|
|
155
|
-
* reads today). Guarding only the PPR tier made the same code legal under
|
|
156
|
-
* cache() but capture-refusing under ppr (issue #672 / #674). The trade is
|
|
157
|
-
* documented: an identity read in a handler-consumed loader bakes the CAPTURE
|
|
158
|
-
* request's value into the shared shell; client-side consumption (useLoader)
|
|
159
|
-
* is the live lane.
|
|
160
|
-
*
|
|
161
|
-
* Keys off `_shellCaptureRun`, NOT the `_shellCapture` descriptor: the descriptor
|
|
162
|
-
* is also present during the FOREGROUND render (it means "a capture is wanted"),
|
|
163
|
-
* and the foreground must read cookies/headers normally to serve the real user.
|
|
164
|
-
* Only the derived capture context sets `_shellCaptureRun`.
|
|
165
|
-
*
|
|
166
|
-
* Applies only to the READ surfaces (cookies(), headers()) whose values
|
|
167
|
-
* become markup. Response directives (invalidateClientCache(),
|
|
168
|
-
* keepClientCache()) stay callable: during capture they are header effects on
|
|
169
|
-
* a discarded response, and on the live HIT path the full pipeline runs so their
|
|
170
|
-
* headers flow to the client normally.
|
|
171
|
-
*
|
|
172
|
-
* The throw makes such a route PPR-ineligible by construction: the capture
|
|
173
|
-
* render errors, nothing is stored, and every request keeps getting the
|
|
174
|
-
* normal axis-1 render.
|
|
144
|
+
* The raw reads (#976): `ctx.request.headers` and `getRequestContext()
|
|
145
|
+
* .cookie()` / `.cookies()`. Same wording as cookies()/headers(), but each
|
|
146
|
+
* returns the data itself, so the read is recorded at the access.
|
|
175
147
|
*/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
148
|
+
const REQUEST_HEADERS_READ: IdentityReadWording = {
|
|
149
|
+
verb: "read",
|
|
150
|
+
fix: HEADERS_READ.fix,
|
|
151
|
+
};
|
|
152
|
+
const RAW_COOKIES_READ: IdentityReadWording = {
|
|
153
|
+
verb: "called",
|
|
154
|
+
fix: COOKIES_READ.fix,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Run the identity guard for a raw cookie read on `ctx`
|
|
159
|
+
* (request-context.ts `cookie()` / `cookies()`). Before #976 they were plain
|
|
160
|
+
* reads of the Cookie header that no shared scope refused. The guard reads
|
|
161
|
+
* the ambient context, as cookies() does, and falls back to `ctx`.
|
|
162
|
+
*/
|
|
163
|
+
export function guardRawCookieRead(
|
|
164
|
+
ctx: RequestContext<any>,
|
|
165
|
+
surface: "getRequestContext().cookie()" | "getRequestContext().cookies()",
|
|
166
|
+
): void {
|
|
167
|
+
guardIdentityRead(_getRequestContext() ?? ctx, surface, RAW_COOKIES_READ);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function guardRequestHeadersRead(): void {
|
|
171
|
+
guardIdentityRead(
|
|
172
|
+
_getRequestContext(),
|
|
173
|
+
"ctx.request.headers",
|
|
174
|
+
REQUEST_HEADERS_READ,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Guard `request.headers` like headers() (#976): the request every ctx
|
|
180
|
+
* exposes (request context, handler, middleware and loader `ctx.request`)
|
|
181
|
+
* gets an own getter that runs guardIdentityRead against the ambient request
|
|
182
|
+
* context, so a read inside a ppr capture, a cache() boundary, a "use cache"
|
|
183
|
+
* body or an unkeyed loader cache() fill refuses as cookies() does. A Request
|
|
184
|
+
* argument keys a "use cache" entry by its URL only (cache-runtime.ts), so a
|
|
185
|
+
* header read there baked the first caller's value into the shared entry.
|
|
186
|
+
* The router's own reads go through requestHeaders() (request-headers.ts).
|
|
187
|
+
* Idempotent; returns `request`.
|
|
188
|
+
*/
|
|
189
|
+
export function guardRequestHeaders(request: Request): Request {
|
|
190
|
+
return shadowRequestHeaders(request, guardRequestHeadersRead);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The fix for a theme read that a cache() boundary or a ppr capture refuses. */
|
|
194
|
+
const THEME_READ_FIX =
|
|
195
|
+
"On ppr and cache() routes, read the theme with useTheme() in a client " +
|
|
196
|
+
"component, or in a live loader (no ssr: false) with cookies().get(<storageKey>). " +
|
|
197
|
+
"The <html> theme class needs neither: the theme script sets it before paint. " +
|
|
198
|
+
"See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).";
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The theme is the visitor's theme cookie, so a read of it is an identity
|
|
202
|
+
* read like cookies() (guardIdentityRead). Before #971 each read was a plain
|
|
203
|
+
* cookie read, so a ppr shell, a cache() entry or a "use cache" entry stored
|
|
204
|
+
* the first visitor's theme and served it to every later visitor.
|
|
205
|
+
*/
|
|
206
|
+
const THEME_READ: IdentityReadWording = {
|
|
207
|
+
verb: "read",
|
|
208
|
+
fix: {
|
|
209
|
+
useCache:
|
|
210
|
+
"The theme comes from the visitor's cookie and is not in the cache " +
|
|
211
|
+
"key, so the first caller's theme would be served to later callers. " +
|
|
212
|
+
"Read it before the cached function and pass it in as an argument.",
|
|
213
|
+
cacheScope:
|
|
214
|
+
"The theme comes from the visitor's cookie, so the first visitor's " +
|
|
215
|
+
"theme would be stored in the shared entry and served to everyone. " +
|
|
216
|
+
THEME_READ_FIX,
|
|
217
|
+
capture:
|
|
218
|
+
"The captured shell is served to every visitor of this URL, so the " +
|
|
219
|
+
`capturing visitor's theme would reach everyone. ${THEME_READ_FIX}`,
|
|
220
|
+
warning: THEME_READ_FIX,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/** The public reads of the visitor's theme cookie that readGuardedTheme guards. */
|
|
225
|
+
export type ThemeReadSurface = "ctx.theme" | "getRequestContext().theme";
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* `ctx`'s theme through guardIdentityRead: the one body of every public theme
|
|
229
|
+
* read (the handler and middleware `ctx.theme`, handler-context.ts and
|
|
230
|
+
* middleware.ts, and `getRequestContext().theme`, request-context.ts). The
|
|
231
|
+
* guard reads the ambient context, as cookies() does, and falls back to
|
|
232
|
+
* `ctx`; the value is `ctx`'s unguarded `_readTheme()`, which the router's
|
|
233
|
+
* own payload read (payloadInitialTheme, rsc/full-payload.ts) calls directly.
|
|
234
|
+
* Undefined without a theme config.
|
|
235
|
+
*/
|
|
236
|
+
export function readGuardedTheme(
|
|
237
|
+
ctx: RequestContext<any> | undefined,
|
|
238
|
+
surface: ThemeReadSurface,
|
|
239
|
+
): Theme | undefined {
|
|
240
|
+
if (!ctx?._themeConfig) return undefined;
|
|
241
|
+
guardIdentityRead(_getRequestContext() ?? ctx, surface, THEME_READ);
|
|
242
|
+
return ctx._readTheme();
|
|
205
243
|
}
|
|
206
244
|
|
|
207
245
|
const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
246
|
+
// Reading one records the read (#972), like the cookies() read methods: a
|
|
247
|
+
// headers() view taken outside a loader and read inside one still counts.
|
|
248
|
+
const HEADERS_READ_PROPS = new Set<string | symbol>([
|
|
249
|
+
"get",
|
|
250
|
+
"has",
|
|
251
|
+
"entries",
|
|
252
|
+
"keys",
|
|
253
|
+
"values",
|
|
254
|
+
"forEach",
|
|
255
|
+
"getSetCookie",
|
|
256
|
+
Symbol.iterator,
|
|
257
|
+
]);
|
|
208
258
|
|
|
209
259
|
/**
|
|
210
260
|
* Get the original request headers (read-only).
|
|
@@ -223,10 +273,10 @@ const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
|
223
273
|
*/
|
|
224
274
|
export function headers(): ReadonlyHeaders {
|
|
225
275
|
const ctx = getRequestContext();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return new Proxy(ctx.request.headers, {
|
|
276
|
+
guardIdentityRead(ctx, "headers()", HEADERS_READ);
|
|
277
|
+
return new Proxy(requestHeaders(ctx.request), {
|
|
229
278
|
get(target, prop, receiver) {
|
|
279
|
+
if (HEADERS_READ_PROPS.has(prop)) recordLoaderIdentityRead("headers()");
|
|
230
280
|
if (typeof prop === "string" && HEADERS_MUTATION_METHODS.has(prop)) {
|
|
231
281
|
return () => {
|
|
232
282
|
throw new Error(
|
|
@@ -263,7 +313,7 @@ export function invalidateClientCache(): void {
|
|
|
263
313
|
}
|
|
264
314
|
return;
|
|
265
315
|
}
|
|
266
|
-
|
|
316
|
+
refuseInCacheScope("invalidateClientCache()", COOKIES_READ);
|
|
267
317
|
ctx._rotateStateCookie();
|
|
268
318
|
}
|
|
269
319
|
|
|
@@ -288,31 +338,31 @@ export function keepClientCache(): void {
|
|
|
288
338
|
}
|
|
289
339
|
return;
|
|
290
340
|
}
|
|
291
|
-
|
|
341
|
+
refuseInCacheScope("keepClientCache()", COOKIES_READ);
|
|
292
342
|
ctx._setKeepCacheDirective();
|
|
293
343
|
}
|
|
294
344
|
|
|
295
345
|
/**
|
|
296
|
-
* Create a CookieStore backed by a RequestContext.
|
|
297
|
-
*
|
|
346
|
+
* Create a CookieStore backed by a RequestContext. Reads go through the
|
|
347
|
+
* context's unguarded `_readCookie()` / `_readCookies()`: cookies() ran the
|
|
348
|
+
* guard at its call and records on each read method here.
|
|
298
349
|
*/
|
|
299
|
-
function createCookieStore(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
options?: Pick<CookieOptions, "domain" | "path">,
|
|
306
|
-
): void;
|
|
307
|
-
}): CookieStore {
|
|
350
|
+
function createCookieStore(
|
|
351
|
+
ctx: Pick<
|
|
352
|
+
RequestContext<any>,
|
|
353
|
+
"_readCookie" | "_readCookies" | "setCookie" | "deleteCookie"
|
|
354
|
+
>,
|
|
355
|
+
): CookieStore {
|
|
308
356
|
return {
|
|
309
357
|
get(name: string): Cookie | undefined {
|
|
310
|
-
|
|
358
|
+
recordLoaderIdentityRead("cookies()");
|
|
359
|
+
const value = ctx._readCookie(name);
|
|
311
360
|
return value !== undefined ? { name, value } : undefined;
|
|
312
361
|
},
|
|
313
362
|
|
|
314
363
|
getAll(name?: string): Cookie[] {
|
|
315
|
-
|
|
364
|
+
recordLoaderIdentityRead("cookies()");
|
|
365
|
+
const all = ctx._readCookies();
|
|
316
366
|
if (name !== undefined) {
|
|
317
367
|
const value = all[name];
|
|
318
368
|
return value !== undefined ? [{ name, value }] : [];
|
|
@@ -321,7 +371,8 @@ function createCookieStore(ctx: {
|
|
|
321
371
|
},
|
|
322
372
|
|
|
323
373
|
has(name: string): boolean {
|
|
324
|
-
|
|
374
|
+
recordLoaderIdentityRead("cookies()");
|
|
375
|
+
return ctx._readCookie(name) !== undefined;
|
|
325
376
|
},
|
|
326
377
|
|
|
327
378
|
set(name: string, value: string, options?: CookieOptions): void {
|