@rangojs/router 0.26.6 → 0.28.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/bin/rango.js +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/action-coordinator.d.ts +0 -4
- package/dist/types/browser/action-fence.d.ts +11 -3
- package/dist/types/browser/event-controller.d.ts +20 -1
- package/dist/types/browser/history-state.d.ts +7 -9
- package/dist/types/browser/intercept-utils.d.ts +23 -0
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/segment-reconciler.d.ts +6 -4
- package/dist/types/browser/types.d.ts +2 -1
- package/dist/types/browser/wire-protocol.d.ts +25 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/tag-invalidation.d.ts +10 -0
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +4 -6
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +4 -4
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +12 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
- package/dist/types/router/match-middleware/cache-lookup.d.ts +35 -87
- package/dist/types/router/match-middleware/cache-store.d.ts +2 -2
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/revalidation.d.ts +6 -1
- package/dist/types/router/router-context.d.ts +9 -63
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +93 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +10 -0
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/serve-shell-request.d.ts +7 -0
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +9 -1
- package/dist/types/types/handler-context.d.ts +18 -7
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +34 -0
- package/dist/vite/index.js +3809 -3710
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +2 -1
- package/skills/document-cache/SKILL.md +18 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +53 -13
- package/skills/layout/SKILL.md +11 -7
- package/skills/loader/SKILL.md +30 -13
- package/skills/middleware/SKILL.md +19 -1
- package/skills/migrate-nextjs/SKILL.md +5 -4
- package/skills/parallel/SKILL.md +5 -4
- package/skills/ppr/SKILL.md +19 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +23 -11
- package/skills/server-actions/SKILL.md +51 -6
- package/skills/testing/predicates.md +1 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/ux-architecture/SKILL.md +3 -2
- package/src/__internal.ts +7 -1
- package/src/browser/action-coordinator.ts +2 -13
- package/src/browser/action-fence.ts +11 -3
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +42 -3
- package/src/browser/history-state.ts +15 -13
- package/src/browser/intercept-utils.ts +51 -0
- package/src/browser/navigation-bridge.ts +34 -30
- package/src/browser/navigation-client.ts +21 -33
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +7 -11
- package/src/browser/partial-update.ts +8 -0
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/rsc-router.tsx +17 -2
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +265 -127
- package/src/browser/types.ts +2 -1
- package/src/browser/wire-protocol.ts +41 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +110 -50
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +42 -15
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +21 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +4 -7
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +4 -4
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +77 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +4 -5
- package/src/router/match-context.ts +33 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +41 -53
- package/src/router/match-middleware/cache-lookup.ts +680 -720
- package/src/router/match-middleware/cache-store.ts +14 -12
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/match-result.ts +1 -20
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -3
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/revalidation.ts +33 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +14 -117
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/loader-cache.ts +14 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +137 -0
- package/src/router/segment-resolution/revalidation.ts +897 -776
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +10 -0
- package/src/router.ts +12 -13
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +757 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +172 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +3 -1
- package/src/rsc/shell-capture.ts +212 -169
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +26 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/ssr/ssr-root.tsx +1 -0
- package/src/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +37 -32
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +21 -7
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +35 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from "../deps/rsc-client.js";
|
|
23
23
|
import {
|
|
24
24
|
getRequestContext,
|
|
25
|
-
|
|
25
|
+
type RequestContext,
|
|
26
26
|
} from "../server/request-context.js";
|
|
27
27
|
import { isUnderTestRunner } from "../runtime-env.js";
|
|
28
28
|
import {
|
|
@@ -34,7 +34,12 @@ import {
|
|
|
34
34
|
} from "./taint.js";
|
|
35
35
|
|
|
36
36
|
export { isCachedFunction };
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
serializeResult,
|
|
39
|
+
deserializeResult,
|
|
40
|
+
isUnsupportedValueError,
|
|
41
|
+
teeResult,
|
|
42
|
+
} from "./segment-codec.js";
|
|
38
43
|
import {
|
|
39
44
|
appendHandles,
|
|
40
45
|
encodeHandles,
|
|
@@ -70,6 +75,7 @@ import {
|
|
|
70
75
|
executionStart,
|
|
71
76
|
invalidatedSince,
|
|
72
77
|
predatesInvalidation,
|
|
78
|
+
runAsBackgroundRefresh,
|
|
73
79
|
type ExecutionStart,
|
|
74
80
|
} from "./tag-invalidation.js";
|
|
75
81
|
import type { CacheItemResult } from "./types.js";
|
|
@@ -384,6 +390,23 @@ function refreshView(arg: unknown): unknown {
|
|
|
384
390
|
});
|
|
385
391
|
}
|
|
386
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Run `fn` inside a "use cache" execution's scopes: its exec scope
|
|
395
|
+
* (cookies()/headers() reads and ctx writes refuse) and its tag scope
|
|
396
|
+
* (cacheTag() and nested calls tag the entry, #980), `recorded` when given.
|
|
397
|
+
* The body runs here, and so does the value's encode: it renders the server
|
|
398
|
+
* components in the value, which are part of the entry. Scar: the encode ran
|
|
399
|
+
* in the tag scope only, so a returned component's cookies() read went
|
|
400
|
+
* through and the entry served the first visitor's value to the next.
|
|
401
|
+
*/
|
|
402
|
+
function inExecution<T>(
|
|
403
|
+
scope: CacheExecScope,
|
|
404
|
+
fn: () => T,
|
|
405
|
+
recorded?: Set<string>,
|
|
406
|
+
): { result: T; tags: Set<string> } {
|
|
407
|
+
return runWithCacheTagScope(() => runWithCacheExecScope(fn, scope), recorded);
|
|
408
|
+
}
|
|
409
|
+
|
|
387
410
|
// ============================================================================
|
|
388
411
|
// Core: registerCachedFunction
|
|
389
412
|
// ============================================================================
|
|
@@ -459,14 +482,6 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
459
482
|
const entryTags = (recorded: Set<string>): string[] => [
|
|
460
483
|
...new Set(normalizeTags([...(profile.tags ?? []), ...recorded])),
|
|
461
484
|
];
|
|
462
|
-
// The value's Flight encode runs back inside its execution's tag scope:
|
|
463
|
-
// it renders the server components in the value, and a "use cache" call
|
|
464
|
-
// or cacheTag() among them belongs to this entry (#980).
|
|
465
|
-
const inTagScope = <T>(
|
|
466
|
-
recorded: Set<string>,
|
|
467
|
-
encode: () => Promise<T>,
|
|
468
|
-
): Promise<T> => runWithCacheTagScope(encode, recorded).result;
|
|
469
|
-
|
|
470
485
|
// Separate request-scoped args (ctx, a Request, the request's env) from
|
|
471
486
|
// key-generating args. For those that carry route context (ctx params,
|
|
472
487
|
// pathname, searchParams; a Request's URL), extract serializable values
|
|
@@ -717,25 +732,24 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
717
732
|
// reads the ambient getRequestContext() (e.g.
|
|
718
733
|
// getRequestContext().env.ApiKey) resolves during the background
|
|
719
734
|
// revalidation instead of throwing "called outside of a request
|
|
720
|
-
// context".
|
|
721
|
-
// synchronous kickoff; its async continuations inherit it.
|
|
735
|
+
// context". runAsBackgroundRefresh sets the store for fn's
|
|
736
|
+
// synchronous kickoff; its async continuations inherit it. No
|
|
737
|
+
// visitor made the invalidations it runs: they set no
|
|
738
|
+
// fresh-reads cookie.
|
|
722
739
|
//
|
|
723
740
|
// The span opens inside the request ALS and wraps the WHOLE task —
|
|
724
741
|
// fn kickoff through the store write — so its platform spans nest
|
|
725
742
|
// under rango.background and a slow or failing setItem is part of
|
|
726
743
|
// the traced revalidation, not an untraced tail.
|
|
727
|
-
await
|
|
744
|
+
await runAsBackgroundRefresh(requestCtx, () =>
|
|
728
745
|
observePhase(
|
|
729
746
|
PHASES.background("use-cache-revalidation"),
|
|
730
747
|
async () => {
|
|
731
748
|
const refreshArgs = args.map(refreshView);
|
|
732
749
|
// The refresh reads its data from here on (#973, #977).
|
|
733
750
|
const start = executionStart();
|
|
734
|
-
const scoped =
|
|
735
|
-
|
|
736
|
-
() => fn.apply(this, refreshArgs),
|
|
737
|
-
bgScope,
|
|
738
|
-
),
|
|
751
|
+
const scoped = inExecution(bgScope, () =>
|
|
752
|
+
fn.apply(this, refreshArgs),
|
|
739
753
|
);
|
|
740
754
|
const freshResult = await scoped.result;
|
|
741
755
|
bgCapture?.stop();
|
|
@@ -744,17 +758,22 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
744
758
|
const onFlightError = (error: unknown): void => {
|
|
745
759
|
flightErrors.push(error);
|
|
746
760
|
};
|
|
747
|
-
const serialized = await
|
|
748
|
-
|
|
749
|
-
|
|
761
|
+
const serialized = await inExecution(
|
|
762
|
+
bgScope,
|
|
763
|
+
() => serializeResult(freshResult, onFlightError),
|
|
764
|
+
scoped.tags,
|
|
765
|
+
).result;
|
|
750
766
|
if (serialized !== null) {
|
|
751
767
|
const encodedHandles = bgCapture
|
|
752
|
-
? await
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
768
|
+
? await inExecution(
|
|
769
|
+
bgScope,
|
|
770
|
+
() =>
|
|
771
|
+
encodeHandles(
|
|
772
|
+
bgCapture.capture.data,
|
|
773
|
+
onFlightError,
|
|
774
|
+
),
|
|
775
|
+
scoped.tags,
|
|
776
|
+
).result
|
|
758
777
|
: undefined;
|
|
759
778
|
// An error row would replace the stale entry; the catch
|
|
760
779
|
// below reports it and the stale entry keeps serving.
|
|
@@ -921,21 +940,24 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
921
940
|
// deliberately NOT stamped: a property on that SHARED object made every
|
|
922
941
|
// parallel read on the request throw for the cached body's whole
|
|
923
942
|
// execution window (a 2s cached fetch poisoned a sibling loader's
|
|
924
|
-
// cookies() — same hazard class as issue #684, plan 010).
|
|
943
|
+
// cookies() — same hazard class as issue #684, plan 010). Not as an
|
|
944
|
+
// argument either (getRequestContext(), any pass: a derived pass inherits
|
|
945
|
+
// `_requestRoot`): a derived pass's writers that it does not route to a
|
|
946
|
+
// sink check the request's own context (server/render-pass.ts), so the
|
|
947
|
+
// stamp failed a shell capture's or a background re-render's writes while
|
|
948
|
+
// a foreground body held it.
|
|
925
949
|
const taintedArgs: unknown[] = [];
|
|
926
950
|
for (const arg of args) {
|
|
927
|
-
if (isTainted(arg)) {
|
|
951
|
+
if (isTainted(arg) && !(arg as RequestContext)._requestRoot) {
|
|
928
952
|
stampCacheExec(arg as object);
|
|
929
953
|
taintedArgs.push(arg);
|
|
930
954
|
}
|
|
931
955
|
}
|
|
932
956
|
|
|
933
957
|
let result: any;
|
|
934
|
-
let scoped: ReturnType<typeof
|
|
958
|
+
let scoped: ReturnType<typeof inExecution>;
|
|
935
959
|
try {
|
|
936
|
-
scoped =
|
|
937
|
-
runWithCacheExecScope(() => fn.apply(this, args), execScope),
|
|
938
|
-
);
|
|
960
|
+
scoped = inExecution(execScope, () => fn.apply(this, args));
|
|
939
961
|
result = await scoped.result;
|
|
940
962
|
} catch (execError) {
|
|
941
963
|
// The function threw: drop the in-flight entry and reject any waiters so
|
|
@@ -957,25 +979,39 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
957
979
|
const recorded = scoped!.tags;
|
|
958
980
|
recordRequestTags(entryTags(recorded), requestCtx);
|
|
959
981
|
|
|
960
|
-
//
|
|
961
|
-
//
|
|
962
|
-
//
|
|
963
|
-
//
|
|
982
|
+
// Encode the value ONCE, inside the execution's scopes, and hand the
|
|
983
|
+
// caller a decoded copy of it, as a hit does. The caller rendering
|
|
984
|
+
// `result` itself would share its lazy state (a React.lazy, a memoizing
|
|
985
|
+
// thenable) with the encode: whichever render reached it first decided
|
|
986
|
+
// its value for both, so a read the caller's render made outside the
|
|
987
|
+
// scope was stored for the next visitor. Then resolve the in-flight
|
|
988
|
+
// envelope so concurrent followers serve a synthetic hit, and persist.
|
|
989
|
+
// When waitUntil is available the leader waits for the copy's root row
|
|
990
|
+
// only, not for the rest of the encode or the store write.
|
|
991
|
+
const flightErrors: unknown[] = [];
|
|
992
|
+
const onFlightError = (error: unknown): void => {
|
|
993
|
+
flightErrors.push(error);
|
|
994
|
+
};
|
|
995
|
+
const encoded = inExecution(
|
|
996
|
+
execScope,
|
|
997
|
+
() => teeResult(result, onFlightError),
|
|
998
|
+
recorded,
|
|
999
|
+
).result;
|
|
964
1000
|
const finalizeAndWrite = async (): Promise<void> => {
|
|
965
1001
|
let serialized: string | null;
|
|
966
1002
|
let encodedHandles: string | undefined;
|
|
967
|
-
const flightErrors: unknown[] = [];
|
|
968
|
-
const onFlightError = (error: unknown): void => {
|
|
969
|
-
flightErrors.push(error);
|
|
970
|
-
};
|
|
971
1003
|
try {
|
|
972
|
-
serialized = await
|
|
973
|
-
|
|
974
|
-
|
|
1004
|
+
serialized = encoded ? await encoded.serialized : null;
|
|
1005
|
+
// The returned tree renders only in the encode, so its cacheTag()
|
|
1006
|
+
// calls tag the entry alone: record the entry's tags onto the
|
|
1007
|
+
// request, as a hit does.
|
|
1008
|
+
recordRequestTags(entryTags(recorded), requestCtx);
|
|
975
1009
|
encodedHandles = capture
|
|
976
|
-
? await
|
|
977
|
-
|
|
978
|
-
|
|
1010
|
+
? await inExecution(
|
|
1011
|
+
execScope,
|
|
1012
|
+
() => encodeHandles(capture.capture.data, onFlightError),
|
|
1013
|
+
recorded,
|
|
1014
|
+
).result
|
|
979
1015
|
: undefined;
|
|
980
1016
|
// Flight encodes an async component that throws, or a rejected
|
|
981
1017
|
// promise in the result or a handle value, as an error row and
|
|
@@ -986,10 +1022,17 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
986
1022
|
assertLoaderReadsClean(execScope);
|
|
987
1023
|
} catch (buildError) {
|
|
988
1024
|
// Serialize/handle-encode failed: no envelope for followers (they run
|
|
989
|
-
// fresh) and nothing to write.
|
|
1025
|
+
// fresh) and nothing to write. Logged too: a read the guard refused
|
|
1026
|
+
// in the value is otherwise silent with no onError. An error the call
|
|
1027
|
+
// also throws reaches onError once (the request dedups by identity).
|
|
990
1028
|
clearSelf();
|
|
991
1029
|
rejectEnvelope(buildError);
|
|
992
|
-
|
|
1030
|
+
reportCacheError(
|
|
1031
|
+
buildError,
|
|
1032
|
+
"cache-write",
|
|
1033
|
+
`[use cache] "${id}" write`,
|
|
1034
|
+
requestCtx,
|
|
1035
|
+
);
|
|
993
1036
|
return;
|
|
994
1037
|
}
|
|
995
1038
|
if (serialized === null) {
|
|
@@ -1039,7 +1082,24 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
1039
1082
|
|
|
1040
1083
|
await runBackground(requestCtx, finalizeAndWrite, true);
|
|
1041
1084
|
|
|
1042
|
-
|
|
1085
|
+
// The encode could not start: nothing is stored, the value renders live.
|
|
1086
|
+
if (!encoded) return result;
|
|
1087
|
+
try {
|
|
1088
|
+
return await encoded.decoded;
|
|
1089
|
+
} catch (decodeError) {
|
|
1090
|
+
// The root row failed. A value Flight cannot encode (a function, a
|
|
1091
|
+
// class instance) is not cached and renders live, as it always has;
|
|
1092
|
+
// anything else, a refused read above all, fails the call as the same
|
|
1093
|
+
// read in the body does, with the encode's own error (production
|
|
1094
|
+
// Flight redacts the decoded one).
|
|
1095
|
+
if (
|
|
1096
|
+
flightErrors.length > 0 &&
|
|
1097
|
+
flightErrors.every(isUnsupportedValueError)
|
|
1098
|
+
) {
|
|
1099
|
+
return result;
|
|
1100
|
+
}
|
|
1101
|
+
throw flightErrors[0] ?? decodeError;
|
|
1102
|
+
}
|
|
1043
1103
|
};
|
|
1044
1104
|
|
|
1045
1105
|
// Brand the wrapper so it can be detected at runtime (e.g., to prevent
|
package/src/cache/cache-scope.ts
CHANGED
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
// pulls @vitejs/plugin-rsc (a virtual: module the plain node/vitest runner cannot
|
|
34
34
|
// resolve). It is imported LAZILY at the two call sites below (deserializeSegments
|
|
35
35
|
// in lookupRoute, serializeSegments in cacheRoute) so that requiring cache-scope —
|
|
36
|
-
// e.g. dispatch
|
|
37
|
-
//
|
|
36
|
+
// e.g. through the request stages dispatch loads for a response route
|
|
37
|
+
// (rsc/request-stages.ts) — does not crash a consumer test that never mocks
|
|
38
|
+
// plugin-rsc. Behavior
|
|
38
39
|
// is unchanged: both methods are async and already awaited the codec.
|
|
39
40
|
import {
|
|
40
41
|
captureHandles,
|
|
@@ -54,14 +55,16 @@ import {
|
|
|
54
55
|
import type { RequestContext } from "../server/request-context.js";
|
|
55
56
|
import { runIdentityExempt } from "./cache-exec-scope.js";
|
|
56
57
|
import { isWarmReplace, noteWarmWrite } from "../prerender/warm-request.js";
|
|
58
|
+
import { WIRE } from "../wire-params.js";
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
|
-
* Narrow
|
|
60
|
-
* to a record
|
|
61
|
-
* record's `expiresAt`), then stale for
|
|
62
|
-
* with no window of its own
|
|
63
|
-
* it undefined), so it narrows
|
|
64
|
-
* or wrote.
|
|
61
|
+
* Narrow a shell capture's route-record window
|
|
62
|
+
* (ShellCaptureState.routeRecordWindow) to a record the capture read or
|
|
63
|
+
* wrote: fresh until `freshUntil` (the record's `expiresAt`), then stale for
|
|
64
|
+
* `swr` seconds. A capture starts with no window of its own
|
|
65
|
+
* (RENDER_PASS_FIELDS["shell-capture"] creates it undefined), so it narrows
|
|
66
|
+
* only to the records the capture itself read or wrote. Only a capture reads
|
|
67
|
+
* the window (shell-capture.ts capShellWindow): another render notes nothing.
|
|
65
68
|
*/
|
|
66
69
|
function noteRouteRecordWindow(
|
|
67
70
|
requestCtx: RequestContext,
|
|
@@ -70,6 +73,8 @@ function noteRouteRecordWindow(
|
|
|
70
73
|
swr: number,
|
|
71
74
|
written: boolean,
|
|
72
75
|
): void {
|
|
76
|
+
const captureState = requestCtx._shellCaptureState;
|
|
77
|
+
if (!captureState) return;
|
|
73
78
|
const next = {
|
|
74
79
|
freshUntil,
|
|
75
80
|
staleUntil: freshUntil + swr * 1000,
|
|
@@ -77,9 +82,9 @@ function noteRouteRecordWindow(
|
|
|
77
82
|
swr,
|
|
78
83
|
written,
|
|
79
84
|
};
|
|
80
|
-
const current =
|
|
85
|
+
const current = captureState.routeRecordWindow;
|
|
81
86
|
// The record that runs out first names the config; freshness is the least.
|
|
82
|
-
|
|
87
|
+
captureState.routeRecordWindow = current
|
|
83
88
|
? {
|
|
84
89
|
...(current.staleUntil <= next.staleUntil ? current : next),
|
|
85
90
|
freshUntil: Math.min(current.freshUntil, freshUntil),
|
|
@@ -123,23 +128,36 @@ function validatedSwr(value: number | undefined): number | undefined {
|
|
|
123
128
|
return isValidCacheSeconds(value, "swr") ? value : undefined;
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
/**
|
|
132
|
+
* The request kind a route record answers: `doc` (a document request),
|
|
133
|
+
* `partial` (a navigation) or `intercept:<intercept>`, where `intercept` is
|
|
134
|
+
* the intercept an intercept navigation selected (interceptRecordKey,
|
|
135
|
+
* router/intercept-resolution.ts). The kinds' records hold different
|
|
136
|
+
* segments (an intercept's holds its slot and no route page), so every record
|
|
137
|
+
* key names its kind, a `key()`'s included (CacheScope.resolveKeyFrom).
|
|
138
|
+
* Keyed by the URL alone, a record written under one intercept answered a
|
|
139
|
+
* navigation that selected another; keyed by a `key()` alone, a document GET
|
|
140
|
+
* rendered an intercept's record and dropped the page.
|
|
141
|
+
*/
|
|
142
|
+
function routeRecordKind(intercept?: string, prefixOverride?: "doc"): string {
|
|
143
|
+
if (intercept !== undefined) return `intercept:${intercept}`;
|
|
144
|
+
if (prefixOverride !== undefined) return prefixOverride;
|
|
145
|
+
const isPartial =
|
|
146
|
+
getRequestContext()?.originalUrl?.searchParams.has(WIRE.partial) ?? false;
|
|
147
|
+
return isPartial ? "partial" : "doc";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** `{kind}:` + the request's key base (routeRecordKind, requestKeyBase). */
|
|
126
151
|
function getDefaultRouteCacheKey(
|
|
127
152
|
pathname: string,
|
|
128
|
-
params
|
|
129
|
-
|
|
130
|
-
prefixOverride?: "doc",
|
|
153
|
+
params: Record<string, string> | undefined,
|
|
154
|
+
kind: string,
|
|
131
155
|
): string {
|
|
132
156
|
const ctx = getRequestContext();
|
|
133
|
-
const isPartial = ctx?.originalUrl?.searchParams.has("_rsc_partial") ?? false;
|
|
134
157
|
const searchParams = ctx?.url.searchParams;
|
|
135
158
|
const host = ctx?.url.host ?? "localhost";
|
|
136
159
|
|
|
137
|
-
|
|
138
|
-
const prefix = isIntercept
|
|
139
|
-
? "intercept"
|
|
140
|
-
: (prefixOverride ?? (isPartial ? "partial" : "doc"));
|
|
141
|
-
|
|
142
|
-
return `${prefix}:${requestKeyBase(ctx, host, pathname, searchParams, params, ctx?._searchParamsFilter)}`;
|
|
160
|
+
return `${kind}:${requestKeyBase(ctx, host, pathname, searchParams, params, ctx?._searchParamsFilter)}`;
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
// ============================================================================
|
|
@@ -236,15 +254,23 @@ function memoizedKey(
|
|
|
236
254
|
return key;
|
|
237
255
|
}
|
|
238
256
|
|
|
257
|
+
/**
|
|
258
|
+
* A route record that answered. `restoreHandles` is set when the lookup left
|
|
259
|
+
* the handle restore to its caller (lookupRouteDetailed `deferHandles`),
|
|
260
|
+
* which names the segments it sends (handle-snapshot.ts restoreHandles).
|
|
261
|
+
*/
|
|
262
|
+
export interface CachedRouteHit {
|
|
263
|
+
segments: ResolvedSegment[];
|
|
264
|
+
shouldRevalidate: boolean;
|
|
265
|
+
restoreHandles?: (sentIds: ReadonlySet<string>) => Promise<void>;
|
|
266
|
+
}
|
|
267
|
+
|
|
239
268
|
/**
|
|
240
269
|
* Discriminated outcome of a route cache lookup — see
|
|
241
270
|
* {@link CacheScope.lookupRouteDetailed} for what each status licenses.
|
|
242
271
|
*/
|
|
243
272
|
export type CacheRouteLookupOutcome =
|
|
244
|
-
| {
|
|
245
|
-
status: "hit";
|
|
246
|
-
result: { segments: ResolvedSegment[]; shouldRevalidate: boolean };
|
|
247
|
-
}
|
|
273
|
+
| { status: "hit"; result: CachedRouteHit }
|
|
248
274
|
| { status: "miss" | "bypass" | "error" };
|
|
249
275
|
|
|
250
276
|
/**
|
|
@@ -455,15 +481,13 @@ export class CacheScope {
|
|
|
455
481
|
private resolveKey(
|
|
456
482
|
pathname: string,
|
|
457
483
|
params: Record<string, string>,
|
|
458
|
-
|
|
484
|
+
intercept?: string,
|
|
459
485
|
): Promise<string> {
|
|
486
|
+
const kind = routeRecordKind(intercept, this.defaultKeyPrefix);
|
|
460
487
|
return this.resolveKeyFrom(
|
|
461
|
-
getDefaultRouteCacheKey(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
isIntercept,
|
|
465
|
-
this.defaultKeyPrefix,
|
|
466
|
-
),
|
|
488
|
+
getDefaultRouteCacheKey(pathname, params, kind),
|
|
489
|
+
"",
|
|
490
|
+
kind,
|
|
467
491
|
);
|
|
468
492
|
}
|
|
469
493
|
|
|
@@ -500,14 +524,18 @@ export class CacheScope {
|
|
|
500
524
|
* enclosing partition (#970, #974). A scope whose own config sets no
|
|
501
525
|
* `key()` appends its own default key (resolveDefaultKey). Reusing the
|
|
502
526
|
* outer `key()` result alone would give every route under the inner
|
|
503
|
-
* cache() one record: a `key()` replaces the
|
|
504
|
-
* outer key that names no route (`tier:gold`) would make `/a` HIT
|
|
505
|
-
* record. A scope with its own `key()` adds no default key:
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
527
|
+
* cache() one record: a `key()` replaces the default key's URL part, so
|
|
528
|
+
* an outer key that names no route (`tier:gold`) would make `/a` HIT
|
|
529
|
+
* `/b`'s record. A scope with its own `key()` adds no default key: its `key()`
|
|
530
|
+
* replaces the URL part, and its store's keyGenerator does not run. It
|
|
531
|
+
* appends `kind` instead (routeRecordKind): the `key()` decides which
|
|
532
|
+
* requests of one kind share a record, never that a document, a
|
|
533
|
+
* navigation and an intercept navigation do. With no partition part it is
|
|
534
|
+
* resolveDefaultKey alone. With a `key()` on the chain but no request
|
|
535
|
+
* context it rejects (keyWithoutContextError). `partitionedPrefix`
|
|
536
|
+
* prefixes a key built from partition parts only (the response family's
|
|
537
|
+
* `response:`, which passes no `kind`: a response route answers every
|
|
538
|
+
* request alike).
|
|
511
539
|
*
|
|
512
540
|
* Each `key()` result lives on the request context (`_resolvedCacheKeys`),
|
|
513
541
|
* by function: it runs once per request whatever the default key
|
|
@@ -518,7 +546,11 @@ export class CacheScope {
|
|
|
518
546
|
* itself: a `key()` calling `cookies()` would trip the capture guard there,
|
|
519
547
|
* and a capture must store the partition its request resolved.
|
|
520
548
|
*/
|
|
521
|
-
resolveKeyFrom(
|
|
549
|
+
resolveKeyFrom(
|
|
550
|
+
defaultKey: string,
|
|
551
|
+
partitionedPrefix = "",
|
|
552
|
+
kind?: string,
|
|
553
|
+
): Promise<string> {
|
|
522
554
|
if (this.fixedKey !== undefined) return Promise.resolve(this.fixedKey);
|
|
523
555
|
const requestCtx = _getRequestContext();
|
|
524
556
|
if (!requestCtx || !this.mayPartition) {
|
|
@@ -534,7 +566,8 @@ export class CacheScope {
|
|
|
534
566
|
]).then(([[keys, storeParts], ownDefault]) => {
|
|
535
567
|
const generated = positionalParts(storeParts);
|
|
536
568
|
if (keys.length === 0 && generated.length === 0) return ownDefault!;
|
|
537
|
-
|
|
569
|
+
const last = ownDefault ?? kind;
|
|
570
|
+
if (last !== undefined) generated.push(last);
|
|
538
571
|
return partitionedPrefix + composeCacheKeys(keys, generated);
|
|
539
572
|
});
|
|
540
573
|
}
|
|
@@ -558,7 +591,7 @@ export class CacheScope {
|
|
|
558
591
|
? undefined
|
|
559
592
|
: this.getStore()?.keyGenerator;
|
|
560
593
|
if (!this.mayPartition && !keyGenerator) return undefined;
|
|
561
|
-
const defaultKey = getDefaultRouteCacheKey(pathname, params,
|
|
594
|
+
const defaultKey = getDefaultRouteCacheKey(pathname, params, "doc");
|
|
562
595
|
const requestCtx = _getRequestContext();
|
|
563
596
|
if (!requestCtx) {
|
|
564
597
|
return this.keyFns.length > 0
|
|
@@ -735,24 +768,26 @@ export class CacheScope {
|
|
|
735
768
|
*
|
|
736
769
|
* @param pathname - URL pathname for cache key generation
|
|
737
770
|
* @param params - Route params for cache key generation
|
|
738
|
-
* @param
|
|
771
|
+
* @param intercept - The intercept an intercept navigation selected
|
|
772
|
+
* (interceptRecordKey): its record has its own key
|
|
739
773
|
* @param ownedPushes - What the record is to each loader's handle pushes
|
|
740
774
|
* (withCacheLookup: loader-cache.ts loaderPins), called on a hit
|
|
775
|
+
* @param deferHandles - Restore no handle value here: the caller calls the
|
|
776
|
+
* hit's `restoreHandles` once it decided which segments it sends
|
|
741
777
|
*/
|
|
742
778
|
async lookupRoute(
|
|
743
779
|
pathname: string,
|
|
744
780
|
params: Record<string, string>,
|
|
745
|
-
|
|
781
|
+
intercept?: string,
|
|
746
782
|
ownedPushes?: () => OwnedPushDelivery,
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
shouldRevalidate: boolean;
|
|
750
|
-
} | null> {
|
|
783
|
+
deferHandles?: boolean,
|
|
784
|
+
): Promise<CachedRouteHit | null> {
|
|
751
785
|
const outcome = await this.lookupRouteDetailed(
|
|
752
786
|
pathname,
|
|
753
787
|
params,
|
|
754
|
-
|
|
788
|
+
intercept,
|
|
755
789
|
ownedPushes,
|
|
790
|
+
deferHandles,
|
|
756
791
|
);
|
|
757
792
|
return outcome.status === "hit" ? outcome.result : null;
|
|
758
793
|
}
|
|
@@ -778,8 +813,9 @@ export class CacheScope {
|
|
|
778
813
|
async lookupRouteDetailed(
|
|
779
814
|
pathname: string,
|
|
780
815
|
params: Record<string, string>,
|
|
781
|
-
|
|
816
|
+
intercept?: string,
|
|
782
817
|
ownedPushes?: () => OwnedPushDelivery,
|
|
818
|
+
deferHandles?: boolean,
|
|
783
819
|
): Promise<CacheRouteLookupOutcome> {
|
|
784
820
|
if (!this.enabled) return { status: "bypass" };
|
|
785
821
|
if (!this.conditionAllows("read")) return { status: "bypass" };
|
|
@@ -801,7 +837,7 @@ export class CacheScope {
|
|
|
801
837
|
// happens here, where a miss is a safe outcome.
|
|
802
838
|
let key: string | undefined;
|
|
803
839
|
try {
|
|
804
|
-
key = await this.resolveKey(pathname, params,
|
|
840
|
+
key = await this.resolveKey(pathname, params, intercept);
|
|
805
841
|
|
|
806
842
|
const result = await store.get(key);
|
|
807
843
|
|
|
@@ -882,17 +918,23 @@ export class CacheScope {
|
|
|
882
918
|
await this.onHit?.();
|
|
883
919
|
|
|
884
920
|
const handleStore = _getRequestContext()?._handleStore;
|
|
921
|
+
let restoreHandles: CachedRouteHit["restoreHandles"];
|
|
885
922
|
// An empty handles string means the route pushed none (the common
|
|
886
923
|
// case); a decode failure skips the restore but keeps the segments.
|
|
887
924
|
if (handleStore) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
cached,
|
|
891
|
-
ownedPushes?.(),
|
|
925
|
+
const owned = ownedPushes?.();
|
|
926
|
+
const keepIds =
|
|
892
927
|
this.boundary !== undefined
|
|
893
928
|
? new Set(segments.map((s) => s.id))
|
|
894
|
-
: undefined
|
|
895
|
-
)
|
|
929
|
+
: undefined;
|
|
930
|
+
if (deferHandles) {
|
|
931
|
+
// The authority holds from here, as with the restore below.
|
|
932
|
+
if (owned) handleStore.setRecordAuthority(owned);
|
|
933
|
+
restoreHandles = (sent) =>
|
|
934
|
+
restoreRecordHandles(handleStore, cached, owned, keepIds, sent);
|
|
935
|
+
} else {
|
|
936
|
+
await restoreRecordHandles(handleStore, cached, owned, keepIds);
|
|
937
|
+
}
|
|
896
938
|
}
|
|
897
939
|
|
|
898
940
|
if (INTERNAL_RANGO_DEBUG) {
|
|
@@ -907,7 +949,10 @@ export class CacheScope {
|
|
|
907
949
|
if (ambientContext)
|
|
908
950
|
this.noteRecordWindow(ambientContext, cached.expiresAt, false);
|
|
909
951
|
|
|
910
|
-
return {
|
|
952
|
+
return {
|
|
953
|
+
status: "hit",
|
|
954
|
+
result: { segments, shouldRevalidate, restoreHandles },
|
|
955
|
+
};
|
|
911
956
|
} catch (error) {
|
|
912
957
|
// Covers a store.get() failure AND a throwing consumer key()/keyGenerator
|
|
913
958
|
// (resolveKey). Either way degrade to an uncached render — reported as
|
|
@@ -969,7 +1014,8 @@ export class CacheScope {
|
|
|
969
1014
|
* @param pathname - URL pathname for cache key generation
|
|
970
1015
|
* @param params - Route params for cache key generation
|
|
971
1016
|
* @param segments - All resolved segments to cache
|
|
972
|
-
* @param
|
|
1017
|
+
* @param intercept - The intercept an intercept navigation selected
|
|
1018
|
+
* (interceptRecordKey): its record has its own key
|
|
973
1019
|
* @param start - Where the render of `segments` started: the record is not
|
|
974
1020
|
* written when one of its tags was invalidated since (#977,
|
|
975
1021
|
* predatesInvalidation). A shell capture's doc record passes none: it
|
|
@@ -979,7 +1025,7 @@ export class CacheScope {
|
|
|
979
1025
|
pathname: string,
|
|
980
1026
|
params: Record<string, string>,
|
|
981
1027
|
segments: ResolvedSegment[],
|
|
982
|
-
|
|
1028
|
+
intercept?: string,
|
|
983
1029
|
start?: ExecutionStart,
|
|
984
1030
|
): Promise<void> {
|
|
985
1031
|
if (!this.enabled || segments.length === 0) return;
|
|
@@ -1006,7 +1052,7 @@ export class CacheScope {
|
|
|
1006
1052
|
this.noteRecordWindow(requestCtx, Date.now() + ttl * 1000, true);
|
|
1007
1053
|
|
|
1008
1054
|
// Resolve cache key early (while request context is available)
|
|
1009
|
-
const key = await this.resolveKey(pathname, params,
|
|
1055
|
+
const key = await this.resolveKey(pathname, params, intercept);
|
|
1010
1056
|
|
|
1011
1057
|
// Doc-namespaced scopes (the shell implicit scope and the capture's
|
|
1012
1058
|
// composed doc scope) publish the canonical document segment key so
|
|
@@ -1022,7 +1068,7 @@ export class CacheScope {
|
|
|
1022
1068
|
recordRequestTags(tags, requestCtx);
|
|
1023
1069
|
|
|
1024
1070
|
// Check if this is a partial request (navigation) vs document request
|
|
1025
|
-
const isPartial = requestCtx.originalUrl.searchParams.has(
|
|
1071
|
+
const isPartial = requestCtx.originalUrl.searchParams.has(WIRE.partial);
|
|
1026
1072
|
|
|
1027
1073
|
if (INTERNAL_RANGO_DEBUG) {
|
|
1028
1074
|
debugCacheLog(
|
|
@@ -1204,7 +1250,7 @@ export function resolveShellPartition(
|
|
|
1204
1250
|
): Promise<string | null> | undefined {
|
|
1205
1251
|
if (routeScope?.enabled) return routeScope.resolvePartition(pathname, params);
|
|
1206
1252
|
if (!appStore?.keyGenerator) return undefined;
|
|
1207
|
-
const defaultKey = getDefaultRouteCacheKey(pathname, params,
|
|
1253
|
+
const defaultKey = getDefaultRouteCacheKey(pathname, params, "doc");
|
|
1208
1254
|
return resolveCacheKey(undefined, appStore, defaultKey).then((resolved) =>
|
|
1209
1255
|
resolved === defaultKey ? null : resolved,
|
|
1210
1256
|
);
|
|
@@ -1259,7 +1305,7 @@ export function createShellImplicitDocScope(
|
|
|
1259
1305
|
* normal partial navigation replay), substitute an implicit doc-level scope
|
|
1260
1306
|
* so withCacheLookup/withCacheStore treat the WHOLE matched route as a
|
|
1261
1307
|
* cache() boundary — the shell entry IS a cache() of the handler layer, with
|
|
1262
|
-
* loaders as the live carve-outs (
|
|
1308
|
+
* loaders as the live carve-outs (serveStoredSegments).
|
|
1263
1309
|
*
|
|
1264
1310
|
* A document HIT tail (`docTail`) always gets the implicit scope: it replays
|
|
1265
1311
|
* the shell's own record and never consults a route-derived scope, whose
|
|
@@ -55,14 +55,14 @@ export const CACHE_REVALIDATING_AT_HEADER = "x-edge-cache-revalidating-at";
|
|
|
55
55
|
* re-arm re-puts the full window and restarts CF's retention clock, pinning a
|
|
56
56
|
* perpetually-stale entry (one whose background revalidation keeps failing) past
|
|
57
57
|
* its intended hard-expiry indefinitely. Mirrors the KVSegmentEnvelope `e`
|
|
58
|
-
* field and the remaining-ttl math in
|
|
58
|
+
* field and the remaining-ttl math in promoteToL1.
|
|
59
59
|
* @internal
|
|
60
60
|
*/
|
|
61
61
|
export const CACHE_EXPIRES_AT_HEADER = "x-edge-cache-expires-at";
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Header stashing the route author's original Cache-Control on L1 document
|
|
65
|
-
* entries. putResponse/
|
|
65
|
+
* entries. putResponse/promoteToL1 overwrite Cache-Control with a long
|
|
66
66
|
* `max-age` so the CF Cache API retains the entry across the whole SWR window;
|
|
67
67
|
* getResponse restores this original value before serving so the client and any
|
|
68
68
|
* upstream CDN see the author's intended directive, not the internal edge TTL.
|