@rangojs/router 0.18.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/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 +31 -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 +24 -4
- 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 +89 -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 +960 -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 +64 -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 +48 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +197 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +105 -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 +28 -3
- 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 +150 -25
- 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 +189 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +123 -84
- 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
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* getItem()/setItem() methods on SegmentCacheStore.
|
|
7
7
|
*
|
|
8
8
|
* Cache key resolution (3-tier, matching CacheScope.resolveKey):
|
|
9
|
-
* 1. options.key(requestCtx) — full override
|
|
9
|
+
* 1. options.key(requestCtx) — full override, stored namespaced as
|
|
10
|
+
* loader:{loaderId}:key:{encodeURIComponent(result)} (#1009)
|
|
10
11
|
* 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
|
|
11
12
|
* 3. loader:{loaderId}:{host}{pathname}:{sortedParams} — default
|
|
12
13
|
*
|
|
@@ -57,13 +58,17 @@ import {
|
|
|
57
58
|
} from "../../cache/handle-capture.js";
|
|
58
59
|
import { encodeHandles, decodeHandles } from "../../cache/handle-snapshot.js";
|
|
59
60
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
61
|
+
import { runIdentityExempt } from "../../cache/cache-exec-scope.js";
|
|
60
62
|
import {
|
|
61
63
|
getRequestContext,
|
|
62
64
|
_getRequestContext,
|
|
63
65
|
runWithRequestContext,
|
|
64
66
|
} from "../../server/request-context.js";
|
|
65
67
|
import { observePhase, PHASES } from "../instrument.js";
|
|
66
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
KEY_PART_PREFIX,
|
|
70
|
+
sortedRouteParams,
|
|
71
|
+
} from "../../cache/cache-key-utils.js";
|
|
67
72
|
import {
|
|
68
73
|
resolveTtl,
|
|
69
74
|
resolveSwrWindow,
|
|
@@ -73,19 +78,24 @@ import {
|
|
|
73
78
|
DEFAULT_ROUTE_TTL,
|
|
74
79
|
} from "../../cache/cache-policy.js";
|
|
75
80
|
import { readThroughItem } from "../../cache/read-through-swr.js";
|
|
81
|
+
import {
|
|
82
|
+
executionStart,
|
|
83
|
+
predatesInvalidation,
|
|
84
|
+
} from "../../cache/tag-invalidation.js";
|
|
76
85
|
import {
|
|
77
86
|
maskNestedContainerThenables,
|
|
78
87
|
overlayLoaderContainer,
|
|
79
88
|
} from "./loader-snapshot.js";
|
|
80
89
|
import {
|
|
81
90
|
SHELL_BAKE_TAG_OWNER,
|
|
82
|
-
armLoaderTagSets,
|
|
83
91
|
captureRecordedTags,
|
|
92
|
+
earliestRecordedStart,
|
|
84
93
|
flattenRecordedTags,
|
|
85
94
|
linkLoaderTags,
|
|
86
95
|
linkLoaderTagsTo,
|
|
87
96
|
linkRecordedTags,
|
|
88
97
|
markIdentityRead,
|
|
98
|
+
markTagSetStart,
|
|
89
99
|
readValueTags,
|
|
90
100
|
recordedIdentityRead,
|
|
91
101
|
recordLoaderTags,
|
|
@@ -128,6 +138,28 @@ function getDefaultLoaderCacheKey(
|
|
|
128
138
|
return `loader:${loaderId}:${host}${base}`;
|
|
129
139
|
}
|
|
130
140
|
|
|
141
|
+
/**
|
|
142
|
+
* A loader's own `key()` result as its entry key (#1009). The result is
|
|
143
|
+
* often request input, and the item family (`getItem`/`setItem`) has no
|
|
144
|
+
* other discriminator, so stored raw it could name another loader's entry
|
|
145
|
+
* or a `"use cache"` entry (`use-cache:<id>:...`, cache-runtime.ts), which
|
|
146
|
+
* shares that family; route records use `get`/`set`. Namespaced, it can't:
|
|
147
|
+
* - it starts with `loader:`, which no `"use cache"` key does;
|
|
148
|
+
* - an encoded result holds no `:`, so the key reads unambiguously from
|
|
149
|
+
* the right (result, `key:`, loader id), and two namespaced keys are
|
|
150
|
+
* equal only for the same loader and result;
|
|
151
|
+
* - a default key `loader:<id>:<host><pathname>...` holds a `/` past its
|
|
152
|
+
* id, and an encoded result holds none, so the two are equal only if one
|
|
153
|
+
* loader's id is literally `<other id>:<host>/<path>...`. Loader ids can
|
|
154
|
+
* hold `:` (a dev id is a root-relative path, absolute `D:/...` on
|
|
155
|
+
* Windows), so this is the guarantee, not "ids hold no `:`".
|
|
156
|
+
* A store keyGenerator result stays the store's own namespace, as on routes
|
|
157
|
+
* (composeCacheKeys).
|
|
158
|
+
*/
|
|
159
|
+
function loaderKeyFromResult(loaderId: string, result: string): string {
|
|
160
|
+
return `loader:${loaderId}:${KEY_PART_PREFIX}${encodeURIComponent(result)}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
131
163
|
/**
|
|
132
164
|
* Resolve cache key using the shared 3-tier priority. `declared`: a key() or
|
|
133
165
|
* store keyGenerator produced it, so it can carry request identity (#972).
|
|
@@ -148,9 +180,14 @@ async function resolveLoaderKey(
|
|
|
148
180
|
const host = getRequestContext()?.url?.host ?? "localhost";
|
|
149
181
|
const defaultKey = getDefaultLoaderCacheKey(loaderId, host, pathname, params);
|
|
150
182
|
if (options === false) return { key: defaultKey, declared: false };
|
|
183
|
+
const keyFn = options.key;
|
|
151
184
|
return {
|
|
152
|
-
key: await resolveCacheKey(
|
|
153
|
-
|
|
185
|
+
key: await resolveCacheKey(
|
|
186
|
+
keyFn && (async (ctx) => loaderKeyFromResult(loaderId, await keyFn(ctx))),
|
|
187
|
+
store,
|
|
188
|
+
defaultKey,
|
|
189
|
+
),
|
|
190
|
+
declared: Boolean(keyFn || store.keyGenerator),
|
|
154
191
|
};
|
|
155
192
|
}
|
|
156
193
|
|
|
@@ -199,6 +236,64 @@ export function bindsLoaderCache(entries: readonly EntryData[]): boolean {
|
|
|
199
236
|
return entries.some(entryBindsLoaderCache);
|
|
200
237
|
}
|
|
201
238
|
|
|
239
|
+
const chainLoaderEntriesMemo = new WeakMap<EntryData, readonly LoaderEntry[]>();
|
|
240
|
+
|
|
241
|
+
// The loaders an entry registers, its orphan layouts' and its parallel
|
|
242
|
+
// slots' included.
|
|
243
|
+
function chainLoaderEntries(entry: EntryData): readonly LoaderEntry[] {
|
|
244
|
+
let loaders = chainLoaderEntriesMemo.get(entry);
|
|
245
|
+
if (loaders === undefined) {
|
|
246
|
+
loaders = [
|
|
247
|
+
...(entry.loader ?? []),
|
|
248
|
+
...(entry.layout ?? []).flatMap(chainLoaderEntries),
|
|
249
|
+
...Object.values(entry.parallel ?? {}).flatMap((p) =>
|
|
250
|
+
p ? chainLoaderEntries(p) : [],
|
|
251
|
+
),
|
|
252
|
+
];
|
|
253
|
+
chainLoaderEntriesMemo.set(entry, loaders);
|
|
254
|
+
}
|
|
255
|
+
return loaders;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The ids of the loaders a request over the matched chain `entries` runs on
|
|
260
|
+
* the live lane (no `ssr: false`), on a chain entry, its orphan layouts or
|
|
261
|
+
* its parallel slots. A document HIT tail restores the record's pushes of
|
|
262
|
+
* every other owner as authoritative (withCacheLookup, HandleStore
|
|
263
|
+
* pushRestored); a live-lane loader is a hole, so its live run replaces its
|
|
264
|
+
* recorded pushes instead. Memoized per entry.
|
|
265
|
+
*/
|
|
266
|
+
export function liveLaneLoaderIds(
|
|
267
|
+
entries: readonly EntryData[],
|
|
268
|
+
): ReadonlySet<string> {
|
|
269
|
+
const ids = new Set<string>();
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
for (const l of chainLoaderEntries(entry)) {
|
|
272
|
+
if (!l.bake) ids.add(l.loader.$$id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return ids;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The lane of each loader the matched chain `entries` registers: "live" (no
|
|
280
|
+
* `ssr: false`) or "bake". A loader registered on both is "live". A PPR
|
|
281
|
+
* capture credits a push to the first registered loader around it when that
|
|
282
|
+
* one is live (shell-capture.ts deriveShellCaptureContext).
|
|
283
|
+
*/
|
|
284
|
+
export function routeLoaderLanes(
|
|
285
|
+
entries: readonly EntryData[],
|
|
286
|
+
): ReadonlyMap<string, "live" | "bake"> {
|
|
287
|
+
const lanes = new Map<string, "live" | "bake">();
|
|
288
|
+
for (const entry of entries) {
|
|
289
|
+
for (const l of chainLoaderEntries(entry)) {
|
|
290
|
+
if (!l.bake) lanes.set(l.loader.$$id, "live");
|
|
291
|
+
else if (!lanes.has(l.loader.$$id)) lanes.set(l.loader.$$id, "bake");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return lanes;
|
|
295
|
+
}
|
|
296
|
+
|
|
202
297
|
function getLoaderStore(
|
|
203
298
|
loaderEntry: LoaderEntry,
|
|
204
299
|
): import("../../cache/types.js").SegmentCacheStore | null {
|
|
@@ -228,31 +323,47 @@ function getLoaderStore(
|
|
|
228
323
|
* replayed one.
|
|
229
324
|
* A PPR shell capture records the replayed pushes under this loader (its
|
|
230
325
|
* push funnel reads the pushReplayed owner, shell-capture.ts), so a shell
|
|
231
|
-
* HIT restores them owned
|
|
232
|
-
*
|
|
326
|
+
* HIT restores them owned (HandleStore.pushRestored). This replay still
|
|
327
|
+
* runs on that HIT: the store drops its settled values, which the record
|
|
328
|
+
* already restored, and keeps the thenable ones the record could not keep
|
|
329
|
+
* (a deferred push, the reason the loader's record carries `runs`).
|
|
233
330
|
*/
|
|
234
331
|
async function replayLoaderHandles(
|
|
235
|
-
encoded: string,
|
|
332
|
+
encoded: string | undefined,
|
|
236
333
|
handleStore: HandleStore,
|
|
237
334
|
segmentId: string,
|
|
335
|
+
cachedLoaderId: string,
|
|
238
336
|
claim: ((loaderId: string) => boolean) | undefined,
|
|
239
337
|
): Promise<void> {
|
|
240
|
-
const recorded = await decodeHandles(encoded);
|
|
338
|
+
const recorded = encoded ? await decodeHandles(encoded) : {};
|
|
241
339
|
if (!recorded) return;
|
|
242
340
|
const delivers = new Map<string, boolean>();
|
|
243
|
-
|
|
244
|
-
const owner = key.slice(key.indexOf(":") + 1);
|
|
341
|
+
const claims = (owner: string): boolean => {
|
|
245
342
|
let deliver = delivers.get(owner);
|
|
246
343
|
if (deliver === undefined) {
|
|
247
344
|
deliver = claim ? claim(owner) : true;
|
|
248
345
|
delivers.set(owner, deliver);
|
|
249
346
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
347
|
+
return deliver;
|
|
348
|
+
};
|
|
349
|
+
const replay = (): void => {
|
|
350
|
+
for (const key in recorded) {
|
|
351
|
+
const owner = key.slice(key.indexOf(":") + 1);
|
|
352
|
+
if (!claims(owner)) continue;
|
|
353
|
+
for (const [handleName, values] of Object.entries(recorded[key])) {
|
|
354
|
+
for (const value of values) {
|
|
355
|
+
handleStore.pushReplayed(handleName, segmentId, value, owner);
|
|
356
|
+
}
|
|
254
357
|
}
|
|
255
358
|
}
|
|
359
|
+
};
|
|
360
|
+
// A hole's entry, pushes or none, takes the place of the shell copies of
|
|
361
|
+
// its pushes (HandleStore.redeliverReplays). Not once the hole delivered
|
|
362
|
+
// in this request: a run a reader started first replaces them itself.
|
|
363
|
+
if (claims(cachedLoaderId)) {
|
|
364
|
+
handleStore.redeliverReplays(cachedLoaderId, replay);
|
|
365
|
+
} else {
|
|
366
|
+
replay();
|
|
256
367
|
}
|
|
257
368
|
}
|
|
258
369
|
|
|
@@ -449,9 +560,10 @@ function executeLoaderData<TEnv>(
|
|
|
449
560
|
|
|
450
561
|
// Evaluate runtime condition if provided
|
|
451
562
|
const options = cacheConfig.options;
|
|
452
|
-
|
|
563
|
+
const condition = options.condition;
|
|
564
|
+
if (condition) {
|
|
453
565
|
const requestCtx = getRequestContext();
|
|
454
|
-
if (requestCtx && !
|
|
566
|
+
if (requestCtx && !runIdentityExempt(() => condition(requestCtx))) {
|
|
455
567
|
return ctx.use(loaderEntry.loader);
|
|
456
568
|
}
|
|
457
569
|
}
|
|
@@ -508,11 +620,8 @@ function executeLoaderData<TEnv>(
|
|
|
508
620
|
const swr = swrWindow || undefined;
|
|
509
621
|
const tags = resolveTags(loaderEntry);
|
|
510
622
|
recordLoaderTags(loaderId, tags);
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
// resolveLoaders call outside a match from running unarmed after this
|
|
514
|
-
// point (loaders it started earlier stay unrecorded).
|
|
515
|
-
armLoaderTagSets();
|
|
623
|
+
// The per-execution loader tag sets were armed before any loader ran: by
|
|
624
|
+
// the match (match-api.ts, bindsLoaderCache), or by runLoader.
|
|
516
625
|
// An execution of this loader outside the binding (a reader started it
|
|
517
626
|
// first, a stale refresh) answers for the cache() tags too.
|
|
518
627
|
if (tags && tags.length > 0) {
|
|
@@ -530,8 +639,10 @@ function executeLoaderData<TEnv>(
|
|
|
530
639
|
const isOwnBodyPush = () => isInsideLoaderBody(loaderId);
|
|
531
640
|
// What a reader of this value takes on (tagLoaderValue below,
|
|
532
641
|
// readValueTags): the cache() tags, plus the entry's tags on a HIT or the
|
|
533
|
-
// execution's on a MISS.
|
|
642
|
+
// execution's on a MISS. A HIT's value is as old as its lookup: a cached
|
|
643
|
+
// reader gates its own write from here at the latest (#977).
|
|
534
644
|
const valueTags = new Set(tags);
|
|
645
|
+
markTagSetStart(valueTags, executionStart());
|
|
535
646
|
|
|
536
647
|
const dataPromise = (async () => {
|
|
537
648
|
const codec = await getCodec();
|
|
@@ -560,7 +671,9 @@ function executeLoaderData<TEnv>(
|
|
|
560
671
|
const bodyTags = new Set(tags);
|
|
561
672
|
let revalidating = false;
|
|
562
673
|
let hitHandles: string | undefined;
|
|
674
|
+
let cachedRead = false;
|
|
563
675
|
const onCachedRead = (label: string, cached: CacheItemResult) => {
|
|
676
|
+
cachedRead = true;
|
|
564
677
|
hitHandles = cached.handles;
|
|
565
678
|
const { read } = unmarkIdentity(cached.value);
|
|
566
679
|
if (read) markIdentityRead(valueTags, read);
|
|
@@ -634,6 +747,15 @@ function executeLoaderData<TEnv>(
|
|
|
634
747
|
// After the handle encode: it settles pending pushes, which can read.
|
|
635
748
|
const read = identityRead();
|
|
636
749
|
const entryTags = [...flattenRecordedTags(bodyTags)];
|
|
750
|
+
// One of the entry's tags was invalidated after the value's oldest
|
|
751
|
+
// part started (#977): this execution, or the run of this loader or
|
|
752
|
+
// of a dependency another reader started earlier. Written now, a
|
|
753
|
+
// value that may predate it would outlive it. Skip the write; the
|
|
754
|
+
// next read runs the body.
|
|
755
|
+
const start = earliestRecordedStart(bodyTags);
|
|
756
|
+
if (start && (await predatesInvalidation(store, entryTags, start))) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
637
759
|
await store.setItem!(k, markIdentity(v, read), {
|
|
638
760
|
...o,
|
|
639
761
|
tags: entryTags.length > 0 ? entryTags : undefined,
|
|
@@ -642,6 +764,7 @@ function executeLoaderData<TEnv>(
|
|
|
642
764
|
}),
|
|
643
765
|
key,
|
|
644
766
|
execute: async () => {
|
|
767
|
+
markTagSetStart(bodyTags, executionStart());
|
|
645
768
|
const value = await captureRecordedTags(bodyTags, runBody);
|
|
646
769
|
identityRead();
|
|
647
770
|
return value;
|
|
@@ -675,12 +798,14 @@ function executeLoaderData<TEnv>(
|
|
|
675
798
|
host: requestCtxForExecute,
|
|
676
799
|
});
|
|
677
800
|
|
|
678
|
-
// An entry
|
|
679
|
-
|
|
801
|
+
// An entry without handles (none recorded, or dropped by an encode
|
|
802
|
+
// timeout) replays none, and still stands for a hole's shell copies.
|
|
803
|
+
if (cachedRead && handleStore && owningSegmentId) {
|
|
680
804
|
await replayLoaderHandles(
|
|
681
805
|
hitHandles,
|
|
682
806
|
handleStore,
|
|
683
807
|
owningSegmentId,
|
|
808
|
+
loaderId,
|
|
684
809
|
internal._claimLoaderPushes,
|
|
685
810
|
);
|
|
686
811
|
}
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
* fresh into it.
|
|
9
9
|
*
|
|
10
10
|
* Capture mode is signalled by `requestCtx._shellCaptureRun`, set to true ONLY on
|
|
11
|
-
* the derived request context of the background capture task (shell-capture.ts)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* for the mask so every loader execution site gates the same way (loader-cache.ts
|
|
11
|
+
* the derived request context of the background capture task (shell-capture.ts);
|
|
12
|
+
* the foreground render never sets it. This module is the single home for the
|
|
13
|
+
* mask so every loader execution site gates the same way (loader-cache.ts
|
|
15
14
|
* `resolveLoaderData`, fresh.ts `resolveLoaders`).
|
|
16
15
|
*/
|
|
17
16
|
|
|
@@ -10,24 +10,18 @@
|
|
|
10
10
|
* server-component element holding a promise, which is a hole as a whole (see
|
|
11
11
|
* elideLoaderContainer):
|
|
12
12
|
*
|
|
13
|
-
* - elide: deep-walk the settled container; a
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* elide anyway, and the overlay keeps decoding SETTLED markers
|
|
20
|
-
* from pre-mask snapshots. The result is promise-free and
|
|
21
|
-
* Flight-serializable.
|
|
13
|
+
* - elide: deep-walk the settled container; a nested promise is a hole,
|
|
14
|
+
* replaced by {@link LOADER_HOLE_KEY}. loader-cache masks every
|
|
15
|
+
* nested thenable at capture ({@link maskNestedContainerThenables},
|
|
16
|
+
* a never-settling promise), so the promise shape alone decides:
|
|
17
|
+
* a value that settled in time does not bake (#692). The result
|
|
18
|
+
* is promise-free and Flight-serializable.
|
|
22
19
|
* - overlay: on a HIT the loader runs fresh (only the loader body can mint
|
|
23
20
|
* the live nested promises), then the recorded container is laid
|
|
24
21
|
* over it: recorded paths win (they are what the prelude froze),
|
|
25
22
|
* hole-marker paths take the fresh run's value (the live hole),
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* handing them the raw value throws React #438 on every HIT (the
|
|
29
|
-
* storefront PDP regression) — and fresh-only paths pass through
|
|
30
|
-
* (they cannot contradict prelude bytes that never rendered them).
|
|
23
|
+
* and fresh-only paths pass through (they cannot contradict
|
|
24
|
+
* prelude bytes that never rendered them).
|
|
31
25
|
*/
|
|
32
26
|
|
|
33
27
|
import { isValidElement, type ReactElement } from "react";
|
|
@@ -64,37 +58,6 @@ export function isLoaderHoleMarker(value: unknown): value is LoaderHoleMarker {
|
|
|
64
58
|
);
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
/**
|
|
68
|
-
* Marker wrapping the inlined value of a NESTED promise that settled during
|
|
69
|
-
* capture. The value baked (physics), but the container key was a promise —
|
|
70
|
-
* the overlay must hand consumers a Promise.resolve(value), not the raw value,
|
|
71
|
-
* or an unconditional use(data.x) throws React #438 on every HIT. The ROOT
|
|
72
|
-
* container is never wrapped: loader-cache overlays against the awaited fresh
|
|
73
|
-
* container value.
|
|
74
|
-
*/
|
|
75
|
-
export const LOADER_SETTLED_KEY = "$rangoLoaderSettled" as const;
|
|
76
|
-
|
|
77
|
-
export interface LoaderSettledMarker {
|
|
78
|
-
[LOADER_SETTLED_KEY]: 1;
|
|
79
|
-
value: unknown;
|
|
80
|
-
/**
|
|
81
|
-
* Present when the pinned subtree contains hole markers. Computed once at
|
|
82
|
-
* capture (elide already visits every node) so the per-HIT overlay never
|
|
83
|
-
* rescans the pinned structure to pick its rehydration path.
|
|
84
|
-
*/
|
|
85
|
-
holes?: 1;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function isLoaderSettledMarker(
|
|
89
|
-
value: unknown,
|
|
90
|
-
): value is LoaderSettledMarker {
|
|
91
|
-
return (
|
|
92
|
-
typeof value === "object" &&
|
|
93
|
-
value !== null &&
|
|
94
|
-
(value as Record<string, unknown>)[LOADER_SETTLED_KEY] === 1
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
61
|
/**
|
|
99
62
|
* Probe whether a promise is already settled without waiting for it: races it
|
|
100
63
|
* against an immediately-resolved sentinel across two microtask hops (then
|
|
@@ -128,12 +91,12 @@ export type ElideResult =
|
|
|
128
91
|
| { state: "rejected" };
|
|
129
92
|
|
|
130
93
|
/**
|
|
131
|
-
* Deep-elide a settled bake-lane container for recording.
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
94
|
+
* Deep-elide a settled bake-lane container for recording. Nested promises
|
|
95
|
+
* become hole markers: the capture masked each of them (never settles), so
|
|
96
|
+
* it postponed as a hole. A REJECTED container poisons the record (error UI
|
|
97
|
+
* must never bake into a shared shell) — the caller refuses the capture.
|
|
98
|
+
* Plain data objects, arrays and the props of host, Suspense/Fragment and
|
|
99
|
+
* client-reference elements are
|
|
137
100
|
* traversed copy-on-write (an element through cloneElementWithProps); a shared
|
|
138
101
|
* subtree or a cycle resolves to the same elided result. An element Flight
|
|
139
102
|
* renders by calling user code (see rendersOnServer) is a leaf, or ONE hole
|
|
@@ -165,17 +128,7 @@ async function elideNested(
|
|
|
165
128
|
seen: Map<object, ElideResult>,
|
|
166
129
|
): Promise<ElideResult> {
|
|
167
130
|
if (isThenable(value)) {
|
|
168
|
-
|
|
169
|
-
if (probed.state === "pending") {
|
|
170
|
-
return { state: "ok", value: { [LOADER_HOLE_KEY]: 1 }, hasHole: true };
|
|
171
|
-
}
|
|
172
|
-
if (probed.state === "rejected") return { state: "rejected" };
|
|
173
|
-
const inner = await elideNested(probed.value, seen);
|
|
174
|
-
if (inner.state === "rejected") return inner;
|
|
175
|
-
const marker: LoaderSettledMarker = inner.hasHole
|
|
176
|
-
? { [LOADER_SETTLED_KEY]: 1, value: inner.value, holes: 1 }
|
|
177
|
-
: { [LOADER_SETTLED_KEY]: 1, value: inner.value };
|
|
178
|
-
return { state: "ok", value: marker, hasHole: inner.hasHole };
|
|
131
|
+
return { state: "ok", value: { [LOADER_HOLE_KEY]: 1 }, hasHole: true };
|
|
179
132
|
}
|
|
180
133
|
if (typeof value !== "object" || value === null) {
|
|
181
134
|
return { state: "ok", value, hasHole: false };
|
|
@@ -324,22 +277,6 @@ export function overlayLoaderContainer(
|
|
|
324
277
|
): unknown {
|
|
325
278
|
if (isLoaderHoleMarker(recorded)) return fresh;
|
|
326
279
|
|
|
327
|
-
if (isLoaderSettledMarker(recorded)) {
|
|
328
|
-
const pinned = recorded.value;
|
|
329
|
-
// Deep holes inside a settled container (capture-computed `holes` bit)
|
|
330
|
-
// need the fresh promise's resolved value to fill them; a fully-pinned
|
|
331
|
-
// container resolves immediately (the prelude already shows it — never
|
|
332
|
-
// gate it on fresh latency). A rejecting fresh run degrades holes to
|
|
333
|
-
// undefined instead of poisoning the pin.
|
|
334
|
-
if (recorded.holes === 1) {
|
|
335
|
-
return Promise.resolve(fresh).then(
|
|
336
|
-
(freshValue) => overlayLoaderContainer(freshValue, pinned),
|
|
337
|
-
() => overlayLoaderContainer(undefined, pinned),
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
return Promise.resolve(overlayLoaderContainer(undefined, pinned));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
280
|
if (Array.isArray(recorded)) {
|
|
344
281
|
const freshArr = Array.isArray(fresh) ? fresh : [];
|
|
345
282
|
let out: unknown[] | undefined;
|
|
@@ -81,30 +81,55 @@ export function cloneElementWithProps(
|
|
|
81
81
|
*/
|
|
82
82
|
export function settleNestedThenables(value: unknown): Promise<void> {
|
|
83
83
|
const pending: Promise<void>[] = [];
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (isValidElement<Record<string, unknown>>(v)) {
|
|
93
|
-
walk(v.props);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (Array.isArray(v) || isPlainDataObject(v)) {
|
|
97
|
-
if (seen.has(v)) return;
|
|
98
|
-
seen.add(v);
|
|
99
|
-
for (const item of Array.isArray(v) ? v : Object.values(v)) walk(item);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
walk(value);
|
|
84
|
+
someNestedThenable(value, (thenable) => {
|
|
85
|
+
pending.push(
|
|
86
|
+
Promise.resolve(thenable).then(settleNestedThenables, () => undefined),
|
|
87
|
+
);
|
|
88
|
+
return false;
|
|
89
|
+
});
|
|
103
90
|
return pending.length === 0
|
|
104
91
|
? Promise.resolve()
|
|
105
92
|
: Promise.all(pending).then(() => undefined);
|
|
106
93
|
}
|
|
107
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);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
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
|
+
|
|
108
133
|
/**
|
|
109
134
|
* Optional single-walk report for maskNestedContainerThenables: `thenable`
|
|
110
135
|
* flips true when the walk masked at least one thenable. The capture push
|
|
@@ -139,9 +164,9 @@ export interface MaskReport {
|
|
|
139
164
|
* nodes included, are leaves. Copy-on-write: a subtree without a thenable
|
|
140
165
|
* comes back by identity, so an element is rebuilt only when its props hold
|
|
141
166
|
* one, and then through cloneElementWithProps, which keeps its type, key,
|
|
142
|
-
* owner, dev fields and key validation. The INPUT IS NEVER MUTATED
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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.
|
|
145
170
|
*/
|
|
146
171
|
export function maskNestedContainerThenables(
|
|
147
172
|
value: unknown,
|
|
@@ -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) {
|