@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
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Capture-side nested
|
|
3
|
-
* shape is the liveness declaration" for
|
|
4
|
-
* (loader-cache.ts) and
|
|
5
|
-
* push wrap in rsc/shell-capture.ts).
|
|
2
|
+
* Capture-side nested thenables. Masking is the mechanism behind "nested-
|
|
3
|
+
* promise shape is the liveness declaration" for the loader lane: bake-lane
|
|
4
|
+
* loader containers (loader-cache.ts) and DSL loader handle pushes (the
|
|
5
|
+
* capture handle store's push wrap in rsc/shell-capture.ts). Handler handle
|
|
6
|
+
* pushes are handler output and bake: the capture waits for their nested
|
|
7
|
+
* promises instead (settleNestedThenables).
|
|
6
8
|
*
|
|
7
9
|
* Deliberately a LEAF module: loader-mask (the other natural home) imports
|
|
8
10
|
* request-context, so any funnel importing the mask from there inherits that
|
|
9
|
-
* graph. This module imports only is-thenable.
|
|
11
|
+
* graph. This module imports only is-thenable and react.
|
|
10
12
|
*/
|
|
11
13
|
|
|
14
|
+
import { cloneElement, isValidElement, type ReactElement } from "react";
|
|
12
15
|
import { isThenable } from "../../handles/is-thenable.js";
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -23,18 +26,127 @@ export function createMaskedLoaderPromise<T = unknown>(): Promise<T> {
|
|
|
23
26
|
return new Promise<T>(() => {});
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
/**
|
|
30
|
+
* A data object the container walks descend into: prototype Object.prototype
|
|
31
|
+
* or null, and no symbol `$$typeof`. React elements, lazy nodes and references
|
|
32
|
+
* are plain objects too, told apart by that symbol (Flight switches on it).
|
|
33
|
+
* They are React's, not data: an element's dev-only fields (`_debugStack`,
|
|
34
|
+
* `_debugTask`, `_debugInfo`, `ref`) are non-enumerable, so a key-by-key copy
|
|
35
|
+
* drops them and dev Flight refuses it ("Attempted to render <x> without
|
|
36
|
+
* development properties", issue #942).
|
|
37
|
+
*/
|
|
38
|
+
export function isPlainDataObject(
|
|
39
|
+
value: unknown,
|
|
40
|
+
): value is Record<string, unknown> {
|
|
27
41
|
if (typeof value !== "object" || value === null) return false;
|
|
28
42
|
const proto = Object.getPrototypeOf(value);
|
|
29
|
-
|
|
43
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
44
|
+
return typeof (value as { $$typeof?: unknown }).$$typeof !== "symbol";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type KeyValidated = { _store?: { validated?: unknown } };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* cloneElement with only the given props replaced, keeping React's dev key
|
|
51
|
+
* validation. React 19 dev cloneElement resets `_store.validated`, so a
|
|
52
|
+
* keyless child of a static children list (validated at creation) would warn
|
|
53
|
+
* "Each child in a list should have a unique key" once cloned. Pass only the
|
|
54
|
+
* props that change: cloneElement re-parents the element (owner) when its
|
|
55
|
+
* config carries `ref`.
|
|
56
|
+
*/
|
|
57
|
+
export function cloneElementWithProps(
|
|
58
|
+
element: ReactElement,
|
|
59
|
+
props: Record<string, unknown>,
|
|
60
|
+
): ReactElement {
|
|
61
|
+
const clone = cloneElement(element, props);
|
|
62
|
+
const from = (element as KeyValidated)._store;
|
|
63
|
+
const to = (clone as KeyValidated)._store;
|
|
64
|
+
if (from && to) to.validated = from.validated;
|
|
65
|
+
return clone;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves once `value` and every thenable reachable from it through plain
|
|
70
|
+
* data objects, arrays and React element props has settled, following what
|
|
71
|
+
* each thenable resolves to. A rejection counts as settled: the consumer that
|
|
72
|
+
* encodes the value reports it. Other objects are leaves (the same walk as
|
|
73
|
+
* maskNestedContainerThenables), so a promise inside a Map is not waited for.
|
|
74
|
+
* An element is walked through its props only (`<X data={promise} />` in a
|
|
75
|
+
* handle value is handler output too), never its owner or type, and is never
|
|
76
|
+
* rebuilt: nothing is copied, only awaited.
|
|
77
|
+
*
|
|
78
|
+
* The capture waits on this for HANDLER handle pushes (shell-capture.ts):
|
|
79
|
+
* handler output is baked, so its promises must settle before the doc record
|
|
80
|
+
* encodes the handles every HIT replays.
|
|
81
|
+
*/
|
|
82
|
+
export function settleNestedThenables(value: unknown): Promise<void> {
|
|
83
|
+
const pending: Promise<void>[] = [];
|
|
84
|
+
someNestedThenable(value, (thenable) => {
|
|
85
|
+
pending.push(
|
|
86
|
+
Promise.resolve(thenable).then(settleNestedThenables, () => undefined),
|
|
87
|
+
);
|
|
88
|
+
return false;
|
|
89
|
+
});
|
|
90
|
+
return pending.length === 0
|
|
91
|
+
? Promise.resolve()
|
|
92
|
+
: Promise.all(pending).then(() => undefined);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The walk settleNestedThenables and holdsThenable share: visit each thenable
|
|
97
|
+
* reachable from `value` through plain data objects, arrays and React element
|
|
98
|
+
* props (never an element's owner or type), the shapes
|
|
99
|
+
* maskNestedContainerThenables masks; every other object is a leaf. Stops at
|
|
100
|
+
* the first visit that returns true and reports whether one did.
|
|
101
|
+
*/
|
|
102
|
+
function someNestedThenable(
|
|
103
|
+
value: unknown,
|
|
104
|
+
visit: (thenable: PromiseLike<unknown>) => boolean,
|
|
105
|
+
): boolean {
|
|
106
|
+
let seen: Set<object> | undefined;
|
|
107
|
+
const walk = (v: unknown): boolean => {
|
|
108
|
+
if (isThenable(v)) return visit(v);
|
|
109
|
+
if (typeof v !== "object" || v === null) return false;
|
|
110
|
+
if (isValidElement<Record<string, unknown>>(v)) return walk(v.props);
|
|
111
|
+
if (!Array.isArray(v) && !isPlainDataObject(v)) return false;
|
|
112
|
+
seen ??= new Set();
|
|
113
|
+
if (seen.has(v)) return false;
|
|
114
|
+
seen.add(v);
|
|
115
|
+
// Own enumerable values, as maskNestedContainerThenables walks
|
|
116
|
+
// Object.keys: an array's non-index properties are walked too.
|
|
117
|
+
return Object.values(v).some(walk);
|
|
118
|
+
};
|
|
119
|
+
return walk(value);
|
|
30
120
|
}
|
|
31
121
|
|
|
32
122
|
/**
|
|
33
|
-
*
|
|
123
|
+
* True when `value` is a thenable or holds one where the capture's loader
|
|
124
|
+
* push funnel masks it (maskNestedContainerThenables): through plain data
|
|
125
|
+
* objects, arrays and React element props. Such a push stays out of a shell's
|
|
126
|
+
* record and reaches a HIT only from a run of its loader there, so a HIT
|
|
127
|
+
* keeps it; a push without one is the record's (HandleStore.pushRestored).
|
|
128
|
+
*/
|
|
129
|
+
export function holdsThenable(value: unknown): boolean {
|
|
130
|
+
return someNestedThenable(value, () => true);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Optional single-walk report for maskNestedContainerThenables: `thenable`
|
|
135
|
+
* flips true when the walk masked at least one thenable. The capture push
|
|
136
|
+
* funnel reads it for a loader push: a container with a masked nested
|
|
137
|
+
* promise keeps its loader tag, so the handle encode never waits on a mask.
|
|
138
|
+
* Same single-pass shape as elideLoaderContainer's `hasHole`.
|
|
139
|
+
*/
|
|
140
|
+
export interface MaskReport {
|
|
141
|
+
thenable: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Copy a container with every NESTED thenable replaced by a masked
|
|
34
146
|
* (never-resolving) promise. Applied during shell capture to (a) bake-lane
|
|
35
|
-
* loader containers (loader-cache.ts) and (b)
|
|
36
|
-
* (shell-capture.ts capture store push wrap) — the
|
|
37
|
-
*
|
|
147
|
+
* loader containers (loader-cache.ts) and (b) DSL loader handle pushes
|
|
148
|
+
* (shell-capture.ts capture store push wrap) — the rango-owned funnels where
|
|
149
|
+
* a loader declares per-request data by promise SHAPE.
|
|
38
150
|
*
|
|
39
151
|
* Why: a nested promise that happened to SETTLE before the capture's quiet
|
|
40
152
|
* window closed used to bake its value into the SHARED shell (and, for
|
|
@@ -46,23 +158,16 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
46
158
|
* Masking makes the consuming subtree postpone as a hole no matter when the
|
|
47
159
|
* promise settles: liveness by declaration, not by racing the window.
|
|
48
160
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
161
|
+
* Plain data objects, arrays and React element props are traversed: JSX
|
|
162
|
+
* carrying a promise (`<Reviews data={fetchReviews()} />`) declares
|
|
163
|
+
* per-request data the same way a plain container does. Other values, lazy
|
|
164
|
+
* nodes included, are leaves. Copy-on-write: a subtree without a thenable
|
|
165
|
+
* comes back by identity, so an element is rebuilt only when its props hold
|
|
166
|
+
* one, and then through cloneElementWithProps, which keeps its type, key,
|
|
167
|
+
* owner, dev fields and key validation. The INPUT IS NEVER MUTATED: a handler
|
|
168
|
+
* that reads the loader (`ctx.use`) reads the raw container and must keep
|
|
169
|
+
* real values. Cycles are preserved as cycles in the copy.
|
|
53
170
|
*/
|
|
54
|
-
/**
|
|
55
|
-
* Optional single-walk report for maskNestedContainerThenables: `thenable`
|
|
56
|
-
* flips true when the walk masked at least one thenable. The shell fast path
|
|
57
|
-
* reads it at handle-push time — a pushed container with a nested thenable
|
|
58
|
-
* declares per-request data, and a shell entry whose HANDLER layer made such
|
|
59
|
-
* a declaration cannot be served handler-free (the hole would never fill).
|
|
60
|
-
* Same single-pass shape as elideLoaderContainer's `hasHole`.
|
|
61
|
-
*/
|
|
62
|
-
export interface MaskReport {
|
|
63
|
-
thenable: boolean;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
171
|
export function maskNestedContainerThenables(
|
|
67
172
|
value: unknown,
|
|
68
173
|
seen: Map<object, unknown> = new Map(),
|
|
@@ -72,27 +177,41 @@ export function maskNestedContainerThenables(
|
|
|
72
177
|
if (report) report.thenable = true;
|
|
73
178
|
return createMaskedLoaderPromise();
|
|
74
179
|
}
|
|
180
|
+
if (typeof value !== "object" || value === null) return value;
|
|
181
|
+
const cached = seen.get(value);
|
|
182
|
+
if (cached !== undefined) return cached;
|
|
75
183
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
184
|
+
if (isValidElement<Record<string, unknown>>(value)) {
|
|
185
|
+
// Placeholder: a cycle back to this element through its props keeps the
|
|
186
|
+
// original.
|
|
187
|
+
seen.set(value, value);
|
|
188
|
+
const props = value.props;
|
|
189
|
+
let maskedProps: Record<string, unknown> | undefined;
|
|
190
|
+
for (const key of Object.keys(props)) {
|
|
191
|
+
const masked = maskNestedContainerThenables(props[key], seen, report);
|
|
192
|
+
if (masked !== props[key]) (maskedProps ??= {})[key] = masked;
|
|
83
193
|
}
|
|
84
|
-
return
|
|
194
|
+
if (!maskedProps) return value;
|
|
195
|
+
const clone = cloneElementWithProps(value, maskedProps);
|
|
196
|
+
seen.set(value, clone);
|
|
197
|
+
return clone;
|
|
85
198
|
}
|
|
86
199
|
|
|
87
|
-
if (
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
200
|
+
if (Array.isArray(value) || isPlainDataObject(value)) {
|
|
201
|
+
const source = value as Record<string, unknown>;
|
|
202
|
+
const out = (Array.isArray(value) ? new Array(value.length) : {}) as Record<
|
|
203
|
+
string,
|
|
204
|
+
unknown
|
|
205
|
+
>;
|
|
91
206
|
seen.set(value, out);
|
|
92
|
-
|
|
93
|
-
|
|
207
|
+
let changed = false;
|
|
208
|
+
for (const key of Object.keys(source)) {
|
|
209
|
+
out[key] = maskNestedContainerThenables(source[key], seen, report);
|
|
210
|
+
if (out[key] !== source[key]) changed = true;
|
|
94
211
|
}
|
|
95
|
-
return out;
|
|
212
|
+
if (changed) return out;
|
|
213
|
+
seen.set(value, value);
|
|
214
|
+
return value;
|
|
96
215
|
}
|
|
97
216
|
|
|
98
217
|
return value;
|
|
@@ -45,7 +45,8 @@ import {
|
|
|
45
45
|
} from "./helpers.js";
|
|
46
46
|
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
47
47
|
import { getRouterContext } from "../router-context.js";
|
|
48
|
-
import { observeEvent,
|
|
48
|
+
import { observeEvent, observeSegmentHandler } from "../instrument.js";
|
|
49
|
+
import { runInSegmentTagScope } from "../../cache/cache-tag.js";
|
|
49
50
|
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
50
51
|
import {
|
|
51
52
|
track,
|
|
@@ -221,7 +222,8 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
221
222
|
// whole-container-mask every loader on this path (a never-settling promise
|
|
222
223
|
// for a loader that should have executed).
|
|
223
224
|
// Outside capture the key only activates the _shellLoaderSeed overlay,
|
|
224
|
-
// which
|
|
225
|
+
// which serveShellHit seeds for a document HIT and the PPR partial replay
|
|
226
|
+
// (matchPartialWithPprReplay) seeds once its doc record hits.
|
|
225
227
|
const bakeLane = !entryLoadingMasksLoaders(entry.loading);
|
|
226
228
|
|
|
227
229
|
const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
|
|
@@ -240,9 +242,10 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
240
242
|
loaderEntry,
|
|
241
243
|
ctx,
|
|
242
244
|
ctx.pathname,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
// `bake`, not awaitBeforeFlush: a navigation evaluation never
|
|
246
|
+
// carries awaitBeforeFlush, and an ssr: false loader must pin
|
|
247
|
+
// here exactly as on the document HIT.
|
|
248
|
+
bakeLane || loaderEntry.bake === true ? segmentId : null,
|
|
246
249
|
),
|
|
247
250
|
),
|
|
248
251
|
entry,
|
|
@@ -476,7 +479,9 @@ async function resolveParallelSlotComponent<TEnv>(args: {
|
|
|
476
479
|
handlerRan = true;
|
|
477
480
|
if (hasLoadingFallback) {
|
|
478
481
|
const result =
|
|
479
|
-
typeof handler === "function"
|
|
482
|
+
typeof handler === "function"
|
|
483
|
+
? runInSegmentTagScope(parallelId, handler, context)
|
|
484
|
+
: handler;
|
|
480
485
|
if (result instanceof Promise) {
|
|
481
486
|
warnOnStreamedResponse(result, parallelId);
|
|
482
487
|
const tracked = deps.trackHandler(result, {
|
|
@@ -497,7 +502,9 @@ async function resolveParallelSlotComponent<TEnv>(args: {
|
|
|
497
502
|
}
|
|
498
503
|
} else {
|
|
499
504
|
component =
|
|
500
|
-
typeof handler === "function"
|
|
505
|
+
typeof handler === "function"
|
|
506
|
+
? await runInSegmentTagScope(parallelId, handler, context)
|
|
507
|
+
: handler;
|
|
501
508
|
}
|
|
502
509
|
}
|
|
503
510
|
}
|
|
@@ -701,7 +708,6 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
701
708
|
transition: applyViewTransitionDefault(
|
|
702
709
|
parallelEntry.transition,
|
|
703
710
|
deps.viewTransitionDefault,
|
|
704
|
-
parallelId,
|
|
705
711
|
),
|
|
706
712
|
params,
|
|
707
713
|
slot,
|
|
@@ -842,14 +848,19 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
842
848
|
: routeEntry.handler;
|
|
843
849
|
if (!routeEntry.loading) {
|
|
844
850
|
const result = handleHandlerResult(
|
|
845
|
-
await
|
|
851
|
+
await observeSegmentHandler(
|
|
852
|
+
entry.shortCode,
|
|
853
|
+
entry.id,
|
|
854
|
+
handler,
|
|
855
|
+
context,
|
|
856
|
+
),
|
|
846
857
|
);
|
|
847
858
|
doneHandler();
|
|
848
859
|
return result;
|
|
849
860
|
}
|
|
850
861
|
if (!actionContext) {
|
|
851
862
|
const result = handleHandlerResult(
|
|
852
|
-
|
|
863
|
+
observeSegmentHandler(entry.shortCode, entry.id, handler, context),
|
|
853
864
|
);
|
|
854
865
|
if (result instanceof Promise) {
|
|
855
866
|
warnOnStreamedResponse(result, routeEntry.id);
|
|
@@ -875,7 +886,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
875
886
|
entryId: entry.id,
|
|
876
887
|
});
|
|
877
888
|
const actionResult = handleHandlerResult(
|
|
878
|
-
await
|
|
889
|
+
await observeSegmentHandler(
|
|
890
|
+
entry.shortCode,
|
|
891
|
+
entry.id,
|
|
892
|
+
handler,
|
|
893
|
+
context,
|
|
894
|
+
),
|
|
879
895
|
);
|
|
880
896
|
doneHandler();
|
|
881
897
|
return {
|
|
@@ -903,7 +919,6 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
903
919
|
transition: applyViewTransitionDefault(
|
|
904
920
|
entry.transition,
|
|
905
921
|
deps.viewTransitionDefault,
|
|
906
|
-
entry.shortCode,
|
|
907
922
|
),
|
|
908
923
|
params,
|
|
909
924
|
belongsToRoute,
|
|
@@ -1244,7 +1259,6 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1244
1259
|
transition: applyViewTransitionDefault(
|
|
1245
1260
|
orphan.transition,
|
|
1246
1261
|
deps.viewTransitionDefault,
|
|
1247
|
-
orphan.shortCode,
|
|
1248
1262
|
),
|
|
1249
1263
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1250
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
|
+
}
|