@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
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Runs in the RSC realm of the build's temp server, AFTER all bundles are
|
|
5
5
|
* written (the prelude embeds built client asset URLs — bootstrap module,
|
|
6
6
|
* chunk preloads — that only exist post-client-build). The capture core is
|
|
7
|
-
* producer A's, verbatim: deriveShellCaptureContext (
|
|
8
|
-
*
|
|
9
|
-
* quiesce, tags union, putShell barrier). Build capture first replays global
|
|
7
|
+
* producer A's, verbatim: deriveShellCaptureContext (push funnel, snapshot
|
|
8
|
+
* recording, implicit doc-cache scope) + settleCaptureRecord (the record-first
|
|
9
|
+
* step) + captureAndStoreShell (gates, quiesce, tags union, putShell barrier). Build capture first replays global
|
|
10
10
|
* and route middleware with a synthetic build request context
|
|
11
11
|
* (`ctx.build === true`, inert `ctx.waitUntil()`); middleware can seed vars or
|
|
12
12
|
* call `ctx.dynamic()` to skip this URL. The sink is an entry collector instead
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import type { ShellCacheEntry } from "../cache/types.js";
|
|
24
|
+
import type { MatchResult } from "../types.js";
|
|
24
25
|
import { MemorySegmentCacheStore } from "../cache/memory-segment-store.js";
|
|
25
26
|
import {
|
|
26
27
|
createRequestContext,
|
|
@@ -31,10 +32,13 @@ import {
|
|
|
31
32
|
import {
|
|
32
33
|
deriveShellCaptureContext,
|
|
33
34
|
captureAndStoreShell,
|
|
35
|
+
settleCaptureRecord,
|
|
34
36
|
delay,
|
|
35
37
|
SHELL_CAPTURE_RETRY_DELAY_MS,
|
|
36
38
|
type ShellCaptureDescriptor,
|
|
37
39
|
} from "../rsc/shell-capture.js";
|
|
40
|
+
import { SHELL_CAPTURE_MAX_WAIT_MS } from "../rsc/shell-capture-constants.js";
|
|
41
|
+
import { raceDeadline } from "../cache/background-task.js";
|
|
38
42
|
import { buildFullPayload } from "../rsc/full-payload.js";
|
|
39
43
|
import { buildRouteMiddlewareEntries } from "../rsc/helpers.js";
|
|
40
44
|
import type { RscPayload, SSRModule } from "../rsc/types.js";
|
|
@@ -134,18 +138,25 @@ export interface BuildShellCaptureResult {
|
|
|
134
138
|
tags?: string[];
|
|
135
139
|
/** On route-mismatch: what this router's match actually landed on. */
|
|
136
140
|
matchedRouteName?: string;
|
|
141
|
+
/**
|
|
142
|
+
* On no-shell: the attempt ran out of ppr.captureTimeout. It is not
|
|
143
|
+
* retried: its match may still be running, and the retry would start the
|
|
144
|
+
* same work beside it (runtime twin: shell-capture.ts runShellCapture).
|
|
145
|
+
*/
|
|
146
|
+
timedOut?: true;
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
/**
|
|
140
150
|
* Capture the PPR shell for one prerendered URL at build time. Retries once
|
|
141
151
|
* in place on `no-shell` (the first attempt warms the temp server's SSR/Flight
|
|
142
|
-
* transform graph, mirroring producer A's cold-start retry — same delay)
|
|
152
|
+
* transform graph, mirroring producer A's cold-start retry — same delay),
|
|
153
|
+
* unless the attempt ran out of ppr.captureTimeout (`timedOut`).
|
|
143
154
|
*/
|
|
144
155
|
export async function captureShellForBuild(
|
|
145
156
|
opts: BuildShellCaptureOptions,
|
|
146
157
|
): Promise<BuildShellCaptureResult> {
|
|
147
158
|
const first = await attemptBuildCapture(opts);
|
|
148
|
-
if (first.outcome !== "no-shell") return first;
|
|
159
|
+
if (first.outcome !== "no-shell" || first.timedOut) return first;
|
|
149
160
|
if (opts.debug) {
|
|
150
161
|
console.log(
|
|
151
162
|
`[rango] shell capture attempt 1/2 for ${opts.urlPath} produced no shell (cold graph?) — retrying`,
|
|
@@ -210,78 +221,81 @@ async function attemptBuildCapture(
|
|
|
210
221
|
tags: opts.tags,
|
|
211
222
|
captureTimeout: opts.captureTimeout,
|
|
212
223
|
store: collector as any,
|
|
213
|
-
debug: opts.debug,
|
|
214
224
|
maxSnapshotBytes: opts.maxSnapshotBytes,
|
|
215
225
|
};
|
|
216
226
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
const result = await runWithRequestContext(
|
|
228
|
+
baseCtx,
|
|
229
|
+
async (): Promise<
|
|
230
|
+
| BuildCaptureRunResult
|
|
231
|
+
| { outcome: "route-mismatch"; matchedRouteName?: string }
|
|
232
|
+
> => {
|
|
233
|
+
const preview =
|
|
234
|
+
typeof router.previewMatch === "function"
|
|
235
|
+
? await router.previewMatch(request, { env })
|
|
236
|
+
: undefined;
|
|
237
|
+
// These preview-based mismatches exit before any middleware/envelope runs,
|
|
238
|
+
// so nothing consumes a response.
|
|
239
|
+
if (preview === null) {
|
|
240
|
+
return { outcome: "route-mismatch" };
|
|
241
|
+
}
|
|
242
|
+
if (preview?.routeKey && preview.routeKey !== opts.routeName) {
|
|
243
|
+
return {
|
|
244
|
+
outcome: "route-mismatch",
|
|
245
|
+
matchedRouteName: preview.routeKey,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
232
248
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
249
|
+
if (preview?.routeKey) {
|
|
250
|
+
setRequestContextParams(preview.params ?? {}, preview.routeKey);
|
|
251
|
+
}
|
|
236
252
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
253
|
+
// Global-only, like the live request's middleware reverse
|
|
254
|
+
// (rsc/handler.ts: createReverseFunction(getRequiredRouteMap())): no
|
|
255
|
+
// include() scope, no param auto-fill. A scoped reverse here resolved
|
|
256
|
+
// `.name` at build while the live request threw `Unknown route`.
|
|
257
|
+
const middlewareReverse = createReverseFunction(getGlobalRouteMap());
|
|
242
258
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
mismatchedRouteName = routeName;
|
|
254
|
-
},
|
|
255
|
-
});
|
|
259
|
+
const runCapture = () =>
|
|
260
|
+
runBuildCaptureFinal({
|
|
261
|
+
baseCtx,
|
|
262
|
+
descriptor,
|
|
263
|
+
env,
|
|
264
|
+
opts,
|
|
265
|
+
request,
|
|
266
|
+
router,
|
|
267
|
+
url,
|
|
268
|
+
});
|
|
256
269
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
270
|
+
const routeMiddleware =
|
|
271
|
+
preview?.routeMiddleware && preview.routeMiddleware.length > 0
|
|
272
|
+
? buildRouteMiddlewareEntries(preview.routeMiddleware)
|
|
273
|
+
: [];
|
|
274
|
+
const runRouteMiddleware = () =>
|
|
275
|
+
runBuildMiddlewareEnvelope(
|
|
276
|
+
routeMiddleware,
|
|
277
|
+
request,
|
|
278
|
+
env,
|
|
279
|
+
variables,
|
|
280
|
+
runCapture,
|
|
281
|
+
middlewareReverse,
|
|
282
|
+
baseCtx,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const globalMiddleware = Array.isArray(router.middleware)
|
|
286
|
+
? matchMiddleware(url.pathname, router.middleware)
|
|
260
287
|
: [];
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
routeMiddleware,
|
|
288
|
+
return runBuildMiddlewareEnvelope(
|
|
289
|
+
globalMiddleware,
|
|
264
290
|
request,
|
|
265
291
|
env,
|
|
266
292
|
variables,
|
|
267
|
-
|
|
293
|
+
runRouteMiddleware,
|
|
268
294
|
middlewareReverse,
|
|
269
295
|
baseCtx,
|
|
270
296
|
);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
? matchMiddleware(url.pathname, router.middleware)
|
|
274
|
-
: [];
|
|
275
|
-
return runBuildMiddlewareEnvelope(
|
|
276
|
-
globalMiddleware,
|
|
277
|
-
request,
|
|
278
|
-
env,
|
|
279
|
-
variables,
|
|
280
|
-
runRouteMiddleware,
|
|
281
|
-
middlewareReverse,
|
|
282
|
-
baseCtx,
|
|
283
|
-
);
|
|
284
|
-
});
|
|
297
|
+
},
|
|
298
|
+
);
|
|
285
299
|
|
|
286
300
|
const outcome = result.outcome;
|
|
287
301
|
if (outcome === "stored" && collected !== null) {
|
|
@@ -289,7 +303,10 @@ async function attemptBuildCapture(
|
|
|
289
303
|
return { outcome, entry: hit.entry, tags: hit.tags };
|
|
290
304
|
}
|
|
291
305
|
if (outcome === "route-mismatch") {
|
|
292
|
-
return { outcome, matchedRouteName:
|
|
306
|
+
return { outcome, matchedRouteName: result.matchedRouteName };
|
|
307
|
+
}
|
|
308
|
+
if (outcome === "no-shell" && "timedOut" in result && result.timedOut) {
|
|
309
|
+
return { outcome, timedOut: true };
|
|
293
310
|
}
|
|
294
311
|
return { outcome };
|
|
295
312
|
}
|
|
@@ -299,6 +316,10 @@ type BuildShellCaptureOutcome = BuildShellCaptureResult["outcome"];
|
|
|
299
316
|
interface BuildCaptureRunResult {
|
|
300
317
|
outcome: BuildShellCaptureOutcome;
|
|
301
318
|
response: Response;
|
|
319
|
+
/** On route-mismatch: what this router's match landed on. */
|
|
320
|
+
matchedRouteName?: string;
|
|
321
|
+
/** On no-shell: the attempt ran out of ppr.captureTimeout. */
|
|
322
|
+
timedOut?: true;
|
|
302
323
|
}
|
|
303
324
|
|
|
304
325
|
interface BuildCaptureFinalOptions {
|
|
@@ -309,7 +330,6 @@ interface BuildCaptureFinalOptions {
|
|
|
309
330
|
request: Request;
|
|
310
331
|
router: any;
|
|
311
332
|
url: URL;
|
|
312
|
-
setMismatchedRouteName(routeName: string | undefined): void;
|
|
313
333
|
}
|
|
314
334
|
|
|
315
335
|
async function runBuildMiddlewareEnvelope<TEnv>(
|
|
@@ -369,61 +389,104 @@ async function runBuildCaptureFinal(
|
|
|
369
389
|
// invoking this when baseCtx._dynamic is set. A loader/handler opting out
|
|
370
390
|
// DURING the capture render is caught by the derivedCtx._dynamic check below.
|
|
371
391
|
|
|
372
|
-
const
|
|
392
|
+
const derivation = deriveShellCaptureContext(baseCtx, {
|
|
373
393
|
ttl: opts.ttl,
|
|
374
394
|
swr: opts.swr,
|
|
375
395
|
});
|
|
396
|
+
const { derivedCtx, freshHandleStore } = derivation;
|
|
397
|
+
// The capture generation starts before the match, as at runtime
|
|
398
|
+
// (ShellCacheEntry.createdAt; the build-shell read-through compares tag
|
|
399
|
+
// invalidations against it).
|
|
400
|
+
const captureStartedAt = Date.now();
|
|
376
401
|
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
402
|
+
const result = await runWithRequestContext(
|
|
403
|
+
derivedCtx,
|
|
404
|
+
async (): Promise<Omit<BuildCaptureRunResult, "response">> => {
|
|
405
|
+
// Same one deadline as the runtime capture: the match, the record-first
|
|
406
|
+
// step, then the prerender with what is left.
|
|
407
|
+
const deadline =
|
|
408
|
+
Date.now() + (opts.captureTimeout ?? SHELL_CAPTURE_MAX_WAIT_MS);
|
|
409
|
+
let match: MatchResult;
|
|
410
|
+
try {
|
|
411
|
+
const matched = await raceDeadline<MatchResult>(
|
|
412
|
+
router.match(request, { env }),
|
|
413
|
+
deadline,
|
|
414
|
+
);
|
|
415
|
+
if (!matched.done) return { outcome: "no-shell", timedOut: true };
|
|
416
|
+
match = matched.value;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (isPlainPathMiss(error, opts.urlPath)) {
|
|
419
|
+
return { outcome: "route-mismatch" };
|
|
420
|
+
}
|
|
421
|
+
throw error;
|
|
384
422
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return "route-mismatch" as const;
|
|
390
|
-
}
|
|
391
|
-
if (match.redirect) return "redirect" as const;
|
|
423
|
+
if (match.routeName !== opts.routeName) {
|
|
424
|
+
return { outcome: "route-mismatch", matchedRouteName: match.routeName };
|
|
425
|
+
}
|
|
426
|
+
if (match.redirect) return { outcome: "redirect" };
|
|
392
427
|
|
|
393
|
-
|
|
428
|
+
setRequestContextParams(match.params, match.routeName);
|
|
394
429
|
|
|
395
|
-
|
|
396
|
-
match,
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
});
|
|
430
|
+
// Same record-first step as the runtime capture. A prerendered URL's
|
|
431
|
+
// match comes from the prerender store (no handler runs, no doc record),
|
|
432
|
+
// which settleCaptureRecord reports as `prerender`.
|
|
433
|
+
const settled = await settleCaptureRecord(
|
|
434
|
+
match,
|
|
435
|
+
derivation,
|
|
436
|
+
descriptor,
|
|
437
|
+
deadline,
|
|
438
|
+
);
|
|
439
|
+
if (settled.kind === "refused") {
|
|
440
|
+
return { outcome: derivedCtx._dynamic ? "dynamic" : "refused" };
|
|
441
|
+
}
|
|
442
|
+
if (settled.kind === "timeout") {
|
|
443
|
+
return { outcome: "no-shell", timedOut: true };
|
|
444
|
+
}
|
|
413
445
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
446
|
+
const payload = buildFullPayload(
|
|
447
|
+
settled.match,
|
|
448
|
+
// buildFullPayload reads only ctx.router.* and ctx.version.
|
|
449
|
+
{
|
|
450
|
+
router,
|
|
451
|
+
version: opts.buildVersion,
|
|
452
|
+
} as unknown as HandlerContext<any>,
|
|
453
|
+
url,
|
|
454
|
+
derivedCtx,
|
|
455
|
+
freshHandleStore,
|
|
456
|
+
);
|
|
457
|
+
const rscStream = renderToReadableStream<RscPayload>(payload, {
|
|
458
|
+
onError: (error: unknown) => {
|
|
459
|
+
if (opts.debug) {
|
|
460
|
+
console.warn(
|
|
461
|
+
`[rango] shell capture render error for ${opts.urlPath}:`,
|
|
462
|
+
error,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const captureOutcome = await captureAndStoreShell(
|
|
469
|
+
{ captureShellHTML: opts.captureShellHTML } as SSRModule,
|
|
470
|
+
rscStream,
|
|
471
|
+
derivedCtx,
|
|
472
|
+
{ ...descriptor, captureTimeout: Math.max(1, deadline - Date.now()) },
|
|
473
|
+
captureStartedAt,
|
|
474
|
+
);
|
|
475
|
+
if (derivedCtx._dynamic) return { outcome: "dynamic" };
|
|
476
|
+
if (captureOutcome === "no-shell" && Date.now() >= deadline) {
|
|
477
|
+
return { outcome: "no-shell", timedOut: true };
|
|
478
|
+
}
|
|
479
|
+
// A route cache() record that ran out mid-capture: the in-place retry
|
|
480
|
+
// (captureShellForBuild) reads or renders a newer one.
|
|
481
|
+
return {
|
|
482
|
+
outcome: captureOutcome === "expired" ? "no-shell" : captureOutcome,
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
);
|
|
423
486
|
|
|
424
487
|
return {
|
|
425
|
-
|
|
426
|
-
response: responseForBuildCaptureOutcome(outcome),
|
|
488
|
+
...result,
|
|
489
|
+
response: responseForBuildCaptureOutcome(result.outcome),
|
|
427
490
|
};
|
|
428
491
|
}
|
|
429
492
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { assertTransitionWhenShape } from "../transition-when-ref.js";
|
|
2
3
|
import type {
|
|
3
4
|
PartialCacheOptions,
|
|
4
5
|
Handler,
|
|
@@ -909,12 +910,13 @@ const loader: RouteHelpers<any, any>["loader"] = (
|
|
|
909
910
|
// resolved here, at DSL-evaluation time, for the same reason
|
|
910
911
|
// loading({ ssr: false }) is (below) — per-isSSR entry caching makes the
|
|
911
912
|
// flag request-mode-correct with no isSSR threading; see LoaderEntry.
|
|
913
|
+
// `bake` is the lane, the same on both evaluations.
|
|
912
914
|
const loaderEntry: LoaderEntry = {
|
|
913
915
|
loader: loaderDef,
|
|
914
916
|
revalidate: [] as ShouldRevalidateFn<any, any>[],
|
|
915
|
-
...(optionsGiven?.ssr === false &&
|
|
916
|
-
|
|
917
|
-
:
|
|
917
|
+
...(optionsGiven?.ssr === false && { bake: true as const }),
|
|
918
|
+
...(optionsGiven?.ssr === false &&
|
|
919
|
+
ctx.isSSR && { awaitBeforeFlush: true as const }),
|
|
918
920
|
};
|
|
919
921
|
|
|
920
922
|
// Merge handler.use defaults (attached to the loader definition) with explicit use
|
|
@@ -1006,6 +1008,7 @@ const transition = (
|
|
|
1006
1008
|
const { store, ctx } = requireDslContext(
|
|
1007
1009
|
"transition() must be called inside urls()",
|
|
1008
1010
|
);
|
|
1011
|
+
assertTransitionWhenShape(config.when);
|
|
1009
1012
|
|
|
1010
1013
|
// Allocate a single index for this transition() call (used in all paths),
|
|
1011
1014
|
// mirroring cache() — the child form uses it for the name, the wrapper form
|
|
@@ -188,7 +188,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
188
188
|
*
|
|
189
189
|
* // Conditional activation via the config object's `when` selector
|
|
190
190
|
* intercept("@modal", "card", <CardModal />, {
|
|
191
|
-
* when: ({ from }) => from.pathname.startsWith("/board"),
|
|
191
|
+
* when: ({ from }) => from.url.pathname.startsWith("/board"),
|
|
192
192
|
* })
|
|
193
193
|
*
|
|
194
194
|
* // Config + other use-items: config is arg 4, use is arg 5
|
|
@@ -196,7 +196,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
196
196
|
* "@modal",
|
|
197
197
|
* "card",
|
|
198
198
|
* <CardModal />,
|
|
199
|
-
* { when: ({ from }) => from.pathname.startsWith("/board") },
|
|
199
|
+
* { when: ({ from }) => from.url.pathname.startsWith("/board") },
|
|
200
200
|
* () => [loader(CardDetailLoader)],
|
|
201
201
|
* )
|
|
202
202
|
* ```
|
|
@@ -501,12 +501,12 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
501
501
|
* transition({}) is startTransition + ViewTransition under the default and
|
|
502
502
|
* startTransition only when the router sets viewTransition: false.
|
|
503
503
|
*
|
|
504
|
-
* Conditional hold: pass `when
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
504
|
+
* Conditional hold: pass `when` to gate the navigation in the browser,
|
|
505
|
+
* inline (the build hoists the literal into a client module) or as a
|
|
506
|
+
* "use client" export. It runs once per
|
|
507
|
+
* navigation with { kind, from, to, isAction, action }, never on the server;
|
|
508
|
+
* false makes the commit urgent (no hold, no view transition). This is
|
|
509
|
+
* distinct from intercept()'s match-time `when` selector.
|
|
510
510
|
*
|
|
511
511
|
* ```typescript
|
|
512
512
|
* // Attach to a single route
|
|
@@ -525,15 +525,15 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
525
525
|
* transition({ viewTransition: false }),
|
|
526
526
|
* ])
|
|
527
527
|
*
|
|
528
|
-
* // Hold only when
|
|
528
|
+
* // Hold only when arriving from the list (a browser predicate):
|
|
529
529
|
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
530
|
-
* transition({ when: (
|
|
530
|
+
* transition({ when: ({ from }) => from.routeName === "products.list" }),
|
|
531
531
|
* ])
|
|
532
532
|
* ```
|
|
533
533
|
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
534
534
|
* default, name), `viewTransition: "auto" | false` to toggle the router
|
|
535
535
|
* boundary (createRouter({ viewTransition }) sets the app-wide default), and
|
|
536
|
-
* `when
|
|
536
|
+
* `when` to gate the transition per navigation (a browser predicate)
|
|
537
537
|
* @param children - Optional callback returning child routes to wrap
|
|
538
538
|
*/
|
|
539
539
|
transition: {
|
package/src/route-name.ts
CHANGED
|
@@ -32,6 +32,18 @@ export function isAutoGeneratedRouteName(name: string): boolean {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The route name user code may see: a user-defined name, or undefined for an
|
|
37
|
+
* internal one (unnamed path(), hidden include scope). Internal names are
|
|
38
|
+
* route keys, not API, and must not leak into transition({ when }) or
|
|
39
|
+
* intercept({ when }) locations.
|
|
40
|
+
*/
|
|
41
|
+
export function publicRouteName(
|
|
42
|
+
routeKey: string | undefined,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
return routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
/**
|
|
36
48
|
* Validate that a user-provided route name does not collide with
|
|
37
49
|
* reserved internal prefixes. Checks every dot-separated segment,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* RSC routes and response routes (JSON, text, image, stream, etc.).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
9
10
|
import type { EntryData } from "../server/context.js";
|
|
10
11
|
import type { NegotiateVariant } from "../build/route-trie.js";
|
|
11
12
|
import type { CollectedMiddleware } from "./middleware-types.js";
|
|
@@ -245,7 +246,9 @@ export async function negotiateRoute(
|
|
|
245
246
|
return null;
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
const acceptEntries = parseAcceptTypes(
|
|
249
|
+
const acceptEntries = parseAcceptTypes(
|
|
250
|
+
requestHeaders(request).get("accept") || "",
|
|
251
|
+
);
|
|
249
252
|
|
|
250
253
|
// Variants carry the variant's own pa (positional param names); the synthetic
|
|
251
254
|
// primary/RSC candidates have none (their params are already keyed correctly).
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createElement, type ReactNode } from "react";
|
|
9
|
-
import type { EntryData } from "../server/context";
|
|
9
|
+
import type { EntryData } from "../server/context.js";
|
|
10
|
+
import { runIdentityExempt } from "../cache/cache-exec-scope.js";
|
|
10
11
|
import type {
|
|
11
12
|
ResolvedSegment,
|
|
12
13
|
ErrorInfo,
|
|
@@ -92,7 +93,9 @@ export function invokeOnError<TEnv = any>(
|
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
try {
|
|
95
|
-
|
|
96
|
+
// An observer: a request header it logs is not rendered, so the identity
|
|
97
|
+
// guards let it read one inside a capture or a cached scope (#976).
|
|
98
|
+
const result = runIdentityExempt(() => onError(errorContext));
|
|
96
99
|
// If onError returns a promise, catch any rejections
|
|
97
100
|
if (result instanceof Promise) {
|
|
98
101
|
result.catch((callbackError) => {
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { HandlerContext, InternalHandlerContext } from "../types";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
_getRequestContext,
|
|
10
|
+
type RequestContext,
|
|
11
|
+
} from "../server/request-context.js";
|
|
9
12
|
import { getSearchSchema, isRouteRootScoped } from "../route-map-builder.js";
|
|
10
13
|
import { parseSearchParams, serializeSearchParams } from "../search-params.js";
|
|
11
14
|
import {
|
|
@@ -18,6 +21,10 @@ import {
|
|
|
18
21
|
assertCachedHeaderWriteAllowed,
|
|
19
22
|
assertNonCacheableReadAllowed,
|
|
20
23
|
} from "../server/context.js";
|
|
24
|
+
import {
|
|
25
|
+
guardRequestHeaders,
|
|
26
|
+
readGuardedTheme,
|
|
27
|
+
} from "../server/cookie-store.js";
|
|
21
28
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
22
29
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
30
|
import { PRERENDER_PASSTHROUGH } from "../prerender.js";
|
|
@@ -29,6 +36,31 @@ import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
|
29
36
|
// once at module load instead of per createHandlerContext (per-request) call.
|
|
30
37
|
const MUTATING_HEADERS_METHODS = new Set(["set", "append", "delete"]);
|
|
31
38
|
|
|
39
|
+
/** The request context a handler ctx's `theme` getter reads (THEME_DESCRIPTOR). */
|
|
40
|
+
const THEME_REQUEST_CONTEXT = Symbol("rango:theme-request-context");
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* ctx.theme is the visitor's theme cookie, guarded like cookies() (#971,
|
|
44
|
+
* readGuardedTheme): a getter, so creating the ctx reads nothing and
|
|
45
|
+
* only a handler that reads the theme refuses a ppr capture or throws on a
|
|
46
|
+
* cache() miss. Non-enumerable, so serializing the ctx does not read it:
|
|
47
|
+
* React's dev Flight debug info JSON-stringifies server component props and
|
|
48
|
+
* replayed console.log args, and an enumerable getter read there refused the
|
|
49
|
+
* capture of a route that never read the theme. The trade: a spread or
|
|
50
|
+
* Object.assign copy of the ctx does not carry `theme`. One shared getter
|
|
51
|
+
* with the request context on a symbol key: a getter closure per ctx gives
|
|
52
|
+
* every ctx its own property shape (V8 dictionary mode). The guard reads the
|
|
53
|
+
* ambient context, as cookies() does, and falls back to the ctx's own when
|
|
54
|
+
* there is none.
|
|
55
|
+
*/
|
|
56
|
+
const THEME_DESCRIPTOR: PropertyDescriptor = {
|
|
57
|
+
get(this: { [THEME_REQUEST_CONTEXT]?: RequestContext }) {
|
|
58
|
+
return readGuardedTheme(this[THEME_REQUEST_CONTEXT], "ctx.theme");
|
|
59
|
+
},
|
|
60
|
+
enumerable: false,
|
|
61
|
+
configurable: true,
|
|
62
|
+
};
|
|
63
|
+
|
|
32
64
|
/**
|
|
33
65
|
* `headers` whose mutating methods run `assertWrite(method)` first. Shared by
|
|
34
66
|
* the handler ctx (createHandlerContext) and the middleware ctx
|
|
@@ -263,7 +295,10 @@ export function createHandlerContext<TEnv>(
|
|
|
263
295
|
requestContext?.dynamic();
|
|
264
296
|
},
|
|
265
297
|
dev: false,
|
|
266
|
-
request
|
|
298
|
+
// Usually the request context's (guarded at creation). A request the
|
|
299
|
+
// router builds and matches (the progressive-enhancement re-render, a
|
|
300
|
+
// navigation-only ppr capture) is guarded here, before any handler runs.
|
|
301
|
+
request: guardRequestHeaders(request),
|
|
267
302
|
searchParams,
|
|
268
303
|
search: searchSchema ? resolvedSearchParams : {},
|
|
269
304
|
pathname,
|
|
@@ -279,7 +314,7 @@ export function createHandlerContext<TEnv>(
|
|
|
279
314
|
// Read-time guard: non-cacheable var inside "use cache" or cache() →
|
|
280
315
|
// throw. Works for both ContextVar tokens and string keys.
|
|
281
316
|
if (isNonCacheable(variables, keyOrVar)) {
|
|
282
|
-
assertNonCacheableReadAllowed(keyOrVar);
|
|
317
|
+
assertNonCacheableReadAllowed(keyOrVar, _getRequestContext());
|
|
283
318
|
}
|
|
284
319
|
return contextGet(variables, keyOrVar);
|
|
285
320
|
}) as HandlerContext<any, TEnv>["get"],
|
|
@@ -295,8 +330,7 @@ export function createHandlerContext<TEnv>(
|
|
|
295
330
|
use: () => {
|
|
296
331
|
throw new Error("ctx.use() called before loaders were initialized");
|
|
297
332
|
},
|
|
298
|
-
//
|
|
299
|
-
theme: requestContext?.theme,
|
|
333
|
+
// theme: THEME_DESCRIPTOR, defined after this literal.
|
|
300
334
|
setTheme: requestContext?.setTheme,
|
|
301
335
|
// Location state support (delegates to request context)
|
|
302
336
|
setLocationState(entries) {
|
|
@@ -325,6 +359,9 @@ export function createHandlerContext<TEnv>(
|
|
|
325
359
|
_responseType: responseType,
|
|
326
360
|
_routeName: routeName,
|
|
327
361
|
};
|
|
362
|
+
(ctx as { [THEME_REQUEST_CONTEXT]?: unknown })[THEME_REQUEST_CONTEXT] =
|
|
363
|
+
requestContext;
|
|
364
|
+
Object.defineProperty(ctx, "theme", THEME_DESCRIPTOR);
|
|
328
365
|
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
329
366
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
330
367
|
return ctx;
|