@rangojs/router 0.27.0 → 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/event-controller.d.ts +2 -1
- package/dist/types/browser/history-state.d.ts +3 -9
- 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/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 +5 -14
- 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 +4 -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 +34 -86
- 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/router-context.d.ts +7 -46
- 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 +1 -1
- 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/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 +9 -1
- 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/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +17 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +5 -1
- package/skills/server-actions/SKILL.md +8 -1
- package/skills/testing/response-routes.md +6 -4
- package/skills/use-cache/SKILL.md +46 -8
- package/src/__internal.ts +7 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +5 -3
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +14 -13
- package/src/browser/navigation-client.ts +18 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- 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 +1 -0
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +50 -57
- 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 +34 -13
- 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 +5 -16
- 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 +20 -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 +675 -726
- package/src/router/match-middleware/cache-store.ts +4 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -4
- 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/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +10 -95
- 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 +164 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +1 -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/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +23 -36
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +12 -1
- 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
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.
|