@timber-js/app 0.2.0-alpha.197 → 0.2.0-alpha.199
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-BS-m5SLv.js → actions-d1hCqnU3.js} +35 -8
- package/dist/_chunks/actions-d1hCqnU3.js.map +1 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{cache-api-DqzgTEqk.js → cache-api-ByagcC-J.js} +2 -2
- package/dist/_chunks/{cache-api-DqzgTEqk.js.map → cache-api-ByagcC-J.js.map} +1 -1
- package/dist/_chunks/canonicalize-CgHoscYO.js +66 -0
- package/dist/_chunks/canonicalize-CgHoscYO.js.map +1 -0
- package/dist/_chunks/{chains-CZG7E5zg.js → chains-Bpb0W4ax.js} +3 -3
- package/dist/_chunks/{chains-CZG7E5zg.js.map → chains-Bpb0W4ax.js.map} +1 -1
- package/dist/_chunks/{cli-check-bE3H5Bjr.js → cli-check-D6VolrDV.js} +3 -3
- package/dist/_chunks/{cli-check-bE3H5Bjr.js.map → cli-check-D6VolrDV.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js → cli-schema-sync-D6rO-VcS.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js.map → cli-schema-sync-D6rO-VcS.js.map} +1 -1
- package/dist/_chunks/{convention-lint-n3RJLgww.js → convention-lint-fRkwVwEH.js} +27 -4
- package/dist/_chunks/convention-lint-fRkwVwEH.js.map +1 -0
- package/dist/_chunks/error-boundary-BfPHZjm0.js +1050 -0
- package/dist/_chunks/error-boundary-BfPHZjm0.js.map +1 -0
- package/dist/_chunks/{live-graph-BXDsdzBv.js → live-graph-D_2D32Ad.js} +3 -3
- package/dist/_chunks/{live-graph-BXDsdzBv.js.map → live-graph-D_2D32Ad.js.map} +1 -1
- package/dist/_chunks/{logger-DDirEsn7.js → logger-uLBuGKDI.js} +471 -440
- package/dist/_chunks/logger-uLBuGKDI.js.map +1 -0
- package/dist/_chunks/{navigation-root-B00jjGd5.js → navigation-context-D0TU0Jog.js} +3 -101
- package/dist/_chunks/navigation-context-D0TU0Jog.js.map +1 -0
- package/dist/_chunks/navigation-root-mHSK9psY.js +126 -0
- package/dist/_chunks/{navigation-root-B00jjGd5.js.map → navigation-root-mHSK9psY.js.map} +1 -1
- package/dist/_chunks/{poison-scan-BoDLgbix.js → poison-scan-Bm9Yyqk9.js} +2 -2
- package/dist/_chunks/{poison-scan-BoDLgbix.js.map → poison-scan-Bm9Yyqk9.js.map} +1 -1
- package/dist/_chunks/{scanner-tdFPvDYi.js → scanner-AiazgH_f.js} +6 -5
- package/dist/_chunks/scanner-AiazgH_f.js.map +1 -0
- package/dist/_chunks/{segment-keys-BhqoHiLc.js → segment-keys-lqtdookO.js} +2 -65
- package/dist/_chunks/segment-keys-lqtdookO.js.map +1 -0
- package/dist/_chunks/{ssr-data-BQGhTPAK.js → ssr-data-D6T6Y3ef.js} +4 -26
- package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +1 -0
- package/dist/_chunks/state-FippDgxN.js +52 -0
- package/dist/_chunks/state-FippDgxN.js.map +1 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js +496 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +1 -0
- package/dist/_chunks/{walkers-DNX05dC0.js → walkers-B6XUtmqK.js} +2 -2
- package/dist/_chunks/{walkers-DNX05dC0.js.map → walkers-B6XUtmqK.js.map} +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts +6 -4
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/action-queue.d.ts +44 -0
- package/dist/client/browser-entry/action-queue.d.ts.map +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/deny-last-resort.d.ts +29 -0
- package/dist/client/deny-last-resort.d.ts.map +1 -0
- package/dist/client/error-boundary.d.ts +48 -2
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -2
- package/dist/client/history.d.ts +21 -2
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +34 -14
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +1 -0
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +272 -1225
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-commit.d.ts +12 -19
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +62 -11
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +9 -8
- package/dist/client/router-effects.d.ts.map +1 -1
- package/dist/client/router-lifecycle.d.ts +60 -17
- package/dist/client/router-lifecycle.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +7 -4
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +63 -8
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +0 -9
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +23 -8
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/state.d.ts +16 -0
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/status-page-marker.d.ts +25 -0
- package/dist/client/status-page-marker.d.ts.map +1 -0
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/holding-server.d.ts +4 -17
- package/dist/dev-tools/holding-server.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -175
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception.d.ts +2 -2
- package/dist/routing/slot-placement.d.ts +2 -2
- package/dist/server/access-gate.d.ts +73 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +16 -1
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +3 -9
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/children-interception.d.ts +1 -1
- package/dist/server/default-status-page.d.ts +2 -2
- package/dist/server/default-status-page.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +15 -9
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +21 -4
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/error-response-headers.d.ts +3 -0
- package/dist/server/error-response-headers.d.ts.map +1 -0
- package/dist/server/index.js +3 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +1 -2
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +2339 -2506
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +2 -5
- package/dist/server/metadata-collector.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +10 -3
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +11 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +3 -89
- package/dist/server/port-resolution.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +38 -10
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/response-cache-policy.d.ts +3 -0
- package/dist/server/response-cache-policy.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +11 -41
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-element-helpers.d.ts +12 -0
- package/dist/server/route-element-helpers.d.ts.map +1 -0
- package/dist/server/route-module-loader.d.ts +37 -0
- package/dist/server/route-module-loader.d.ts.map +1 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +22 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +4 -11
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/rsc-error-envelope.d.ts +11 -0
- package/dist/server/rsc-error-envelope.d.ts.map +1 -0
- package/dist/server/skippable-prefix.d.ts +18 -15
- package/dist/server/skippable-prefix.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts +54 -0
- package/dist/server/slot-subtree-contain.d.ts.map +1 -0
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +10 -0
- package/dist/server/utils/element-type.d.ts.map +1 -1
- package/dist/shared/rsc-error-envelope.d.ts +0 -9
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -1
- package/dist/shared/status-reason-phrase.d.ts +26 -0
- package/dist/shared/status-reason-phrase.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +4 -2
- package/docs/api/31-api-client.mdx +5 -1
- package/docs/api/36-cli.mdx +5 -3
- package/docs/learn/12-error-handling.mdx +5 -1
- package/package.json +10 -10
- package/src/client/browser-entry/action-dispatch.ts +166 -99
- package/src/client/browser-entry/action-queue.ts +90 -0
- package/src/client/browser-entry/router-init.ts +60 -35
- package/src/client/deny-last-resort.tsx +54 -0
- package/src/client/error-boundary.tsx +144 -42
- package/src/client/history.ts +52 -3
- package/src/client/internal.ts +1 -0
- package/src/client/link.tsx +70 -35
- package/src/client/navigation-commit.ts +79 -27
- package/src/client/navigation-transition.ts +176 -127
- package/src/client/router-effects.ts +14 -17
- package/src/client/router-lifecycle.ts +181 -115
- package/src/client/router-pipeline.ts +94 -71
- package/src/client/router-types.ts +61 -7
- package/src/client/router.ts +147 -74
- package/src/client/rsc-fetch.ts +0 -13
- package/src/client/segment-cache.ts +43 -10
- package/src/client/state.ts +26 -0
- package/src/client/status-page-marker.tsx +32 -0
- package/src/dev-tools/holding-server.ts +4 -17
- package/src/index.ts +18 -34
- package/src/plugins/dev-server.ts +2 -1
- package/src/react-canary.d.ts +2 -0
- package/src/routing/codegen-write.ts +2 -0
- package/src/routing/interception.ts +2 -2
- package/src/routing/slot-placement.ts +2 -2
- package/src/server/access-gate.tsx +89 -21
- package/src/server/action-client.ts +2 -2
- package/src/server/action-handler.ts +23 -10
- package/src/server/actions.ts +81 -34
- package/src/server/als-registry.ts +3 -9
- package/src/server/children-interception.ts +1 -1
- package/src/server/default-status-page.ts +7 -47
- package/src/server/deny-boundary.ts +45 -28
- package/src/server/deny-renderer.ts +6 -2
- package/src/server/error-boundary-wrapper.ts +23 -4
- package/src/server/error-response-headers.ts +18 -0
- package/src/server/internal.ts +2 -10
- package/src/server/metadata-collector.ts +3 -18
- package/src/server/param-coercion.ts +13 -4
- package/src/server/pipeline-outcome.ts +35 -13
- package/src/server/pipeline-phases.ts +22 -16
- package/src/server/port-resolution.ts +3 -165
- package/src/server/prebuilt-builder.ts +4 -4
- package/src/server/primitives.ts +75 -11
- package/src/server/response-cache-policy.ts +45 -0
- package/src/server/route-element-builder.ts +149 -412
- package/src/server/route-element-helpers.ts +37 -0
- package/src/server/route-handler.ts +2 -2
- package/src/server/route-module-loader.ts +161 -0
- package/src/server/rsc-cache-key-guard.ts +2 -42
- package/src/server/rsc-entry/action-middleware-runner.ts +4 -4
- package/src/server/rsc-entry/api-handler.ts +5 -5
- package/src/server/rsc-entry/error-renderer.ts +3 -4
- package/src/server/rsc-entry/helpers.ts +1 -1
- package/src/server/rsc-entry/index.ts +3 -3
- package/src/server/rsc-entry/render-route.ts +4 -8
- package/src/server/rsc-entry/rsc-payload.ts +59 -42
- package/src/server/rsc-entry/rsc-stream.ts +48 -27
- package/src/server/rsc-entry/ssr-renderer.ts +6 -10
- package/src/server/rsc-error-envelope.ts +18 -0
- package/src/server/skippable-prefix.ts +105 -7
- package/src/server/slot-resolver.ts +43 -12
- package/src/server/slot-subtree-contain.ts +255 -0
- package/src/server/stream-utils.ts +12 -8
- package/src/server/utils/element-type.ts +18 -2
- package/src/shared/rsc-error-envelope.ts +0 -15
- package/src/shared/status-reason-phrase.ts +61 -0
- package/dist/_chunks/actions-BS-m5SLv.js.map +0 -1
- package/dist/_chunks/convention-lint-n3RJLgww.js.map +0 -1
- package/dist/_chunks/error-boundary-BvRCCmbN.js +0 -353
- package/dist/_chunks/error-boundary-BvRCCmbN.js.map +0 -1
- package/dist/_chunks/logger-DDirEsn7.js.map +0 -1
- package/dist/_chunks/mdx-file-CXyHGUpS.js +0 -25
- package/dist/_chunks/mdx-file-CXyHGUpS.js.map +0 -1
- package/dist/_chunks/router-ref-8gr8qsxN.js +0 -28
- package/dist/_chunks/router-ref-8gr8qsxN.js.map +0 -1
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +0 -40
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +0 -1
- package/dist/_chunks/scanner-tdFPvDYi.js.map +0 -1
- package/dist/_chunks/segment-keys-BhqoHiLc.js.map +0 -1
- package/dist/_chunks/ssr-data-BQGhTPAK.js.map +0 -1
- package/dist/server/tree-builder.d.ts +0 -150
- package/dist/server/tree-builder.d.ts.map +0 -1
- package/src/server/tree-builder.ts +0 -313
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* that makes the page current, and hand the result over inside a transition.
|
|
7
7
|
*
|
|
8
8
|
* `router.ts` keeps the *operations* — `navigate`, `refresh`, `handlePopState`,
|
|
9
|
-
* `prefetch`, `
|
|
9
|
+
* `prefetch`, `applyActionResult` — and each of them is a call into here. The
|
|
10
10
|
* split is the same one `router-effects.ts` and `router-lifecycle.ts` made
|
|
11
11
|
* (design/18-build-system.md §"No file >500 lines").
|
|
12
12
|
*
|
|
@@ -22,6 +22,7 @@ import type { StateTree } from '../shared/segment-info.ts';
|
|
|
22
22
|
import type { NavigationState } from './navigation-context.ts';
|
|
23
23
|
import { isPartialNavigation, type NavigationCommitter } from './navigation-commit.ts';
|
|
24
24
|
import type { RouterDeps } from './router-types.ts';
|
|
25
|
+
import type { CommitOutcome, RenderOwner } from './navigation-transition.ts';
|
|
25
26
|
import { recordSkew } from './router-skew.ts';
|
|
26
27
|
import { SingleflightTimeoutError } from '../cache/singleflight.ts';
|
|
27
28
|
|
|
@@ -64,9 +65,9 @@ export interface NavigationPipelineDeps extends Pick<
|
|
|
64
65
|
deps: RouterDeps;
|
|
65
66
|
prefetchCache: PrefetchCache;
|
|
66
67
|
/** Record that this navigation's tree reached React (router-lifecycle.ts). */
|
|
67
|
-
markHandedOff: (owner:
|
|
68
|
+
markHandedOff: (owner: RenderOwner) => void;
|
|
68
69
|
/** React committed this navigation's tree (router-lifecycle.ts). */
|
|
69
|
-
forgetOlderHandoffs: (owner:
|
|
70
|
+
forgetOlderHandoffs: (owner: RenderOwner) => void;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
export interface NavigationPipeline {
|
|
@@ -78,8 +79,10 @@ export interface NavigationPipeline {
|
|
|
78
79
|
/** Hand a payload to React inside a transition, or render it directly in tests. */
|
|
79
80
|
renderViaTransition: (
|
|
80
81
|
url: string,
|
|
81
|
-
owner:
|
|
82
|
-
perform: () => Promise<NavigationPayload
|
|
82
|
+
owner: RenderOwner,
|
|
83
|
+
perform: () => Promise<NavigationPayload>,
|
|
84
|
+
/** See `NavigationOptions.onCommit`. */
|
|
85
|
+
onCommit?: (outcome: CommitOutcome) => void
|
|
83
86
|
) => Promise<void>;
|
|
84
87
|
/** Render a decoded payload into the DOM if a renderer is available. */
|
|
85
88
|
renderPayload: (
|
|
@@ -222,79 +225,96 @@ export function createNavigationPipeline({
|
|
|
222
225
|
*/
|
|
223
226
|
async function renderViaTransition(
|
|
224
227
|
url: string,
|
|
225
|
-
owner:
|
|
226
|
-
perform: () => Promise<NavigationPayload
|
|
228
|
+
owner: RenderOwner,
|
|
229
|
+
perform: () => Promise<NavigationPayload>,
|
|
230
|
+
onCommit?: (outcome: CommitOutcome) => void
|
|
227
231
|
): Promise<void> {
|
|
228
232
|
// Record that THIS navigation's payload has reached React, at the moment
|
|
229
233
|
// the tree is built and handed back for `navigateTransition` to give to
|
|
230
234
|
// React — NOT on commit, which is a whole commit phase too late. See
|
|
231
|
-
// `
|
|
235
|
+
// `RenderOwner.handedOff` in router-lifecycle.ts.
|
|
232
236
|
const handOff = (): void => markHandedOff(owner);
|
|
233
237
|
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
238
|
+
// Settle the owner as committed once React has committed this tree.
|
|
239
|
+
// Wrapped around the commit thunk so it runs on React's commit and not
|
|
240
|
+
// at handover; see `forgetOlderHandoffs`.
|
|
237
241
|
const commitAndForget = (commit: () => void) => (): void => {
|
|
238
242
|
forgetOlderHandoffs(owner);
|
|
239
243
|
commit();
|
|
240
244
|
};
|
|
241
245
|
|
|
242
246
|
if (deps.navigateTransition) {
|
|
243
|
-
await deps.navigateTransition(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
247
|
+
await deps.navigateTransition(
|
|
248
|
+
url,
|
|
249
|
+
owner,
|
|
250
|
+
async (wrapPayload) => {
|
|
251
|
+
const result = await perform();
|
|
252
|
+
// Await the payload's *root row* — the same thing React would suspend
|
|
253
|
+
// on to render the tree, and the first thing Flight emits. The
|
|
254
|
+
// previous UI stays on screen while this resolves: nothing has been
|
|
255
|
+
// handed to React yet, and when it is, NavigationRoot hands it over
|
|
256
|
+
// inside a transition so a tree that is still streaming does not
|
|
257
|
+
// displace the departing page (TIM-1306).
|
|
258
|
+
//
|
|
259
|
+
// Reading them here rather than with `use()` above the tree is what
|
|
260
|
+
// keeps a decode failure contained: `result.params` never rejects, so
|
|
261
|
+
// the rejection reaches React only through the tree, inside whatever
|
|
262
|
+
// error boundary the app put around it (TIM-1297).
|
|
263
|
+
const params = await result.params;
|
|
257
264
|
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
if (isPartialNavigation(result.skippedSegments)) {
|
|
266
|
+
const segmentUpdates = buildSegmentUpdates(result);
|
|
260
267
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
// Re-wrap the CURRENT element with new context values.
|
|
269
|
+
// SegmentOutlets read updates from SegmentUpdateContext;
|
|
270
|
+
// NavigationProvider gets the new pathname; the params published
|
|
271
|
+
// above the retained tree are the *incoming* route's.
|
|
272
|
+
const element = wrapPayload(
|
|
273
|
+
deps._getCurrentPayload?.() ?? result.payload,
|
|
274
|
+
result.navState,
|
|
275
|
+
params,
|
|
276
|
+
segmentUpdates
|
|
277
|
+
);
|
|
278
|
+
handOff();
|
|
279
|
+
return {
|
|
280
|
+
element,
|
|
281
|
+
decodePromise: observeSkew(result.decodePromise),
|
|
282
|
+
commit: commitAndForget(result.commit),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Full navigation — empty updates, render the new tree.
|
|
287
|
+
const element = wrapPayload(result.payload, result.navState, params);
|
|
271
288
|
handOff();
|
|
272
289
|
return {
|
|
273
290
|
element,
|
|
274
291
|
decodePromise: observeSkew(result.decodePromise),
|
|
275
292
|
commit: commitAndForget(result.commit),
|
|
276
293
|
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const element = wrapPayload(result.payload, result.navState, params);
|
|
281
|
-
handOff();
|
|
282
|
-
return {
|
|
283
|
-
element,
|
|
284
|
-
decodePromise: observeSkew(result.decodePromise),
|
|
285
|
-
commit: commitAndForget(result.commit),
|
|
286
|
-
};
|
|
287
|
-
});
|
|
294
|
+
},
|
|
295
|
+
onCommit
|
|
296
|
+
);
|
|
288
297
|
return;
|
|
289
298
|
}
|
|
290
|
-
// Fallback: no transition (tests, no React tree)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
299
|
+
// Fallback: no transition (tests, no React tree). The commit is
|
|
300
|
+
// synchronous here, so the outcome is always 'committed' or 'failed' —
|
|
301
|
+
// no transition counter to supersede. The hook fires on the way out,
|
|
302
|
+
// on failure too, so it still fires exactly once.
|
|
303
|
+
let fallbackOutcome: CommitOutcome = 'committed';
|
|
304
|
+
try {
|
|
305
|
+
const result = await perform();
|
|
306
|
+
handOff();
|
|
307
|
+
const commit = commitAndForget(result.commit);
|
|
308
|
+
if (isPartialNavigation(result.skippedSegments)) {
|
|
309
|
+
commit();
|
|
310
|
+
} else {
|
|
311
|
+
renderPayload(result.payload, result.navState, await result.params, commit);
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
fallbackOutcome = 'failed';
|
|
315
|
+
throw error;
|
|
316
|
+
} finally {
|
|
317
|
+
onCommit?.(fallbackOutcome);
|
|
298
318
|
}
|
|
299
319
|
}
|
|
300
320
|
|
|
@@ -346,9 +366,6 @@ export function createNavigationPipeline({
|
|
|
346
366
|
decodePromise: prefetched.decodePromise ?? null,
|
|
347
367
|
segmentInfo: prefetched.segmentInfo ?? null,
|
|
348
368
|
skippedSegments: prefetched.skippedSegments ?? null,
|
|
349
|
-
// Entries written before this field carried it are treated as
|
|
350
|
-
// cacheable, which is what they were before a deny could be one.
|
|
351
|
-
status: prefetched.status ?? 200,
|
|
352
369
|
}
|
|
353
370
|
: undefined;
|
|
354
371
|
|
|
@@ -361,21 +378,28 @@ export function createNavigationPipeline({
|
|
|
361
378
|
if (result === undefined) {
|
|
362
379
|
const inflight = prefetchCache.joinInflight(cacheKey);
|
|
363
380
|
if (inflight) {
|
|
381
|
+
// Capture the eviction generation before awaiting the flight.
|
|
382
|
+
// If an action evicts caches while we wait, the result predates
|
|
383
|
+
// the mutation and must be discarded (TIM-1476, codex on #1131).
|
|
384
|
+
const genBefore = prefetchCache.getEvictionGen();
|
|
364
385
|
try {
|
|
365
386
|
const outcome = await raceAbort(inflight, options.signal);
|
|
366
|
-
if (
|
|
387
|
+
if (prefetchCache.getEvictionGen() !== genBefore) {
|
|
388
|
+
// Eviction happened while we waited — discard stale result,
|
|
389
|
+
// fall through to a fresh fetch below.
|
|
390
|
+
} else if (outcome.kind === 'non-route') {
|
|
367
391
|
throw new NonRscResponse(url);
|
|
392
|
+
} else {
|
|
393
|
+
// Consume the ready entry so a second click re-fetches
|
|
394
|
+
prefetchCache.consume(cacheKey);
|
|
395
|
+
result = {
|
|
396
|
+
payload: outcome.result.payload,
|
|
397
|
+
params: outcome.result.params ?? readPublishedParams(undefined),
|
|
398
|
+
decodePromise: outcome.result.decodePromise ?? null,
|
|
399
|
+
segmentInfo: outcome.result.segmentInfo ?? null,
|
|
400
|
+
skippedSegments: outcome.result.skippedSegments ?? null,
|
|
401
|
+
};
|
|
368
402
|
}
|
|
369
|
-
// Consume the ready entry so a second click re-fetches
|
|
370
|
-
prefetchCache.consume(cacheKey);
|
|
371
|
-
result = {
|
|
372
|
-
payload: outcome.result.payload,
|
|
373
|
-
params: outcome.result.params ?? readPublishedParams(undefined),
|
|
374
|
-
decodePromise: outcome.result.decodePromise ?? null,
|
|
375
|
-
segmentInfo: outcome.result.segmentInfo ?? null,
|
|
376
|
-
skippedSegments: outcome.result.skippedSegments ?? null,
|
|
377
|
-
status: outcome.result.status ?? 200,
|
|
378
|
-
};
|
|
379
403
|
} catch (error) {
|
|
380
404
|
// Supersession aborts propagate — don't retry a cancelled navigation
|
|
381
405
|
if (error instanceof DOMException && error.name === 'AbortError') throw error;
|
|
@@ -440,7 +464,6 @@ export function createNavigationPipeline({
|
|
|
440
464
|
payload,
|
|
441
465
|
params,
|
|
442
466
|
segmentInfo: result.segmentInfo,
|
|
443
|
-
status: result.status,
|
|
444
467
|
skippedSegments: result.skippedSegments,
|
|
445
468
|
});
|
|
446
469
|
|
|
@@ -14,13 +14,23 @@ import type { ParamsSource } from '../shared/payload-root.ts';
|
|
|
14
14
|
import type { SegmentInfo } from '../shared/segment-info.ts';
|
|
15
15
|
import type { SegmentCache, PrefetchCache } from './segment-cache.ts';
|
|
16
16
|
import type { HistoryStack } from './history.ts';
|
|
17
|
-
import type { TransitionResult } from './navigation-transition.ts';
|
|
17
|
+
import type { TransitionResult, CommitOutcome, RenderOwner } from './navigation-transition.ts';
|
|
18
|
+
import type { NavigationEpoch } from './router-lifecycle.ts';
|
|
18
19
|
|
|
19
20
|
export interface NavigationOptions {
|
|
20
21
|
/** Set to false to prevent scroll-to-top on forward navigation */
|
|
21
22
|
scroll?: boolean;
|
|
22
23
|
/** Use replaceState instead of pushState (replaces current history entry) */
|
|
23
24
|
replace?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Runs exactly once, when React commits this navigation's tree — or when
|
|
27
|
+
* the navigation is abandoned (superseded or failed) and no such commit
|
|
28
|
+
* will come. The returned promise resolves on decode, which can be before
|
|
29
|
+
* OR after the commit; this is the hook for the other event. `<Link>`
|
|
30
|
+
* holds `isPending` until both have happened (TIM-1418). The outcome says
|
|
31
|
+
* which of the three it was.
|
|
32
|
+
*/
|
|
33
|
+
onCommit?: (outcome: CommitOutcome) => void;
|
|
24
34
|
/**
|
|
25
35
|
* @internal AbortSignal from the Navigation API's NavigateEvent.
|
|
26
36
|
* When provided, the signal is linked to the router's per-navigation
|
|
@@ -111,6 +121,7 @@ export interface RouterDeps {
|
|
|
111
121
|
*/
|
|
112
122
|
navigateTransition?: (
|
|
113
123
|
pendingUrl: string,
|
|
124
|
+
owner: RenderOwner,
|
|
114
125
|
perform: (
|
|
115
126
|
wrapPayload: (
|
|
116
127
|
payload: unknown,
|
|
@@ -125,7 +136,9 @@ export interface RouterDeps {
|
|
|
125
136
|
params: ParamsSource,
|
|
126
137
|
segmentUpdates?: Map<string, unknown>
|
|
127
138
|
) => unknown
|
|
128
|
-
) => Promise<TransitionResult<unknown
|
|
139
|
+
) => Promise<TransitionResult<unknown>>,
|
|
140
|
+
/** See `NavigationOptions.onCommit`; forwarded to `navigateTransition`. */
|
|
141
|
+
onCommit?: (outcome: CommitOutcome) => void
|
|
129
142
|
) => Promise<void>;
|
|
130
143
|
|
|
131
144
|
/**
|
|
@@ -196,7 +209,7 @@ export interface RouterInstance {
|
|
|
196
209
|
/** Navigate to a new URL (forward navigation) */
|
|
197
210
|
navigate(url: string, options?: NavigationOptions): Promise<void>;
|
|
198
211
|
/** Full re-render of the current URL — no state tree sent */
|
|
199
|
-
refresh(): Promise<void>;
|
|
212
|
+
refresh(options?: { onCommit?: (outcome: CommitOutcome) => void }): Promise<void>;
|
|
200
213
|
/** Handle a popstate event (back/forward button). scrollY is read from history.state. */
|
|
201
214
|
handlePopState(url: string, scrollY?: number, externalSignal?: AbortSignal): Promise<void>;
|
|
202
215
|
/** Whether a navigation is currently in flight */
|
|
@@ -208,12 +221,53 @@ export interface RouterInstance {
|
|
|
208
221
|
/** Prefetch an RSC payload for a URL (used by Link hover) */
|
|
209
222
|
prefetch(url: string): void;
|
|
210
223
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
|
|
224
|
+
* Snapshot the router's navigation state before dispatching a server action.
|
|
225
|
+
* Hand the epoch back to `applyActionResult` with the response (TIM-1474).
|
|
226
|
+
*/
|
|
227
|
+
epoch(): NavigationEpoch;
|
|
228
|
+
/**
|
|
229
|
+
* Reconcile the page after a server action. `tree` is the piggybacked
|
|
230
|
+
* revalidation payload root, or `undefined` when the server sent none.
|
|
231
|
+
*
|
|
232
|
+
* Returns `true` when the result was applied, `false` when stale (a
|
|
233
|
+
* navigation happened since `epoch` was taken or the router was busy at
|
|
234
|
+
* dispatch). Async because the no-tree path awaits `refresh()` — the next
|
|
235
|
+
* queued action must not capture its epoch while the refresh is in-flight.
|
|
236
|
+
*
|
|
237
|
+
* Does NOT call `runWhenIdle` — that is the action queue's responsibility
|
|
238
|
+
* (gated on "queue empty", not just "router idle").
|
|
239
|
+
*
|
|
214
240
|
* See design/08-forms-and-actions.md §"Single-Roundtrip Revalidation".
|
|
215
241
|
*/
|
|
216
|
-
|
|
242
|
+
applyActionResult(epoch: NavigationEpoch, tree?: unknown): Promise<boolean>;
|
|
243
|
+
/**
|
|
244
|
+
* Run `task` now if the router is idle (not pending, no uncommitted tree),
|
|
245
|
+
* otherwise once when it next becomes idle. Single slot — the action queue
|
|
246
|
+
* uses this to flush one deferred refresh when both queue-empty and
|
|
247
|
+
* router-idle are true (TIM-1474).
|
|
248
|
+
*/
|
|
249
|
+
runWhenIdle(task: () => void): void;
|
|
250
|
+
/**
|
|
251
|
+
* Settle any handed-off-but-uncommitted owners as superseded, then flush
|
|
252
|
+
* idle tasks. Called by `syncShallowSearch` when a shallow URL update
|
|
253
|
+
* displaces a suspended transition (TIM-1480).
|
|
254
|
+
*/
|
|
255
|
+
settleHandoffs(): void;
|
|
256
|
+
/**
|
|
257
|
+
* Invalidate client caches for a path so the next navigation fetches
|
|
258
|
+
* fresh data. Called when `revalidatePath` targeted a non-current path.
|
|
259
|
+
*/
|
|
260
|
+
invalidatePath(path: string): void;
|
|
261
|
+
/**
|
|
262
|
+
* Evict all prefetch cache entries and history stack payloads (TIM-1476).
|
|
263
|
+
* Called after a server action that revalidated data — the mutation may
|
|
264
|
+
* affect any route's data, not just the paths the server named, so every
|
|
265
|
+
* cached payload is stale. In-flight prefetch singleflights are fenced
|
|
266
|
+
* by the eviction generation — their results are discarded if they
|
|
267
|
+
* complete after eviction. The current URL's history entry is preserved
|
|
268
|
+
* so `applyActionResult` can read its segment metadata.
|
|
269
|
+
*/
|
|
270
|
+
evictStaleCaches(): void;
|
|
217
271
|
/**
|
|
218
272
|
* Populate the segment cache from server-provided segment metadata.
|
|
219
273
|
* Called on initial hydration with segment info embedded in the HTML.
|