@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
|
@@ -17,6 +17,7 @@ import type { CookieOptions } from "../router/middleware.js";
|
|
|
17
17
|
import {
|
|
18
18
|
KEEP_CACHE_HEADER,
|
|
19
19
|
getRawCookieValue,
|
|
20
|
+
freshReadsCookieName,
|
|
20
21
|
mintStateValue,
|
|
21
22
|
serializeStateCookie,
|
|
22
23
|
} from "../browser/cookie-name.js";
|
|
@@ -48,7 +49,7 @@ import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
|
48
49
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
49
50
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
50
51
|
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
51
|
-
import type {
|
|
52
|
+
import type { TransitionWhenRecord } from "../router/transition-when.js";
|
|
52
53
|
import type { ResolvedTracing } from "../router/tracing.js";
|
|
53
54
|
import type { RenderMode, RenderPhase } from "../router/timeout.js";
|
|
54
55
|
import {
|
|
@@ -64,6 +65,12 @@ import {
|
|
|
64
65
|
assertNonCacheableReadAllowed,
|
|
65
66
|
clearPprHeaderScope,
|
|
66
67
|
} from "./context.js";
|
|
68
|
+
import {
|
|
69
|
+
guardRawCookieRead,
|
|
70
|
+
guardRequestHeaders,
|
|
71
|
+
readGuardedTheme,
|
|
72
|
+
} from "./cookie-store.js";
|
|
73
|
+
import { requestHeaders } from "./request-headers.js";
|
|
67
74
|
import {
|
|
68
75
|
createReverseFunction,
|
|
69
76
|
stripInternalParams,
|
|
@@ -74,6 +81,10 @@ import {
|
|
|
74
81
|
getSearchSchema,
|
|
75
82
|
} from "../route-map-builder.js";
|
|
76
83
|
import { parseSearchParams } from "../search-params.js";
|
|
84
|
+
import {
|
|
85
|
+
executionStart,
|
|
86
|
+
type ExecutionStart,
|
|
87
|
+
} from "../cache/invalidation-order.js";
|
|
77
88
|
import { invariant } from "../errors.js";
|
|
78
89
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
79
90
|
|
|
@@ -144,10 +155,24 @@ export interface RequestContext<
|
|
|
144
155
|
/** @internal Request-local PPR opt-out marker set by ctx.dynamic(). */
|
|
145
156
|
_dynamic?: boolean;
|
|
146
157
|
|
|
147
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* @internal Get a cookie value (effective: request + response mutations).
|
|
160
|
+
* Use cookies().get() instead. Guarded like cookies() (#976).
|
|
161
|
+
*/
|
|
148
162
|
cookie(name: string): string | undefined;
|
|
149
|
-
/**
|
|
163
|
+
/**
|
|
164
|
+
* @internal Get all cookies (effective merged view). Use
|
|
165
|
+
* cookies().getAll() instead. Guarded like cookies() (#976).
|
|
166
|
+
*/
|
|
150
167
|
cookies(): Record<string, string>;
|
|
168
|
+
/**
|
|
169
|
+
* @internal `cookie()` without the identity guard: the read behind the
|
|
170
|
+
* cookies() store, which guards at its own call and records per read
|
|
171
|
+
* method. Non-enumerable, like `_readTheme`.
|
|
172
|
+
*/
|
|
173
|
+
_readCookie(name: string): string | undefined;
|
|
174
|
+
/** @internal `cookies()` without the identity guard (see `_readCookie`). */
|
|
175
|
+
_readCookies(): Record<string, string>;
|
|
151
176
|
/** @internal Set a cookie on the response. Use cookies().set() instead. */
|
|
152
177
|
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
153
178
|
/** @internal Delete a cookie. Use cookies().delete() instead. */
|
|
@@ -165,6 +190,39 @@ export interface RequestContext<
|
|
|
165
190
|
_rotateStateCookie(): void;
|
|
166
191
|
/** @internal Set the keepClientCache() directive header on the response. */
|
|
167
192
|
_setKeepCacheDirective(): void;
|
|
193
|
+
/**
|
|
194
|
+
* @internal This request carries the fresh-reads cookie: it follows an
|
|
195
|
+
* updateTag()/revalidateTag() by the same user, so the stores read tag
|
|
196
|
+
* markers and shells from the store, not from an isolate memo.
|
|
197
|
+
*/
|
|
198
|
+
_freshReads?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* @internal Set the fresh-reads cookie on this response for `maxAgeMs`
|
|
201
|
+
* (rounded up to whole seconds; the longest stays). Called by
|
|
202
|
+
* updateTag()/revalidateTag().
|
|
203
|
+
*/
|
|
204
|
+
_setFreshReadsCookie(maxAgeMs: number): void;
|
|
205
|
+
/**
|
|
206
|
+
* @internal The context createRequestContext() returned. A context derived
|
|
207
|
+
* from it with Object.create (a PPR HIT tail, a shell capture, a background
|
|
208
|
+
* revalidation render) inherits it, so per-request invalidation state (the
|
|
209
|
+
* stores' masks) is keyed by the request, not by whichever derived object
|
|
210
|
+
* called or read (#973).
|
|
211
|
+
*/
|
|
212
|
+
_requestRoot?: RequestContext<any, any>;
|
|
213
|
+
/**
|
|
214
|
+
* @internal Where the request started in the isolate's invalidation order
|
|
215
|
+
* (cache/invalidation-order.ts), taken when createRequestContext() ran. The
|
|
216
|
+
* document cache gates its write on it (#977): a middleware ahead of the
|
|
217
|
+
* document cache can read tagged data the document bakes.
|
|
218
|
+
*/
|
|
219
|
+
_requestStart?: ExecutionStart;
|
|
220
|
+
/**
|
|
221
|
+
* @internal The handler has handed the response to the host (rsc/handler.ts
|
|
222
|
+
* `rango.response`): header and cookie writes after this are lost, as from
|
|
223
|
+
* a streaming loader or render.
|
|
224
|
+
*/
|
|
225
|
+
_responseSent?: boolean;
|
|
168
226
|
|
|
169
227
|
/**
|
|
170
228
|
* Access loader data or push handle data.
|
|
@@ -206,16 +264,13 @@ export interface RequestContext<
|
|
|
206
264
|
readonly _handleStore: HandleStore;
|
|
207
265
|
|
|
208
266
|
/**
|
|
209
|
-
* @internal transition({ when })
|
|
210
|
-
* keyed by segment id.
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
267
|
+
* @internal transition({ when }) client references of the matched entry
|
|
268
|
+
* chain, keyed by segment id. Recorded at match time from the static route
|
|
269
|
+
* definition (router/transition-when.ts) and attached to the payload's
|
|
270
|
+
* segments right before Flight (rsc/attach-transition-when.ts). The server
|
|
271
|
+
* never calls them, and no cache or snapshot stores them.
|
|
214
272
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
/** @internal PPR transition decisions evaluated before cache lookup/handlers. */
|
|
218
|
-
_pprTransitionDecisions?: Map<string, boolean>;
|
|
273
|
+
_transitionWhenRefs?: ReadonlyMap<string, TransitionWhenRecord>;
|
|
219
274
|
|
|
220
275
|
/**
|
|
221
276
|
* @internal Post-match serve-source truth for the PPR replay reporter.
|
|
@@ -243,8 +298,8 @@ export interface RequestContext<
|
|
|
243
298
|
* capture task's derived request context (built by shell-capture.ts). This is
|
|
244
299
|
* the switch every capture-specific behavior reads: loader masking
|
|
245
300
|
* (loader-mask.ts isShellCaptureActive / fresh.ts emitStreaming) and the
|
|
246
|
-
*
|
|
247
|
-
*
|
|
301
|
+
* identity-read capture guard (server/context.ts guardIdentityRead). The
|
|
302
|
+
* foreground render never sets it, so the
|
|
248
303
|
* served response is byte-identical to axis 1. The capture descriptor itself
|
|
249
304
|
* (key/ttl/swr/tags/store) is NOT threaded through the request context — the
|
|
250
305
|
* integrated PPR serve path (rsc/shell-serve.ts + rsc-rendering.ts) builds it
|
|
@@ -264,6 +319,59 @@ export interface RequestContext<
|
|
|
264
319
|
*/
|
|
265
320
|
_shellCaptureLoaderRecords?: Map<string, Promise<unknown>>;
|
|
266
321
|
|
|
322
|
+
/**
|
|
323
|
+
* @internal Set on a capture's derived context when a loader pushed a
|
|
324
|
+
* value the record cannot keep (a deferred push, masked nested promises,
|
|
325
|
+
* or a push outside a loader body): the loader records then carry
|
|
326
|
+
* `runs: 1`, so a HIT still runs their bodies for those pushes.
|
|
327
|
+
*/
|
|
328
|
+
_shellCaptureUnrecordedLoaderPush?: true;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @internal The lane of each loader the capture's matched route registers
|
|
332
|
+
* (loader-cache.ts routeLoaderLanes), set by createMatchContextForFull
|
|
333
|
+
* before any loader runs. The capture's push funnel credits a push made
|
|
334
|
+
* under a live-lane loader's body, at any depth, to that loader
|
|
335
|
+
* (shell-capture.ts deriveShellCaptureContext). Own property of the
|
|
336
|
+
* capture's derived context only.
|
|
337
|
+
*/
|
|
338
|
+
_shellCaptureLoaderLanes?: ReadonlyMap<string, "live" | "bake">;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @internal Record keys resolved for this request (CacheScope
|
|
342
|
+
* resolveKeyFrom): by the scope's `key()` (one result) or its store's
|
|
343
|
+
* keyGenerator (one per default key). A shell capture's derived context
|
|
344
|
+
* reads it through the prototype chain, so the capture reuses the
|
|
345
|
+
* partition its request resolved instead of running `key()` again.
|
|
346
|
+
*/
|
|
347
|
+
_resolvedCacheKeys?: Map<unknown, Map<string, Promise<string>>>;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @internal The shell store key the PPR serve path resolved for this
|
|
351
|
+
* document request (rsc-rendering.ts shellServePlan), request partition
|
|
352
|
+
* included. Read by the testing helper serveShellRequest.
|
|
353
|
+
*/
|
|
354
|
+
_shellKey?: string;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @internal The request carried the forced-MISS reload marker
|
|
358
|
+
* (shell-serve.ts SHELL_MISS_PARAM). The handler strips the marker from the
|
|
359
|
+
* request on entry and sets this; the shell gate renders the request like a
|
|
360
|
+
* cache miss.
|
|
361
|
+
*/
|
|
362
|
+
_shellForcedMiss?: true;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @internal The freshness window of the route cache() records this
|
|
366
|
+
* request read or wrote (cache-scope.ts noteRouteRecordWindow), as ms
|
|
367
|
+
* timestamps: `freshUntil` is the record's `expiresAt`, `staleUntil` adds
|
|
368
|
+
* its scope's swr. The narrowest record wins. A shell capture caps its
|
|
369
|
+
* entry's ttl and ttl+swr to it (shell-capture.ts capShellWindow), so a
|
|
370
|
+
* shell is never fresher, or served longer, than the route cache() entry
|
|
371
|
+
* it replayed or wrote.
|
|
372
|
+
*/
|
|
373
|
+
_routeRecordWindow?: RouteRecordWindow;
|
|
374
|
+
|
|
267
375
|
/**
|
|
268
376
|
* @internal Loader-family snapshot seed for a shell HIT's tail render:
|
|
269
377
|
* segment-key -> the capture's elided container (already Flight-deserialized
|
|
@@ -271,8 +379,10 @@ export interface RequestContext<
|
|
|
271
379
|
* overlays it onto the fresh run's container (recorded paths pinned,
|
|
272
380
|
* hole-marker paths keep the fresh nested promises) so the payload's baked
|
|
273
381
|
* bytes match the frozen prelude; a hole-free entry resolves pin-first
|
|
274
|
-
*
|
|
275
|
-
* context
|
|
382
|
+
* and does not run the loader. Two owners: serveShellHit sets it on the
|
|
383
|
+
* HIT tail's derived context, and matchPartialWithPprReplay sets it on the
|
|
384
|
+
* request context of a PPR partial replay once its doc record hits, and
|
|
385
|
+
* restores the previous value after the match.
|
|
276
386
|
*/
|
|
277
387
|
_shellLoaderSeed?: Map<
|
|
278
388
|
string,
|
|
@@ -280,16 +390,17 @@ export interface RequestContext<
|
|
|
280
390
|
>;
|
|
281
391
|
|
|
282
392
|
/**
|
|
283
|
-
* @internal
|
|
284
|
-
*
|
|
393
|
+
* @internal The doc record marker: makes the next match treat the whole
|
|
394
|
+
* matched route as an implicit doc-level cache() boundary (see
|
|
285
395
|
* resolveShellImplicitCacheScope in cache/cache-scope.ts). Set ONLY on
|
|
286
396
|
* (a) the capture's derived context — with a record-only store so the
|
|
287
397
|
* capture's cacheRoute write lands in the snapshot, never the real store —
|
|
288
|
-
* (b) a HIT tail's seeded context, and (c) an eligible
|
|
289
|
-
* replay, where `store` is a request-local segment
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
398
|
+
* (b) a document HIT tail's seeded context (`docTail`), and (c) an eligible
|
|
399
|
+
* normal-route partial replay, where `store` is a request-local segment
|
|
400
|
+
* overlay.
|
|
401
|
+
* A document HIT tail replays the record even for a route with its own
|
|
402
|
+
* cache() config (the gate before the commit already evaluated that
|
|
403
|
+
* scope's opt-outs). Elsewhere the route's scope stays authoritative. On the
|
|
293
404
|
* navigation-replay serve path (`onExplicitHit` set) the marker COMPOSES with
|
|
294
405
|
* such a scope instead of being ignored: the seeded doc record supplies the match
|
|
295
406
|
* only when the explicit tier misses (withCacheLookup). cache(false) and a
|
|
@@ -306,12 +417,17 @@ export interface RequestContext<
|
|
|
306
417
|
* segment record even when the triggering request is partial.
|
|
307
418
|
*/
|
|
308
419
|
keyPrefix?: "doc";
|
|
309
|
-
/**
|
|
310
|
-
|
|
420
|
+
/**
|
|
421
|
+
* @internal Called only after the implicit cache hit decodes
|
|
422
|
+
* successfully; the lookup awaits it before returning the hit, so work
|
|
423
|
+
* it starts lands before the route's loaders resolve.
|
|
424
|
+
*/
|
|
425
|
+
onHit?: () => void | Promise<void>;
|
|
311
426
|
/**
|
|
312
427
|
* @internal Called when the seeded document record fails server-side
|
|
313
428
|
* deserialization. Partial replay uses it to replace the enclosing shell
|
|
314
|
-
* snapshot after serving the fresh fallback.
|
|
429
|
+
* snapshot after serving the fresh fallback. A document HIT tail does not
|
|
430
|
+
* set it: withCacheLookup throws ShellRecordUnavailableError instead.
|
|
315
431
|
*/
|
|
316
432
|
onCorrupt?: () => void;
|
|
317
433
|
/**
|
|
@@ -322,6 +438,23 @@ export interface RequestContext<
|
|
|
322
438
|
* require the exact consumable record instead of "any segment record".
|
|
323
439
|
*/
|
|
324
440
|
docKey?: string;
|
|
441
|
+
/**
|
|
442
|
+
* @internal Set on a document HIT tail (serveShellHit). The implicit doc
|
|
443
|
+
* scope replaces any route-derived cache() scope for the tail's match
|
|
444
|
+
* (resolveShellImplicitCacheScope), and a lookup that does not hit throws
|
|
445
|
+
* ShellRecordUnavailableError instead of resolving segments
|
|
446
|
+
* (withCacheLookup): a HIT never runs a handler. The record's
|
|
447
|
+
* loader-owned handle pushes restore as authoritative
|
|
448
|
+
* (HandleStore.pushRestored): the prelude rendered them.
|
|
449
|
+
*/
|
|
450
|
+
docTail?: true;
|
|
451
|
+
/**
|
|
452
|
+
* @internal The key a document HIT tail looks the doc record up by: the
|
|
453
|
+
* entry's own `docKey`. Recomputing it could name another key (a store
|
|
454
|
+
* keyGenerator folding request data, a build-time capture's synthetic
|
|
455
|
+
* host) and miss the record.
|
|
456
|
+
*/
|
|
457
|
+
fixedDocKey?: string;
|
|
325
458
|
/**
|
|
326
459
|
* @internal Set ONLY by matchPartialWithPprReplay on the navigation-replay
|
|
327
460
|
* serve path. Its presence arms the explicit-scope composition in
|
|
@@ -365,31 +498,17 @@ export interface RequestContext<
|
|
|
365
498
|
_shellFragmentPayload?: boolean;
|
|
366
499
|
|
|
367
500
|
/**
|
|
368
|
-
* @internal
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* executing during the capture (loader-resolution.ts — its consumption-lane
|
|
374
|
-
* value would freeze on a handler-free HIT). captureAndStoreShell folds it
|
|
375
|
-
* into ShellCacheEntry.handlerLiveHoles at the putShell barrier. Own
|
|
376
|
-
* property of the capture's derived context only.
|
|
377
|
-
*/
|
|
378
|
-
_shellCaptureHandleLiveness?: {
|
|
379
|
-
holes: boolean;
|
|
380
|
-
pendingPushes: number;
|
|
381
|
-
handlerInvokedLoader: boolean;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* @internal Set (to the offending fn name) by the cookies()/headers()
|
|
386
|
-
* capture guard when it throws DURING a capture render. Load-bearing for the
|
|
501
|
+
* @internal Set by the capture guard (server/context.ts
|
|
502
|
+
* guardIdentityRead) when it throws DURING a capture render: the read
|
|
503
|
+
* ("cookies()", "headers()", `ctx.get() for a non-cacheable variable`,
|
|
504
|
+
* "ctx.theme") and the fix the
|
|
505
|
+
* refusal warning gives for it. Load-bearing for the
|
|
387
506
|
* bake lane: a guard throw inside an executing loader is swallowed by
|
|
388
507
|
* wrapLoaderPromise into per-loader error UI, which would otherwise bake
|
|
389
508
|
* silently into the shared shell — the capture checks this flag after the
|
|
390
509
|
* render and refuses instead. Deterministic, so the capture does not retry.
|
|
391
510
|
*/
|
|
392
|
-
_shellCaptureGuardTripped?: string;
|
|
511
|
+
_shellCaptureGuardTripped?: { surface: string; fix: string };
|
|
393
512
|
|
|
394
513
|
/**
|
|
395
514
|
* @internal The loader $$id whose BODY was executing when the capture guard
|
|
@@ -420,6 +539,23 @@ export interface RequestContext<
|
|
|
420
539
|
*/
|
|
421
540
|
_requestTags: Set<string>;
|
|
422
541
|
|
|
542
|
+
/**
|
|
543
|
+
* @internal This request can write a route cache() record: its match
|
|
544
|
+
* resolved a cache scope (match-api.ts). Arms the record tag owners
|
|
545
|
+
* (cache-tag.ts armRecordTagOwners); off, the segment scopes, loader links
|
|
546
|
+
* and owned tag sets are skipped entirely. A capture's or background
|
|
547
|
+
* re-render's derived context (Object.create) inherits it.
|
|
548
|
+
*/
|
|
549
|
+
_recordTagOwners?: true;
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* @internal This request resolves a loader bound with its own cache()
|
|
553
|
+
* (match-api.ts, or the binding's funnel). Arms the per-execution loader
|
|
554
|
+
* tag sets (cache-tag.ts armLoaderTagSets); off, a loader execution
|
|
555
|
+
* allocates no set and a read links nothing. Derived contexts inherit it.
|
|
556
|
+
*/
|
|
557
|
+
_recordLoaderTags?: true;
|
|
558
|
+
|
|
423
559
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
424
560
|
_cacheProfiles?: Record<
|
|
425
561
|
string,
|
|
@@ -467,6 +603,11 @@ export interface RequestContext<
|
|
|
467
603
|
* Returns the theme value from the cookie, or the default theme if not set.
|
|
468
604
|
* This is the user's preference ("light", "dark", or "system"), not the resolved value.
|
|
469
605
|
*
|
|
606
|
+
* The visitor's cookie, so it is guarded like `cookies()`: reading it
|
|
607
|
+
* refuses a ppr shell capture and throws inside a `cache()` boundary or a
|
|
608
|
+
* `"use cache"` function (#971). A non-enumerable getter: a spread or
|
|
609
|
+
* `Object.assign` copy of the context does not carry it.
|
|
610
|
+
*
|
|
470
611
|
* @example
|
|
471
612
|
* ```typescript
|
|
472
613
|
* route("settings", (ctx) => {
|
|
@@ -475,7 +616,7 @@ export interface RequestContext<
|
|
|
475
616
|
* });
|
|
476
617
|
* ```
|
|
477
618
|
*/
|
|
478
|
-
theme?: Theme;
|
|
619
|
+
readonly theme?: Theme;
|
|
479
620
|
|
|
480
621
|
/**
|
|
481
622
|
* Set the theme (only available when theme is enabled in router config)
|
|
@@ -499,6 +640,30 @@ export interface RequestContext<
|
|
|
499
640
|
/** @internal Theme configuration (null if theme not enabled) */
|
|
500
641
|
_themeConfig?: ResolvedThemeConfig | null;
|
|
501
642
|
|
|
643
|
+
/**
|
|
644
|
+
* @internal The theme cookie (or the default) read with no identity guard.
|
|
645
|
+
* The guarded reads (`theme` here, the handler and middleware `ctx.theme`)
|
|
646
|
+
* call it after guardIdentityRead (readGuardedTheme); the router's own payload read goes
|
|
647
|
+
* through payloadInitialTheme (rsc/full-payload.ts), except the unmatched
|
|
648
|
+
* route's 404 (rsc/handler.ts), which is never stored. Undefined without
|
|
649
|
+
* theme config.
|
|
650
|
+
*/
|
|
651
|
+
_readTheme(): Theme | undefined;
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* @internal Set by the document-cache middleware (cache/document-cache.ts)
|
|
655
|
+
* before it runs a render whose response it may store: the MISS and the
|
|
656
|
+
* stale refresh. payloadInitialTheme reads it (#978).
|
|
657
|
+
*/
|
|
658
|
+
_documentCacheRender?: boolean;
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* @internal Set by payloadInitialTheme when the document payload carries
|
|
662
|
+
* the visitor's theme and it differs from the no-cookie default. The
|
|
663
|
+
* document cache does not store that response (#978).
|
|
664
|
+
*/
|
|
665
|
+
_payloadVisitorTheme?: boolean;
|
|
666
|
+
|
|
502
667
|
/**
|
|
503
668
|
* Attach location state entries to the current response.
|
|
504
669
|
*
|
|
@@ -542,20 +707,6 @@ export interface RequestContext<
|
|
|
542
707
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
543
708
|
_prevRouteKey?: string;
|
|
544
709
|
|
|
545
|
-
/**
|
|
546
|
-
* @internal Navigation/action source data the transition({ when }) gate reads
|
|
547
|
-
* to build its ShouldRevalidateFn-shaped predicate context. currentUrl/Params
|
|
548
|
-
* come from the navigation snapshot (set at match time); action* are stashed
|
|
549
|
-
* at the action-bearing gate call sites. All undefined when there is no source
|
|
550
|
-
* (initial full load) or no action (plain navigation).
|
|
551
|
-
*/
|
|
552
|
-
_gateCurrentUrl?: URL;
|
|
553
|
-
_gateCurrentParams?: Record<string, string>;
|
|
554
|
-
_gateActionId?: string;
|
|
555
|
-
_gateActionUrl?: URL;
|
|
556
|
-
_gateActionResult?: unknown;
|
|
557
|
-
_gateFormData?: FormData;
|
|
558
|
-
|
|
559
710
|
/**
|
|
560
711
|
* @internal True while the post-action revalidation render is running (set by
|
|
561
712
|
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
@@ -726,6 +877,21 @@ export interface RequestContext<
|
|
|
726
877
|
_cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
|
|
727
878
|
}
|
|
728
879
|
|
|
880
|
+
/**
|
|
881
|
+
* @internal The freshness window of a route cache() record, as ms
|
|
882
|
+
* timestamps: fresh until `freshUntil` (its `expiresAt`), stale until
|
|
883
|
+
* `staleUntil` (plus its scope's swr). `ttl`, `swr` and `written` describe
|
|
884
|
+
* the record that sets `staleUntil`: its cache() config, and whether this
|
|
885
|
+
* request wrote it (cacheRoute) rather than read it.
|
|
886
|
+
*/
|
|
887
|
+
export interface RouteRecordWindow {
|
|
888
|
+
freshUntil: number;
|
|
889
|
+
staleUntil: number;
|
|
890
|
+
ttl: number;
|
|
891
|
+
swr: number;
|
|
892
|
+
written: boolean;
|
|
893
|
+
}
|
|
894
|
+
|
|
729
895
|
/**
|
|
730
896
|
* Public view of RequestContext, without internal methods and fields.
|
|
731
897
|
*
|
|
@@ -739,11 +905,12 @@ export type PublicRequestContext<
|
|
|
739
905
|
RequestContext<TEnv, TParams>,
|
|
740
906
|
| "cookie"
|
|
741
907
|
| "cookies"
|
|
908
|
+
| "_readCookie"
|
|
909
|
+
| "_readCookies"
|
|
742
910
|
| "setCookie"
|
|
743
911
|
| "deleteCookie"
|
|
744
912
|
| "_handleStore"
|
|
745
|
-
| "
|
|
746
|
-
| "_pprTransitionDecisions"
|
|
913
|
+
| "_transitionWhenRefs"
|
|
747
914
|
| "_pprReplayPostMatchReason"
|
|
748
915
|
| "_cacheStore"
|
|
749
916
|
| "_searchParamsFilter"
|
|
@@ -751,25 +918,29 @@ export type PublicRequestContext<
|
|
|
751
918
|
| "_shellImplicitCache"
|
|
752
919
|
| "_shellLoaderSeed"
|
|
753
920
|
| "_shellCaptureLoaderRecords"
|
|
754
|
-
| "
|
|
921
|
+
| "_shellCaptureUnrecordedLoaderPush"
|
|
922
|
+
| "_shellCaptureLoaderLanes"
|
|
923
|
+
| "_resolvedCacheKeys"
|
|
924
|
+
| "_shellKey"
|
|
925
|
+
| "_shellForcedMiss"
|
|
926
|
+
| "_routeRecordWindow"
|
|
755
927
|
| "_shellFragmentPayload"
|
|
756
928
|
| "_shellCaptureGuardTripped"
|
|
757
929
|
| "_shellCaptureGuardTrippedLoaderId"
|
|
758
930
|
| "_explicitTaggedStores"
|
|
759
931
|
| "_requestTags"
|
|
932
|
+
| "_recordTagOwners"
|
|
933
|
+
| "_recordLoaderTags"
|
|
760
934
|
| "_cacheProfiles"
|
|
761
935
|
| "_onResponseCallbacks"
|
|
762
936
|
| "_pendingBackgroundTasks"
|
|
763
937
|
| "_themeConfig"
|
|
938
|
+
| "_readTheme"
|
|
939
|
+
| "_documentCacheRender"
|
|
940
|
+
| "_payloadVisitorTheme"
|
|
764
941
|
| "_locationState"
|
|
765
942
|
| "_routeName"
|
|
766
943
|
| "_prevRouteKey"
|
|
767
|
-
| "_gateCurrentUrl"
|
|
768
|
-
| "_gateCurrentParams"
|
|
769
|
-
| "_gateActionId"
|
|
770
|
-
| "_gateActionUrl"
|
|
771
|
-
| "_gateActionResult"
|
|
772
|
-
| "_gateFormData"
|
|
773
944
|
| "_inActionRevalidation"
|
|
774
945
|
| "_reportedErrors"
|
|
775
946
|
| "_renderErrors"
|
|
@@ -795,6 +966,11 @@ export type PublicRequestContext<
|
|
|
795
966
|
| "_setStatus"
|
|
796
967
|
| "_rotateStateCookie"
|
|
797
968
|
| "_setKeepCacheDirective"
|
|
969
|
+
| "_freshReads"
|
|
970
|
+
| "_setFreshReadsCookie"
|
|
971
|
+
| "_requestRoot"
|
|
972
|
+
| "_requestStart"
|
|
973
|
+
| "_responseSent"
|
|
798
974
|
| "_variables"
|
|
799
975
|
| "_classifiedRoute"
|
|
800
976
|
| "_requestMode"
|
|
@@ -897,17 +1073,10 @@ export function setRequestContextParams(
|
|
|
897
1073
|
*/
|
|
898
1074
|
export function setRequestContextPrevRouteKey(
|
|
899
1075
|
prevRouteKey: string | undefined,
|
|
900
|
-
currentUrl?: URL,
|
|
901
|
-
currentParams?: Record<string, string>,
|
|
902
1076
|
): void {
|
|
903
1077
|
const ctx = requestContextStorage.getStore();
|
|
904
1078
|
if (!ctx) return;
|
|
905
1079
|
if (prevRouteKey !== undefined) ctx._prevRouteKey = prevRouteKey;
|
|
906
|
-
// Source URL/params for the transition({ when }) gate (effectiveFromUrl /
|
|
907
|
-
// effectiveFromMatch.params from the navigation snapshot). Same write point as
|
|
908
|
-
// _prevRouteKey, which doubles as fromRouteName.
|
|
909
|
-
if (currentUrl !== undefined) ctx._gateCurrentUrl = currentUrl;
|
|
910
|
-
if (currentParams !== undefined) ctx._gateCurrentParams = currentParams;
|
|
911
1080
|
}
|
|
912
1081
|
|
|
913
1082
|
/**
|
|
@@ -989,8 +1158,15 @@ export function createRequestContext<TEnv>(
|
|
|
989
1158
|
stateCookieName,
|
|
990
1159
|
version: stateVersion,
|
|
991
1160
|
} = options;
|
|
992
|
-
|
|
1161
|
+
// ctx.request.headers is guarded like headers() (#976); every router read
|
|
1162
|
+
// of this request's headers goes through requestHeaders().
|
|
1163
|
+
guardRequestHeaders(request);
|
|
1164
|
+
const cookieHeader = requestHeaders(request).get("Cookie");
|
|
993
1165
|
let rangoStateRotated = false;
|
|
1166
|
+
const freshCookie = stateCookieName
|
|
1167
|
+
? freshReadsCookieName(stateCookieName)
|
|
1168
|
+
: undefined;
|
|
1169
|
+
let freshCookieMaxAge = 0;
|
|
994
1170
|
let parsedCookies: Record<string, string> | null = null;
|
|
995
1171
|
|
|
996
1172
|
let stubResponse = initialResponse
|
|
@@ -1084,6 +1260,26 @@ export function createRequestContext<TEnv>(
|
|
|
1084
1260
|
return getParsedCookies()[name];
|
|
1085
1261
|
};
|
|
1086
1262
|
|
|
1263
|
+
// The effective jar: request cookies minus deletions plus same-request writes.
|
|
1264
|
+
const effectiveCookies = (): Record<string, string> => {
|
|
1265
|
+
const parsed = getParsedCookies();
|
|
1266
|
+
const mutations = getResponseCookies();
|
|
1267
|
+
if (mutations.size === 0) return { ...parsed };
|
|
1268
|
+
// Build result without delete (avoids V8 dictionary-mode de-opt)
|
|
1269
|
+
const deleted = new Set<string>();
|
|
1270
|
+
for (const [k, v] of mutations) {
|
|
1271
|
+
if (v === null) deleted.add(k);
|
|
1272
|
+
}
|
|
1273
|
+
const result: Record<string, string> = {};
|
|
1274
|
+
for (const key of Object.keys(parsed)) {
|
|
1275
|
+
if (!deleted.has(key)) result[key] = parsed[key];
|
|
1276
|
+
}
|
|
1277
|
+
for (const [k, v] of mutations) {
|
|
1278
|
+
if (v !== null) result[k] = v;
|
|
1279
|
+
}
|
|
1280
|
+
return result;
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1087
1283
|
const getTheme = (): Theme | undefined => {
|
|
1088
1284
|
if (!themeConfig) return undefined;
|
|
1089
1285
|
|
|
@@ -1144,7 +1340,7 @@ export function createRequestContext<TEnv>(
|
|
|
1144
1340
|
_dynamic: false,
|
|
1145
1341
|
get: ((keyOrVar: any) => {
|
|
1146
1342
|
if (isNonCacheable(variables, keyOrVar)) {
|
|
1147
|
-
assertNonCacheableReadAllowed(keyOrVar);
|
|
1343
|
+
assertNonCacheableReadAllowed(keyOrVar, _getRequestContext());
|
|
1148
1344
|
}
|
|
1149
1345
|
return contextGet(variables, keyOrVar);
|
|
1150
1346
|
}) as RequestContext<TEnv>["get"],
|
|
@@ -1163,29 +1359,23 @@ export function createRequestContext<TEnv>(
|
|
|
1163
1359
|
);
|
|
1164
1360
|
},
|
|
1165
1361
|
|
|
1362
|
+
// The visitor's cookies, guarded like cookies() (#976): before, a plain
|
|
1363
|
+
// read that a ppr shell, a cache() entry or a "use cache" entry stored
|
|
1364
|
+
// for every visitor.
|
|
1166
1365
|
cookie(name: string): string | undefined {
|
|
1366
|
+
guardRawCookieRead(ctx, "getRequestContext().cookie()");
|
|
1167
1367
|
return effectiveCookie(name);
|
|
1168
1368
|
},
|
|
1169
1369
|
|
|
1170
1370
|
cookies(): Record<string, string> {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
if (mutations.size === 0) return { ...parsed };
|
|
1174
|
-
// Build result without delete (avoids V8 dictionary-mode de-opt)
|
|
1175
|
-
const deleted = new Set<string>();
|
|
1176
|
-
for (const [k, v] of mutations) {
|
|
1177
|
-
if (v === null) deleted.add(k);
|
|
1178
|
-
}
|
|
1179
|
-
const result: Record<string, string> = {};
|
|
1180
|
-
for (const key of Object.keys(parsed)) {
|
|
1181
|
-
if (!deleted.has(key)) result[key] = parsed[key];
|
|
1182
|
-
}
|
|
1183
|
-
for (const [k, v] of mutations) {
|
|
1184
|
-
if (v !== null) result[k] = v;
|
|
1185
|
-
}
|
|
1186
|
-
return result;
|
|
1371
|
+
guardRawCookieRead(ctx, "getRequestContext().cookies()");
|
|
1372
|
+
return effectiveCookies();
|
|
1187
1373
|
},
|
|
1188
1374
|
|
|
1375
|
+
// Non-enumerable, defined after this literal.
|
|
1376
|
+
_readCookie: effectiveCookie,
|
|
1377
|
+
_readCookies: effectiveCookies,
|
|
1378
|
+
|
|
1189
1379
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
1190
1380
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
1191
1381
|
stubResponse.headers.append(
|
|
@@ -1234,7 +1424,7 @@ export function createRequestContext<TEnv>(
|
|
|
1234
1424
|
// decodeStateValue decodes exactly once) AND is the same parser the client
|
|
1235
1425
|
// mirror uses, so both seats read the same jar entry.
|
|
1236
1426
|
const prevRaw =
|
|
1237
|
-
(request.
|
|
1427
|
+
(requestHeaders(request).get("x-rango-state") || null) ??
|
|
1238
1428
|
getRawCookieValue(cookieHeader, stateCookieName);
|
|
1239
1429
|
const value = mintStateValue(stateVersion ?? "0", prevRaw);
|
|
1240
1430
|
// rawStubHeaders: guard-exempt internal writer — invalidateClientCache()
|
|
@@ -1254,6 +1444,48 @@ export function createRequestContext<TEnv>(
|
|
|
1254
1444
|
rawStubHeaders.set(KEEP_CACHE_HEADER, "1");
|
|
1255
1445
|
},
|
|
1256
1446
|
|
|
1447
|
+
// The fresh-reads cookie (browser/cookie-name.ts): one Set-Cookie per
|
|
1448
|
+
// response, the longest Max-Age any invalidation asked for. HttpOnly (the
|
|
1449
|
+
// client never reads it), SameSite=Lax, Path=/. rawStubHeaders: an
|
|
1450
|
+
// internal writer, allowed wherever updateTag()/revalidateTag() are. A
|
|
1451
|
+
// shared cache never stores it (Set-Cookie is a per-client signal).
|
|
1452
|
+
_setFreshReadsCookie(maxAgeMs: number): void {
|
|
1453
|
+
if (!freshCookie) return;
|
|
1454
|
+
const maxAge = Math.ceil(maxAgeMs / 1000);
|
|
1455
|
+
// Already set for at least this long (an earlier invalidation in this
|
|
1456
|
+
// request): nothing to add, sent or not.
|
|
1457
|
+
if (maxAge <= freshCookieMaxAge) return;
|
|
1458
|
+
if (ctx._responseSent) {
|
|
1459
|
+
// A streaming loader or render: the headers already left. The
|
|
1460
|
+
// invalidation itself still ran; only the mutating user's bypass of
|
|
1461
|
+
// the stores' isolate memos is lost.
|
|
1462
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1463
|
+
console.warn(
|
|
1464
|
+
`[rango] updateTag()/revalidateTag() ran after the response ` +
|
|
1465
|
+
`headers were sent (from a streaming loader or render), so the ` +
|
|
1466
|
+
`${freshCookie} cookie was not set: this user's next requests ` +
|
|
1467
|
+
`can be served from a store's isolate memo for a few seconds. ` +
|
|
1468
|
+
`Invalidate in a server action, a route handler, or middleware.`,
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const serialized = (age: number) =>
|
|
1474
|
+
`${freshCookie}=1; Max-Age=${age}; Path=/; HttpOnly; SameSite=Lax` +
|
|
1475
|
+
(url.protocol === "https:" ? "; Secure" : "");
|
|
1476
|
+
if (freshCookieMaxAge > 0) {
|
|
1477
|
+
const prior = serialized(freshCookieMaxAge);
|
|
1478
|
+
const kept = rawStubHeaders
|
|
1479
|
+
.getSetCookie()
|
|
1480
|
+
.filter((cookie) => cookie !== prior);
|
|
1481
|
+
rawStubHeaders.delete("Set-Cookie");
|
|
1482
|
+
for (const cookie of kept) rawStubHeaders.append("Set-Cookie", cookie);
|
|
1483
|
+
}
|
|
1484
|
+
freshCookieMaxAge = maxAge;
|
|
1485
|
+
rawStubHeaders.append("Set-Cookie", serialized(maxAge));
|
|
1486
|
+
invalidateResponseCookieCache();
|
|
1487
|
+
},
|
|
1488
|
+
|
|
1257
1489
|
setStatus(status: number): void {
|
|
1258
1490
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
1259
1491
|
assertResponseWriteAllowed("setStatus");
|
|
@@ -1269,12 +1501,14 @@ export function createRequestContext<TEnv>(
|
|
|
1269
1501
|
method: request.method,
|
|
1270
1502
|
|
|
1271
1503
|
_handleStore: handleStore,
|
|
1272
|
-
_transitionWhen: [],
|
|
1273
1504
|
_cacheStore: cacheStore,
|
|
1274
1505
|
_searchParamsFilter: searchParamsFilter,
|
|
1275
1506
|
_explicitTaggedStores: explicitTaggedStores,
|
|
1276
1507
|
_requestTags: new Set<string>(),
|
|
1277
1508
|
_cacheProfiles: cacheProfiles,
|
|
1509
|
+
_freshReads:
|
|
1510
|
+
freshCookie !== undefined &&
|
|
1511
|
+
getRawCookieValue(cookieHeader, freshCookie) !== null,
|
|
1278
1512
|
|
|
1279
1513
|
waitUntil(fn: () => Promise<void>): void {
|
|
1280
1514
|
if (ctx.build) return;
|
|
@@ -1316,9 +1550,8 @@ export function createRequestContext<TEnv>(
|
|
|
1316
1550
|
this._onResponseCallbacks.push(callback);
|
|
1317
1551
|
},
|
|
1318
1552
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
},
|
|
1553
|
+
// theme: a guarded, non-enumerable getter defined after this literal.
|
|
1554
|
+
_readTheme: getTheme,
|
|
1322
1555
|
setTheme: themeConfig
|
|
1323
1556
|
? (theme: Theme) => {
|
|
1324
1557
|
assertNotInsideCacheExec(ctx, "setTheme");
|
|
@@ -1359,7 +1592,38 @@ export function createRequestContext<TEnv>(
|
|
|
1359
1592
|
getContext: () => ctx,
|
|
1360
1593
|
});
|
|
1361
1594
|
|
|
1595
|
+
// getRequestContext().theme is the visitor's cookie, guarded like cookies()
|
|
1596
|
+
// (#971); before it was a plain read that a ppr shell, a cache() entry or a
|
|
1597
|
+
// "use cache" entry stored for every visitor. Non-enumerable like the
|
|
1598
|
+
// handler ctx.theme (handler-context.ts THEME_DESCRIPTOR): serializing the
|
|
1599
|
+
// context (React dev debug info) must not count as a read. The guard reads
|
|
1600
|
+
// the ambient context, as cookies() does, and falls back to the receiver.
|
|
1601
|
+
// _readTheme is the unguarded read: non-enumerable too, so a spread copy of
|
|
1602
|
+
// the context (the fetchable-loader ctx, loader-fetch.ts) cannot carry it.
|
|
1603
|
+
// The same holds for the unguarded cookie reads (#976).
|
|
1604
|
+
Object.defineProperties(ctx, {
|
|
1605
|
+
theme: {
|
|
1606
|
+
get(this: RequestContext<TEnv>): Theme | undefined {
|
|
1607
|
+
return readGuardedTheme(this, "getRequestContext().theme");
|
|
1608
|
+
},
|
|
1609
|
+
enumerable: false,
|
|
1610
|
+
configurable: true,
|
|
1611
|
+
},
|
|
1612
|
+
_readTheme: { enumerable: false },
|
|
1613
|
+
_readCookie: { enumerable: false },
|
|
1614
|
+
_readCookies: { enumerable: false },
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1362
1617
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
1618
|
+
// Non-enumerable: a self-reference would make the context circular for
|
|
1619
|
+
// JSON.stringify, spreads and React's dev debug-info serialization of props.
|
|
1620
|
+
Object.defineProperty(ctx, "_requestRoot", {
|
|
1621
|
+
value: ctx,
|
|
1622
|
+
enumerable: false,
|
|
1623
|
+
configurable: true,
|
|
1624
|
+
writable: true,
|
|
1625
|
+
});
|
|
1626
|
+
ctx._requestStart = executionStart();
|
|
1363
1627
|
return ctx;
|
|
1364
1628
|
}
|
|
1365
1629
|
|