@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
|
@@ -7,7 +7,11 @@ import type {
|
|
|
7
7
|
UpdateSubscriber,
|
|
8
8
|
} from "./types.js";
|
|
9
9
|
import { addTransitionType, createPartialUpdater } from "./partial-update.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
enterActionFence,
|
|
12
|
+
exitActionFence,
|
|
13
|
+
isActionFenceActive,
|
|
14
|
+
} from "./action-fence.js";
|
|
11
15
|
import { KEEP_CACHE_HEADER } from "./cookie-name.js";
|
|
12
16
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
13
17
|
import {
|
|
@@ -16,7 +20,12 @@ import {
|
|
|
16
20
|
} from "./segment-reconciler.js";
|
|
17
21
|
import { startTransition } from "react";
|
|
18
22
|
import { liveHandleDataForCache } from "./navigation-store.js";
|
|
19
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
EventController,
|
|
25
|
+
ActionHandle,
|
|
26
|
+
NavigationEntry,
|
|
27
|
+
NavigationHandle,
|
|
28
|
+
} from "./event-controller.js";
|
|
20
29
|
import {
|
|
21
30
|
toNetworkError,
|
|
22
31
|
emitNetworkError,
|
|
@@ -43,6 +52,7 @@ import { stampLocationState } from "./react/location-state-shared.js";
|
|
|
43
52
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
44
53
|
import { revalidationErrorOf } from "../decode-loader-results.js";
|
|
45
54
|
import { getAppVersion } from "./app-version.js";
|
|
55
|
+
import { writePartialParams } from "./wire-protocol.js";
|
|
46
56
|
import {
|
|
47
57
|
decideCommitGatedOff,
|
|
48
58
|
findActionFormData,
|
|
@@ -190,30 +200,153 @@ export function createServerActionBridge(
|
|
|
190
200
|
action,
|
|
191
201
|
});
|
|
192
202
|
|
|
193
|
-
|
|
194
|
-
* Refetch current route via a navigation transaction.
|
|
195
|
-
* Encapsulates the repeated pattern of creating a navTx + fetchPartialUpdate
|
|
196
|
-
* used by navigated-away, hmr-missing, and consolidation-needed scenarios.
|
|
197
|
-
* Resolves with the error of a loader that threw on the refetch.
|
|
198
|
-
*/
|
|
199
|
-
async function refetchRoute(opts?: {
|
|
203
|
+
type RefetchOptions = {
|
|
200
204
|
segments?: string[];
|
|
201
205
|
interceptSourceUrl?: string | null;
|
|
202
206
|
actionId?: string;
|
|
203
207
|
/** transition({ when }) `action` fields for the refetch commit. */
|
|
204
208
|
action?: TransitionWhenActionInput;
|
|
205
|
-
}
|
|
206
|
-
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** The follow-up's transition({ when }) fields: the last owing action's. */
|
|
212
|
+
type FollowUp = Pick<RefetchOptions, "actionId" | "action">;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* A navigation the user started (a click, Back/Forward, refresh(), an
|
|
216
|
+
* action's redirect) that a refetch may supersede: not committed yet, or
|
|
217
|
+
* committed with its transaction (a `prefetch: false` fill) or its stream
|
|
218
|
+
* still open (NavigationEntry.settled). A refetch never starts while one
|
|
219
|
+
* is open: its startNavigation would abort it, and with it the fill. A
|
|
220
|
+
* background revalidation (skipLoadingState: the follow-up, a repair, a
|
|
221
|
+
* Back/Forward entry's SWR) is not one.
|
|
222
|
+
*/
|
|
223
|
+
const unsettledUserNavigation = (): NavigationEntry | undefined => {
|
|
224
|
+
const pending = eventController.getUnsettledNavigation();
|
|
225
|
+
return pending && !pending.options?.skipLoadingState ? pending : undefined;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* A navigation the user started that may commit data read before an
|
|
230
|
+
* action's write: it has not committed yet. One that committed and only
|
|
231
|
+
* streams does not count: the action's own response re-rendered its page.
|
|
232
|
+
*/
|
|
233
|
+
const pendingUserNavigation = (): boolean => {
|
|
234
|
+
const pending = eventController.getCurrentNavigation();
|
|
235
|
+
return !!pending && !pending.options?.skipLoadingState;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The follow-up the page on screen is owed (finalizeAction sets it, a
|
|
240
|
+
* redirect clears it as it starts, followUp() takes it). Closure state of
|
|
241
|
+
* this bridge, as are the three below: no hook reads it, and the store
|
|
242
|
+
* stays the one source of truth for what hooks render (AGENTS.md Hard
|
|
243
|
+
* rule 8).
|
|
244
|
+
*/
|
|
245
|
+
let owed: FollowUp | undefined;
|
|
246
|
+
/** An action of the batch wrote (R1 applied); reset when the fence drops. */
|
|
247
|
+
let batchWrote = false;
|
|
248
|
+
/**
|
|
249
|
+
* Follow-ups started so far. Only the newest one runs again when it ends
|
|
250
|
+
* left: a newer one started after it, so its request read every write so
|
|
251
|
+
* far. A left follow-up's response still arrives (its request is not tied
|
|
252
|
+
* to its signal), and re-owing from it would start one that leaves the
|
|
253
|
+
* newer one, whose late answer would do the same, without end.
|
|
254
|
+
*/
|
|
255
|
+
let followUpsStarted = 0;
|
|
256
|
+
/** The newest follow-up, until it ends: leaveFollowUp() aborts it. */
|
|
257
|
+
let followUpRunning: { handle: NavigationHandle; due: FollowUp } | undefined;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The follow-up revalidation: the rules are R1-R7 in
|
|
261
|
+
* docs/internal/execution-model.md. Once the batch's last action exited,
|
|
262
|
+
* the page on screen that missed a write is refetched once, whole (no
|
|
263
|
+
* segments held, so every loader re-runs), in the action lane (storeOnly,
|
|
264
|
+
* skipLoadingState: useNavigation() stays idle).
|
|
265
|
+
*
|
|
266
|
+
* - Never over an intercept: a request holding no segments drops the page
|
|
267
|
+
* under it (match-result.ts buildMatchResult). Checked when it would run,
|
|
268
|
+
* against the page then on screen; the debt is dropped there.
|
|
269
|
+
* - Never supersedes a navigation the user started (dc-t114): it waits for
|
|
270
|
+
* NavigationEntry.settled, then looks again. A navigation that failed
|
|
271
|
+
* keeps its error on screen, and the debt is dropped.
|
|
272
|
+
* - Runs again when it is left before its commit (a navigation, or an action
|
|
273
|
+
* that started: leaveFollowUp), for the page then on screen.
|
|
274
|
+
*/
|
|
275
|
+
function followUp(): void {
|
|
276
|
+
if (!owed || isActionFenceActive()) return;
|
|
277
|
+
const pending = unsettledUserNavigation();
|
|
278
|
+
if (pending) {
|
|
279
|
+
void pending.settled.then((committed) => {
|
|
280
|
+
if (!committed && !pending.abort.signal.aborted) owed = undefined;
|
|
281
|
+
followUp();
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const due = owed;
|
|
286
|
+
owed = undefined;
|
|
287
|
+
if (store.getInterceptSourceUrl()) return;
|
|
288
|
+
const generation = ++followUpsStarted;
|
|
289
|
+
refetchRoute(due, {
|
|
290
|
+
started: (handle) => (followUpRunning = { handle, due }),
|
|
291
|
+
ended: (left) => {
|
|
292
|
+
if (generation !== followUpsStarted) return;
|
|
293
|
+
followUpRunning = undefined;
|
|
294
|
+
if (!left) return;
|
|
295
|
+
owed ??= due;
|
|
296
|
+
followUp();
|
|
297
|
+
},
|
|
298
|
+
}).catch((error) => {
|
|
299
|
+
if (isBackgroundSuppressible(error)) return;
|
|
300
|
+
console.error("[Browser] Background revalidation failed:", error);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* R6: an action that starts while the follow-up has not committed leaves
|
|
306
|
+
* it, and the batch's end owes it again. Its request may predate that
|
|
307
|
+
* action's write, and its commit could land after the action's own,
|
|
308
|
+
* moving data on screen backwards (dc-t110). Through the controller, so
|
|
309
|
+
* the navigation it started stops being current.
|
|
310
|
+
*/
|
|
311
|
+
function leaveFollowUp(): void {
|
|
312
|
+
const running = followUpRunning;
|
|
313
|
+
const current = eventController.getCurrentNavigation();
|
|
314
|
+
if (!running || current?.abort !== running.handle.abort) return;
|
|
315
|
+
owed ??= running.due;
|
|
316
|
+
eventController.abortNavigation(true);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Refetch the page on screen through a navigation transaction in the
|
|
321
|
+
* action lane: the follow-up, and the hmr-missing and consolidation-needed
|
|
322
|
+
* repairs. Resolves with whether it started and the error of a loader
|
|
323
|
+
* that threw on it. Never starts while a navigation the user started may
|
|
324
|
+
* be superseded (unsettledUserNavigation): its startNavigation would
|
|
325
|
+
* abort it (dc-t114). A repair that cannot start is owed at the batch's
|
|
326
|
+
* end instead (finalizeAction). `run` (the follow-up's) is handed the
|
|
327
|
+
* refetch's navigation, and told once whether it ended left before its
|
|
328
|
+
* commit.
|
|
329
|
+
*/
|
|
330
|
+
async function refetchRoute(
|
|
331
|
+
opts: RefetchOptions = {},
|
|
332
|
+
run?: {
|
|
333
|
+
started(handle: NavigationHandle): void;
|
|
334
|
+
ended(left: boolean): void;
|
|
335
|
+
},
|
|
336
|
+
): Promise<{ started: boolean; error?: Error }> {
|
|
337
|
+
if (unsettledUserNavigation()) return { started: false };
|
|
338
|
+
const src = opts.interceptSourceUrl ?? null;
|
|
207
339
|
const navTx = createNavigationTransaction(
|
|
208
340
|
store,
|
|
209
341
|
eventController,
|
|
210
342
|
window.location.href,
|
|
211
343
|
{ replace: true, skipLoadingState: true },
|
|
212
344
|
);
|
|
345
|
+
run?.started(navTx.handle);
|
|
213
346
|
try {
|
|
214
|
-
|
|
347
|
+
const error = await fetchPartialUpdate(
|
|
215
348
|
window.location.href,
|
|
216
|
-
opts
|
|
349
|
+
opts.segments ?? [],
|
|
217
350
|
false,
|
|
218
351
|
navTx.handle.signal,
|
|
219
352
|
navTx.with({
|
|
@@ -224,12 +357,14 @@ export function createServerActionBridge(
|
|
|
224
357
|
{
|
|
225
358
|
type: "action" as const,
|
|
226
359
|
...(src ? { interceptSourceUrl: src } : {}),
|
|
227
|
-
...(opts
|
|
228
|
-
...(opts
|
|
360
|
+
...(opts.actionId !== undefined ? { actionId: opts.actionId } : {}),
|
|
361
|
+
...(opts.action ? { action: opts.action } : {}),
|
|
229
362
|
},
|
|
230
363
|
);
|
|
364
|
+
return { started: true, error };
|
|
231
365
|
} finally {
|
|
232
366
|
navTx[Symbol.dispose]();
|
|
367
|
+
run?.ended(navTx.handle.signal.aborted && !navTx.handle.completed);
|
|
233
368
|
}
|
|
234
369
|
}
|
|
235
370
|
|
|
@@ -244,7 +379,18 @@ export function createServerActionBridge(
|
|
|
244
379
|
if (tx) browserDebugLog(tx, msg, details);
|
|
245
380
|
};
|
|
246
381
|
|
|
382
|
+
leaveFollowUp();
|
|
247
383
|
const locationKey = window.history.state?.key;
|
|
384
|
+
// Capture URL pathname at action start to detect navigation during action
|
|
385
|
+
// Must use window.location (not store.path) because intercepts change URL
|
|
386
|
+
// without changing store.path (e.g., /kanban -> /kanban/card/1)
|
|
387
|
+
const actionStartPathname = window.location.pathname;
|
|
388
|
+
// The page the action was sent from is no longer on screen: a navigation
|
|
389
|
+
// committed since it started (the classifyActionOutcome rule). Its
|
|
390
|
+
// response then commits nothing, and its exit owes the follow-up.
|
|
391
|
+
const navigatedSince = (): boolean =>
|
|
392
|
+
window.location.pathname !== actionStartPathname ||
|
|
393
|
+
window.history.state?.key !== locationKey;
|
|
248
394
|
log("action start", { id, argsCount: args.length });
|
|
249
395
|
|
|
250
396
|
// Start action in event controller - handles lifecycle tracking. The
|
|
@@ -272,6 +418,23 @@ export function createServerActionBridge(
|
|
|
272
418
|
// its own latch. Latched so the finally AND the early SPA-redirect returns
|
|
273
419
|
// (whose Flight stream never settles) can both call it safely.
|
|
274
420
|
let actionFinalized = false;
|
|
421
|
+
// Set where the action navigates to its redirect's target.
|
|
422
|
+
let redirected = false;
|
|
423
|
+
// Set where its repair refetch could not start (refetchRoute).
|
|
424
|
+
let repairOwed = false;
|
|
425
|
+
// R5: the redirect fetches its page after every write so far, so the
|
|
426
|
+
// batch's debt is cleared as it starts, not at the action's exit: a
|
|
427
|
+
// sibling that lands during its navigation owes for the page it commits.
|
|
428
|
+
const startRedirect = (): void => {
|
|
429
|
+
redirected = true;
|
|
430
|
+
owed = undefined;
|
|
431
|
+
};
|
|
432
|
+
// transition({ when }) `action` for this action's commit, whichever lane
|
|
433
|
+
// applies it (its own, a repair refetch, or the follow-up).
|
|
434
|
+
let whenAction: TransitionWhenActionInput = {
|
|
435
|
+
id,
|
|
436
|
+
formData: findActionFormData(args),
|
|
437
|
+
};
|
|
275
438
|
// skipInvalidation: the version-mismatch reload terminal released nothing
|
|
276
439
|
// server-side, so it releases the fence without invalidating.
|
|
277
440
|
const finalizeAction = (skipInvalidation = false): void => {
|
|
@@ -284,9 +447,32 @@ export function createServerActionBridge(
|
|
|
284
447
|
// marking the cache stale + broadcasting cross-tab would be spurious.
|
|
285
448
|
if (responseReceived && !keepCache && !skipInvalidation) {
|
|
286
449
|
store.markCacheAsStaleAndBroadcast();
|
|
450
|
+
batchWrote = true;
|
|
451
|
+
// The write may be missing from the page on screen: that page is
|
|
452
|
+
// not the one the response re-rendered, or a navigation the user
|
|
453
|
+
// started has not committed and its request may predate the write.
|
|
454
|
+
// A navigation that committed and only streams owes nothing: this
|
|
455
|
+
// response re-rendered its page. A later land never clears this:
|
|
456
|
+
// its request was read after its own write, not necessarily after
|
|
457
|
+
// this one. A redirect owes nothing: its navigation cleared the
|
|
458
|
+
// debt (startRedirect).
|
|
459
|
+
if (!redirected && (navigatedSince() || pendingUserNavigation())) {
|
|
460
|
+
owed = { actionId: id, action: whenAction };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// A repair that could not start is owed at the batch's end when an
|
|
464
|
+
// action of the batch wrote. The repair runs whatever this action's
|
|
465
|
+
// keepClientCache(), so a keep action that lands last discharges a
|
|
466
|
+
// directive-free sibling's consolidation; a lone keep action owes
|
|
467
|
+
// nothing.
|
|
468
|
+
if (repairOwed && batchWrote) {
|
|
469
|
+
owed = { actionId: id, action: whenAction };
|
|
287
470
|
}
|
|
288
471
|
} finally {
|
|
289
472
|
exitActionFence();
|
|
473
|
+
if (!isActionFenceActive()) batchWrote = false;
|
|
474
|
+
// The batch's last exit runs it (followUp returns while the fence is up).
|
|
475
|
+
followUp();
|
|
290
476
|
}
|
|
291
477
|
};
|
|
292
478
|
try {
|
|
@@ -302,33 +488,16 @@ export function createServerActionBridge(
|
|
|
302
488
|
// Create temporary references for serialization
|
|
303
489
|
const temporaryReferences = deps.createTemporaryReferenceSet();
|
|
304
490
|
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
segmentState.currentSegmentIds.join(","),
|
|
316
|
-
);
|
|
317
|
-
// Add version param for version mismatch detection
|
|
318
|
-
const version = getAppVersion();
|
|
319
|
-
if (version) {
|
|
320
|
-
url.searchParams.set("_rsc_v", version);
|
|
321
|
-
}
|
|
322
|
-
// Add router ID for app switch detection
|
|
323
|
-
const rid = store.getRouterId?.();
|
|
324
|
-
if (rid) {
|
|
325
|
-
url.searchParams.set("_rsc_rid", rid);
|
|
326
|
-
}
|
|
327
|
-
// The page's rewrite target: the server diffs the re-render against it.
|
|
328
|
-
const rewriteTarget = store.getHistoryEntryMemory()?.rewriteTarget;
|
|
329
|
-
if (rewriteTarget) {
|
|
330
|
-
url.searchParams.set("_rsc_rt", rewriteTarget);
|
|
331
|
-
}
|
|
491
|
+
// Build action request URL with current segments; the server diffs the
|
|
492
|
+
// re-render against the page's rewrite target.
|
|
493
|
+
const url = writePartialParams(new URL(window.location.href), {
|
|
494
|
+
action: id,
|
|
495
|
+
segmentIds: segmentState.currentSegmentIds,
|
|
496
|
+
emptySegments: "send",
|
|
497
|
+
version: getAppVersion(),
|
|
498
|
+
routerId: store.getRouterId?.(),
|
|
499
|
+
rewriteTarget: store.getHistoryEntryMemory()?.rewriteTarget,
|
|
500
|
+
});
|
|
332
501
|
|
|
333
502
|
// Encode arguments
|
|
334
503
|
const encodedBody = await deps.encodeReply(args, { temporaryReferences });
|
|
@@ -411,6 +580,7 @@ export function createServerActionBridge(
|
|
|
411
580
|
if (redirect && redirect !== "blocked" && !handle.signal.aborted) {
|
|
412
581
|
log("action simple redirect", { url: redirect.url });
|
|
413
582
|
handle.complete(undefined);
|
|
583
|
+
startRedirect();
|
|
414
584
|
// This path returns a never-settling promise, so the finally never
|
|
415
585
|
// runs: invalidate + release the fence here (the mutation committed
|
|
416
586
|
// and we're navigating away). Latched, so the finally is a no-op.
|
|
@@ -529,7 +699,10 @@ export function createServerActionBridge(
|
|
|
529
699
|
: "blocked action redirect payload",
|
|
530
700
|
{ url: redirect.url },
|
|
531
701
|
);
|
|
532
|
-
|
|
702
|
+
return returnValue?.data;
|
|
703
|
+
}
|
|
704
|
+
startRedirect();
|
|
705
|
+
if (redirect.external) {
|
|
533
706
|
log("external action redirect", { url: target });
|
|
534
707
|
window.location.assign(target);
|
|
535
708
|
} else {
|
|
@@ -556,41 +729,54 @@ export function createServerActionBridge(
|
|
|
556
729
|
}
|
|
557
730
|
|
|
558
731
|
// Handle error responses with error boundary UI
|
|
559
|
-
if (isError && isPartial && segments
|
|
732
|
+
if (isError && isPartial && segments) {
|
|
560
733
|
log("processing error boundary response");
|
|
561
734
|
|
|
562
|
-
//
|
|
563
|
-
//
|
|
735
|
+
// transition({ when }) sees action.error on the boundary commit and on
|
|
736
|
+
// a dropped one's follow-up.
|
|
737
|
+
whenAction = {
|
|
738
|
+
...whenAction,
|
|
739
|
+
error: returnValue && !returnValue.ok ? returnValue.data : undefined,
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// No consolidation: the boundary replaces the page, or the page left.
|
|
743
|
+
handle.clearConsolidation();
|
|
744
|
+
|
|
745
|
+
const currentKey = store.getHistoryKey();
|
|
746
|
+
const pageLeft = () =>
|
|
747
|
+
navigatedSince() || store.getHistoryKey() !== currentKey;
|
|
748
|
+
// The action's page left the screen (during the flight or the error
|
|
749
|
+
// render): drop the boundary; the call rejects and the exit owes the
|
|
750
|
+
// follow-up.
|
|
751
|
+
const dropBoundary = () => {
|
|
752
|
+
log("user navigated during error render, skipping");
|
|
753
|
+
if (returnValue && !returnValue.ok) throw returnValue.data;
|
|
754
|
+
handle.complete(undefined);
|
|
755
|
+
return undefined;
|
|
756
|
+
};
|
|
757
|
+
if (pageLeft()) return dropBoundary();
|
|
758
|
+
|
|
759
|
+
// fail() settles once the response's stream ends; the abort below
|
|
760
|
+
// spares this action, so its entry keeps the error for useAction().
|
|
564
761
|
if (returnValue && !returnValue.ok) {
|
|
565
762
|
handle.fail(returnValue.data);
|
|
566
763
|
}
|
|
567
764
|
|
|
568
|
-
// Abort
|
|
569
|
-
//
|
|
570
|
-
eventController.abortAllActions();
|
|
571
|
-
|
|
572
|
-
// Clear concurrent action tracking - no consolidation needed when showing error
|
|
573
|
-
handle.clearConsolidation();
|
|
765
|
+
// Abort the other actions in flight: the error takes precedence, so
|
|
766
|
+
// none completes over the boundary.
|
|
767
|
+
eventController.abortAllActions(handle.id);
|
|
574
768
|
|
|
575
769
|
// Get current page's cached segments
|
|
576
|
-
const currentKey = store.getHistoryKey();
|
|
577
770
|
const cached = store.getCachedSegments(currentKey);
|
|
578
771
|
const cachedSegments = cached?.segments || [];
|
|
579
772
|
|
|
580
773
|
// Reconcile error segments with cached tree
|
|
581
774
|
const errorResult = reconcileErrorSegments(cachedSegments, segments);
|
|
582
775
|
|
|
583
|
-
// transition({ when }) decides the error-boundary commit too, with
|
|
584
|
-
// `action.error` set.
|
|
585
776
|
const errorGatedOff = decideActionGatedOff(
|
|
586
777
|
errorResult.segments,
|
|
587
778
|
metadata,
|
|
588
|
-
|
|
589
|
-
id,
|
|
590
|
-
formData: findActionFormData(args),
|
|
591
|
-
error:
|
|
592
|
-
returnValue && !returnValue.ok ? returnValue.data : undefined,
|
|
593
|
-
},
|
|
779
|
+
whenAction,
|
|
594
780
|
);
|
|
595
781
|
|
|
596
782
|
// Render the full tree with error segment merged with parent layouts
|
|
@@ -603,36 +789,18 @@ export function createServerActionBridge(
|
|
|
603
789
|
: undefined,
|
|
604
790
|
});
|
|
605
791
|
|
|
606
|
-
|
|
607
|
-
// navigated away while the error tree was being prepared.
|
|
608
|
-
if (window.location.pathname !== actionStartPathname) {
|
|
609
|
-
log("user navigated during error render, skipping");
|
|
610
|
-
if (returnValue && !returnValue.ok) {
|
|
611
|
-
throw returnValue.data;
|
|
612
|
-
}
|
|
613
|
-
handle.complete(undefined);
|
|
614
|
-
return undefined;
|
|
615
|
-
}
|
|
616
|
-
const currentKeyNow = store.getHistoryKey();
|
|
617
|
-
if (currentKeyNow !== currentKey) {
|
|
618
|
-
log("history key changed during error render, skipping cache update");
|
|
619
|
-
if (returnValue && !returnValue.ok) {
|
|
620
|
-
throw returnValue.data;
|
|
621
|
-
}
|
|
622
|
-
handle.complete(undefined);
|
|
623
|
-
return undefined;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Update segment tracking to exclude error segment IDs
|
|
627
|
-
const errorSegmentIds = new Set(diff);
|
|
628
|
-
const segmentIdsAfterError = segmentState.currentSegmentIds.filter(
|
|
629
|
-
(id) => !errorSegmentIds.has(id),
|
|
630
|
-
);
|
|
792
|
+
if (pageLeft()) return dropBoundary();
|
|
631
793
|
|
|
632
794
|
// Cache (and bump the nav instance) BEFORE the UI update so a deferred
|
|
633
795
|
// handle pushed by the error-boundary render still applies — see the
|
|
634
|
-
// "normal" case below for why caching after onUpdate dropped it.
|
|
635
|
-
|
|
796
|
+
// "normal" case below for why caching after onUpdate dropped it. The
|
|
797
|
+
// page stops claiming the error segments only: the loaders the
|
|
798
|
+
// response re-ran above the boundary stay held.
|
|
799
|
+
store.setSegmentIds(
|
|
800
|
+
segmentState.currentSegmentIds.filter(
|
|
801
|
+
(id) => !segments.some((s) => s.id === id && s.type === "error"),
|
|
802
|
+
),
|
|
803
|
+
);
|
|
636
804
|
const currentHandleData = liveHandleDataForCache(
|
|
637
805
|
eventController,
|
|
638
806
|
store,
|
|
@@ -709,6 +877,7 @@ export function createServerActionBridge(
|
|
|
709
877
|
let revalidationError: Error | undefined;
|
|
710
878
|
|
|
711
879
|
if (returnValue && !returnValue.ok) {
|
|
880
|
+
whenAction = { ...whenAction, error: returnValue.data };
|
|
712
881
|
handle.fail(returnValue.data);
|
|
713
882
|
throw returnValue.data;
|
|
714
883
|
}
|
|
@@ -725,7 +894,6 @@ export function createServerActionBridge(
|
|
|
725
894
|
currentLocationKey: window.history.state?.key,
|
|
726
895
|
reconciledSegmentCount: fullSegments.length,
|
|
727
896
|
matchedCount: matched.length,
|
|
728
|
-
currentInterceptSource: store.getInterceptSourceUrl(),
|
|
729
897
|
});
|
|
730
898
|
|
|
731
899
|
// Apply server-set location state exhaustively here, as a successful-
|
|
@@ -745,51 +913,17 @@ export function createServerActionBridge(
|
|
|
745
913
|
eventController,
|
|
746
914
|
);
|
|
747
915
|
}
|
|
748
|
-
|
|
749
|
-
// lane applies it (normal, or a refetch below).
|
|
750
|
-
const whenAction: TransitionWhenActionInput = {
|
|
751
|
-
id,
|
|
752
|
-
formData: findActionFormData(args),
|
|
753
|
-
result: returnData,
|
|
754
|
-
};
|
|
916
|
+
whenAction = { ...whenAction, result: returnData };
|
|
755
917
|
|
|
756
918
|
switch (scenario.type) {
|
|
757
919
|
case "navigated-away": {
|
|
758
920
|
log("user navigated away during action", {
|
|
759
921
|
from: actionStartPathname,
|
|
760
922
|
to: window.location.pathname,
|
|
761
|
-
historyKeyChanged: scenario.historyKeyChanged,
|
|
762
923
|
});
|
|
763
924
|
// Clear concurrent action tracking - don't consolidate for old route's segments
|
|
764
925
|
handle.clearConsolidation();
|
|
765
|
-
|
|
766
|
-
if (scenario.historyKeyChanged) {
|
|
767
|
-
// Invalidation is deferred to finalizeAction(); here we only trigger
|
|
768
|
-
// the revalidation refetch of the new route (suppressed on keep).
|
|
769
|
-
if (!scenario.onInterceptRoute && !keepCache) {
|
|
770
|
-
refetchRoute({ actionId: id, action: whenAction }).catch(
|
|
771
|
-
(error) => {
|
|
772
|
-
if (isBackgroundSuppressible(error)) return;
|
|
773
|
-
console.error(
|
|
774
|
-
"[Browser] Background revalidation failed:",
|
|
775
|
-
error,
|
|
776
|
-
);
|
|
777
|
-
},
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
break;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Same history key but different pathname - safe to refetch current
|
|
784
|
-
// route. Invalidation is deferred to finalizeAction(); here we only
|
|
785
|
-
// trigger the revalidation refetch (suppressed on keep).
|
|
786
|
-
if (!keepCache) {
|
|
787
|
-
revalidationError = await refetchRoute({
|
|
788
|
-
interceptSourceUrl: store.getInterceptSourceUrl(),
|
|
789
|
-
actionId: id,
|
|
790
|
-
action: whenAction,
|
|
791
|
-
});
|
|
792
|
-
}
|
|
926
|
+
// finalizeAction() owes the page on screen the follow-up.
|
|
793
927
|
break;
|
|
794
928
|
}
|
|
795
929
|
|
|
@@ -801,11 +935,13 @@ export function createServerActionBridge(
|
|
|
801
935
|
// resolving last must discharge a directive-free sibling's repair.
|
|
802
936
|
// See the keep row in docs/design/rango-state-cookie.md (the all-keep
|
|
803
937
|
// edge, and the benign re-mark-stale-after-refetch end-state delta).
|
|
804
|
-
|
|
938
|
+
const repair = await refetchRoute({
|
|
805
939
|
interceptSourceUrl,
|
|
806
940
|
actionId: id,
|
|
807
941
|
action: whenAction,
|
|
808
942
|
});
|
|
943
|
+
revalidationError = repair.error;
|
|
944
|
+
repairOwed = !repair.started;
|
|
809
945
|
break;
|
|
810
946
|
}
|
|
811
947
|
|
|
@@ -824,12 +960,14 @@ export function createServerActionBridge(
|
|
|
824
960
|
handle.clearConsolidation();
|
|
825
961
|
|
|
826
962
|
// Ungated on keepCache, same as hmr-missing above (see the keep row).
|
|
827
|
-
|
|
963
|
+
const repair = await refetchRoute({
|
|
828
964
|
segments: segmentsToSend,
|
|
829
965
|
interceptSourceUrl,
|
|
830
966
|
actionId: id,
|
|
831
967
|
action: whenAction,
|
|
832
968
|
});
|
|
969
|
+
revalidationError = repair.error;
|
|
970
|
+
repairOwed = !repair.started;
|
|
833
971
|
break;
|
|
834
972
|
}
|
|
835
973
|
|
package/src/browser/types.ts
CHANGED
|
@@ -649,7 +649,8 @@ export interface NavigationStore {
|
|
|
649
649
|
handlesPending?: boolean;
|
|
650
650
|
}
|
|
651
651
|
| undefined;
|
|
652
|
-
|
|
652
|
+
/** Whether an intercept entry is cached for the plain `historyKey`, from any source page. */
|
|
653
|
+
hasInterceptCache(historyKey: string): boolean;
|
|
653
654
|
/**
|
|
654
655
|
* Update only the handleData (and optionally the stale / handlesPending flags)
|
|
655
656
|
* of an existing cache entry. When a flag is omitted the entry's current value
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
version?: string;
|
|
17
|
+
routerId?: string;
|
|
18
|
+
rewriteTarget?: string;
|
|
19
|
+
fill?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Writes the partial-request parameters onto `url` in the one order every
|
|
24
|
+
* sender uses. Navigation and prefetch cache keys are built from the result
|
|
25
|
+
* (prefetch/cache.ts), so a navigation adopts a prefetch only while both
|
|
26
|
+
* write identically: browser/__tests__/wire-urls.test.ts pins the strings.
|
|
27
|
+
*/
|
|
28
|
+
export function writePartialParams(url: URL, init: PartialRequestInit): URL {
|
|
29
|
+
const params = url.searchParams;
|
|
30
|
+
if (init.action !== undefined) params.set(WIRE.action, init.action);
|
|
31
|
+
else params.set(WIRE.partial, "true");
|
|
32
|
+
if (init.segmentIds.length > 0 || init.emptySegments === "send") {
|
|
33
|
+
params.set(WIRE.segments, init.segmentIds.join(","));
|
|
34
|
+
}
|
|
35
|
+
if (init.stale) params.set(WIRE.stale, "true");
|
|
36
|
+
if (init.version) params.set(WIRE.version, init.version);
|
|
37
|
+
if (init.routerId) params.set(WIRE.routerId, init.routerId);
|
|
38
|
+
if (init.rewriteTarget) params.set(WIRE.rewriteTarget, init.rewriteTarget);
|
|
39
|
+
if (init.fill) params.set(WIRE.fill, "1");
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
@@ -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.
|