@rangojs/router 0.27.0 → 0.29.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-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +8 -2
- package/dist/types/browser/history-state.d.ts +3 -9
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/react/use-action.d.ts +7 -2
- package/dist/types/browser/react/use-handle.d.ts +19 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/segment-reconciler.d.ts +32 -4
- package/dist/types/browser/types.d.ts +24 -5
- package/dist/types/browser/wire-protocol.d.ts +26 -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/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 +11 -5
- 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 +6 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
- 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/navigation-snapshot.d.ts +7 -0
- 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/helpers.d.ts +19 -3
- 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 +95 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- 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 +11 -1
- 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 +53 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +30 -3
- 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 +39 -0
- package/dist/vite/index.js +3822 -3727
- 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 +4 -2
- package/skills/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +33 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +16 -5
- package/skills/intercept/SKILL.md +23 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/observability/SKILL.md +30 -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 +15 -1
- package/skills/testing/client-components.md +25 -18
- 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/view-transitions/SKILL.md +1 -1
- package/src/__internal.ts +7 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +37 -13
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +59 -26
- package/src/browser/navigation-client.ts +20 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- 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/react/use-action.ts +24 -48
- package/src/browser/react/use-handle.ts +22 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +92 -41
- package/src/browser/rsc-router.tsx +1 -0
- package/src/browser/segment-reconciler.ts +109 -53
- package/src/browser/server-action-bridge.ts +81 -61
- package/src/browser/types.ts +25 -5
- package/src/browser/wire-protocol.ts +43 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +132 -75
- 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 +64 -46
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +6 -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 +22 -10
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +82 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +5 -5
- package/src/router/match-context.ts +23 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +46 -57
- package/src/router/match-middleware/cache-lookup.ts +677 -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 +14 -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/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +15 -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 +139 -0
- package/src/router/segment-resolution/revalidation.ts +939 -789
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- 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 +11 -1
- package/src/router.ts +19 -17
- 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 +759 -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 +275 -227
- 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 +75 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/testing/dispatch.ts +226 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/render-route.tsx +199 -13
- package/src/testing/serve-shell-request.ts +36 -40
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +30 -3
- 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 +40 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -46,6 +46,60 @@ export interface ReconcileResult {
|
|
|
46
46
|
interceptSegments: ResolvedSegment[];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* A segment the server sent over its cached copy, keeping the cached
|
|
51
|
+
* structural properties so the React tree keeps its shape. Changing them
|
|
52
|
+
* between renders alters the element nesting (with/without
|
|
53
|
+
* RouteContentWrapper, MountContextProvider, etc.), which remounts components
|
|
54
|
+
* and destroys useActionState.
|
|
55
|
+
*/
|
|
56
|
+
function mergeCached(
|
|
57
|
+
fromServer: ResolvedSegment,
|
|
58
|
+
fromCache: ResolvedSegment,
|
|
59
|
+
actor: ReconcileActor,
|
|
60
|
+
): ResolvedSegment {
|
|
61
|
+
let merged = fromServer;
|
|
62
|
+
|
|
63
|
+
// When server returns component: null for a layout segment, it means
|
|
64
|
+
// "this segment doesn't need re-rendering" - preserve the cached component
|
|
65
|
+
// to maintain the outlet chain and prevent React tree changes
|
|
66
|
+
if (
|
|
67
|
+
fromServer.component === null &&
|
|
68
|
+
fromServer.type === "layout" &&
|
|
69
|
+
fromCache.component != null
|
|
70
|
+
) {
|
|
71
|
+
merged = { ...merged, component: fromCache.component };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Loading preservation is actor-aware:
|
|
75
|
+
// - action/stale-revalidation: always preserve cached value to prevent
|
|
76
|
+
// tree remount (even when cached is undefined, to avoid adding a
|
|
77
|
+
// Suspense boundary that wasn't there before)
|
|
78
|
+
// - navigation: only when cached is defined (building a new tree)
|
|
79
|
+
if (
|
|
80
|
+
fromServer.loading !== fromCache.loading &&
|
|
81
|
+
(actor !== "navigation" || fromCache.loading !== undefined)
|
|
82
|
+
) {
|
|
83
|
+
merged = { ...merged, loading: fromCache.loading };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// mountPath: SSR segments may lack mountPath while revalidated segments
|
|
87
|
+
// include it. The conditional MountContextProvider wrapper changes tree depth.
|
|
88
|
+
if (fromServer.mountPath !== fromCache.mountPath) {
|
|
89
|
+
merged = { ...merged, mountPath: fromCache.mountPath };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Dev-mode assertion: warn if the merged result still differs from cache
|
|
93
|
+
// in tree-structural properties. This catches bugs where the merge code
|
|
94
|
+
// above fails to preserve a value it should have.
|
|
95
|
+
assertSegmentStructure(
|
|
96
|
+
fromCache,
|
|
97
|
+
merged,
|
|
98
|
+
actor === "action" ? "action-bridge" : "partial-update",
|
|
99
|
+
);
|
|
100
|
+
return merged;
|
|
101
|
+
}
|
|
102
|
+
|
|
49
103
|
/**
|
|
50
104
|
* Single source of truth for merging server segments with cached segments.
|
|
51
105
|
*
|
|
@@ -70,7 +124,6 @@ export interface ReconcileResult {
|
|
|
70
124
|
*/
|
|
71
125
|
export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
72
126
|
const { actor, matched, diff, insertMissingDiff } = input;
|
|
73
|
-
const context = actor === "action" ? "action-bridge" : "partial-update";
|
|
74
127
|
|
|
75
128
|
// Build lookup maps from arrays
|
|
76
129
|
const serverSegments = new Map<string, ResolvedSegment>();
|
|
@@ -111,59 +164,13 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
111
164
|
|
|
112
165
|
if (fromServer) {
|
|
113
166
|
const inDiff = diffSet.has(segId);
|
|
114
|
-
// Preserve cached structural properties to maintain consistent React tree.
|
|
115
|
-
// Changing these between renders alters the element nesting
|
|
116
|
-
// (with/without RouteContentWrapper, MountContextProvider, etc.),
|
|
117
|
-
// causing React to remount components and destroy useActionState.
|
|
118
167
|
if (fromCache) {
|
|
119
|
-
let merged = fromServer;
|
|
120
|
-
|
|
121
|
-
// When server returns component: null for a layout segment, it means
|
|
122
|
-
// "this segment doesn't need re-rendering" - preserve the cached component
|
|
123
|
-
// to maintain the outlet chain and prevent React tree changes
|
|
124
|
-
if (
|
|
125
|
-
fromServer.component === null &&
|
|
126
|
-
fromServer.type === "layout" &&
|
|
127
|
-
fromCache.component != null
|
|
128
|
-
) {
|
|
129
|
-
merged = { ...merged, component: fromCache.component };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Loading preservation is actor-aware:
|
|
133
|
-
// - action/stale-revalidation: always preserve cached value to prevent
|
|
134
|
-
// tree remount (even when cached is undefined, to avoid adding a
|
|
135
|
-
// Suspense boundary that wasn't there before)
|
|
136
|
-
// - navigation: only when cached is defined (building a new tree)
|
|
137
|
-
if (actor !== "navigation") {
|
|
138
|
-
if (fromServer.loading !== fromCache.loading) {
|
|
139
|
-
merged = { ...merged, loading: fromCache.loading };
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
if (
|
|
143
|
-
fromCache.loading !== undefined &&
|
|
144
|
-
fromServer.loading !== fromCache.loading
|
|
145
|
-
) {
|
|
146
|
-
merged = { ...merged, loading: fromCache.loading };
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// mountPath: SSR segments may lack mountPath while revalidated segments
|
|
151
|
-
// include it. The conditional MountContextProvider wrapper changes tree depth.
|
|
152
|
-
if (fromServer.mountPath !== fromCache.mountPath) {
|
|
153
|
-
merged = { ...merged, mountPath: fromCache.mountPath };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Dev-mode assertion: warn if the merged result still differs from cache
|
|
157
|
-
// in tree-structural properties. This catches bugs where the merge code
|
|
158
|
-
// above fails to preserve a value it should have.
|
|
159
|
-
assertSegmentStructure(fromCache, merged, context);
|
|
160
|
-
|
|
161
168
|
if (IS_BROWSER_DEBUG) {
|
|
162
169
|
debugLog(
|
|
163
170
|
`[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
|
|
164
171
|
);
|
|
165
172
|
}
|
|
166
|
-
return
|
|
173
|
+
return mergeCached(fromServer, fromCache, actor);
|
|
167
174
|
}
|
|
168
175
|
if (IS_BROWSER_DEBUG) {
|
|
169
176
|
debugLog(
|
|
@@ -221,11 +228,13 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
/**
|
|
224
|
-
* Reconcile error
|
|
231
|
+
* Reconcile an error-boundary response with cached segments.
|
|
225
232
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* preserving sibling layouts that aren't in
|
|
233
|
+
* The server returns the error boundary segment and, above it, what the
|
|
234
|
+
* action's revalidation re-ran (rsc/server-action.ts). This function overlays
|
|
235
|
+
* them onto the full cached tree, preserving sibling layouts that aren't in
|
|
236
|
+
* the error parent chain. The error segment replaces its cached copy; every
|
|
237
|
+
* other segment merges as an action's does (mergeCached).
|
|
229
238
|
*/
|
|
230
239
|
export function reconcileErrorSegments(
|
|
231
240
|
cachedSegments: ResolvedSegment[],
|
|
@@ -236,7 +245,11 @@ export function reconcileErrorSegments(
|
|
|
236
245
|
|
|
237
246
|
const segments = cachedSegments.map((cached) => {
|
|
238
247
|
const fromServer = errorMap.get(cached.id);
|
|
239
|
-
return fromServer
|
|
248
|
+
return !fromServer
|
|
249
|
+
? cached
|
|
250
|
+
: fromServer.type === "error"
|
|
251
|
+
? fromServer
|
|
252
|
+
: mergeCached(fromServer, cached, "action");
|
|
240
253
|
});
|
|
241
254
|
|
|
242
255
|
const { main, intercept } = splitInterceptSegments(segments);
|
|
@@ -247,3 +260,46 @@ export function reconcileErrorSegments(
|
|
|
247
260
|
interceptSegments: intercept,
|
|
248
261
|
};
|
|
249
262
|
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The ids a page holds once an action's error boundary `boundary` shows: the
|
|
266
|
+
* ones above it. Nothing at or below the boundary ran after the action's
|
|
267
|
+
* write (its response and the follow-up cut there), so the next request must
|
|
268
|
+
* not hold it. ShortCodes nest, so those ids start with the boundary's: the
|
|
269
|
+
* boundary, its loaders (`${id}D…`), its slots and every entry under it. A
|
|
270
|
+
* sibling with a longer index (`…R10` beside `…R1`) is kept.
|
|
271
|
+
*/
|
|
272
|
+
export function idsAboveBoundary(
|
|
273
|
+
ids: string[],
|
|
274
|
+
boundary: string | undefined,
|
|
275
|
+
): string[] {
|
|
276
|
+
return boundary
|
|
277
|
+
? ids.filter(
|
|
278
|
+
(id) =>
|
|
279
|
+
!id.startsWith(boundary) || /\d/.test(id.charAt(boundary.length)),
|
|
280
|
+
)
|
|
281
|
+
: ids;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The error segment of every action error boundary committed, by identity
|
|
286
|
+
* (server-action-bridge.ts adds and deletes). An entry shows one while its
|
|
287
|
+
* cached list still holds that object: a later commit on the entry replaces
|
|
288
|
+
* the list, and a refetch that holds the boundary keeps it. Weak, so a
|
|
289
|
+
* replaced boundary is not kept alive. Two routers never share a segment
|
|
290
|
+
* object. No hook reads it.
|
|
291
|
+
*/
|
|
292
|
+
export const actionBoundaries: WeakSet<ResolvedSegment> = new WeakSet();
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The index of the deepest action error boundary in `segments`, -1 for none.
|
|
296
|
+
* It is that action's outcome, which no GET reproduces: a background refetch
|
|
297
|
+
* of the page (the follow-up, Back/Forward's stale revalidation, the
|
|
298
|
+
* cross-tab refresh) holds it and renders what is above it. At 0 nothing is
|
|
299
|
+
* above it.
|
|
300
|
+
*/
|
|
301
|
+
export function actionBoundaryAt(segments: readonly ResolvedSegment[]): number {
|
|
302
|
+
let at = segments.length;
|
|
303
|
+
while (at-- && !actionBoundaries.has(segments[at]));
|
|
304
|
+
return at;
|
|
305
|
+
}
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
import { KEEP_CACHE_HEADER } from "./cookie-name.js";
|
|
16
16
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
17
17
|
import {
|
|
18
|
+
actionBoundaries,
|
|
19
|
+
actionBoundaryAt,
|
|
20
|
+
idsAboveBoundary,
|
|
18
21
|
reconcileSegments,
|
|
19
22
|
reconcileErrorSegments,
|
|
20
23
|
} from "./segment-reconciler.js";
|
|
@@ -52,6 +55,7 @@ import { stampLocationState } from "./react/location-state-shared.js";
|
|
|
52
55
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
53
56
|
import { revalidationErrorOf } from "../decode-loader-results.js";
|
|
54
57
|
import { getAppVersion } from "./app-version.js";
|
|
58
|
+
import { writePartialParams } from "./wire-protocol.js";
|
|
55
59
|
import {
|
|
56
60
|
decideCommitGatedOff,
|
|
57
61
|
findActionFormData,
|
|
@@ -205,6 +209,8 @@ export function createServerActionBridge(
|
|
|
205
209
|
actionId?: string;
|
|
206
210
|
/** transition({ when }) `action` fields for the refetch commit. */
|
|
207
211
|
action?: TransitionWhenActionInput;
|
|
212
|
+
/** The follow-up's held error boundary (UpdateMode `action`). */
|
|
213
|
+
boundary?: string;
|
|
208
214
|
};
|
|
209
215
|
|
|
210
216
|
/** The follow-up's transition({ when }) fields: the last owing action's. */
|
|
@@ -254,13 +260,13 @@ export function createServerActionBridge(
|
|
|
254
260
|
let followUpsStarted = 0;
|
|
255
261
|
/** The newest follow-up, until it ends: leaveFollowUp() aborts it. */
|
|
256
262
|
let followUpRunning: { handle: NavigationHandle; due: FollowUp } | undefined;
|
|
257
|
-
|
|
258
263
|
/**
|
|
259
264
|
* The follow-up revalidation: the rules are R1-R7 in
|
|
260
265
|
* docs/internal/execution-model.md. Once the batch's last action exited,
|
|
261
266
|
* the page on screen that missed a write is refetched once, whole (no
|
|
262
|
-
* segments held, so every loader re-runs
|
|
263
|
-
*
|
|
267
|
+
* segments held, so every loader re-runs; above an action's boundary, see
|
|
268
|
+
* below), in the action lane (storeOnly, skipLoadingState:
|
|
269
|
+
* useNavigation() stays idle).
|
|
264
270
|
*
|
|
265
271
|
* - Never over an intercept: a request holding no segments drops the page
|
|
266
272
|
* under it (match-result.ts buildMatchResult). Checked when it would run,
|
|
@@ -270,6 +276,10 @@ export function createServerActionBridge(
|
|
|
270
276
|
* keeps its error on screen, and the debt is dropped.
|
|
271
277
|
* - Runs again when it is left before its commit (a navigation, or an action
|
|
272
278
|
* that started: leaveFollowUp), for the page then on screen.
|
|
279
|
+
* - Holds the action error boundary the page shows (actionBoundaryAt): it
|
|
280
|
+
* is that action's outcome, which no GET reproduces, so the server
|
|
281
|
+
* renders every segment above it and nothing at or below it. With
|
|
282
|
+
* nothing above it (the page's first segment), the debt is dropped.
|
|
273
283
|
*/
|
|
274
284
|
function followUp(): void {
|
|
275
285
|
if (!owed || isActionFenceActive()) return;
|
|
@@ -284,8 +294,16 @@ export function createServerActionBridge(
|
|
|
284
294
|
const due = owed;
|
|
285
295
|
owed = undefined;
|
|
286
296
|
if (store.getInterceptSourceUrl()) return;
|
|
297
|
+
// The deepest action boundary the page shows: none (-1) refetches the
|
|
298
|
+
// page whole, the first segment (0) leaves nothing above it.
|
|
299
|
+
const shown =
|
|
300
|
+
store.getCachedSegments(store.getHistoryKey())?.segments ?? [];
|
|
301
|
+
const at = actionBoundaryAt(shown);
|
|
302
|
+
if (at === 0) return;
|
|
303
|
+
// Not on `due`: a run owed again (owed ??= due) looks again.
|
|
304
|
+
const held = { ...due, boundary: shown[at]?.id };
|
|
287
305
|
const generation = ++followUpsStarted;
|
|
288
|
-
refetchRoute(
|
|
306
|
+
refetchRoute(held, {
|
|
289
307
|
started: (handle) => (followUpRunning = { handle, due }),
|
|
290
308
|
ended: (left) => {
|
|
291
309
|
if (generation !== followUpsStarted) return;
|
|
@@ -358,6 +376,7 @@ export function createServerActionBridge(
|
|
|
358
376
|
...(src ? { interceptSourceUrl: src } : {}),
|
|
359
377
|
...(opts.actionId !== undefined ? { actionId: opts.actionId } : {}),
|
|
360
378
|
...(opts.action ? { action: opts.action } : {}),
|
|
379
|
+
boundary: opts.boundary,
|
|
361
380
|
},
|
|
362
381
|
);
|
|
363
382
|
return { started: true, error };
|
|
@@ -487,28 +506,16 @@ export function createServerActionBridge(
|
|
|
487
506
|
// Create temporary references for serialization
|
|
488
507
|
const temporaryReferences = deps.createTemporaryReferenceSet();
|
|
489
508
|
|
|
490
|
-
// Build action request URL with current segments
|
|
491
|
-
|
|
492
|
-
url.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
url.searchParams.set("_rsc_v", version);
|
|
501
|
-
}
|
|
502
|
-
// Add router ID for app switch detection
|
|
503
|
-
const rid = store.getRouterId?.();
|
|
504
|
-
if (rid) {
|
|
505
|
-
url.searchParams.set("_rsc_rid", rid);
|
|
506
|
-
}
|
|
507
|
-
// The page's rewrite target: the server diffs the re-render against it.
|
|
508
|
-
const rewriteTarget = store.getHistoryEntryMemory()?.rewriteTarget;
|
|
509
|
-
if (rewriteTarget) {
|
|
510
|
-
url.searchParams.set("_rsc_rt", rewriteTarget);
|
|
511
|
-
}
|
|
509
|
+
// Build action request URL with current segments; the server diffs the
|
|
510
|
+
// re-render against the page's rewrite target.
|
|
511
|
+
const url = writePartialParams(new URL(window.location.href), {
|
|
512
|
+
action: id,
|
|
513
|
+
segmentIds: segmentState.currentSegmentIds,
|
|
514
|
+
emptySegments: "send",
|
|
515
|
+
version: getAppVersion(),
|
|
516
|
+
routerId: store.getRouterId?.(),
|
|
517
|
+
rewriteTarget: store.getHistoryEntryMemory()?.rewriteTarget,
|
|
518
|
+
});
|
|
512
519
|
|
|
513
520
|
// Encode arguments
|
|
514
521
|
const encodedBody = await deps.encodeReply(args, { temporaryReferences });
|
|
@@ -740,36 +747,50 @@ export function createServerActionBridge(
|
|
|
740
747
|
}
|
|
741
748
|
|
|
742
749
|
// Handle error responses with error boundary UI
|
|
743
|
-
if (isError && isPartial && segments
|
|
750
|
+
if (isError && isPartial && segments) {
|
|
744
751
|
log("processing error boundary response");
|
|
745
752
|
|
|
746
|
-
//
|
|
747
|
-
//
|
|
753
|
+
// transition({ when }) sees action.error on the boundary commit and on
|
|
754
|
+
// a dropped one's follow-up.
|
|
755
|
+
whenAction = {
|
|
756
|
+
...whenAction,
|
|
757
|
+
error: returnValue && !returnValue.ok ? returnValue.data : undefined,
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// No consolidation: the boundary replaces the page, or the page left.
|
|
761
|
+
handle.clearConsolidation();
|
|
762
|
+
|
|
763
|
+
const currentKey = store.getHistoryKey();
|
|
764
|
+
const pageLeft = () =>
|
|
765
|
+
navigatedSince() || store.getHistoryKey() !== currentKey;
|
|
766
|
+
// The action's page left the screen (during the flight or the error
|
|
767
|
+
// render): drop the boundary; the call rejects and the exit owes the
|
|
768
|
+
// follow-up.
|
|
769
|
+
const dropBoundary = () => {
|
|
770
|
+
log("user navigated during error render, skipping");
|
|
771
|
+
if (returnValue && !returnValue.ok) throw returnValue.data;
|
|
772
|
+
handle.complete(undefined);
|
|
773
|
+
return undefined;
|
|
774
|
+
};
|
|
775
|
+
if (pageLeft()) return dropBoundary();
|
|
776
|
+
|
|
777
|
+
// fail() settles once the response's stream ends; the abort below
|
|
778
|
+
// spares this action, so its entry keeps the error for useAction().
|
|
748
779
|
if (returnValue && !returnValue.ok) {
|
|
749
780
|
handle.fail(returnValue.data);
|
|
750
781
|
}
|
|
751
782
|
|
|
752
|
-
// Abort
|
|
753
|
-
//
|
|
754
|
-
eventController.abortAllActions();
|
|
755
|
-
|
|
756
|
-
// Clear concurrent action tracking - no consolidation needed when showing error
|
|
757
|
-
handle.clearConsolidation();
|
|
783
|
+
// Abort the other actions in flight: the error takes precedence, so
|
|
784
|
+
// none completes over the boundary.
|
|
785
|
+
eventController.abortAllActions(handle.id);
|
|
758
786
|
|
|
759
787
|
// Get current page's cached segments
|
|
760
|
-
const currentKey = store.getHistoryKey();
|
|
761
788
|
const cached = store.getCachedSegments(currentKey);
|
|
762
789
|
const cachedSegments = cached?.segments || [];
|
|
763
790
|
|
|
764
791
|
// Reconcile error segments with cached tree
|
|
765
792
|
const errorResult = reconcileErrorSegments(cachedSegments, segments);
|
|
766
793
|
|
|
767
|
-
// transition({ when }) decides the error-boundary commit too, with
|
|
768
|
-
// `action.error` set.
|
|
769
|
-
whenAction = {
|
|
770
|
-
...whenAction,
|
|
771
|
-
error: returnValue && !returnValue.ok ? returnValue.data : undefined,
|
|
772
|
-
};
|
|
773
794
|
const errorGatedOff = decideActionGatedOff(
|
|
774
795
|
errorResult.segments,
|
|
775
796
|
metadata,
|
|
@@ -786,28 +807,22 @@ export function createServerActionBridge(
|
|
|
786
807
|
: undefined,
|
|
787
808
|
});
|
|
788
809
|
|
|
789
|
-
|
|
790
|
-
// flight or while the error tree rendered: the boundary is not shown,
|
|
791
|
-
// and the action's exit owes the page on screen the follow-up.
|
|
792
|
-
if (navigatedSince() || store.getHistoryKey() !== currentKey) {
|
|
793
|
-
log("user navigated during error render, skipping");
|
|
794
|
-
if (returnValue && !returnValue.ok) {
|
|
795
|
-
throw returnValue.data;
|
|
796
|
-
}
|
|
797
|
-
handle.complete(undefined);
|
|
798
|
-
return undefined;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Update segment tracking to exclude error segment IDs
|
|
802
|
-
const errorSegmentIds = new Set(diff);
|
|
803
|
-
const segmentIdsAfterError = segmentState.currentSegmentIds.filter(
|
|
804
|
-
(id) => !errorSegmentIds.has(id),
|
|
805
|
-
);
|
|
810
|
+
if (pageLeft()) return dropBoundary();
|
|
806
811
|
|
|
807
812
|
// Cache (and bump the nav instance) BEFORE the UI update so a deferred
|
|
808
813
|
// handle pushed by the error-boundary render still applies — see the
|
|
809
|
-
// "normal" case below for why caching after onUpdate dropped it.
|
|
810
|
-
|
|
814
|
+
// "normal" case below for why caching after onUpdate dropped it. The
|
|
815
|
+
// action's boundary is the last error segment: the server's cut stops
|
|
816
|
+
// there (segment-resolution/revalidation.ts resolveAllSegments). The
|
|
817
|
+
// page holds what is above it, the loaders the response re-ran
|
|
818
|
+
// included, and no error segment.
|
|
819
|
+
const errors = segments.filter((s) => s.type === "error");
|
|
820
|
+
const boundary = errors[errors.length - 1];
|
|
821
|
+
store.setSegmentIds(
|
|
822
|
+
idsAboveBoundary(segmentState.currentSegmentIds, boundary?.id).filter(
|
|
823
|
+
(id) => !errors.some((s) => s.id === id),
|
|
824
|
+
),
|
|
825
|
+
);
|
|
811
826
|
const currentHandleData = liveHandleDataForCache(
|
|
812
827
|
eventController,
|
|
813
828
|
store,
|
|
@@ -819,6 +834,7 @@ export function createServerActionBridge(
|
|
|
819
834
|
currentHandleData,
|
|
820
835
|
);
|
|
821
836
|
store.rememberDisplayedEntry();
|
|
837
|
+
if (boundary) actionBoundaries.add(boundary);
|
|
822
838
|
|
|
823
839
|
// Update UI with error boundary.
|
|
824
840
|
if (process.env.NODE_ENV !== "production") auditTreeCause("action");
|
|
@@ -919,6 +935,10 @@ export function createServerActionBridge(
|
|
|
919
935
|
metadata?.locationState,
|
|
920
936
|
eventController,
|
|
921
937
|
);
|
|
938
|
+
// A success on the page supersedes the action boundary it shows
|
|
939
|
+
// (R4): a repair that stands in for this commit, direct or owed to
|
|
940
|
+
// the batch's end, holds none.
|
|
941
|
+
for (const s of cachedSegments) actionBoundaries.delete(s);
|
|
922
942
|
}
|
|
923
943
|
whenAction = { ...whenAction, result: returnData };
|
|
924
944
|
|
package/src/browser/types.ts
CHANGED
|
@@ -201,13 +201,27 @@ export interface InflightAction {
|
|
|
201
201
|
* @internal This type is an implementation detail. Use PublicNavigationState instead.
|
|
202
202
|
*/
|
|
203
203
|
export interface NavigationState {
|
|
204
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Lifecycle state: `"loading"` while a navigation is in flight and while a
|
|
206
|
+
* server action runs, `"idle"` otherwise
|
|
207
|
+
*/
|
|
205
208
|
state: "idle" | "loading";
|
|
206
209
|
|
|
207
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Whether RSC data is currently streaming: a navigation's response, or a
|
|
212
|
+
* server action's response (initial load, navigation or action)
|
|
213
|
+
*/
|
|
208
214
|
isStreaming: boolean;
|
|
209
215
|
|
|
210
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* True while a navigation is in flight (fetching or streaming, before its
|
|
218
|
+
* commit); false during a server action, whose `state` is still `"loading"`.
|
|
219
|
+
* Drive a navigation progress bar from this and an action spinner from
|
|
220
|
+
* `state`.
|
|
221
|
+
*/
|
|
222
|
+
isNavigating: boolean;
|
|
223
|
+
|
|
224
|
+
/** The page on screen; moves when the next page shows (`pendingUrl` is the page being fetched) */
|
|
211
225
|
location: NavigationLocation;
|
|
212
226
|
|
|
213
227
|
/** URL being navigated to (null when idle) */
|
|
@@ -505,6 +519,8 @@ export interface NavigateOptionsInternal extends Omit<
|
|
|
505
519
|
_skipCache?: boolean;
|
|
506
520
|
/** Server redirects already followed by this navigation chain (0 for a user navigation) */
|
|
507
521
|
_redirectHops?: number;
|
|
522
|
+
/** The URL a redirected navigation chain started from: a link to it stays pending (useLinkStatus) */
|
|
523
|
+
_redirectFrom?: string;
|
|
508
524
|
}
|
|
509
525
|
|
|
510
526
|
/**
|
|
@@ -649,7 +665,8 @@ export interface NavigationStore {
|
|
|
649
665
|
handlesPending?: boolean;
|
|
650
666
|
}
|
|
651
667
|
| undefined;
|
|
652
|
-
|
|
668
|
+
/** Whether an intercept entry is cached for the plain `historyKey`, from any source page. */
|
|
669
|
+
hasInterceptCache(historyKey: string): boolean;
|
|
653
670
|
/**
|
|
654
671
|
* Update only the handleData (and optionally the stale / handlesPending flags)
|
|
655
672
|
* of an existing cache entry. When a flag is omitted the entry's current value
|
|
@@ -717,6 +734,8 @@ export interface FetchPartialOptions {
|
|
|
717
734
|
signal?: AbortSignal;
|
|
718
735
|
/** If true, this is a stale cache revalidation request - server should force revalidators */
|
|
719
736
|
staleRevalidation?: boolean;
|
|
737
|
+
/** Sent as `_rsc_boundary` (wire-params.ts WIRE.boundary). */
|
|
738
|
+
boundary?: string;
|
|
720
739
|
interceptSourceUrl?: string;
|
|
721
740
|
/** RSC version for cache invalidation detection */
|
|
722
741
|
version?: string;
|
|
@@ -802,7 +821,8 @@ export interface ServerActionBridgeConfig {
|
|
|
802
821
|
*/
|
|
803
822
|
export interface NavigationBridge {
|
|
804
823
|
navigate(url: string, options?: NavigateOptionsInternal): Promise<void>;
|
|
805
|
-
|
|
824
|
+
/** `crossTab`: another tab's write; an action's error boundary stays. */
|
|
825
|
+
refresh(crossTab?: boolean): Promise<void>;
|
|
806
826
|
/** `event`: the popstate event; a traversal the browser animated itself (hasUAVisualTransition) is not animated again. */
|
|
807
827
|
handlePopstate(event?: PopStateEvent): Promise<void>;
|
|
808
828
|
registerLinkInterception(): () => void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { WIRE } from "../wire-params.js";
|
|
2
|
+
|
|
3
|
+
/** The parameters of a partial request: a navigation, a prefetch or a JS server action. */
|
|
4
|
+
export interface PartialRequestInit {
|
|
5
|
+
/** A JS server action's id: written as `_rsc_action` in place of `_rsc_partial`. */
|
|
6
|
+
action?: string;
|
|
7
|
+
/** The mounted segment ids the server diffs against. */
|
|
8
|
+
segmentIds: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* An empty `segmentIds`: "send" writes `_rsc_segments=` (navigation,
|
|
11
|
+
* action), "omit" leaves it out (prefetch). The prefetch cache keys carry
|
|
12
|
+
* the difference.
|
|
13
|
+
*/
|
|
14
|
+
emptySegments: "send" | "omit";
|
|
15
|
+
stale?: boolean;
|
|
16
|
+
boundary?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
routerId?: string;
|
|
19
|
+
rewriteTarget?: string;
|
|
20
|
+
fill?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Writes the partial-request parameters onto `url` in the one order every
|
|
25
|
+
* sender uses. Navigation and prefetch cache keys are built from the result
|
|
26
|
+
* (prefetch/cache.ts), so a navigation adopts a prefetch only while both
|
|
27
|
+
* write identically: browser/__tests__/wire-urls.test.ts pins the strings.
|
|
28
|
+
*/
|
|
29
|
+
export function writePartialParams(url: URL, init: PartialRequestInit): URL {
|
|
30
|
+
const params = url.searchParams;
|
|
31
|
+
if (init.action !== undefined) params.set(WIRE.action, init.action);
|
|
32
|
+
else params.set(WIRE.partial, "true");
|
|
33
|
+
if (init.segmentIds.length > 0 || init.emptySegments === "send") {
|
|
34
|
+
params.set(WIRE.segments, init.segmentIds.join(","));
|
|
35
|
+
}
|
|
36
|
+
if (init.stale) params.set(WIRE.stale, "true");
|
|
37
|
+
if (init.boundary) params.set(WIRE.boundary, init.boundary);
|
|
38
|
+
if (init.version) params.set(WIRE.version, init.version);
|
|
39
|
+
if (init.routerId) params.set(WIRE.routerId, init.routerId);
|
|
40
|
+
if (init.rewriteTarget) params.set(WIRE.rewriteTarget, init.rewriteTarget);
|
|
41
|
+
if (init.fill) params.set(WIRE.fill, "1");
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
@@ -187,8 +187,10 @@ export const KEY_PART_PREFIX = "key:";
|
|
|
187
187
|
* (CacheScope.resolveKeyFrom, CacheScope.resolvePartition): the `key()`
|
|
188
188
|
* results of the scope's cache() chain, outermost first, then its default
|
|
189
189
|
* parts: the keyGenerator results of enclosing scopes on another store
|
|
190
|
-
* (#974) and, for a record key
|
|
191
|
-
* default key (or its store's keyGenerator result,
|
|
190
|
+
* (#974) and, for a route record key, the scope's own part: without a
|
|
191
|
+
* `key()` of its own, its default key (or its store's keyGenerator result,
|
|
192
|
+
* #970); with one, the default key's request kind (`doc`, `partial` or
|
|
193
|
+
* `intercept:<intercept>`, routeRecordKind in cache-scope.ts).
|
|
192
194
|
*
|
|
193
195
|
* Key scheme invariant (#975). A `key()` result is often request input,
|
|
194
196
|
* and a record key has no route or scope discriminator (the store reads
|
|
@@ -214,9 +216,13 @@ export const KEY_PART_PREFIX = "key:";
|
|
|
214
216
|
* A namespaced `key()` result starts with `key:` and holds no `|`. A
|
|
215
217
|
* composed key holds a `|` and either starts with `key:` or holds no `:`.
|
|
216
218
|
* Among composed keys, splitting on `|` recovers each part, a part's kind
|
|
217
|
-
* (a `key:` prefix, or no `:` at all) and its value.
|
|
218
|
-
*
|
|
219
|
-
*
|
|
219
|
+
* (a `key:` prefix, or no `:` at all) and its value. A request kind part is
|
|
220
|
+
* a default key's prefix without the URL after it, so it never equals a
|
|
221
|
+
* default key part; a keyGenerator part can equal it (`doc`) only when the
|
|
222
|
+
* keyGenerator returns a constant, which already gives every URL one record.
|
|
223
|
+
* Pinned by the collision probe in
|
|
224
|
+
* src/cache/__tests__/cache-scope-chain.test.ts. A keyGenerator result is
|
|
225
|
+
* the store's own namespace, as before.
|
|
220
226
|
*
|
|
221
227
|
* Shared with the testing helper `shellCacheKey`, so a test builds the
|
|
222
228
|
* production partition.
|