@rangojs/router 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +37 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +48 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +126 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +962 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +67 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +54 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +202 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +209 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +42 -5
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +152 -26
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +263 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +134 -87
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -708,7 +708,6 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
708
708
|
transition: applyViewTransitionDefault(
|
|
709
709
|
parallelEntry.transition,
|
|
710
710
|
deps.viewTransitionDefault,
|
|
711
|
-
parallelId,
|
|
712
711
|
),
|
|
713
712
|
params,
|
|
714
713
|
slot,
|
|
@@ -920,7 +919,6 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
920
919
|
transition: applyViewTransitionDefault(
|
|
921
920
|
entry.transition,
|
|
922
921
|
deps.viewTransitionDefault,
|
|
923
|
-
entry.shortCode,
|
|
924
922
|
),
|
|
925
923
|
params,
|
|
926
924
|
belongsToRoute,
|
|
@@ -1261,7 +1259,6 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1261
1259
|
transition: applyViewTransitionDefault(
|
|
1262
1260
|
orphan.transition,
|
|
1263
1261
|
deps.viewTransitionDefault,
|
|
1264
|
-
orphan.shortCode,
|
|
1265
1262
|
),
|
|
1266
1263
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1267
1264
|
});
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { EntryData } from "../../server/context";
|
|
11
|
-
import { getRequestContext } from "../../server/request-context.js";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Resolve a segment's transition config: stamp the `viewTransition` default and
|
|
@@ -22,34 +21,20 @@ import { getRequestContext } from "../../server/request-context.js";
|
|
|
22
21
|
* (or "auto") value is left untouched because it already means "wrap" at the
|
|
23
22
|
* gate, which also avoids needless object allocation and payload growth.
|
|
24
23
|
*
|
|
25
|
-
* `when`: a server
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* `when`: a client reference the server never calls. It is STRIPPED here so no
|
|
25
|
+
* segment record (segment cache, prerender artifact, shell snapshot) carries
|
|
26
|
+
* it: those stores JSON-serialize the config and would silently drop it on
|
|
27
|
+
* some stores and keep it on others. rsc/attach-transition-when.ts attaches it
|
|
28
|
+
* from the route definition right before Flight instead. Used by both fresh
|
|
29
|
+
* and revalidation resolution.
|
|
29
30
|
*/
|
|
30
31
|
export function applyViewTransitionDefault(
|
|
31
32
|
transition: EntryData["transition"],
|
|
32
33
|
viewTransitionDefault: "auto" | false | undefined,
|
|
33
|
-
segmentId?: string,
|
|
34
34
|
): EntryData["transition"] {
|
|
35
35
|
if (!transition) return transition;
|
|
36
36
|
let result = transition;
|
|
37
37
|
if (result.when) {
|
|
38
|
-
if (segmentId !== undefined) {
|
|
39
|
-
try {
|
|
40
|
-
const ctx = getRequestContext();
|
|
41
|
-
if (!ctx._pprTransitionDecisions?.has(segmentId)) {
|
|
42
|
-
(ctx._transitionWhen ??= []).push({
|
|
43
|
-
id: segmentId,
|
|
44
|
-
when: result.when,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
// No active request context (e.g. a unit test calling this util
|
|
49
|
-
// directly). Skip collection; the strip below still applies so the
|
|
50
|
-
// serialized config never carries the function.
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
38
|
const { when: _when, ...rest } = result;
|
|
54
39
|
result = rest;
|
|
55
40
|
}
|
package/src/router/telemetry.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - revalidation.decision (revalidation evaluation)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
18
|
+
|
|
17
19
|
interface BaseEvent {
|
|
18
20
|
/** Monotonic timestamp from performance.now() */
|
|
19
21
|
timestamp: number;
|
|
@@ -294,10 +296,11 @@ export function getRequestId(request: Request): string {
|
|
|
294
296
|
const existing = requestIds.get(request);
|
|
295
297
|
if (existing) return existing;
|
|
296
298
|
|
|
299
|
+
const headers = requestHeaders(request);
|
|
297
300
|
const candidate =
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
headers.get("x-rsc-router-request-id") ??
|
|
302
|
+
headers.get("x-request-id") ??
|
|
303
|
+
headers.get("cf-ray");
|
|
301
304
|
|
|
302
305
|
let id: string;
|
|
303
306
|
if (candidate) {
|
|
@@ -1,76 +1,58 @@
|
|
|
1
1
|
import { getParallelSlotEntries, type EntryData } from "../server/context.js";
|
|
2
2
|
import type { RequestContext } from "../server/request-context.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { TransitionWhenSite } from "../transition-when-ref.js";
|
|
4
|
+
import type { TransitionWhenFn } from "../types/segments.js";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export function createTransitionWhenContext<TEnv>(
|
|
11
|
-
ctx: RequestContext<TEnv>,
|
|
12
|
-
target?: {
|
|
13
|
-
params: Record<string, string>;
|
|
14
|
-
routeName?: string;
|
|
15
|
-
},
|
|
16
|
-
): TransitionWhenContext<Record<string, string>, TEnv> {
|
|
17
|
-
return {
|
|
18
|
-
currentUrl: ctx._gateCurrentUrl,
|
|
19
|
-
currentParams: ctx._gateCurrentParams,
|
|
20
|
-
fromRouteName: ctx._prevRouteKey as TransitionWhenContext["fromRouteName"],
|
|
21
|
-
nextUrl: ctx.url,
|
|
22
|
-
nextParams: target?.params ?? ctx.params,
|
|
23
|
-
toRouteName: (target?.routeName ??
|
|
24
|
-
ctx.routeName) as TransitionWhenContext["toRouteName"],
|
|
25
|
-
actionId: ctx._gateActionId,
|
|
26
|
-
actionUrl: ctx._gateActionUrl,
|
|
27
|
-
actionResult: ctx._gateActionResult,
|
|
28
|
-
formData: ctx._gateFormData,
|
|
29
|
-
method: ctx.request.method,
|
|
30
|
-
get: ctx.get,
|
|
31
|
-
env: ctx.env,
|
|
32
|
-
};
|
|
6
|
+
/** A matched entry's transition({ when }) and where it was declared. */
|
|
7
|
+
export interface TransitionWhenRecord {
|
|
8
|
+
readonly when: TransitionWhenFn;
|
|
9
|
+
readonly site: TransitionWhenSite;
|
|
33
10
|
}
|
|
34
11
|
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Record the transition({ when }) references of a matched entry chain, keyed
|
|
14
|
+
* by the segment id each entry resolves to. The server never calls them:
|
|
15
|
+
* rsc/attach-transition-when.ts attaches them to the payload's segments right
|
|
16
|
+
* before Flight, and the browser decides (browser/transition-when.ts).
|
|
17
|
+
*
|
|
18
|
+
* Recorded from the static route definition on EVERY match (fresh, cache hit,
|
|
19
|
+
* prerender, PPR replay) because stored segments never carry them:
|
|
20
|
+
* applyViewTransitionDefault strips `when` at resolution, since segment stores
|
|
21
|
+
* JSON-serialize the transition config (dropping a function on some stores,
|
|
22
|
+
* keeping it by reference on others). Segment ids follow resolution: an
|
|
23
|
+
* entry's shortCode, and `${owner.shortCode}.${slot}` for a rendered parallel
|
|
24
|
+
* slot.
|
|
25
|
+
*/
|
|
26
|
+
export function recordTransitionWhenRefs<TEnv>(
|
|
27
|
+
entries: readonly EntryData[] | undefined,
|
|
38
28
|
ctx: RequestContext<TEnv>,
|
|
39
|
-
|
|
40
|
-
params: Record<string, string>;
|
|
41
|
-
routeName?: string;
|
|
42
|
-
},
|
|
43
|
-
reportError: TransitionWhenErrorReporter,
|
|
29
|
+
route: { routeName?: string; pattern?: string } = {},
|
|
44
30
|
): void {
|
|
45
|
-
const
|
|
46
|
-
const whenContext = createTransitionWhenContext(ctx, target);
|
|
31
|
+
const refs = new Map<string, TransitionWhenRecord>();
|
|
47
32
|
const visited = new Set<EntryData>();
|
|
48
33
|
|
|
49
|
-
const
|
|
34
|
+
const record = (entry: EntryData, segmentId: string): void => {
|
|
50
35
|
const when = entry.transition?.when;
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
reportError(error, segmentId);
|
|
57
|
-
}
|
|
36
|
+
if (when === undefined) return;
|
|
37
|
+
refs.set(segmentId, {
|
|
38
|
+
when,
|
|
39
|
+
site: { ...route, entryType: entry.type },
|
|
40
|
+
});
|
|
58
41
|
};
|
|
59
42
|
|
|
60
43
|
const visitEntryAndOrphanLayouts = (entry: EntryData): void => {
|
|
61
44
|
if (visited.has(entry)) return;
|
|
62
45
|
visited.add(entry);
|
|
63
|
-
|
|
46
|
+
record(entry, entry.shortCode);
|
|
64
47
|
|
|
65
48
|
for (const orphan of entry.layout) visitEntryAndOrphanLayouts(orphan);
|
|
66
|
-
// Rendered parallel slots are leaves identified by their owning segment.
|
|
67
49
|
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
68
50
|
entry.parallel,
|
|
69
51
|
)) {
|
|
70
|
-
|
|
52
|
+
record(parallelEntry, `${entry.shortCode}.${slot}`);
|
|
71
53
|
}
|
|
72
54
|
};
|
|
73
55
|
|
|
74
|
-
for (const entry of entries) visitEntryAndOrphanLayouts(entry);
|
|
75
|
-
ctx.
|
|
56
|
+
for (const entry of entries ?? []) visitEntryAndOrphanLayouts(entry);
|
|
57
|
+
ctx._transitionWhenRefs = refs.size > 0 ? refs : undefined;
|
|
76
58
|
}
|
package/src/router.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestHeaders } from "./server/request-headers.js";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
2
3
|
import { createCacheScope } from "./cache/cache-scope.js";
|
|
3
4
|
import { resolveCacheProfiles } from "./cache/profile-registry.js";
|
|
@@ -1161,7 +1162,7 @@ export function createRouter<TEnv = any>(
|
|
|
1161
1162
|
| null = null;
|
|
1162
1163
|
|
|
1163
1164
|
return async (request: Request, input: RouterRequestInput<TEnv> = {}) => {
|
|
1164
|
-
const requestedDiscoveryEpoch = request.
|
|
1165
|
+
const requestedDiscoveryEpoch = requestHeaders(request).get(
|
|
1165
1166
|
DEV_DISCOVERY_PROBE_HEADER,
|
|
1166
1167
|
);
|
|
1167
1168
|
if (
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { MatchResult } from "../types.js";
|
|
2
|
+
import type { getRequestContext } from "../server/request-context.js";
|
|
3
|
+
import {
|
|
4
|
+
createTransitionWhenError,
|
|
5
|
+
isTransitionWhenValidationEnabled,
|
|
6
|
+
transitionWhenProblem,
|
|
7
|
+
} from "../transition-when-ref.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Attach transition({ when }) references to a payload's segments, right before
|
|
11
|
+
* Flight. Returns a copy for every segment it changes: the input segments may
|
|
12
|
+
* be the records a cache, prerender, or shell snapshot holds.
|
|
13
|
+
*
|
|
14
|
+
* Only a segment that carries `transition` gets its predicate. The config
|
|
15
|
+
* itself never varies per request, so the segment key and the <ViewTransition>
|
|
16
|
+
* wrapper depend only on the route definition (#995); the browser decides for
|
|
17
|
+
* every committed segment, kept ones included (#989).
|
|
18
|
+
*
|
|
19
|
+
* Render-time backstop for an invalid `when` that route discovery did not see
|
|
20
|
+
* (a route evaluated lazily at runtime): it throws the discovery error. A
|
|
21
|
+
* plain function reaching Flight would otherwise fail with React's generic
|
|
22
|
+
* "Functions cannot be passed directly to Client Components", or be dropped.
|
|
23
|
+
*/
|
|
24
|
+
export function attachTransitionWhen(
|
|
25
|
+
segments: MatchResult["segments"],
|
|
26
|
+
ctx: ReturnType<typeof getRequestContext>,
|
|
27
|
+
): MatchResult["segments"] {
|
|
28
|
+
const refs = ctx?._transitionWhenRefs;
|
|
29
|
+
if (!refs) return segments;
|
|
30
|
+
const strict = isTransitionWhenValidationEnabled();
|
|
31
|
+
let out = segments;
|
|
32
|
+
for (let i = 0; i < segments.length; i++) {
|
|
33
|
+
const segment = segments[i];
|
|
34
|
+
const record = segment.transition ? refs.get(segment.id) : undefined;
|
|
35
|
+
if (!record || segment.transition!.when === record.when) continue;
|
|
36
|
+
const problem = transitionWhenProblem(record.when, strict);
|
|
37
|
+
if (problem) {
|
|
38
|
+
throw createTransitionWhenError(record.when, problem, record.site);
|
|
39
|
+
}
|
|
40
|
+
if (out === segments) out = [...segments];
|
|
41
|
+
out[i] = {
|
|
42
|
+
...segment,
|
|
43
|
+
transition: { ...segment.transition, when: record.when },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
package/src/rsc/full-payload.ts
CHANGED
|
@@ -14,7 +14,9 @@ import type { HandlerContext } from "./handler-context.js";
|
|
|
14
14
|
import type { RequestContext } from "../server/request-context.js";
|
|
15
15
|
import type { HandleStore } from "../server/handle-store.js";
|
|
16
16
|
import type { Theme } from "../theme/types.js";
|
|
17
|
-
import {
|
|
17
|
+
import { documentCacheStoresRender } from "../cache/document-cache.js";
|
|
18
|
+
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
19
|
+
import { publicRouteName } from "../route-name.js";
|
|
18
20
|
import { resolvedHandleStream } from "../handles/deferred-resolution.js";
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -39,11 +41,12 @@ export function buildFullPayload(
|
|
|
39
41
|
pathname: url.pathname,
|
|
40
42
|
routerId: ctx.router.id,
|
|
41
43
|
basename: ctx.router.basename,
|
|
42
|
-
segments:
|
|
44
|
+
segments: attachTransitionWhen(m.segments, reqCtx),
|
|
43
45
|
matched: m.matched,
|
|
44
46
|
diff: m.diff,
|
|
45
47
|
resolvedIds: m.resolvedIds,
|
|
46
48
|
params: m.params,
|
|
49
|
+
routeName: publicRouteName(m.routeName),
|
|
47
50
|
isPartial: false,
|
|
48
51
|
interceptTargets: m.interceptTargets,
|
|
49
52
|
rootLayout: ctx.router.rootLayout,
|
|
@@ -82,20 +85,37 @@ export function buildFullPayload(
|
|
|
82
85
|
* ThemeProvider's first state), read with no identity guard: it is the
|
|
83
86
|
* router's own read, not handler code (#971).
|
|
84
87
|
*
|
|
85
|
-
* A
|
|
86
|
-
* theme
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
88
|
+
* A render other visitors may be served carries the no-cookie default, not
|
|
89
|
+
* the rendering visitor's theme: a shell capture, and a render the document
|
|
90
|
+
* cache runs whose response opted in before next() (documentCacheStoresRender,
|
|
91
|
+
* #978). The shell, its ShellCacheEntry.initialTheme and the stored document
|
|
92
|
+
* serve every visitor, and ThemeProvider re-syncs only from an explicitly
|
|
93
|
+
* stored theme, so a visitor with none kept the first visitor's
|
|
94
|
+
* `useTheme().theme` for the page's lifetime. A visitor with a stored theme
|
|
95
|
+
* gets it pre-paint (theme script) and in the provider after mount.
|
|
96
|
+
*
|
|
97
|
+
* Any other render carries the visitor's theme (the unmatched-route 404 reads
|
|
98
|
+
* it without this function, rsc/handler.ts); when that differs from the
|
|
99
|
+
* default it marks `_payloadVisitorTheme`, and the document cache does not
|
|
100
|
+
* store the response.
|
|
91
101
|
*/
|
|
92
102
|
export function payloadInitialTheme(
|
|
93
103
|
reqCtx: Pick<
|
|
94
104
|
RequestContext<any>,
|
|
95
|
-
|
|
105
|
+
| "_themeConfig"
|
|
106
|
+
| "_shellCaptureRun"
|
|
107
|
+
| "_readTheme"
|
|
108
|
+
| "_documentCacheRender"
|
|
109
|
+
| "_payloadVisitorTheme"
|
|
110
|
+
| "res"
|
|
96
111
|
>,
|
|
97
112
|
): Theme | undefined {
|
|
98
113
|
const config = reqCtx._themeConfig;
|
|
99
114
|
if (!config) return undefined;
|
|
100
|
-
|
|
115
|
+
if (reqCtx._shellCaptureRun || documentCacheStoresRender(reqCtx)) {
|
|
116
|
+
return config.defaultTheme;
|
|
117
|
+
}
|
|
118
|
+
const theme = reqCtx._readTheme();
|
|
119
|
+
if (theme !== config.defaultTheme) reqCtx._payloadVisitorTheme = true;
|
|
120
|
+
return theme;
|
|
101
121
|
}
|
package/src/rsc/handler.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* and progressive enhancement (no-JS form submissions).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
10
11
|
import { isRouteNotFoundError } from "../errors.js";
|
|
11
12
|
import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
|
|
12
13
|
import {
|
|
@@ -31,7 +32,6 @@ import {
|
|
|
31
32
|
} from "./helpers.js";
|
|
32
33
|
import { renderRscFlightStage, renderRscResponse } from "./render-pipeline.js";
|
|
33
34
|
import { guardOutgoingRedirect } from "./redirect-guard.js";
|
|
34
|
-
import { payloadInitialTheme } from "./full-payload.js";
|
|
35
35
|
import {
|
|
36
36
|
resolveSoftRedirectUrl,
|
|
37
37
|
resolveExternalRedirect,
|
|
@@ -925,8 +925,10 @@ export function createRSCHandler<
|
|
|
925
925
|
originPhase,
|
|
926
926
|
);
|
|
927
927
|
if (originResult) {
|
|
928
|
+
const origin = requestHeaders(request).get("origin");
|
|
929
|
+
const host = requestHeaders(request).get("host");
|
|
928
930
|
const originError = new Error(
|
|
929
|
-
`Origin check rejected: ${
|
|
931
|
+
`Origin check rejected: ${origin ?? "none"} vs ${host ?? "none"}`,
|
|
930
932
|
);
|
|
931
933
|
originError.name = "OriginCheckError";
|
|
932
934
|
|
|
@@ -935,11 +937,7 @@ export function createRSCHandler<
|
|
|
935
937
|
url,
|
|
936
938
|
env,
|
|
937
939
|
handledByBoundary: false,
|
|
938
|
-
metadata: {
|
|
939
|
-
phase: originPhase,
|
|
940
|
-
origin: request.headers.get("origin"),
|
|
941
|
-
host: request.headers.get("host"),
|
|
942
|
-
},
|
|
940
|
+
metadata: { phase: originPhase, origin, host },
|
|
943
941
|
});
|
|
944
942
|
|
|
945
943
|
if (router.telemetry) {
|
|
@@ -950,8 +948,8 @@ export function createRSCHandler<
|
|
|
950
948
|
method: request.method,
|
|
951
949
|
pathname: url.pathname,
|
|
952
950
|
phase: originPhase,
|
|
953
|
-
origin
|
|
954
|
-
host
|
|
951
|
+
origin,
|
|
952
|
+
host,
|
|
955
953
|
});
|
|
956
954
|
}
|
|
957
955
|
|
|
@@ -1367,7 +1365,11 @@ export function createRSCHandler<
|
|
|
1367
1365
|
themeConfig: router.themeConfig,
|
|
1368
1366
|
warmupEnabled: router.warmupEnabled,
|
|
1369
1367
|
strictMode: router.strictMode,
|
|
1370
|
-
|
|
1368
|
+
// The visitor's theme, not payloadInitialTheme (full-payload.ts):
|
|
1369
|
+
// that picks the default when middleware opted the URL into the
|
|
1370
|
+
// document cache before next(), but a 404 is never stored or
|
|
1371
|
+
// captured (#978).
|
|
1372
|
+
initialTheme: getRequestContext()._readTheme(),
|
|
1371
1373
|
},
|
|
1372
1374
|
};
|
|
1373
1375
|
|
package/src/rsc/loader-fetch.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* has already extracted params from it.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
14
15
|
import { getLoaderLazy } from "../server/loader-registry.js";
|
|
15
16
|
import { isDataNotFoundError } from "../errors.js";
|
|
16
17
|
import { executeLoaderMiddleware } from "../router/middleware.js";
|
|
@@ -144,7 +145,7 @@ export async function handleLoaderFetch<TEnv>(
|
|
|
144
145
|
|
|
145
146
|
if (isBodyMethod) {
|
|
146
147
|
try {
|
|
147
|
-
const contentType = request.
|
|
148
|
+
const contentType = requestHeaders(request).get("content-type") || "";
|
|
148
149
|
if (contentType.includes("multipart/form-data")) {
|
|
149
150
|
// FormData body — sent by load() when body is a FormData instance.
|
|
150
151
|
// Preserves File objects and binary data.
|
package/src/rsc/manifest-init.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
setRouterPrecomputedEntries,
|
|
15
15
|
} from "../route-map-builder.js";
|
|
16
16
|
import { mergeFullManifests } from "../build/merge-full-manifests.js";
|
|
17
|
+
import { isTransitionWhenValidationEnabled } from "../transition-when-ref.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Build a fresh route trie from the router's URL-pattern mounts and store it in
|
|
@@ -49,6 +50,9 @@ export async function buildRouterTrieFromUrlpatterns(
|
|
|
49
50
|
await generateManifestFull(mount.patterns, mount.mountIndex, {
|
|
50
51
|
routerId: router.id,
|
|
51
52
|
...(router.basename ? { urlPrefix: router.basename } : {}),
|
|
53
|
+
// Under the Vite plugin the realm validates like discovery does, so
|
|
54
|
+
// a Cloudflare dev rebuild fails on an invalid when too.
|
|
55
|
+
validateTransitionWhen: isTransitionWhenValidationEnabled(),
|
|
52
56
|
}),
|
|
53
57
|
);
|
|
54
58
|
}
|
package/src/rsc/origin-guard.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* navigations, bookmarks, and non-browser clients don't send Origin.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
12
13
|
import type { RequestPlan } from "../router/request-classification.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -69,10 +70,11 @@ export type OriginCheckConfig<TEnv = any> =
|
|
|
69
70
|
* Returns true to allow, false to reject.
|
|
70
71
|
*/
|
|
71
72
|
export function defaultOriginCheck(request: Request, url: URL): boolean {
|
|
72
|
-
|
|
73
|
+
const headers = requestHeaders(request);
|
|
74
|
+
let requestOrigin = headers.get("origin");
|
|
73
75
|
|
|
74
76
|
if (!requestOrigin) {
|
|
75
|
-
const referer =
|
|
77
|
+
const referer = headers.get("referer");
|
|
76
78
|
if (referer) {
|
|
77
79
|
try {
|
|
78
80
|
requestOrigin = new URL(referer).origin;
|
|
@@ -92,7 +94,7 @@ export function defaultOriginCheck(request: Request, url: URL): boolean {
|
|
|
92
94
|
// :authority), so a missing Host here is anomalous. Fail closed rather than
|
|
93
95
|
// fall back to url.host (derived from the request line) when the trusted Host
|
|
94
96
|
// cannot be established.
|
|
95
|
-
const expectedHost =
|
|
97
|
+
const expectedHost = headers.get("host");
|
|
96
98
|
if (!expectedHost) return false;
|
|
97
99
|
|
|
98
100
|
const expectedOrigin = `${url.protocol}//${expectedHost}`;
|
|
@@ -102,11 +104,12 @@ export function defaultOriginCheck(request: Request, url: URL): boolean {
|
|
|
102
104
|
|
|
103
105
|
function createForbiddenResponse(request: Request): Response {
|
|
104
106
|
const isDev = process.env.NODE_ENV !== "production";
|
|
107
|
+
const headers = requestHeaders(request);
|
|
105
108
|
const body = isDev
|
|
106
109
|
? "Forbidden: Origin mismatch. The request origin does not match the server host. " +
|
|
107
110
|
`Set originCheck: false in createRouter() to disable this check. ` +
|
|
108
|
-
`(Origin: ${
|
|
109
|
-
`Host: ${
|
|
111
|
+
`(Origin: ${headers.get("origin") ?? "none"}, ` +
|
|
112
|
+
`Host: ${headers.get("host") ?? "none"})`
|
|
110
113
|
: "Forbidden";
|
|
111
114
|
|
|
112
115
|
return new Response(body, {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* reference. We detect these and return HTML instead of RSC stream.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
9
10
|
import {
|
|
10
11
|
getRequestContext,
|
|
11
12
|
setRequestContextParams,
|
|
@@ -14,7 +15,7 @@ import { createSsrHtmlStage } from "./ssr-setup.js";
|
|
|
14
15
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
15
16
|
import { executeMiddleware } from "../router/middleware.js";
|
|
16
17
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
17
|
-
import {
|
|
18
|
+
import { attachTransitionWhen } from "./attach-transition-when.js";
|
|
18
19
|
import { payloadInitialTheme } from "./full-payload.js";
|
|
19
20
|
import { resolvedHandleStream } from "../handles/deferred-resolution.js";
|
|
20
21
|
import type { RscPayload, ReactFormState } from "./types.js";
|
|
@@ -64,7 +65,7 @@ export async function handleProgressiveEnhancement<TEnv>(
|
|
|
64
65
|
nonce: string | undefined,
|
|
65
66
|
routeMwInfo?: PeRouteMiddlewareInfo,
|
|
66
67
|
): Promise<Response | null> {
|
|
67
|
-
const contentType = request.
|
|
68
|
+
const contentType = requestHeaders(request).get("content-type") || "";
|
|
68
69
|
const isFormSubmission =
|
|
69
70
|
contentType.includes("multipart/form-data") ||
|
|
70
71
|
contentType.includes("application/x-www-form-urlencoded");
|
|
@@ -312,7 +313,7 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
312
313
|
// custom headers) so loaders that read request headers/cookies behave
|
|
313
314
|
// identically under PE and the JS action path. Drop body-framing headers
|
|
314
315
|
// from the bodyless GET and force the HTML accept.
|
|
315
|
-
const headers = new Headers(request
|
|
316
|
+
const headers = new Headers(requestHeaders(request));
|
|
316
317
|
headers.delete("content-type");
|
|
317
318
|
headers.delete("content-length");
|
|
318
319
|
headers.delete("content-encoding");
|
|
@@ -326,15 +327,9 @@ async function handleProgressiveEnhancementInner<TEnv>(
|
|
|
326
327
|
// JS/PE parity: this is an action's revalidation render, so mark it BEFORE
|
|
327
328
|
// matching — a stale `foregroundOnAction` cache entry must re-execute in the
|
|
328
329
|
// foreground during the re-render, exactly as the JS path's
|
|
329
|
-
// revalidateAfterAction does.
|
|
330
|
-
// while foregroundOnAction reads _inActionRevalidation there too, so all
|
|
331
|
-
// action metadata must be available before matching.
|
|
330
|
+
// revalidateAfterAction does.
|
|
332
331
|
const peReqCtx = getRequestContext();
|
|
333
332
|
peReqCtx._inActionRevalidation = true;
|
|
334
|
-
peReqCtx._gateActionId = directActionId ?? undefined;
|
|
335
|
-
peReqCtx._gateActionUrl = new URL(url);
|
|
336
|
-
peReqCtx._gateActionResult = actionResult;
|
|
337
|
-
peReqCtx._gateFormData = formData;
|
|
338
333
|
|
|
339
334
|
return runRoutine(
|
|
340
335
|
peRenderPlan({
|
|
@@ -427,11 +422,7 @@ function renderPeResponse<TEnv>(
|
|
|
427
422
|
pathname: url.pathname,
|
|
428
423
|
routerId: ctx.router.id,
|
|
429
424
|
basename: ctx.router.basename,
|
|
430
|
-
segments:
|
|
431
|
-
match.segments,
|
|
432
|
-
getRequestContext(),
|
|
433
|
-
ctx.router.onError,
|
|
434
|
-
),
|
|
425
|
+
segments: attachTransitionWhen(match.segments, getRequestContext()),
|
|
435
426
|
matched: match.matched,
|
|
436
427
|
diff: match.diff,
|
|
437
428
|
resolvedIds: match.resolvedIds,
|
|
@@ -603,16 +594,6 @@ async function matchPeErrorBoundary<TEnv>(
|
|
|
603
594
|
|
|
604
595
|
setRequestContextParams(errorResult.params, errorResult.routeName);
|
|
605
596
|
|
|
606
|
-
// Only the failing action id + URL are in scope here (no formData/actionResult
|
|
607
|
-
// thread into this helper). Expose the URL only when the action id is known:
|
|
608
|
-
// this helper also handles malformed form bodies before action detection, and
|
|
609
|
-
// those should not look like action-triggered renders to transition({ when }).
|
|
610
|
-
if (actionId != null) {
|
|
611
|
-
const peErrCtx = getRequestContext();
|
|
612
|
-
peErrCtx._gateActionId = actionId;
|
|
613
|
-
peErrCtx._gateActionUrl = new URL(url);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
597
|
return errorResult;
|
|
617
598
|
}
|
|
618
599
|
|
|
@@ -628,11 +609,7 @@ function renderPeErrorResponse<TEnv>(
|
|
|
628
609
|
pathname: url.pathname,
|
|
629
610
|
routerId: ctx.router.id,
|
|
630
611
|
basename: ctx.router.basename,
|
|
631
|
-
segments:
|
|
632
|
-
errorResult.segments,
|
|
633
|
-
getRequestContext(),
|
|
634
|
-
ctx.router.onError,
|
|
635
|
-
),
|
|
612
|
+
segments: attachTransitionWhen(errorResult.segments, getRequestContext()),
|
|
636
613
|
matched: errorResult.matched,
|
|
637
614
|
diff: errorResult.diff,
|
|
638
615
|
resolvedIds: errorResult.resolvedIds,
|