@timber-js/app 0.2.0-alpha.209 → 0.2.0-alpha.210
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/_chunks/{actions-BerlqoXA.js → actions-Rjk4htmA.js} +19 -25
- package/dist/_chunks/{actions-BerlqoXA.js.map → actions-Rjk4htmA.js.map} +1 -1
- package/dist/_chunks/als-registry-DaxkVjt5.js.map +1 -1
- package/dist/_chunks/{cache-api-CR23J_NC.js → cache-api-DGdYfNJn.js} +4 -4
- package/dist/_chunks/{cache-api-CR23J_NC.js.map → cache-api-DGdYfNJn.js.map} +1 -1
- package/dist/_chunks/{chains-CpFg56UB.js → chains-BoO51joc.js} +2 -2
- package/dist/_chunks/{chains-CpFg56UB.js.map → chains-BoO51joc.js.map} +1 -1
- package/dist/_chunks/{cli-check-C6Ev6wBO.js → cli-check-ajNY3B2e.js} +3 -3
- package/dist/_chunks/{cli-check-C6Ev6wBO.js.map → cli-check-ajNY3B2e.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-CbT2AUUI.js → cli-schema-sync-D2eI8jEg.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-CbT2AUUI.js.map → cli-schema-sync-D2eI8jEg.js.map} +1 -1
- package/dist/_chunks/{file-cache-Dw6BJPG7.js → codegen-Bps1sLKJ.js} +3 -29
- package/dist/_chunks/codegen-Bps1sLKJ.js.map +1 -0
- package/dist/_chunks/{convention-lint-jKTwKwPe.js → convention-lint-DLmhGsRS.js} +54 -316
- package/dist/_chunks/convention-lint-DLmhGsRS.js.map +1 -0
- package/dist/_chunks/{dev-server-C4WZdB7L.js → dev-server-v97rQH4b.js} +99 -9
- package/dist/_chunks/dev-server-v97rQH4b.js.map +1 -0
- package/dist/_chunks/{error-boundary-tA7kVfs4.js → error-boundary-DsNScGRM.js} +4 -4
- package/dist/_chunks/{error-boundary-tA7kVfs4.js.map → error-boundary-DsNScGRM.js.map} +1 -1
- package/dist/_chunks/{json-lossy-check-ip0Qi0MT.js → json-lossy-check-CVuRs2hG.js} +2 -2
- package/dist/_chunks/{json-lossy-check-ip0Qi0MT.js.map → json-lossy-check-CVuRs2hG.js.map} +1 -1
- package/dist/_chunks/{live-graph-Dv-JJCZw.js → live-graph-9cSnn_h9.js} +3 -3
- package/dist/_chunks/{live-graph-Dv-JJCZw.js.map → live-graph-9cSnn_h9.js.map} +1 -1
- package/dist/_chunks/{logger-DiDt5ppH.js → logger-BP0LN6vP.js} +17 -2
- package/dist/_chunks/{logger-DiDt5ppH.js.map → logger-BP0LN6vP.js.map} +1 -1
- package/dist/_chunks/metadata-routes-DSDjM_hJ.js.map +1 -1
- package/dist/_chunks/navigation-root-BQfo1-kG.js.map +1 -1
- package/dist/_chunks/{poison-scan-CpeT6_OJ.js → poison-scan-vGV7Re0B.js} +2 -2
- package/dist/_chunks/{poison-scan-CpeT6_OJ.js.map → poison-scan-vGV7Re0B.js.map} +1 -1
- package/dist/_chunks/{scanner-Bw0oq1HB.js → scanner-DmqdxzbW.js} +392 -7
- package/dist/_chunks/scanner-DmqdxzbW.js.map +1 -0
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -1
- package/dist/_chunks/{sizeof-UwzwB1uM.js → sizeof-BM1409x2.js} +2 -2
- package/dist/_chunks/{sizeof-UwzwB1uM.js.map → sizeof-BM1409x2.js.map} +1 -1
- package/dist/_chunks/{status-page-marker-gaihi0KZ.js → status-page-marker-BRX9Ib-d.js} +1 -45
- package/dist/_chunks/status-page-marker-BRX9Ib-d.js.map +1 -0
- package/dist/_chunks/{walkers-BXExhzzk.js → walkers-Czu2jXFq.js} +3 -3
- package/dist/_chunks/{walkers-BXExhzzk.js.map → walkers-Czu2jXFq.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +2 -2
- package/dist/cache/stores/memory.js +1 -1
- package/dist/cli.js +3 -3
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/history.d.ts +0 -9
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +9 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +25 -38
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +22 -0
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +14 -2
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +9 -1
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +6 -1
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/react-root.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +10 -3
- package/dist/client/router-effects.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +3 -1
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +65 -9
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +0 -15
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +13 -6
- package/dist/client/use-router.d.ts.map +1 -1
- package/dist/config-validation.d.ts +19 -2
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +19 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +3 -3
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/types.d.ts +7 -0
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +9 -4
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/csrf.d.ts +38 -19
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +8 -4
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/form-flash.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +249 -16
- package/dist/server/internal.js.map +1 -1
- package/dist/server/logger.d.ts +16 -3
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/metadata-collector.d.ts +2 -0
- package/dist/server/metadata-collector.d.ts.map +1 -1
- package/dist/server/metadata-routes.d.ts +12 -1
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +29 -1
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +8 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +8 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/{wrap-action-dispatch.d.ts → action-dispatcher.d.ts} +18 -40
- package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts +5 -12
- package/dist/server/safe-load.d.ts.map +1 -1
- package/docs/api/30-api-server.mdx +1 -1
- package/docs/api/31-api-client.mdx +23 -20
- package/docs/api/34-api-config.mdx +4 -2
- package/package.json +1 -1
- package/src/client/browser-entry/action-dispatch.ts +28 -28
- package/src/client/browser-entry/post-hydration.ts +11 -1
- package/src/client/browser-entry/router-init.ts +10 -4
- package/src/client/history.ts +0 -22
- package/src/client/index.ts +2 -1
- package/src/client/link.tsx +30 -1
- package/src/client/navigation-api.ts +36 -3
- package/src/client/navigation-root.tsx +13 -1
- package/src/client/navigation-transition.ts +17 -7
- package/src/client/react-root.ts +11 -2
- package/src/client/router-effects.ts +11 -4
- package/src/client/router-pipeline.ts +4 -0
- package/src/client/router-types.ts +80 -6
- package/src/client/router.ts +36 -24
- package/src/client/segment-cache.ts +0 -65
- package/src/client/use-router.ts +25 -6
- package/src/config-validation.ts +121 -5
- package/src/index.ts +5 -8
- package/src/routing/convention-lint.ts +75 -0
- package/src/routing/export-detect.ts +88 -0
- package/src/routing/manifest-codegen.ts +6 -0
- package/src/routing/scanner.ts +9 -0
- package/src/routing/types.ts +7 -0
- package/src/server/action-handler.ts +14 -11
- package/src/server/actions.ts +37 -42
- package/src/server/als-registry.ts +1 -1
- package/src/server/csrf.ts +100 -72
- package/src/server/error-boundary-wrapper.ts +11 -12
- package/src/server/fallback-error.ts +13 -21
- package/src/server/form-flash.ts +1 -1
- package/src/server/logger.ts +19 -3
- package/src/server/metadata-collector.ts +4 -1
- package/src/server/metadata-routes.ts +23 -8
- package/src/server/pipeline-helpers.ts +89 -8
- package/src/server/pipeline.ts +27 -2
- package/src/server/route-element-builder.ts +1 -0
- package/src/server/route-matcher.ts +11 -0
- package/src/server/rsc-entry/{wrap-action-dispatch.ts → action-dispatcher.ts} +20 -62
- package/src/server/rsc-entry/index.ts +14 -27
- package/src/server/safe-load.ts +5 -12
- package/dist/_chunks/convention-lint-jKTwKwPe.js.map +0 -1
- package/dist/_chunks/dev-server-C4WZdB7L.js.map +0 -1
- package/dist/_chunks/file-cache-Dw6BJPG7.js.map +0 -1
- package/dist/_chunks/scanner-Bw0oq1HB.js.map +0 -1
- package/dist/_chunks/status-page-marker-gaihi0KZ.js.map +0 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +0 -1
|
@@ -44,7 +44,7 @@ export function setupServerActions(): void {
|
|
|
44
44
|
refreshWhenIdle(): void {
|
|
45
45
|
const router = getRouterOrNull();
|
|
46
46
|
if (!router) return;
|
|
47
|
-
router.runWhenIdle(() => void router.refresh());
|
|
47
|
+
router.runWhenIdle(() => void router.refresh({ transitionType: 'revalidate' }));
|
|
48
48
|
},
|
|
49
49
|
// Every hold below is `applyActionResult` — the router's own refresh
|
|
50
50
|
// or piggybacked commit. If it has not settled in `holdTimeoutMs`, the
|
|
@@ -56,7 +56,7 @@ export function setupServerActions(): void {
|
|
|
56
56
|
recoverStalledHold(): void {
|
|
57
57
|
const router = getRouterOrNull();
|
|
58
58
|
if (!router) return;
|
|
59
|
-
void router.refresh();
|
|
59
|
+
void router.refresh({ transitionType: 'revalidate' });
|
|
60
60
|
},
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -74,7 +74,7 @@ export function setupServerActions(): void {
|
|
|
74
74
|
|
|
75
75
|
let hasRevalidation = false;
|
|
76
76
|
let hasRedirect = false;
|
|
77
|
-
|
|
77
|
+
let invalidated = false;
|
|
78
78
|
|
|
79
79
|
const actionHeaders: Record<string, string> = {
|
|
80
80
|
'Accept': RSC_CONTENT_TYPE,
|
|
@@ -103,30 +103,35 @@ export function setupServerActions(): void {
|
|
|
103
103
|
}
|
|
104
104
|
hasRevalidation = res.headers.get('X-Timber-Revalidation') === '1';
|
|
105
105
|
hasRedirect = res.headers.get('X-Timber-Redirect') != null;
|
|
106
|
-
|
|
106
|
+
invalidated = res.headers.get('X-Timber-Invalidated') === '1';
|
|
107
107
|
return res;
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
const decoded = await createFromFetch(response);
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
// The mutation happened server-side, and the next thing on screen is
|
|
113
|
+
// not a full render of the current page: revalidatePath named only
|
|
114
|
+
// other pages (TIM-1454), or the action redirected — from its body, or
|
|
115
|
+
// from a current-page revalidation whose render redirected (TIM-1461).
|
|
116
|
+
// Cached payloads go with the eviction below; this covers the mounted
|
|
117
|
+
// shared layouts the segment cache would otherwise let the server skip
|
|
118
|
+
// on the next navigation (TIM-1466). The tree and refresh branches
|
|
119
|
+
// re-render every layout, so they need neither.
|
|
120
|
+
if (router && (invalidated || hasRedirect)) {
|
|
121
|
+
router.suppressSegmentReuse();
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
// TIM-1476:
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
124
|
+
// TIM-1476: Evict all prefetch cache entries and history stack
|
|
125
|
+
// payloads after every action — the mutation may affect data any route
|
|
126
|
+
// reads, not just the paths the server named, and an action that
|
|
127
|
+
// revalidated nothing still mutated (TIM-1518). Placed before every
|
|
128
|
+
// branch: before the redirect so an internal redirect via
|
|
129
|
+
// router.navigate() cannot consume a stale prefetch (codex on #1131),
|
|
130
|
+
// and synchronously before applyActionResult's hold so a navigation
|
|
131
|
+
// that supersedes the refresh cannot either. In-flight prefetch
|
|
132
|
+
// singleflights are fenced by the eviction generation and will not
|
|
133
|
+
// repopulate the cache.
|
|
134
|
+
if (router) {
|
|
130
135
|
router.evictStaleCaches();
|
|
131
136
|
}
|
|
132
137
|
|
|
@@ -178,7 +183,7 @@ export function setupServerActions(): void {
|
|
|
178
183
|
.then(
|
|
179
184
|
(applied) => {
|
|
180
185
|
if (!applied) {
|
|
181
|
-
// Synchronous eviction already ran
|
|
186
|
+
// Synchronous eviction already ran above — only
|
|
182
187
|
// the refresh flag needs deferral (TIM-1518).
|
|
183
188
|
queue.markNeedsRefresh();
|
|
184
189
|
}
|
|
@@ -192,7 +197,7 @@ export function setupServerActions(): void {
|
|
|
192
197
|
return wrapper._action;
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
// revalidatePath was called but only for
|
|
200
|
+
// revalidatePath was called but only for other pages —
|
|
196
201
|
// caches were already invalidated above, so no refresh is needed
|
|
197
202
|
// unless a navigation ran while this action was in flight. That
|
|
198
203
|
// navigation may have consumed a pre-mutation prefetch before the
|
|
@@ -200,7 +205,7 @@ export function setupServerActions(): void {
|
|
|
200
205
|
// now on screen. The other branches catch this through
|
|
201
206
|
// applyActionResult's epoch check; this one must check itself
|
|
202
207
|
// (TIM-1483).
|
|
203
|
-
if (
|
|
208
|
+
if (invalidated) {
|
|
204
209
|
if (router && epoch && !router.isEpochCurrent(epoch)) {
|
|
205
210
|
queue.markNeedsRefresh();
|
|
206
211
|
}
|
|
@@ -212,11 +217,6 @@ export function setupServerActions(): void {
|
|
|
212
217
|
// the queue until the refresh commits so the next action captures a
|
|
213
218
|
// current epoch (codex on #1125).
|
|
214
219
|
if (router && epoch) {
|
|
215
|
-
// TIM-1518: Evict synchronously so a navigation that supersedes
|
|
216
|
-
// this action's refresh cannot consume a pre-mutation prefetch.
|
|
217
|
-
// The deferred eviction in .then() ran as a microtask — after the
|
|
218
|
-
// superseding navigation had already consumed a stale entry.
|
|
219
|
-
router.evictStaleCaches();
|
|
220
220
|
hold(
|
|
221
221
|
router.applyActionResult(epoch).then(
|
|
222
222
|
(applied) => {
|
|
@@ -61,7 +61,17 @@ export function setupPostHydration({ router, navApiController }: PostHydrationOp
|
|
|
61
61
|
|
|
62
62
|
const state = window.history.state;
|
|
63
63
|
const scrollY = state && typeof state.scrollY === 'number' ? state.scrollY : 0;
|
|
64
|
-
|
|
64
|
+
// `popstate` does not say which way the user went, and timber writes no
|
|
65
|
+
// index into `history.state` to work it out: any `pushState` the app
|
|
66
|
+
// makes itself (nuqs, a shallow update) can overwrite it. So the render is
|
|
67
|
+
// tagged `navigation-traverse`. The Navigation API path (navigation-api.ts)
|
|
68
|
+
// is the one that knows back from forward (TIM-1471).
|
|
69
|
+
void router.handlePopState(
|
|
70
|
+
window.location.pathname + locationSearch(),
|
|
71
|
+
scrollY,
|
|
72
|
+
undefined,
|
|
73
|
+
'navigation-traverse'
|
|
74
|
+
);
|
|
65
75
|
});
|
|
66
76
|
|
|
67
77
|
// Keep scroll position up to date as the user scrolls.
|
|
@@ -215,7 +215,12 @@ export function createTimberRouter(options: RouterInitOptions): RouterInitResult
|
|
|
215
215
|
queueMicrotask(() => router?.settleHandoffs());
|
|
216
216
|
|
|
217
217
|
publishTree(tree, params, newNavState);
|
|
218
|
-
|
|
218
|
+
// No transition types: this is not a navigation, and a
|
|
219
|
+
// `<ViewTransition>` keyed on the router's types must not mistake a
|
|
220
|
+
// query-string sync for one. It is still a transition commit, though, so
|
|
221
|
+
// it claims whatever types a still-suspended navigation queued
|
|
222
|
+
// (design/37-navigation-api.md §"The claim window").
|
|
223
|
+
render(renderTree(tree, newNavState, params), null, []);
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
window.history.pushState = function (
|
|
@@ -294,7 +299,7 @@ export function createTimberRouter(options: RouterInitOptions): RouterInitResult
|
|
|
294
299
|
//
|
|
295
300
|
// `_url` names the pending URL the router already published to its own
|
|
296
301
|
// pending store before calling here; the transition has no use for it.
|
|
297
|
-
navigateTransition: (_url: string, owner: RenderOwner, perform, onCommit) => {
|
|
302
|
+
navigateTransition: (_url: string, owner: RenderOwner, types, perform, onCommit) => {
|
|
298
303
|
return navigateTransition(
|
|
299
304
|
owner,
|
|
300
305
|
async () => {
|
|
@@ -330,6 +335,7 @@ export function createTimberRouter(options: RouterInitOptions): RouterInitResult
|
|
|
330
335
|
};
|
|
331
336
|
},
|
|
332
337
|
render,
|
|
338
|
+
types,
|
|
333
339
|
onCommit
|
|
334
340
|
);
|
|
335
341
|
},
|
|
@@ -364,9 +370,9 @@ export function createTimberRouter(options: RouterInitOptions): RouterInitResult
|
|
|
364
370
|
_departingUrl: departingUrl,
|
|
365
371
|
});
|
|
366
372
|
},
|
|
367
|
-
onTraverse: async (url, scrollY, signal) => {
|
|
373
|
+
onTraverse: async (url, scrollY, signal, direction) => {
|
|
368
374
|
// Back/forward — delegate to the router's popstate handler.
|
|
369
|
-
await router.handlePopState(url, scrollY, signal);
|
|
375
|
+
await router.handlePopState(url, scrollY, signal, direction);
|
|
370
376
|
},
|
|
371
377
|
onShallowNavigate: (destinationUrl: string) => {
|
|
372
378
|
const search = new URL(destinationUrl).search;
|
package/src/client/history.ts
CHANGED
|
@@ -93,28 +93,6 @@ export class HistoryStack {
|
|
|
93
93
|
return this.entries.has(routerUrlKey(url));
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
delete(url: string): boolean {
|
|
97
|
-
return this.entries.delete(routerUrlKey(url));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Delete all entries whose pathname matches `pathname` (TIM-1465).
|
|
102
|
-
* Used by `invalidatePath` when the invalidation target has no search
|
|
103
|
-
* string — `/products` should evict `/products?page=1` too, because
|
|
104
|
-
* `revalidatePath('/products')` invalidates the route regardless of
|
|
105
|
-
* query.
|
|
106
|
-
*/
|
|
107
|
-
deleteByPathname(pathname: string): void {
|
|
108
|
-
pathname = routerUrlKey(pathname);
|
|
109
|
-
for (const url of this.entries.keys()) {
|
|
110
|
-
const qIndex = url.indexOf('?');
|
|
111
|
-
const urlPathname = qIndex === -1 ? url : url.slice(0, qIndex);
|
|
112
|
-
if (urlPathname === pathname) {
|
|
113
|
-
this.entries.delete(url);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
96
|
/**
|
|
119
97
|
* Evict every entry (TIM-1476). Called after a server action that
|
|
120
98
|
* revalidated data — history entries are equally stale since they replay
|
package/src/client/index.ts
CHANGED
|
@@ -64,7 +64,8 @@ export { usePendingNavigation } from './use-pending-navigation.ts';
|
|
|
64
64
|
export { useLinkStatus, LinkStatusContext } from './use-link-status.ts';
|
|
65
65
|
export type { LinkStatus } from './use-link-status.ts';
|
|
66
66
|
export { useRouter } from './use-router.ts';
|
|
67
|
-
export type { AppRouterInstance } from './use-router.ts';
|
|
67
|
+
export type { AppRouterInstance, NavigateOptions } from './use-router.ts';
|
|
68
|
+
export type { NavigationTransitionType } from './router-types.ts';
|
|
68
69
|
export { usePathname } from './use-pathname.ts';
|
|
69
70
|
export { replaceUrl } from './shallow-url.ts';
|
|
70
71
|
// useSearchParams removed from public exports — lives in next/navigation shim only.
|
package/src/client/link.tsx
CHANGED
|
@@ -94,6 +94,11 @@ export interface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorEleme
|
|
|
94
94
|
* Set to false for tabbed interfaces where content changes within a fixed layout.
|
|
95
95
|
*/
|
|
96
96
|
scroll?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Replace the current history entry instead of pushing a new one, so Back
|
|
99
|
+
* skips the page the link was on.
|
|
100
|
+
*/
|
|
101
|
+
replace?: boolean;
|
|
97
102
|
/**
|
|
98
103
|
* Preserve search params from the current URL across navigation.
|
|
99
104
|
*
|
|
@@ -117,6 +122,23 @@ export interface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorEleme
|
|
|
117
122
|
* Has no effect during SSR.
|
|
118
123
|
*/
|
|
119
124
|
onNavigate?: OnNavigateHandler;
|
|
125
|
+
/**
|
|
126
|
+
* View transition types to add to this link's navigation, beside the
|
|
127
|
+
* router's own `navigation-forward`. A `<ViewTransition>` in the
|
|
128
|
+
* destination (or the page being left) can key its animation on them:
|
|
129
|
+
*
|
|
130
|
+
* ```tsx
|
|
131
|
+
* <Link href="/products/1" transitionTypes={['to-detail']}>…</Link>
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* The router adds them in the transition that renders the destination. A
|
|
135
|
+
* `startTransition(() => { addTransitionType(t); router.push(href) })` in
|
|
136
|
+
* an `onClick` does not work in timber: the destination reaches React only
|
|
137
|
+
* after its fetch, in a later transition, and React hands types added in
|
|
138
|
+
* the click to whatever transition commits next — not to that one
|
|
139
|
+
* (design/37-navigation-api.md §"Transition types").
|
|
140
|
+
*/
|
|
141
|
+
transitionTypes?: readonly string[];
|
|
120
142
|
children?: ReactNode;
|
|
121
143
|
}
|
|
122
144
|
|
|
@@ -475,10 +497,12 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
|
|
|
475
497
|
href,
|
|
476
498
|
prefetch,
|
|
477
499
|
scroll,
|
|
500
|
+
replace,
|
|
478
501
|
segmentParams,
|
|
479
502
|
searchParams,
|
|
480
503
|
preserveSearchParams,
|
|
481
504
|
onNavigate,
|
|
505
|
+
transitionTypes,
|
|
482
506
|
onClick: userOnClick,
|
|
483
507
|
onMouseEnter: userOnMouseEnter,
|
|
484
508
|
children,
|
|
@@ -620,7 +644,12 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
|
|
|
620
644
|
if (committed) clear();
|
|
621
645
|
};
|
|
622
646
|
setIsPending(true);
|
|
623
|
-
const navigation = router.navigate(absoluteHref, {
|
|
647
|
+
const navigation = router.navigate(absoluteHref, {
|
|
648
|
+
scroll: shouldScroll,
|
|
649
|
+
replace,
|
|
650
|
+
transitionTypes,
|
|
651
|
+
onCommit,
|
|
652
|
+
});
|
|
624
653
|
navigation.then(settle, (error: unknown) => {
|
|
625
654
|
clear();
|
|
626
655
|
// Rethrow, so a navigation error that the router did not already
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { isHardNavigating } from './navigation-root.tsx';
|
|
21
21
|
import { stripRscCacheKey } from '../shared/rsc-cache-key.ts';
|
|
22
|
+
import type { TraverseDirection } from './router-types.ts';
|
|
22
23
|
|
|
23
24
|
// ─── Feature Detection ───────────────────────────────────────────
|
|
24
25
|
|
|
@@ -38,6 +39,29 @@ export function getNavigationApi(): Navigation | null {
|
|
|
38
39
|
return window.navigation;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
// ─── Traverse Direction ──────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Which way a traversal goes, as the view transition type the router adds
|
|
46
|
+
* for it (design/37-navigation-api.md §"Transition types").
|
|
47
|
+
*
|
|
48
|
+
* The Navigation API numbers the session's entries, so a traversal to a
|
|
49
|
+
* lower index is back and to a higher one is forward — including a jump of
|
|
50
|
+
* several entries (`history.go(-3)`). An index of -1 means the entry is not
|
|
51
|
+
* in this document's list, and then the direction is unknown.
|
|
52
|
+
*/
|
|
53
|
+
export function traverseDirection(
|
|
54
|
+
destinationIndex: number,
|
|
55
|
+
currentIndex: number | undefined
|
|
56
|
+
): TraverseDirection {
|
|
57
|
+
if (currentIndex === undefined || currentIndex < 0 || destinationIndex < 0) {
|
|
58
|
+
return 'navigation-traverse';
|
|
59
|
+
}
|
|
60
|
+
if (destinationIndex < currentIndex) return 'navigation-back';
|
|
61
|
+
if (destinationIndex > currentIndex) return 'navigation-forward';
|
|
62
|
+
return 'navigation-traverse';
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
// ─── Navigation API Controller ───────────────────────────────────
|
|
42
66
|
|
|
43
67
|
/**
|
|
@@ -60,9 +84,15 @@ export interface NavigationApiCallbacks {
|
|
|
60
84
|
|
|
61
85
|
/**
|
|
62
86
|
* Handle a traversal (back/forward button). The Navigation API intercepts
|
|
63
|
-
* the traversal and delegates to us for RSC replay/fetch.
|
|
87
|
+
* the traversal and delegates to us for RSC replay/fetch. `direction` is
|
|
88
|
+
* the view transition type the render adds — see `traverseDirection`.
|
|
64
89
|
*/
|
|
65
|
-
onTraverse: (
|
|
90
|
+
onTraverse: (
|
|
91
|
+
url: string,
|
|
92
|
+
scrollY: number,
|
|
93
|
+
signal: AbortSignal,
|
|
94
|
+
direction: TraverseDirection
|
|
95
|
+
) => Promise<void>;
|
|
66
96
|
|
|
67
97
|
/**
|
|
68
98
|
* Called when a shallow URL update is intercepted (e.g., nuqs with
|
|
@@ -231,6 +261,9 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
|
|
|
231
261
|
| null
|
|
232
262
|
| undefined;
|
|
233
263
|
const scrollY = entryState && typeof entryState.scrollY === 'number' ? entryState.scrollY : 0;
|
|
264
|
+
// Read before intercept(): the current entry is still the one the
|
|
265
|
+
// user is leaving.
|
|
266
|
+
const direction = traverseDirection(event.destination.index, nav.currentEntry?.index);
|
|
234
267
|
|
|
235
268
|
event.intercept({
|
|
236
269
|
// Manual scroll — we handle scroll restoration ourselves
|
|
@@ -238,7 +271,7 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
|
|
|
238
271
|
scroll: 'manual',
|
|
239
272
|
focusReset: 'manual',
|
|
240
273
|
async handler() {
|
|
241
|
-
await callbacks.onTraverse(url, scrollY, event.signal);
|
|
274
|
+
await callbacks.onTraverse(url, scrollY, event.signal, direction);
|
|
242
275
|
},
|
|
243
276
|
});
|
|
244
277
|
} else if (event.navigationType === 'push' || event.navigationType === 'replace') {
|
|
@@ -71,8 +71,20 @@ export interface RenderedTree {
|
|
|
71
71
|
* navigation, a revalidation, a popstate replay, a shallow search re-wrap —
|
|
72
72
|
* and it is always a synchronous `startTransition` around `root.render`.
|
|
73
73
|
* The production one is `createReactRoot().render`.
|
|
74
|
+
*
|
|
75
|
+
* `types` are the view transition types this render animates as
|
|
76
|
+
* (design/37-navigation-api.md §"Transition types"). They are added with
|
|
77
|
+
* `addTransitionType` inside that same `startTransition`, and nowhere else:
|
|
78
|
+
* React queues types per root and hands them to the next transition-only
|
|
79
|
+
* commit, so a type added in any other scope describes whichever tree commits
|
|
80
|
+
* next rather than this one (TIM-1463, TIM-1471). Empty for the shallow search
|
|
81
|
+
* re-wrap, which re-renders the displayed page rather than changing it.
|
|
74
82
|
*/
|
|
75
|
-
export type NavigationRender = (
|
|
83
|
+
export type NavigationRender = (
|
|
84
|
+
element: ReactNode,
|
|
85
|
+
publish: (() => void) | null,
|
|
86
|
+
types: readonly string[]
|
|
87
|
+
) => void;
|
|
76
88
|
|
|
77
89
|
// ─── Hard Navigation Guard ──────────────────────────────────────
|
|
78
90
|
|
|
@@ -224,6 +224,11 @@ export function createRenderOwner(kind: 'navigation' | 'revalidation'): RenderOw
|
|
|
224
224
|
* how `<Link>` keeps `isPending` up until the destination is on screen
|
|
225
225
|
* without scheduling anything against React's lanes (TIM-1418).
|
|
226
226
|
*
|
|
227
|
+
* `types` are the view transition types `render` adds beside the tree
|
|
228
|
+
* (design/37-navigation-api.md §"Transition types"). They are fixed before
|
|
229
|
+
* `perform` runs — what a render animates as is decided by the operation that
|
|
230
|
+
* started it, not by what the fetch returns.
|
|
231
|
+
*
|
|
227
232
|
* `owner` is the `RenderOwner` for this render (TIM-1481). Supersession is
|
|
228
233
|
* detected via `owner.outcome` (sync) and `owner.displaced` (async).
|
|
229
234
|
* No module-level state; no globalThis singleton.
|
|
@@ -234,6 +239,7 @@ export function navigateTransition(
|
|
|
234
239
|
owner: RenderOwner,
|
|
235
240
|
perform: () => Promise<TransitionResult>,
|
|
236
241
|
render: NavigationRender,
|
|
242
|
+
types: readonly string[],
|
|
237
243
|
onCommit?: (outcome: CommitOutcome) => void
|
|
238
244
|
): Promise<void> {
|
|
239
245
|
const superseded = () => new DOMException('Navigation superseded', 'AbortError');
|
|
@@ -267,13 +273,17 @@ export function navigateTransition(
|
|
|
267
273
|
//
|
|
268
274
|
// This is THE update that must not hide the departing page (TIM-1306);
|
|
269
275
|
// `render` is the synchronous transition around `root.render`.
|
|
270
|
-
render(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
render(
|
|
277
|
+
element,
|
|
278
|
+
() => {
|
|
279
|
+
try {
|
|
280
|
+
commit();
|
|
281
|
+
} finally {
|
|
282
|
+
settleCommit('committed');
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
types
|
|
286
|
+
);
|
|
277
287
|
// React may commit the tree before this settles — that is the point:
|
|
278
288
|
// the destination reveals as React is able to render it
|
|
279
289
|
// rather than waiting for the whole Flight stream. The await is here so
|
package/src/client/react-root.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `container` is `document` in production; tests pass an element.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { createElement, startTransition, type ReactNode } from 'react';
|
|
22
|
+
import { addTransitionType, createElement, startTransition, type ReactNode } from 'react';
|
|
23
23
|
import { createRoot, hydrateRoot, type HydrationOptions, type Root } from 'react-dom/client';
|
|
24
24
|
import { NavigationRoot, type NavigationRender, type RenderedTree } from './navigation-root.tsx';
|
|
25
25
|
import type { TopLoaderConfig } from './top-loader.tsx';
|
|
@@ -55,13 +55,22 @@ export function createReactRoot(options: {
|
|
|
55
55
|
hydrate(element, hydrationOptions) {
|
|
56
56
|
root = hydrateRoot(container, rootElement({ element, publish: null }), hydrationOptions);
|
|
57
57
|
},
|
|
58
|
-
render(element, publish) {
|
|
58
|
+
render(element, publish, types) {
|
|
59
59
|
const target = (root ??= createRoot(container));
|
|
60
60
|
// Synchronous, and returns nothing: an async callback would drop the
|
|
61
61
|
// transition scope at its first await, and a returned thenable would
|
|
62
62
|
// open an action scope that holds the commit until it settles
|
|
63
63
|
// (TIM-1306, TIM-1307 — see client/navigation-transition.ts).
|
|
64
|
+
//
|
|
65
|
+
// The view transition types go in this scope and no other: React
|
|
66
|
+
// queues them on the root when the scope closes, and only if the root
|
|
67
|
+
// then has a transition pending — which `target.render` just made true.
|
|
68
|
+
// They are claimed by the next transition-only commit, so from here
|
|
69
|
+
// until this tree commits, another transition commit on the root can
|
|
70
|
+
// take them, and a superseding render merges its types with these
|
|
71
|
+
// (design/37-navigation-api.md §"The claim window", TIM-1471).
|
|
64
72
|
startTransition(() => {
|
|
73
|
+
for (const type of types) addTransitionType(type);
|
|
65
74
|
target.render(rootElement({ element, publish }));
|
|
66
75
|
});
|
|
67
76
|
},
|
|
@@ -146,8 +146,13 @@ export interface NavigationRecoveryDeps {
|
|
|
146
146
|
/** The owner that holds the router right now, or null when idle. */
|
|
147
147
|
currentOwner: () => RenderOwner | null;
|
|
148
148
|
leaveSpaIfOwned: SpaExits['leaveSpaIfOwned'];
|
|
149
|
-
/**
|
|
150
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Replace the current entry with `url` — the router's own `navigate()`,
|
|
151
|
+
* rendering with `types`: the view transition types of the render the
|
|
152
|
+
* redirect interrupted, so a redirected Back still animates as Back and a
|
|
153
|
+
* link's own `transitionTypes` survive the hop (TIM-1471).
|
|
154
|
+
*/
|
|
155
|
+
navigate: (url: string, types: readonly string[]) => Promise<void>;
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
/**
|
|
@@ -181,13 +186,15 @@ export function createNavigationRecovery({
|
|
|
181
186
|
error: unknown,
|
|
182
187
|
owner: RenderOwner,
|
|
183
188
|
url: string,
|
|
184
|
-
fromUrl: string
|
|
189
|
+
fromUrl: string,
|
|
190
|
+
/** The view transition types of the render that failed. */
|
|
191
|
+
types: readonly string[]
|
|
185
192
|
): Promise<boolean> {
|
|
186
193
|
if (error instanceof RedirectError) {
|
|
187
194
|
// Same ownership rule as leaving the SPA: a superseded navigation must
|
|
188
195
|
// not steer the document to the destination it was abandoned for.
|
|
189
196
|
if (currentOwner() !== owner) return true;
|
|
190
|
-
await navigate(error.redirectUrl);
|
|
197
|
+
await navigate(error.redirectUrl, types);
|
|
191
198
|
return true;
|
|
192
199
|
}
|
|
193
200
|
// A server error, a non-RSC response and a version skew all end the same
|
|
@@ -81,6 +81,8 @@ export interface NavigationPipeline {
|
|
|
81
81
|
renderViaTransition: (
|
|
82
82
|
url: string,
|
|
83
83
|
owner: RenderOwner,
|
|
84
|
+
/** The view transition types the render adds (`NavigationRender`). */
|
|
85
|
+
types: readonly string[],
|
|
84
86
|
perform: () => Promise<NavigationPayload>,
|
|
85
87
|
/** See `NavigationOptions.onCommit`. */
|
|
86
88
|
onCommit?: (outcome: CommitOutcome) => void
|
|
@@ -183,6 +185,7 @@ export function createNavigationPipeline({
|
|
|
183
185
|
async function renderViaTransition(
|
|
184
186
|
url: string,
|
|
185
187
|
owner: RenderOwner,
|
|
188
|
+
types: readonly string[],
|
|
186
189
|
perform: () => Promise<NavigationPayload>,
|
|
187
190
|
onCommit?: (outcome: CommitOutcome) => void
|
|
188
191
|
): Promise<void> {
|
|
@@ -203,6 +206,7 @@ export function createNavigationPipeline({
|
|
|
203
206
|
await deps.navigateTransition(
|
|
204
207
|
url,
|
|
205
208
|
owner,
|
|
209
|
+
types,
|
|
206
210
|
async (wrapPayload) => {
|
|
207
211
|
const result = await perform();
|
|
208
212
|
// Await the payload's *root row* — the same thing React would suspend
|
|
@@ -16,11 +16,70 @@ import type { SegmentCache, PrefetchCache } from './segment-cache.ts';
|
|
|
16
16
|
import type { TransitionResult, CommitOutcome, RenderOwner } from './navigation-transition.ts';
|
|
17
17
|
import type { NavigationEpoch } from './router-lifecycle.ts';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The view transition type the router adds to a render it hands React —
|
|
21
|
+
* exactly one per navigation, traversal, refresh or revalidation render,
|
|
22
|
+
* beside any `transitionTypes` the caller passed.
|
|
23
|
+
* A `<ViewTransition>` keys its animations on these
|
|
24
|
+
* (design/37-navigation-api.md §"Transition types", TIM-1471).
|
|
25
|
+
*
|
|
26
|
+
* - `navigation-forward` — a push or replace (`<Link>`, `router.push`,
|
|
27
|
+
* a server action redirect, a non-shallow nuqs update), or a traversal
|
|
28
|
+
* forward through history.
|
|
29
|
+
* - `navigation-back` — a traversal back through history.
|
|
30
|
+
* - `navigation-traverse` — a traversal whose direction the router cannot
|
|
31
|
+
* know: the History API fallback, where `popstate` carries no index.
|
|
32
|
+
* - `refresh` — `router.refresh()`.
|
|
33
|
+
* - `revalidate` — the page re-rendered after a server action.
|
|
34
|
+
*
|
|
35
|
+
* The shallow search re-wrap and hydration add none. A server redirect the
|
|
36
|
+
* router follows keeps the types of the render it interrupted.
|
|
37
|
+
*/
|
|
38
|
+
export type NavigationTransitionType =
|
|
39
|
+
| 'navigation-forward'
|
|
40
|
+
| 'navigation-back'
|
|
41
|
+
| 'navigation-traverse'
|
|
42
|
+
| 'refresh'
|
|
43
|
+
| 'revalidate';
|
|
44
|
+
|
|
45
|
+
/** The type a traversal adds: its direction, when the router knows it. */
|
|
46
|
+
export type TraverseDirection = Extract<
|
|
47
|
+
NavigationTransitionType,
|
|
48
|
+
'navigation-forward' | 'navigation-back' | 'navigation-traverse'
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
/** Options for `RouterInstance.refresh`. */
|
|
52
|
+
export interface RefreshOptions {
|
|
53
|
+
/** See `NavigationOptions.onCommit`. */
|
|
54
|
+
onCommit?: (outcome: CommitOutcome) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Why the page is being re-fetched, as the view transition type the render
|
|
57
|
+
* adds: `refresh` (the default) when the user asked, `revalidate` when a
|
|
58
|
+
* server action left the page to be re-fetched.
|
|
59
|
+
*/
|
|
60
|
+
transitionType?: Extract<NavigationTransitionType, 'refresh' | 'revalidate'>;
|
|
61
|
+
}
|
|
62
|
+
|
|
19
63
|
export interface NavigationOptions {
|
|
20
64
|
/** Set to false to prevent scroll-to-top on forward navigation */
|
|
21
65
|
scroll?: boolean;
|
|
22
66
|
/** Use replaceState instead of pushState (replaces current history entry) */
|
|
23
67
|
replace?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* View transition types to add to this navigation, beside the router's own
|
|
70
|
+
* `navigation-forward`. They are added in the router's transition, the one
|
|
71
|
+
* that renders the destination — not in the caller's: React claims types
|
|
72
|
+
* per root at the next transition commit, and the destination is handed to
|
|
73
|
+
* React only after its fetch, so a type added around the `navigate()` call
|
|
74
|
+
* never reaches it (design/37-navigation-api.md §"Transition types").
|
|
75
|
+
*/
|
|
76
|
+
transitionTypes?: readonly string[];
|
|
77
|
+
/**
|
|
78
|
+
* @internal The complete view transition types for this render, replacing
|
|
79
|
+
* `navigation-forward` + `transitionTypes`. Set only when the router
|
|
80
|
+
* follows a redirect, which keeps the types of the render it interrupted.
|
|
81
|
+
*/
|
|
82
|
+
_renderTypes?: readonly string[];
|
|
24
83
|
/**
|
|
25
84
|
* Runs exactly once, when React commits this navigation's tree — or when
|
|
26
85
|
* the navigation is abandoned (superseded or failed) and no such commit
|
|
@@ -97,6 +156,8 @@ export interface RouterDeps {
|
|
|
97
156
|
navigateTransition: (
|
|
98
157
|
pendingUrl: string,
|
|
99
158
|
owner: RenderOwner,
|
|
159
|
+
/** The view transition types this render adds; see `NavigationRender`. */
|
|
160
|
+
types: readonly string[],
|
|
100
161
|
perform: (
|
|
101
162
|
wrapPayload: (
|
|
102
163
|
payload: unknown,
|
|
@@ -184,9 +245,19 @@ export interface RouterInstance {
|
|
|
184
245
|
/** Navigate to a new URL (forward navigation) */
|
|
185
246
|
navigate(url: string, options?: NavigationOptions): Promise<void>;
|
|
186
247
|
/** Full re-render of the current URL — no state tree sent */
|
|
187
|
-
refresh(options?:
|
|
188
|
-
/**
|
|
189
|
-
|
|
248
|
+
refresh(options?: RefreshOptions): Promise<void>;
|
|
249
|
+
/**
|
|
250
|
+
* Handle a traversal (back/forward button). scrollY is read from the
|
|
251
|
+
* destination entry's state. `direction` is the transition type the
|
|
252
|
+
* render adds; it defaults to `navigation-traverse`, the type for a
|
|
253
|
+
* traversal whose direction is unknown.
|
|
254
|
+
*/
|
|
255
|
+
handlePopState(
|
|
256
|
+
url: string,
|
|
257
|
+
scrollY?: number,
|
|
258
|
+
externalSignal?: AbortSignal,
|
|
259
|
+
direction?: TraverseDirection
|
|
260
|
+
): Promise<void>;
|
|
190
261
|
/** Whether a navigation is currently in flight */
|
|
191
262
|
isPending(): boolean;
|
|
192
263
|
/** The URL currently being navigated to, or null if idle */
|
|
@@ -237,10 +308,13 @@ export interface RouterInstance {
|
|
|
237
308
|
*/
|
|
238
309
|
settleHandoffs(): void;
|
|
239
310
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
311
|
+
* Make the next navigation a full render: the server re-renders every
|
|
312
|
+
* segment instead of skipping the shared layouts the client reports as
|
|
313
|
+
* mounted (TIM-1466). Called when `revalidatePath` named a page other
|
|
314
|
+
* than the current one — a layout that page shares with this one may now
|
|
315
|
+
* be stale, and the server cannot tell that from the state tree.
|
|
242
316
|
*/
|
|
243
|
-
|
|
317
|
+
suppressSegmentReuse(): void;
|
|
244
318
|
/**
|
|
245
319
|
* Evict all prefetch cache entries and history stack payloads (TIM-1476).
|
|
246
320
|
* Called after a server action that revalidated data — the mutation may
|