@rangojs/router 0.18.0 → 0.19.1
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/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -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 +37 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +48 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- 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/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/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/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- 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 +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +126 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- 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-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- 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 +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +962 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- 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 +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +67 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -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 +4 -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 +54 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +202 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +209 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- 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 +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- 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 +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +42 -5
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +152 -26
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- 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 +30 -10
- package/src/rsc/handler.ts +12 -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 +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +263 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +134 -87
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -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 +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- 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 +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -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 +75 -5
- 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,
|
|
16
19
|
recordLoaderIdentityRead,
|
|
20
|
+
refuseInCacheScope,
|
|
21
|
+
requestReadCaptureFix,
|
|
22
|
+
type IdentityReadWording,
|
|
17
23
|
} from "./context.js";
|
|
18
|
-
import {
|
|
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,66 +96,98 @@ 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.)
|
|
116
|
-
*
|
|
117
|
-
* The loader cache() identity reads (#972) are recorded by the read methods
|
|
118
|
-
* instead (createCookieStore, the headers() view), so a write alone is not one.
|
|
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.)
|
|
119
107
|
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const COOKIES_READ: IdentityReadWording = requestReadWording("cookies");
|
|
141
|
+
const HEADERS_READ: IdentityReadWording = requestReadWording("headers");
|
|
142
|
+
|
|
143
|
+
/**
|
|
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.
|
|
147
|
+
*/
|
|
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
|
+
);
|
|
143
176
|
}
|
|
144
177
|
|
|
145
178
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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`.
|
|
149
188
|
*/
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
if (isInsideCacheScope()) return "cache()";
|
|
153
|
-
return undefined;
|
|
189
|
+
export function guardRequestHeaders(request: Request): Request {
|
|
190
|
+
return shadowRequestHeaders(request, guardRequestHeadersRead);
|
|
154
191
|
}
|
|
155
192
|
|
|
156
193
|
/** The fix for a theme read that a cache() boundary or a ppr capture refuses. */
|
|
@@ -160,57 +197,49 @@ const THEME_READ_FIX =
|
|
|
160
197
|
"The <html> theme class needs neither: the theme script sets it before paint. " +
|
|
161
198
|
"See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).";
|
|
162
199
|
|
|
163
|
-
/**
|
|
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. */
|
|
164
225
|
export type ThemeReadSurface = "ctx.theme" | "getRequestContext().theme";
|
|
165
226
|
|
|
166
227
|
/**
|
|
167
|
-
*
|
|
168
|
-
* `ctx.theme
|
|
169
|
-
* (request-context.ts). The
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* entry or a "use cache" entry stored the first visitor's theme and served it
|
|
175
|
-
* to every later visitor.
|
|
176
|
-
*
|
|
177
|
-
* A read the guards allow is recorded on the current loader execution, as a
|
|
178
|
-
* cookies() read method records it (#972, recordLoaderIdentityRead).
|
|
179
|
-
*
|
|
180
|
-
* `ctx` is the request context at read time, as cookies() reads it. The
|
|
181
|
-
* router's own payload read (payloadInitialTheme, rsc/full-payload.ts) goes
|
|
182
|
-
* through the unguarded `_readTheme()`.
|
|
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.
|
|
183
235
|
*/
|
|
184
|
-
export function
|
|
185
|
-
ctx:
|
|
236
|
+
export function readGuardedTheme(
|
|
237
|
+
ctx: RequestContext<any> | undefined,
|
|
186
238
|
surface: ThemeReadSurface,
|
|
187
|
-
):
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
`${surface} cannot be read inside a "use cache" function. The theme ` +
|
|
192
|
-
`comes from the visitor's cookie and is not in the cache key, so the ` +
|
|
193
|
-
`first caller's theme would be served to later callers. Read it ` +
|
|
194
|
-
`before the cached function and pass it in as an argument.`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
if (scope === "cache()") {
|
|
198
|
-
throw new Error(
|
|
199
|
-
`${surface} cannot be read inside a cache() boundary. The theme comes ` +
|
|
200
|
-
`from the visitor's cookie, so the first visitor's theme would be ` +
|
|
201
|
-
`stored in the shared entry and served to everyone. ${THEME_READ_FIX}`,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
if (tripShellCaptureGuard(ctx, surface, THEME_READ_FIX)) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`${surface} cannot be read while capturing a shared shell (ppr shell ` +
|
|
207
|
-
`capture). The captured shell is served to every visitor of this URL, ` +
|
|
208
|
-
`so the capturing visitor's theme would reach everyone. ${THEME_READ_FIX}`,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
// An identity read like a cookies() read method: a loader cache() fill with
|
|
212
|
-
// no key() refuses an execution that made one (#972).
|
|
213
|
-
recordLoaderIdentityRead(surface, "read");
|
|
239
|
+
): Theme | undefined {
|
|
240
|
+
if (!ctx?._themeConfig) return undefined;
|
|
241
|
+
guardIdentityRead(_getRequestContext() ?? ctx, surface, THEME_READ);
|
|
242
|
+
return ctx._readTheme();
|
|
214
243
|
}
|
|
215
244
|
|
|
216
245
|
const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
@@ -244,9 +273,8 @@ const HEADERS_READ_PROPS = new Set<string | symbol>([
|
|
|
244
273
|
*/
|
|
245
274
|
export function headers(): ReadonlyHeaders {
|
|
246
275
|
const ctx = getRequestContext();
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return new Proxy(ctx.request.headers, {
|
|
276
|
+
guardIdentityRead(ctx, "headers()", HEADERS_READ);
|
|
277
|
+
return new Proxy(requestHeaders(ctx.request), {
|
|
250
278
|
get(target, prop, receiver) {
|
|
251
279
|
if (HEADERS_READ_PROPS.has(prop)) recordLoaderIdentityRead("headers()");
|
|
252
280
|
if (typeof prop === "string" && HEADERS_MUTATION_METHODS.has(prop)) {
|
|
@@ -285,7 +313,7 @@ export function invalidateClientCache(): void {
|
|
|
285
313
|
}
|
|
286
314
|
return;
|
|
287
315
|
}
|
|
288
|
-
|
|
316
|
+
refuseInCacheScope("invalidateClientCache()", COOKIES_READ);
|
|
289
317
|
ctx._rotateStateCookie();
|
|
290
318
|
}
|
|
291
319
|
|
|
@@ -310,33 +338,31 @@ export function keepClientCache(): void {
|
|
|
310
338
|
}
|
|
311
339
|
return;
|
|
312
340
|
}
|
|
313
|
-
|
|
341
|
+
refuseInCacheScope("keepClientCache()", COOKIES_READ);
|
|
314
342
|
ctx._setKeepCacheDirective();
|
|
315
343
|
}
|
|
316
344
|
|
|
317
345
|
/**
|
|
318
|
-
* Create a CookieStore backed by a RequestContext.
|
|
319
|
-
*
|
|
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.
|
|
320
349
|
*/
|
|
321
|
-
function createCookieStore(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
options?: Pick<CookieOptions, "domain" | "path">,
|
|
328
|
-
): void;
|
|
329
|
-
}): CookieStore {
|
|
350
|
+
function createCookieStore(
|
|
351
|
+
ctx: Pick<
|
|
352
|
+
RequestContext<any>,
|
|
353
|
+
"_readCookie" | "_readCookies" | "setCookie" | "deleteCookie"
|
|
354
|
+
>,
|
|
355
|
+
): CookieStore {
|
|
330
356
|
return {
|
|
331
357
|
get(name: string): Cookie | undefined {
|
|
332
358
|
recordLoaderIdentityRead("cookies()");
|
|
333
|
-
const value = ctx.
|
|
359
|
+
const value = ctx._readCookie(name);
|
|
334
360
|
return value !== undefined ? { name, value } : undefined;
|
|
335
361
|
},
|
|
336
362
|
|
|
337
363
|
getAll(name?: string): Cookie[] {
|
|
338
364
|
recordLoaderIdentityRead("cookies()");
|
|
339
|
-
const all = ctx.
|
|
365
|
+
const all = ctx._readCookies();
|
|
340
366
|
if (name !== undefined) {
|
|
341
367
|
const value = all[name];
|
|
342
368
|
return value !== undefined ? [{ name, value }] : [];
|
|
@@ -346,7 +372,7 @@ function createCookieStore(ctx: {
|
|
|
346
372
|
|
|
347
373
|
has(name: string): boolean {
|
|
348
374
|
recordLoaderIdentityRead("cookies()");
|
|
349
|
-
return ctx.
|
|
375
|
+
return ctx._readCookie(name) !== undefined;
|
|
350
376
|
},
|
|
351
377
|
|
|
352
378
|
set(name: string, value: string, options?: CookieOptions): void {
|