@rangojs/router 0.0.0-experimental.122 → 0.0.0-experimental.125
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/bin/rango.js +10 -6
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +55 -48
- package/package.json +61 -21
- package/skills/caching/SKILL.md +2 -1
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +16 -2
- package/skills/intercept/SKILL.md +4 -2
- package/skills/layout/SKILL.md +11 -6
- package/skills/loader/SKILL.md +6 -2
- package/skills/middleware/SKILL.md +4 -2
- package/skills/migrate-nextjs/SKILL.md +3 -1
- package/skills/parallel/SKILL.md +9 -4
- package/skills/rango/SKILL.md +12 -0
- package/skills/route/SKILL.md +10 -2
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +118 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +14 -1
- package/src/browser/navigation-client.ts +14 -1
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +26 -51
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +1 -83
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/fetch.ts +7 -0
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -99
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +12 -4
- package/src/browser/server-action-bridge.ts +77 -15
- package/src/browser/types.ts +7 -2
- package/src/browser/validate-redirect-origin.ts +4 -5
- package/src/build/route-trie.ts +3 -0
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +27 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +94 -46
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +11 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +2 -48
- package/src/cache/profile-registry.ts +7 -3
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +1 -22
- package/src/client.tsx +14 -38
- package/src/component-utils.ts +19 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +28 -18
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +42 -3
- package/src/index.ts +31 -1
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +58 -3
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +11 -1
- package/src/route-map-builder.ts +0 -16
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -30
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +3 -2
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +1 -54
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -21
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-cookies.ts +0 -13
- package/src/router/middleware-types.ts +0 -115
- package/src/router/middleware.ts +7 -30
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +1 -33
- package/src/router/prerender-match.ts +33 -45
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +5 -58
- package/src/router/router-context.ts +0 -26
- package/src/router/router-interfaces.ts +7 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +10 -13
- package/src/router/segment-resolution/revalidation.ts +5 -42
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +63 -40
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +40 -9
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/helpers.ts +34 -0
- package/src/rsc/origin-guard.ts +0 -12
- package/src/rsc/progressive-enhancement.ts +4 -1
- package/src/rsc/rsc-rendering.ts +4 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +30 -28
- package/src/rsc/types.ts +2 -1
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/cookie-store.ts +52 -1
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +74 -77
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +119 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +127 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +186 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +98 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +311 -0
- package/src/testing/render-route.tsx +504 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/error-types.ts +25 -89
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +16 -13
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +6 -7
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +3 -1
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/use-cache-transform.ts +0 -36
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +1 -108
- package/src/vite/router-discovery.ts +2 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
import type { EventController } from "./event-controller.js";
|
|
23
23
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
24
24
|
import { initRangoState } from "./rango-state.js";
|
|
25
|
+
import { registerNavigationStore } from "./navigation-store-handle.js";
|
|
25
26
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
26
27
|
import { setPrefetchDecoder } from "./prefetch/fetch.js";
|
|
27
28
|
import { setAppVersion } from "./app-version.js";
|
|
@@ -175,6 +176,12 @@ export async function initBrowserApp(
|
|
|
175
176
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
176
177
|
});
|
|
177
178
|
|
|
179
|
+
// Register the active store on the module-level handle and wire the
|
|
180
|
+
// jar-divergence observer before any getRangoState() read can detect a
|
|
181
|
+
// cross-tab/server rotation. There is no global store singleton, so this
|
|
182
|
+
// handle is the live reference.
|
|
183
|
+
registerNavigationStore(store);
|
|
184
|
+
|
|
178
185
|
// Seed router identity from the initial SSR payload so the first
|
|
179
186
|
// cross-app SPA navigation can detect the app switch.
|
|
180
187
|
if (initialPayload.metadata?.routerId) {
|
|
@@ -228,10 +235,11 @@ export async function initBrowserApp(
|
|
|
228
235
|
version,
|
|
229
236
|
});
|
|
230
237
|
|
|
231
|
-
// Initialize the
|
|
232
|
-
//
|
|
233
|
-
// namespaces the
|
|
234
|
-
|
|
238
|
+
// Initialize the rango state cookie for cache invalidation. The build version
|
|
239
|
+
// busts cached prefetches on deploy; the server-resolved cookie name
|
|
240
|
+
// namespaces the cookie so sibling apps on the same origin don't collide
|
|
241
|
+
// (falls back to the bare default prefix if metadata lacks the name).
|
|
242
|
+
initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
|
|
235
243
|
setAppVersion(version);
|
|
236
244
|
|
|
237
245
|
// Initialize the in-memory prefetch cache TTL from server config.
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
RscPayload,
|
|
5
5
|
} from "./types.js";
|
|
6
6
|
import { createPartialUpdater } from "./partial-update.js";
|
|
7
|
+
import { enterActionFence, exitActionFence } from "./action-fence.js";
|
|
8
|
+
import { KEEP_CACHE_HEADER } from "./cookie-name.js";
|
|
7
9
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
8
10
|
import {
|
|
9
11
|
reconcileSegments,
|
|
@@ -156,12 +158,40 @@ export function createServerActionBridge(
|
|
|
156
158
|
|
|
157
159
|
// Start action in event controller - handles lifecycle tracking
|
|
158
160
|
const handle = eventController.startAction(id, args);
|
|
161
|
+
// Whether the action's response carried the keepClientCache() directive.
|
|
162
|
+
// Set when the response arrives; gates the deferred invalidation below.
|
|
163
|
+
let keepCache = false;
|
|
164
|
+
// Single deferred invalidation + fence release, run exactly ONCE however the
|
|
165
|
+
// action terminates (normal, redirect, error, abort, intercept, concurrent).
|
|
166
|
+
// This replaces main's eager clear at action start: every directive-free
|
|
167
|
+
// action invalidates once; keepClientCache() suppresses only the automatic
|
|
168
|
+
// invalidation, so a concurrent directive-free action still invalidates via
|
|
169
|
+
// its own latch. Latched so the finally AND the early SPA-redirect returns
|
|
170
|
+
// (whose Flight stream never settles) can both call it safely.
|
|
171
|
+
let actionFinalized = false;
|
|
172
|
+
// skipInvalidation: the version-mismatch reload terminal released nothing
|
|
173
|
+
// server-side, so it releases the fence without invalidating.
|
|
174
|
+
const finalizeAction = (skipInvalidation = false): void => {
|
|
175
|
+
if (actionFinalized) return;
|
|
176
|
+
actionFinalized = true;
|
|
177
|
+
// finally so a throw in invalidation cannot leak the fence (latch is set).
|
|
178
|
+
try {
|
|
179
|
+
if (!keepCache && !skipInvalidation) {
|
|
180
|
+
store.markCacheAsStaleAndBroadcast();
|
|
181
|
+
}
|
|
182
|
+
} finally {
|
|
183
|
+
exitActionFence();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
159
186
|
try {
|
|
160
187
|
const segmentState = store.getSegmentState();
|
|
161
188
|
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
store
|
|
189
|
+
// Raise the action fence (replaces the old eager clear). Nothing is wiped,
|
|
190
|
+
// rotated, or broadcast yet: navigations during the flight fetch fresh
|
|
191
|
+
// (no-store) and popstate is treated as SWR, but the decision to
|
|
192
|
+
// invalidate is deferred to the response so a no-op action (keepClientCache)
|
|
193
|
+
// can leave the caches and the jar untouched.
|
|
194
|
+
enterActionFence();
|
|
165
195
|
|
|
166
196
|
// Create temporary references for serialization
|
|
167
197
|
const temporaryReferences = deps.createTemporaryReferenceSet();
|
|
@@ -237,11 +267,22 @@ export function createServerActionBridge(
|
|
|
237
267
|
// abortAllActions() doesn't disrupt the in-progress Flight stream.
|
|
238
268
|
handle.signal.removeEventListener("abort", onHandleAbort);
|
|
239
269
|
|
|
270
|
+
// Did the action call keepClientCache()? If so the deferred invalidation
|
|
271
|
+
// below is suppressed for THIS action (a concurrent directive-free
|
|
272
|
+
// action still invalidates via its own response).
|
|
273
|
+
keepCache = response.headers.get(KEEP_CACHE_HEADER) === "1";
|
|
274
|
+
|
|
240
275
|
// Check for version mismatch - server wants us to reload
|
|
241
276
|
const reloadResult = handleReloadHeader(response, {
|
|
242
277
|
onBlocked: resolveStreamComplete,
|
|
243
|
-
onReload: (url) =>
|
|
244
|
-
log("version mismatch on action, reloading", { reloadUrl: url })
|
|
278
|
+
onReload: (url) => {
|
|
279
|
+
log("version mismatch on action, reloading", { reloadUrl: url });
|
|
280
|
+
// Never-settling terminal (navigates away), so the finally never
|
|
281
|
+
// runs: release the fence here. skipInvalidation — the mismatch
|
|
282
|
+
// short-circuits the action server-side, so nothing mutated and a
|
|
283
|
+
// broadcast would only risk hard-reloading a sibling mid-task.
|
|
284
|
+
finalizeAction(true);
|
|
285
|
+
},
|
|
245
286
|
});
|
|
246
287
|
if (reloadResult) return reloadResult;
|
|
247
288
|
|
|
@@ -253,6 +294,10 @@ export function createServerActionBridge(
|
|
|
253
294
|
if (redirect && redirect !== "blocked" && !handle.signal.aborted) {
|
|
254
295
|
log("action simple redirect", { url: redirect.url });
|
|
255
296
|
handle.complete(undefined);
|
|
297
|
+
// This path returns a never-settling promise, so the finally never
|
|
298
|
+
// runs: invalidate + release the fence here (the mutation committed
|
|
299
|
+
// and we're navigating away). Latched, so the finally is a no-op.
|
|
300
|
+
finalizeAction();
|
|
256
301
|
await dispatchRedirect(redirect.url);
|
|
257
302
|
return new Promise<Response>(() => {});
|
|
258
303
|
}
|
|
@@ -277,6 +322,9 @@ export function createServerActionBridge(
|
|
|
277
322
|
log("action router id mismatch, reloading to re-sync");
|
|
278
323
|
handle.complete(undefined);
|
|
279
324
|
resolveStreamComplete();
|
|
325
|
+
// Never-settling return: release the fence before the reload (the
|
|
326
|
+
// reload resets module state anyway, but stay balanced). Latched.
|
|
327
|
+
finalizeAction();
|
|
280
328
|
window.location.reload();
|
|
281
329
|
return new Promise<Response>(() => {});
|
|
282
330
|
}
|
|
@@ -542,8 +590,9 @@ export function createServerActionBridge(
|
|
|
542
590
|
handle.clearConsolidation();
|
|
543
591
|
|
|
544
592
|
if (scenario.historyKeyChanged) {
|
|
545
|
-
|
|
546
|
-
|
|
593
|
+
// Invalidation is deferred to finalizeAction(); here we only trigger
|
|
594
|
+
// the revalidation refetch of the new route (suppressed on keep).
|
|
595
|
+
if (!scenario.onInterceptRoute && !keepCache) {
|
|
547
596
|
refetchRoute().catch((error) => {
|
|
548
597
|
if (isBackgroundSuppressible(error)) return;
|
|
549
598
|
console.error(
|
|
@@ -555,11 +604,14 @@ export function createServerActionBridge(
|
|
|
555
604
|
break;
|
|
556
605
|
}
|
|
557
606
|
|
|
558
|
-
// Same history key but different pathname - safe to refetch current
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
607
|
+
// Same history key but different pathname - safe to refetch current
|
|
608
|
+
// route. Invalidation is deferred to finalizeAction(); here we only
|
|
609
|
+
// trigger the revalidation refetch (suppressed on keep).
|
|
610
|
+
if (!keepCache) {
|
|
611
|
+
await refetchRoute({
|
|
612
|
+
interceptSourceUrl: store.getInterceptSourceUrl(),
|
|
613
|
+
});
|
|
614
|
+
}
|
|
563
615
|
break;
|
|
564
616
|
}
|
|
565
617
|
|
|
@@ -567,8 +619,11 @@ export function createServerActionBridge(
|
|
|
567
619
|
console.warn(
|
|
568
620
|
`[Browser] Missing segments after action (HMR detected), refetching...`,
|
|
569
621
|
);
|
|
622
|
+
// Repair (not revalidation), so ungated on keepCache: a keep action
|
|
623
|
+
// resolving last must discharge a directive-free sibling's repair.
|
|
624
|
+
// See the keep row in docs/design/rango-state-cookie.md (the all-keep
|
|
625
|
+
// edge, and the benign re-mark-stale-after-refetch end-state delta).
|
|
570
626
|
await refetchRoute({ interceptSourceUrl });
|
|
571
|
-
store.broadcastCacheInvalidation();
|
|
572
627
|
break;
|
|
573
628
|
}
|
|
574
629
|
|
|
@@ -585,11 +640,11 @@ export function createServerActionBridge(
|
|
|
585
640
|
// Clear consolidation tracking before fetch
|
|
586
641
|
handle.clearConsolidation();
|
|
587
642
|
|
|
643
|
+
// Ungated on keepCache, same as hmr-missing above (see the keep row).
|
|
588
644
|
await refetchRoute({
|
|
589
645
|
segments: segmentsToSend,
|
|
590
646
|
interceptSourceUrl,
|
|
591
647
|
});
|
|
592
|
-
store.broadcastCacheInvalidation();
|
|
593
648
|
break;
|
|
594
649
|
}
|
|
595
650
|
|
|
@@ -653,7 +708,9 @@ export function createServerActionBridge(
|
|
|
653
708
|
fullSegments,
|
|
654
709
|
currentHandleData,
|
|
655
710
|
);
|
|
656
|
-
|
|
711
|
+
// Invalidation deferred to finalizeAction() (runs after this caches
|
|
712
|
+
// the fresh segments), suppressed when the action called
|
|
713
|
+
// keepClientCache().
|
|
657
714
|
break;
|
|
658
715
|
}
|
|
659
716
|
}
|
|
@@ -661,6 +718,11 @@ export function createServerActionBridge(
|
|
|
661
718
|
handle.complete(returnData);
|
|
662
719
|
return returnData;
|
|
663
720
|
} finally {
|
|
721
|
+
// The single deferred invalidation + fence release for this action. Runs
|
|
722
|
+
// for every terminal that settles (normal, navigated-away, error, abort,
|
|
723
|
+
// intercept, concurrent); the SPA-redirect paths above already ran it.
|
|
724
|
+
// Latched, so it fires exactly once.
|
|
725
|
+
finalizeAction();
|
|
664
726
|
handle[Symbol.dispose]();
|
|
665
727
|
}
|
|
666
728
|
}
|
package/src/browser/types.ts
CHANGED
|
@@ -14,7 +14,6 @@ import type { RenderSegmentsOptions } from "../segment-system.js";
|
|
|
14
14
|
export interface RscPayload<TMetadata = RscMetadata> {
|
|
15
15
|
metadata?: TMetadata;
|
|
16
16
|
returnValue?: ActionResult;
|
|
17
|
-
formState?: unknown;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
/**
|
|
@@ -71,6 +70,12 @@ export interface RscMetadata {
|
|
|
71
70
|
* Sent on initial render so the browser can configure its cache duration.
|
|
72
71
|
*/
|
|
73
72
|
prefetchCacheTTL?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Server-resolved rango state cookie name (`{prefix}_{routerId}`). The client
|
|
75
|
+
* reads it verbatim and binds the rango state cookie to it; composition
|
|
76
|
+
* happens only server-side.
|
|
77
|
+
*/
|
|
78
|
+
stateCookieName?: string;
|
|
74
79
|
/**
|
|
75
80
|
* Theme configuration from router.
|
|
76
81
|
* Included when theme is enabled in router config.
|
|
@@ -433,9 +438,9 @@ export interface NavigationStore {
|
|
|
433
438
|
hasHistoryCache(historyKey: string): boolean;
|
|
434
439
|
updateCacheHandleData(historyKey: string, handleData: HandleData): void;
|
|
435
440
|
markCacheAsStale(): void;
|
|
441
|
+
markHistoryCacheStale(): void;
|
|
436
442
|
markCacheAsStaleAndBroadcast(): void;
|
|
437
443
|
clearHistoryCache(): void;
|
|
438
|
-
broadcastCacheInvalidation(): void;
|
|
439
444
|
|
|
440
445
|
// Cross-tab refresh callback (set by navigation bridge)
|
|
441
446
|
setCrossTabRefreshCallback(callback: () => void): void;
|
|
@@ -17,11 +17,10 @@ export function validateRedirectOrigin(
|
|
|
17
17
|
);
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
|
-
//
|
|
21
|
-
// This normalizes protocol-relative and other
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
: target.pathname + target.search + target.hash;
|
|
20
|
+
// Origin matched above, so target.href is same-origin: return the
|
|
21
|
+
// canonical full href. This normalizes protocol-relative and other
|
|
22
|
+
// ambiguous forms.
|
|
23
|
+
return target.href;
|
|
25
24
|
} catch {
|
|
26
25
|
console.error(`[rango] Redirect blocked: invalid URL "${url}"`);
|
|
27
26
|
return null;
|
package/src/build/route-trie.ts
CHANGED
|
@@ -63,6 +63,9 @@ export interface TrieNode {
|
|
|
63
63
|
* @param routeAncestry - Map of route name to ancestry shortCodes
|
|
64
64
|
* @param routeToStaticPrefix - Map of route name to its entry's staticPrefix
|
|
65
65
|
* @param routeTrailingSlash - Optional map of route name to trailing slash mode
|
|
66
|
+
* @param prerenderRouteNames - Optional set of prerendered route names (sets leaf.pr)
|
|
67
|
+
* @param passthroughRouteNames - Optional set of passthrough route names (sets leaf.pt)
|
|
68
|
+
* @param responseTypeRoutes - Optional map of route name to response type (sets leaf.rt)
|
|
66
69
|
*/
|
|
67
70
|
export function buildRouteTrie(
|
|
68
71
|
routeManifest: Record<string, string>,
|
|
@@ -37,12 +37,15 @@ export function formatRouteEntry(
|
|
|
37
37
|
): string {
|
|
38
38
|
const hasSearch = search && Object.keys(search).length > 0;
|
|
39
39
|
|
|
40
|
+
// JSON.stringify the pattern and search values so backslashes and quotes in a
|
|
41
|
+
// route pattern (e.g. a custom regex constraint) survive interpolation into
|
|
42
|
+
// both the type-level string and the runtime NamedRoutes value.
|
|
40
43
|
if (!hasSearch) {
|
|
41
|
-
return ` ${key}:
|
|
44
|
+
return ` ${key}: ${JSON.stringify(pattern)},`;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
const searchBody = Object.entries(search!)
|
|
45
|
-
.map(([k, v]) => `${k}:
|
|
48
|
+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
46
49
|
.join(", ");
|
|
47
|
-
return ` ${key}: { path:
|
|
50
|
+
return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
|
|
48
51
|
}
|
|
@@ -617,9 +617,6 @@ export function writeCombinedRouteTypes(
|
|
|
617
617
|
? readFileSync(outPath, "utf-8")
|
|
618
618
|
: null;
|
|
619
619
|
|
|
620
|
-
// When the static parser can't extract routes (e.g. callback-style urls()),
|
|
621
|
-
// write an empty placeholder so the build-time transform's injected import
|
|
622
|
-
// resolves. Runtime discovery will overwrite this with the real routes.
|
|
623
620
|
if (Object.keys(result.routes).length === 0) {
|
|
624
621
|
if (!existing) {
|
|
625
622
|
const emptySource = generateRouteTypesSource({});
|
|
@@ -635,11 +632,6 @@ export function writeCombinedRouteTypes(
|
|
|
635
632
|
hasSearchSchemas ? result.searchSchemas : undefined,
|
|
636
633
|
);
|
|
637
634
|
if (existing !== source) {
|
|
638
|
-
// On initial dev startup, don't overwrite a file from runtime discovery
|
|
639
|
-
// (which has all dynamic routes) with a smaller set from the static
|
|
640
|
-
// parser. The static parser can't see routes generated by Array.from()
|
|
641
|
-
// or other dynamic code. During HMR (file watcher), always write so
|
|
642
|
-
// newly added routes appear immediately.
|
|
643
635
|
if (opts?.preserveIfLarger && existing) {
|
|
644
636
|
const existingCount = countPublicRouteEntries(existing);
|
|
645
637
|
const newCount = Object.keys(result.routes).filter(
|
|
@@ -69,24 +69,6 @@ export function computeExpiration(
|
|
|
69
69
|
return { staleAt, expiresAt };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// Cache Key Resolution
|
|
74
|
-
// ============================================================================
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Resolve cache key using the 3-tier priority:
|
|
78
|
-
* 1. keyFn (full override from route/loader cache options)
|
|
79
|
-
* 2. store.keyGenerator (modifies default key)
|
|
80
|
-
* 3. defaultKey (used when neither keyFn nor keyGenerator is provided)
|
|
81
|
-
*
|
|
82
|
-
* Errors from keyFn and store.keyGenerator propagate to the caller.
|
|
83
|
-
* Cache identity is correctness-critical: if explicit key logic throws,
|
|
84
|
-
* silently remapping to a different key could cause cache collisions or
|
|
85
|
-
* serve stale/wrong data. Callers must handle the error or let it surface.
|
|
86
|
-
*
|
|
87
|
-
* Uses _getRequestContext (non-throwing) so that calls outside ALS
|
|
88
|
-
* (e.g. build-time) gracefully fall back to defaultKey.
|
|
89
|
-
*/
|
|
90
72
|
export async function resolveCacheKey(
|
|
91
73
|
keyFn: ((ctx: RequestContext) => string | Promise<string>) | undefined,
|
|
92
74
|
store: SegmentCacheStore | null,
|
|
@@ -95,34 +77,17 @@ export async function resolveCacheKey(
|
|
|
95
77
|
): Promise<string> {
|
|
96
78
|
const requestCtx = _getRequestContext();
|
|
97
79
|
|
|
98
|
-
// Priority 1: Route/loader-level key function (full override)
|
|
99
80
|
if (keyFn && requestCtx) {
|
|
100
81
|
return await keyFn(requestCtx);
|
|
101
82
|
}
|
|
102
83
|
|
|
103
|
-
// Priority 2: Store-level keyGenerator (modifies default key)
|
|
104
84
|
if (store?.keyGenerator && requestCtx) {
|
|
105
85
|
return await store.keyGenerator(requestCtx, defaultKey);
|
|
106
86
|
}
|
|
107
87
|
|
|
108
|
-
// Priority 3: Default key (no custom key logic provided)
|
|
109
88
|
return defaultKey;
|
|
110
89
|
}
|
|
111
90
|
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// Cache Tag Resolution
|
|
114
|
-
// ============================================================================
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Resolve cache tags from a tags option (static array or function of ctx).
|
|
118
|
-
*
|
|
119
|
-
* Fails open: a thrown tag callback falls back to no tags rather than
|
|
120
|
-
* aborting the request. Tags are additive metadata (not identity), so a
|
|
121
|
-
* missing tag does not cause cache collisions, only a missed invalidation.
|
|
122
|
-
*
|
|
123
|
-
* Shared by the cache() DSL (cache-scope) and loader caching (loader-cache)
|
|
124
|
-
* so tag resolution behaves identically across every cache axis.
|
|
125
|
-
*/
|
|
126
91
|
export function resolveTagsOption<TEnv>(
|
|
127
92
|
tags: string[] | ((ctx: RequestContext<TEnv>) => string[]) | undefined,
|
|
128
93
|
ctx: RequestContext<TEnv> | undefined,
|
|
@@ -131,10 +96,6 @@ export function resolveTagsOption<TEnv>(
|
|
|
131
96
|
if (!tags) return undefined;
|
|
132
97
|
if (typeof tags === "function") {
|
|
133
98
|
if (!ctx) {
|
|
134
|
-
// A dynamic tags function needs the request context to run. Without it
|
|
135
|
-
// (e.g. resolved outside a request, at build/prerender time) the entry is
|
|
136
|
-
// cached UNTAGGED and can never be invalidated - surface that rather than
|
|
137
|
-
// silently dropping the tags, matching the thrown-callback branch below.
|
|
138
99
|
console.warn(
|
|
139
100
|
`[${label}] Dynamic tags function present but no request context; ` +
|
|
140
101
|
`caching without tags (this entry will not be tag-invalidatable).`,
|
|
@@ -167,25 +128,10 @@ function normalizeTagList(tags: string[]): string[] | undefined {
|
|
|
167
128
|
return out.length > 0 ? out : undefined;
|
|
168
129
|
}
|
|
169
130
|
|
|
170
|
-
// ============================================================================
|
|
171
|
-
// Cache Store Resolution
|
|
172
|
-
// ============================================================================
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Resolve cache store from the 2-tier priority:
|
|
176
|
-
* 1. Explicit store from cache options
|
|
177
|
-
* 2. App-level store from request context
|
|
178
|
-
*/
|
|
179
131
|
export function resolveCacheStore(
|
|
180
132
|
explicitStore: SegmentCacheStore | undefined,
|
|
181
133
|
): SegmentCacheStore | null {
|
|
182
134
|
if (explicitStore) {
|
|
183
|
-
// Register explicit per-scope stores so updateTag()/revalidateTag() can
|
|
184
|
-
// reach them. This is the single chokepoint every cache axis (segment,
|
|
185
|
-
// response, loader) resolves through, so registering here covers them all
|
|
186
|
-
// eagerly - no dependence on whether a tagged write has happened yet. The
|
|
187
|
-
// app-level store is intentionally not registered (always reachable via
|
|
188
|
-
// ctx._cacheStore).
|
|
189
135
|
registerExplicitTaggedStore(explicitStore);
|
|
190
136
|
return explicitStore;
|
|
191
137
|
}
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
runWithCacheTagScope,
|
|
47
47
|
} from "./cache-tag.js";
|
|
48
48
|
import { reportCacheError } from "./cache-error.js";
|
|
49
|
+
import type { CacheItemResult } from "./types.js";
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Convert encodeReply result to a stable string key.
|
|
@@ -84,6 +85,11 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
84
85
|
// cacheTag() call inside the function degrades to a no-op rather than
|
|
85
86
|
// throwing "must be called inside a use cache function" - adopting cacheTag()
|
|
86
87
|
// must not hard-fail in apps/tests without an item-capable cache configured.
|
|
88
|
+
// Note: the INSIDE_CACHE_EXEC guard (cookies()/headers()/ctx.set() rejection)
|
|
89
|
+
// is intentionally NOT stamped here. It is a cached-path-only check; in the
|
|
90
|
+
// bypass the body actually executes, so the guarded side effects take effect
|
|
91
|
+
// and nothing is lost on a (non-existent) hit. Same applies to the
|
|
92
|
+
// non-serializable-args bypass below.
|
|
87
93
|
if (!store?.getItem) {
|
|
88
94
|
const scoped = runWithCacheTagScope(() => fn.apply(this, args));
|
|
89
95
|
const result = await scoped.result;
|
|
@@ -185,23 +191,29 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
185
191
|
// Cache lookup
|
|
186
192
|
const cached = await store.getItem(cacheKey);
|
|
187
193
|
|
|
194
|
+
// Serve a cached entry on the hit path: deserialize the stored value,
|
|
195
|
+
// replay handle data (gated on tainted args), and surface the entry's tags
|
|
196
|
+
// to the request set (the function did not re-run, so its runtime cacheTag()
|
|
197
|
+
// tags are only available from the stored entry). Shared by the fresh-hit
|
|
198
|
+
// and stale-hit branches; the only divergence is the stale branch scheduling
|
|
199
|
+
// background revalidation, which it does after this returns.
|
|
200
|
+
const serveCached = async (entry: CacheItemResult): Promise<any> => {
|
|
201
|
+
const result = await deserializeResult(entry.value);
|
|
202
|
+
if (entry.handles && hasTaintedArgs) {
|
|
203
|
+
const handleStore = requestCtx?._handleStore;
|
|
204
|
+
if (handleStore) {
|
|
205
|
+
const r = await decodeHandles(entry.handles);
|
|
206
|
+
if (r) restoreHandles(r, handleStore);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
recordRequestTags(entry.tags, requestCtx);
|
|
210
|
+
return result;
|
|
211
|
+
};
|
|
212
|
+
|
|
188
213
|
if (cached && !cached.shouldRevalidate) {
|
|
189
214
|
// Fresh hit: deserialize and return
|
|
190
215
|
try {
|
|
191
|
-
|
|
192
|
-
// Restore handle data if present
|
|
193
|
-
if (cached.handles && hasTaintedArgs) {
|
|
194
|
-
const handleStore = requestCtx?._handleStore;
|
|
195
|
-
if (handleStore) {
|
|
196
|
-
const r = await decodeHandles(cached.handles);
|
|
197
|
-
if (r) restoreHandles(r, handleStore);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// Surface the hit's tags to the request set so a document built from a
|
|
201
|
-
// cached item is still tagged (the function did not re-run, so its
|
|
202
|
-
// runtime cacheTag() tags are only available from the stored entry).
|
|
203
|
-
recordRequestTags(cached.tags, requestCtx);
|
|
204
|
-
return result;
|
|
216
|
+
return await serveCached(cached);
|
|
205
217
|
} catch (error) {
|
|
206
218
|
// The stored value is corrupt/partial (failed RSC deserialize). Report
|
|
207
219
|
// it, then fall through to fresh execution - the miss path below re-runs
|
|
@@ -217,16 +229,7 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
217
229
|
if (cached?.shouldRevalidate) {
|
|
218
230
|
// Stale hit: return stale value, revalidate in background
|
|
219
231
|
try {
|
|
220
|
-
const result = await
|
|
221
|
-
if (cached.handles && hasTaintedArgs) {
|
|
222
|
-
const handleStore = requestCtx?._handleStore;
|
|
223
|
-
if (handleStore) {
|
|
224
|
-
const r = await decodeHandles(cached.handles);
|
|
225
|
-
if (r) restoreHandles(r, handleStore);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Tag the request with the stale entry's tags (see fresh-hit note).
|
|
229
|
-
recordRequestTags(cached.tags, requestCtx);
|
|
232
|
+
const result = await serveCached(cached);
|
|
230
233
|
// Background revalidation — must capture handles if tainted args present.
|
|
231
234
|
// Use an isolated handle store so background pushes don't pollute the
|
|
232
235
|
// live response or throw LateHandlePushError on the completed store.
|
package/src/cache/cache-scope.ts
CHANGED
|
@@ -34,12 +34,6 @@ import {
|
|
|
34
34
|
} from "./cache-policy.js";
|
|
35
35
|
import type { RequestContext } from "../server/request-context.js";
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Resolve tags for a cache() boundary from its config (static array or
|
|
39
|
-
* function of ctx). Thin wrapper over the shared resolveTagsOption so the
|
|
40
|
-
* cache() DSL and loader caching resolve tags identically.
|
|
41
|
-
* @internal
|
|
42
|
-
*/
|
|
43
37
|
export function resolveCacheTags(
|
|
44
38
|
config: PartialCacheOptions | false,
|
|
45
39
|
ctx: RequestContext | undefined,
|
|
@@ -54,17 +48,6 @@ function debugCacheLog(message: string): void {
|
|
|
54
48
|
}
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// Key Generation (internal)
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Generate cache key base from host, pathname, route params, and search params.
|
|
63
|
-
* Host is included to prevent cross-host cache collisions on shared stores.
|
|
64
|
-
* Route params and search params are sorted alphabetically for deterministic keys.
|
|
65
|
-
* Internal _rsc* and __* query params are excluded.
|
|
66
|
-
* @internal
|
|
67
|
-
*/
|
|
68
51
|
function getCacheKeyBase(
|
|
69
52
|
host: string,
|
|
70
53
|
pathname: string,
|
|
@@ -80,16 +63,6 @@ function getCacheKeyBase(
|
|
|
80
63
|
return key;
|
|
81
64
|
}
|
|
82
65
|
|
|
83
|
-
/**
|
|
84
|
-
* Generate default cache key for a route request.
|
|
85
|
-
* Includes pathname, route params, and user-facing search params for
|
|
86
|
-
* correct scoping. Internal _rsc* params are excluded.
|
|
87
|
-
* Includes request type prefix since they produce different segment sets:
|
|
88
|
-
* - doc: document requests (full page load)
|
|
89
|
-
* - partial: navigation requests (client-side navigation)
|
|
90
|
-
* - intercept: intercept navigation (modal/overlay routes)
|
|
91
|
-
* @internal
|
|
92
|
-
*/
|
|
93
66
|
function getDefaultRouteCacheKey(
|
|
94
67
|
pathname: string,
|
|
95
68
|
params?: Record<string, string>,
|
package/src/cache/cache-tag.ts
CHANGED
|
@@ -18,35 +18,11 @@ import {
|
|
|
18
18
|
|
|
19
19
|
const cacheTagStorage = new AsyncLocalStorage<Set<string>>();
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* Normalize a tag for storage.
|
|
23
|
-
*
|
|
24
|
-
* Returns the tag unchanged if usable, or null if it is empty/whitespace-only
|
|
25
|
-
* (dropped consistently in every environment - an empty tag matches nothing).
|
|
26
|
-
*
|
|
27
|
-
* Backend-specific constraints are intentionally NOT enforced here so the tag
|
|
28
|
-
* primitive stays backend-agnostic. In particular, the CFCacheStore
|
|
29
|
-
* encodeURIComponent's tags at serialization time so commas/spaces/non-Latin1
|
|
30
|
-
* characters cannot corrupt the comma-delimited Cloudflare Cache-Tag header or
|
|
31
|
-
* the HTTP marker header (it does not reject them). Keep tags short and
|
|
32
|
-
* low-cardinality: a tag's KV marker key must stay under Cloudflare's 512-byte
|
|
33
|
-
* limit, and a Cache-Tag value under 1024 bytes. The in-memory store has no
|
|
34
|
-
* such limitations.
|
|
35
|
-
*
|
|
36
|
-
* @internal
|
|
37
|
-
*/
|
|
38
21
|
export function normalizeTag(tag: string): string | null {
|
|
39
22
|
if (!tag || !tag.trim()) return null;
|
|
40
23
|
return tag;
|
|
41
24
|
}
|
|
42
25
|
|
|
43
|
-
/**
|
|
44
|
-
* Normalize a tag collection: drop empty/whitespace-only tags so the WRITE path
|
|
45
|
-
* matches the invalidate path (updateTag/revalidateTag/cacheTag all normalize).
|
|
46
|
-
* Does not deduplicate - callers that need that wrap with a Set.
|
|
47
|
-
*
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
26
|
export function normalizeTags(tags: Iterable<string>): string[] {
|
|
51
27
|
const out: string[] = [];
|
|
52
28
|
for (const tag of tags) {
|
|
@@ -89,19 +65,6 @@ export function cacheTag(...tags: string[]): void {
|
|
|
89
65
|
}
|
|
90
66
|
}
|
|
91
67
|
|
|
92
|
-
/**
|
|
93
|
-
* Record `tags` into the request-scoped tag set (ctx._requestTags), the union of
|
|
94
|
-
* every cache tag resolved while producing the response. The document cache reads
|
|
95
|
-
* this after the render settles so a full-page entry is tagged with everything its
|
|
96
|
-
* content used, making it invalidatable by updateTag()/revalidateTag().
|
|
97
|
-
*
|
|
98
|
-
* Called at the tag-resolution sites: "use cache" stores (cache-runtime, both the
|
|
99
|
-
* miss and read/hit paths), loader cache (cache-policy/loader-cache), and segment
|
|
100
|
-
* cache() (cache-scope). Writes the field directly (not via ctx.set()) so it does
|
|
101
|
-
* not trip the cache-scope side-effect guard, mirroring cacheTag() itself.
|
|
102
|
-
*
|
|
103
|
-
* @internal
|
|
104
|
-
*/
|
|
105
68
|
export function recordRequestTags(
|
|
106
69
|
tags: Iterable<string> | undefined,
|
|
107
70
|
ctx: RequestContext | undefined = _getRequestContext(),
|