@rangojs/router 0.24.3 → 0.25.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/README.md +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-root.d.ts +4 -1
- package/dist/types/client-urls/client-urls.d.ts +28 -2
- package/dist/types/client-urls/navigation.d.ts +6 -8
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +20 -5
- package/dist/types/client-urls/types.d.ts +35 -17
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +19 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/urls/path-helper.d.ts +5 -0
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +195 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +119 -98
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-reverse.ts +4 -17
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-root.tsx +10 -5
- package/src/client-urls/client-urls.ts +246 -166
- package/src/client-urls/navigation.ts +45 -38
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +286 -75
- package/src/client-urls/types.ts +40 -16
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +37 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/urls/path-helper.ts +16 -3
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
serializeStateCookie,
|
|
24
24
|
} from "../browser/cookie-name.js";
|
|
25
25
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
26
|
-
import type {
|
|
26
|
+
import type { AutofillAwareReverseFunction } from "../types/handler-context.js";
|
|
27
27
|
import type {
|
|
28
28
|
DefaultEnv,
|
|
29
29
|
DefaultReverseRouteMap,
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
} from "../theme/constants.js";
|
|
66
66
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
67
67
|
import type { RoutineTrace } from "../rsc/routine-plan.js";
|
|
68
|
+
import type { ActionContinuation } from "../rsc/server-action.js";
|
|
69
|
+
import type { FormActionOutcome } from "../rsc/progressive-enhancement.js";
|
|
68
70
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
69
71
|
import {
|
|
70
72
|
assertCachedHeaderWriteAllowed,
|
|
@@ -345,6 +347,13 @@ export interface RequestContext<
|
|
|
345
347
|
*/
|
|
346
348
|
_shellCaptureRun?: boolean;
|
|
347
349
|
|
|
350
|
+
/**
|
|
351
|
+
* @internal Set beside `_shellCaptureRun` when the shell is keyed by the
|
|
352
|
+
* visitor's URL (`rewrite(...).shell("visitor")`): the capture renders the
|
|
353
|
+
* visitor's location and `ctx.rewrittenFrom`; the theme stays the default.
|
|
354
|
+
*/
|
|
355
|
+
_shellCaptureForVisitor?: boolean;
|
|
356
|
+
|
|
348
357
|
/**
|
|
349
358
|
* @internal Bake-lane loader containers collected DURING a shell capture:
|
|
350
359
|
* segment-key -> the loader's (pre-wrap) result promise. Populated by
|
|
@@ -774,8 +783,15 @@ export interface RequestContext<
|
|
|
774
783
|
* Generate URLs from route names.
|
|
775
784
|
* Uses the global route map. After route matching, scoped (`.name`) resolution
|
|
776
785
|
* works within the matched include() scope.
|
|
777
|
-
|
|
778
|
-
|
|
786
|
+
*
|
|
787
|
+
* Params are optional in the type because the fill depends on the phase.
|
|
788
|
+
* After the route match (route middleware, handlers, loaders, server actions)
|
|
789
|
+
* missing params come from the matched route's params; explicit params win.
|
|
790
|
+
* In global middleware before `next()` nothing is matched and the fill is
|
|
791
|
+
* empty, so a route with a required param throws `Missing param "<name>" for
|
|
792
|
+
* route "<route>"` at runtime (a param-less route still resolves).
|
|
793
|
+
*/
|
|
794
|
+
reverse: AutofillAwareReverseFunction<
|
|
779
795
|
Record<string, string>,
|
|
780
796
|
DefaultReverseRouteMap
|
|
781
797
|
>;
|
|
@@ -786,6 +802,13 @@ export interface RequestContext<
|
|
|
786
802
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
787
803
|
_prevRouteKey?: string;
|
|
788
804
|
|
|
805
|
+
/**
|
|
806
|
+
* @internal Leaving a rewritten page, the visitor's URL moved
|
|
807
|
+
* (router/match-api.ts): the default revalidation counts it as a search
|
|
808
|
+
* change, since the render may read `ctx.rewrittenFrom`.
|
|
809
|
+
*/
|
|
810
|
+
_visitorMoved?: boolean;
|
|
811
|
+
|
|
789
812
|
/**
|
|
790
813
|
* @internal True while the post-action revalidation render is running (set by
|
|
791
814
|
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
@@ -796,6 +819,19 @@ export interface RequestContext<
|
|
|
796
819
|
*/
|
|
797
820
|
_inActionRevalidation?: boolean;
|
|
798
821
|
|
|
822
|
+
/**
|
|
823
|
+
* @internal The outcome of the action this request ran (a JS server
|
|
824
|
+
* action's continuation, a no-JS form post's form state), set before route
|
|
825
|
+
* middleware wraps the re-render. While it is unset, applyRewrite
|
|
826
|
+
* (rsc/rewrite-passes.ts) moves a mutation with its body, so the action
|
|
827
|
+
* runs on the target's pass, or refuses it (`disallowMutations()`, or a
|
|
828
|
+
* body middleware read); once set, the request moves to the target, whose
|
|
829
|
+
* pass re-renders from it instead of running the action again (#1145).
|
|
830
|
+
*/
|
|
831
|
+
_actionRan?:
|
|
832
|
+
| { kind: "action"; continuation: ActionContinuation }
|
|
833
|
+
| { kind: "form"; outcome: FormActionOutcome };
|
|
834
|
+
|
|
799
835
|
/**
|
|
800
836
|
* @internal Render barrier for experimental `rendered()` API.
|
|
801
837
|
* Resolves when all non-loader segments have settled and handle data
|
|
@@ -1017,6 +1053,7 @@ export type PublicRequestContext<
|
|
|
1017
1053
|
| "_onDemandProducer"
|
|
1018
1054
|
| "_searchParamsFilter"
|
|
1019
1055
|
| "_shellCaptureRun"
|
|
1056
|
+
| "_shellCaptureForVisitor"
|
|
1020
1057
|
| "_shellImplicitCache"
|
|
1021
1058
|
| "_shellLoaderSeed"
|
|
1022
1059
|
| "_shellCaptureLoaderRecords"
|
|
@@ -1048,7 +1085,9 @@ export type PublicRequestContext<
|
|
|
1048
1085
|
| "_locationState"
|
|
1049
1086
|
| "_routeName"
|
|
1050
1087
|
| "_prevRouteKey"
|
|
1088
|
+
| "_visitorMoved"
|
|
1051
1089
|
| "_inActionRevalidation"
|
|
1090
|
+
| "_actionRan"
|
|
1052
1091
|
| "_reportedErrors"
|
|
1053
1092
|
| "_renderErrors"
|
|
1054
1093
|
| "_recoveredHandlerErrors"
|
|
@@ -45,6 +45,38 @@ export function requestHeaders(request: Request): Headers {
|
|
|
45
45
|
return RAW_HEADERS.get(request) ?? request.headers;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* A copy of `request`'s headers for a bodyless mirror of it (the PE
|
|
50
|
+
* re-render, route middleware around it, a rewrite pass after an action),
|
|
51
|
+
* without the body-framing ones and `content-type`, so a body check gated on
|
|
52
|
+
* the content-type passes over a request that has no body.
|
|
53
|
+
*/
|
|
54
|
+
export function bodylessHeaders(request: Request): Headers {
|
|
55
|
+
const headers = new Headers(requestHeaders(request));
|
|
56
|
+
headers.delete("content-length");
|
|
57
|
+
headers.delete("content-encoding");
|
|
58
|
+
headers.delete("transfer-encoding");
|
|
59
|
+
headers.delete("content-type");
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A bodyless mirror of `request` at `url`: `method`, its
|
|
65
|
+
* {@link bodylessHeaders}, the signal and workerd's `cf`.
|
|
66
|
+
*/
|
|
67
|
+
export function bodylessRequest(
|
|
68
|
+
url: string | URL,
|
|
69
|
+
request: Request,
|
|
70
|
+
method: string,
|
|
71
|
+
): Request {
|
|
72
|
+
return new Request(url, {
|
|
73
|
+
method,
|
|
74
|
+
headers: bodylessHeaders(request),
|
|
75
|
+
signal: request.signal,
|
|
76
|
+
cf: (request as { cf?: unknown }).cf,
|
|
77
|
+
} as RequestInit);
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
/**
|
|
49
81
|
* Shadow `request.headers` with an own, non-enumerable getter that calls
|
|
50
82
|
* `beforeRead` and returns the request's own Headers (stable per request, so
|
|
@@ -11,8 +11,15 @@
|
|
|
11
11
|
import { contextGet, contextSet, createVar } from "../context-var.js";
|
|
12
12
|
import { RewriteLoopError } from "../errors.js";
|
|
13
13
|
import { documentCacheStoresRender } from "../cache/document-cache-policy.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
guardIdentityRead,
|
|
16
|
+
recordLoaderIdentityRead,
|
|
17
|
+
refuseInCacheScope,
|
|
18
|
+
type IdentityReadWording,
|
|
19
|
+
} from "./context.js";
|
|
20
|
+
import { isInsideIdentityExempt } from "../cache/cache-exec-scope.js";
|
|
15
21
|
import { stripInternalParams } from "../router/internal-params.js";
|
|
22
|
+
import type { RewriteShell } from "../route-definition/rewrite.js";
|
|
16
23
|
import type { RequestContext } from "./request-context.js";
|
|
17
24
|
|
|
18
25
|
/** Most rewrites one request may take; the next one is a RewriteLoopError. */
|
|
@@ -23,7 +30,8 @@ const REWRITE_TRAIL = createVar<readonly string[]>();
|
|
|
23
30
|
|
|
24
31
|
type TrailContext = Pick<RequestContext<any>, "_variables">;
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
/** A URL's path and search: what a rewrite hop, a target and a visitor's move compare. */
|
|
34
|
+
export function pathKey(url: URL): string {
|
|
27
35
|
return url.pathname + url.search;
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -60,6 +68,25 @@ export function recordRewriteHop(
|
|
|
60
68
|
return hops;
|
|
61
69
|
}
|
|
62
70
|
|
|
71
|
+
/** The `.shell()` choice of the hop that reached the route that renders. */
|
|
72
|
+
const REWRITE_SHELL = createVar<RewriteShell | undefined>();
|
|
73
|
+
|
|
74
|
+
export function recordRewriteShell(
|
|
75
|
+
reqCtx: TrailContext,
|
|
76
|
+
audience: RewriteShell | undefined,
|
|
77
|
+
): void {
|
|
78
|
+
contextSet(reqCtx._variables, REWRITE_SHELL, audience);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The last hop's `rewrite(...).shell()` choice; undefined: none. */
|
|
82
|
+
export function rewriteShellAudience(
|
|
83
|
+
reqCtx: TrailContext | undefined,
|
|
84
|
+
): RewriteShell | undefined {
|
|
85
|
+
return reqCtx?._variables
|
|
86
|
+
? (contextGet(reqCtx._variables, REWRITE_SHELL) as RewriteShell | undefined)
|
|
87
|
+
: undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
type AudienceContext = Pick<
|
|
64
91
|
RequestContext<any>,
|
|
65
92
|
"_shellCaptureRun" | "_documentCacheRender" | "res"
|
|
@@ -68,7 +95,8 @@ type AudienceContext = Pick<
|
|
|
68
95
|
/**
|
|
69
96
|
* A render other visitors may be served: a PPR shell capture, or a render
|
|
70
97
|
* the document cache stores (its response opted in before next()). It shows
|
|
71
|
-
*
|
|
98
|
+
* no visitor theme, and no visitor URL unless it is a visitor-keyed shell
|
|
99
|
+
* capture (`_shellCaptureForVisitor`).
|
|
72
100
|
*/
|
|
73
101
|
export function rendersForSharedAudience(reqCtx: AudienceContext): boolean {
|
|
74
102
|
return reqCtx._shellCaptureRun === true || documentCacheStoresRender(reqCtx);
|
|
@@ -112,9 +140,9 @@ export type RewrittenFromReader = "render" | "middleware";
|
|
|
112
140
|
* The read behind every `ctx.rewrittenFrom`. Guarded like cookies(): inside
|
|
113
141
|
* a `cache()` boundary or a `"use cache"` function it throws, and a loader's
|
|
114
142
|
* own `cache()` fill records it. A render other visitors may be served sees
|
|
115
|
-
* `undefined`, as on a direct visit
|
|
116
|
-
* URL
|
|
117
|
-
* it.
|
|
143
|
+
* `undefined`, as on a direct visit, except a visitor-keyed shell capture,
|
|
144
|
+
* whose key covers the URL; any other render that sees the visitor's URL
|
|
145
|
+
* marks `_payloadVisitorLocation`, so the document cache does not store it.
|
|
118
146
|
*/
|
|
119
147
|
function readRewrittenFrom(
|
|
120
148
|
reqCtx: RequestContext<any> | undefined,
|
|
@@ -122,7 +150,15 @@ function readRewrittenFrom(
|
|
|
122
150
|
): string | undefined {
|
|
123
151
|
if (!reqCtx) return undefined;
|
|
124
152
|
if (reader === "render" && rendersForSharedAudience(reqCtx)) {
|
|
125
|
-
return undefined;
|
|
153
|
+
if (!reqCtx._shellCaptureForVisitor) return undefined;
|
|
154
|
+
// guardIdentityRead minus the capture guard: a cache() boundary, a "use
|
|
155
|
+
// cache" body or a loader's own cache() inside the capture is keyed
|
|
156
|
+
// without the URL.
|
|
157
|
+
if (!isInsideIdentityExempt()) {
|
|
158
|
+
refuseInCacheScope("ctx.rewrittenFrom", REWRITTEN_FROM_READ, reqCtx);
|
|
159
|
+
recordLoaderIdentityRead("ctx.rewrittenFrom", REWRITTEN_FROM_READ.verb);
|
|
160
|
+
}
|
|
161
|
+
return rewriteTrail(reqCtx)?.[0];
|
|
126
162
|
}
|
|
127
163
|
guardIdentityRead(reqCtx, "ctx.rewrittenFrom", REWRITTEN_FROM_READ);
|
|
128
164
|
const href = rewriteTrail(reqCtx)?.[0];
|
|
@@ -145,15 +181,25 @@ function copyFor(holder: object, href: string | undefined): URL | undefined {
|
|
|
145
181
|
return copy;
|
|
146
182
|
}
|
|
147
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The ctx defineRewrittenFrom ran on: `holder`, or the one it derives from
|
|
186
|
+
* (a layout's ctx is an Object.create child of the route's, withReverseScope).
|
|
187
|
+
*/
|
|
188
|
+
function definedOn(holder: object): object {
|
|
189
|
+
while (!SOURCES.has(holder)) holder = Object.getPrototypeOf(holder);
|
|
190
|
+
return holder;
|
|
191
|
+
}
|
|
192
|
+
|
|
148
193
|
/**
|
|
149
194
|
* Non-enumerable, like `ctx.theme`: serializing a ctx (React's dev debug
|
|
150
195
|
* info) must not count as a read.
|
|
151
196
|
*/
|
|
152
197
|
const SCOPED_DESCRIPTOR: PropertyDescriptor = {
|
|
153
198
|
get(this: object): URL | undefined {
|
|
199
|
+
const owner = definedOn(this);
|
|
154
200
|
const href = readRewrittenFrom(
|
|
155
|
-
SOURCES.get(
|
|
156
|
-
READERS.get(
|
|
201
|
+
SOURCES.get(owner),
|
|
202
|
+
READERS.get(owner) ?? "render",
|
|
157
203
|
);
|
|
158
204
|
return copyFor(this, href);
|
|
159
205
|
},
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -16,9 +16,10 @@ import type { NavigationStoreContextValue } from "../browser/react/context.js";
|
|
|
16
16
|
import type { HandleData } from "../browser/types.js";
|
|
17
17
|
import type { ResolvedSegment } from "../types.js";
|
|
18
18
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_ACTION_STATE,
|
|
21
|
+
type EventController,
|
|
22
|
+
type DerivedNavigationState,
|
|
22
23
|
} from "../browser/event-controller.js";
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -111,16 +112,11 @@ function createSsrEventController(opts: {
|
|
|
111
112
|
getState: () => state,
|
|
112
113
|
getLocation: () => location,
|
|
113
114
|
subscribe: () => () => {},
|
|
114
|
-
getActionState: () =>
|
|
115
|
-
state: "idle",
|
|
116
|
-
actionId: null,
|
|
117
|
-
payload: null,
|
|
118
|
-
error: null,
|
|
119
|
-
result: null,
|
|
120
|
-
}),
|
|
115
|
+
getActionState: () => DEFAULT_ACTION_STATE,
|
|
121
116
|
subscribeToAction: () => () => {},
|
|
122
117
|
subscribeToHandles: () => () => {},
|
|
123
118
|
flushRouteState: () => {},
|
|
119
|
+
releaseRouteState: () => {},
|
|
124
120
|
setHandleData: () => {},
|
|
125
121
|
getHandleState: () => handleState,
|
|
126
122
|
lockHydration: () => {},
|
package/src/testing/dispatch.ts
CHANGED
|
@@ -129,7 +129,7 @@ import type { CacheProfile } from "../cache/profile-registry.js";
|
|
|
129
129
|
// onto the whole testing barrel's eager graph and break every consumer suite
|
|
130
130
|
// that imports `@rangojs/router/testing` without mocking plugin-rsc.
|
|
131
131
|
import type { EntryData } from "../server/context.js";
|
|
132
|
-
import {
|
|
132
|
+
import { ensureRouterTrie } from "../rsc/manifest-init.js";
|
|
133
133
|
import { RESPONSE_TYPE_MIME } from "../router/content-negotiation.js";
|
|
134
134
|
import { RouterError } from "../errors.js";
|
|
135
135
|
import { createProblemDetails } from "../rsc/response-error.js";
|
|
@@ -170,6 +170,8 @@ interface DispatchableRouter<TEnv> {
|
|
|
170
170
|
/** createRouter({ version }), the consumer-set version. */
|
|
171
171
|
version?: string;
|
|
172
172
|
routeMap: Record<string, unknown>;
|
|
173
|
+
/** The routes() patterns the dev trie is rebuilt from (rsc/manifest-init.ts). */
|
|
174
|
+
urlpatterns?: unknown;
|
|
173
175
|
middleware: MiddlewareEntry<TEnv>[];
|
|
174
176
|
onError?: OnErrorCallback<TEnv>;
|
|
175
177
|
/**
|
|
@@ -409,11 +411,8 @@ export async function dispatch<TEnv = any>(
|
|
|
409
411
|
const url = new URL(req.url);
|
|
410
412
|
const env = (opts.env ?? {}) as TEnv;
|
|
411
413
|
|
|
412
|
-
// Seed the per-router manifest so reverse() resolves during handler execution.
|
|
413
414
|
const routerId = router.id ?? router.routerId;
|
|
414
|
-
|
|
415
|
-
setRouterManifest(routerId, router.routeMap as Record<string, string>);
|
|
416
|
-
}
|
|
415
|
+
await ensureRouterTrie(router);
|
|
417
416
|
|
|
418
417
|
// The visitor's URL is matched before any middleware runs, so an RSC route
|
|
419
418
|
// fails here, before a middleware side effect. A rewrite target is matched
|
package/src/testing/e2e/index.ts
CHANGED
package/src/testing/index.ts
CHANGED
|
@@ -19,9 +19,10 @@ import type { ShouldRevalidateFn } from "../../types.js";
|
|
|
19
19
|
*
|
|
20
20
|
* A Vitest project runs no hoist plugin, so the source's revalidate()
|
|
21
21
|
* predicates are plain functions. Each is registered under a key named for
|
|
22
|
-
* its reference, route and loader, as the plugin's generated
|
|
23
|
-
* its own, and the projection carries the keys:
|
|
24
|
-
* and the server runs them, as in an app.
|
|
22
|
+
* its reference, route or intercept, and loader, as the plugin's generated
|
|
23
|
+
* module registers its own, and the projection carries the keys:
|
|
24
|
+
* materialization attaches them and the server runs them, as in an app. A
|
|
25
|
+
* boolean rides in the projection as itself, as the plugin leaves a literal.
|
|
25
26
|
*/
|
|
26
27
|
export function installClientUrlsProjection(
|
|
27
28
|
referenceId: string,
|
|
@@ -50,21 +51,40 @@ function withRevalidateKeys(
|
|
|
50
51
|
projection: ClientUrlProjection,
|
|
51
52
|
): ClientUrlProjection {
|
|
52
53
|
const fns: Record<string, ShouldRevalidateFn<any, any>> = {};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// A route's or an intercept's loaders, keyed under `owner`.
|
|
55
|
+
const withKeys = <T extends object>(
|
|
56
|
+
projected: T,
|
|
57
|
+
owner: string,
|
|
58
|
+
loaders: ClientUrlPatterns["routes"][number]["loaders"] | undefined,
|
|
59
|
+
): T => {
|
|
60
|
+
const loaderRevalidate = (loaders ?? []).map(
|
|
56
61
|
({ revalidate }, loaderIndex) =>
|
|
57
62
|
revalidate.map((entry, index) => {
|
|
58
63
|
if (isHoistedRevalidateRef(entry)) return entry.$$rangoRevalidate;
|
|
59
|
-
|
|
64
|
+
if (typeof entry === "boolean") return entry;
|
|
65
|
+
const key = `${referenceId}:${owner}:${loaderIndex}:${index}`;
|
|
60
66
|
fns[key] = entry;
|
|
61
67
|
return key;
|
|
62
68
|
}),
|
|
63
69
|
);
|
|
64
70
|
return loaderRevalidate.some((keys) => keys.length > 0)
|
|
65
|
-
? { ...
|
|
66
|
-
:
|
|
67
|
-
}
|
|
71
|
+
? { ...projected, loaderRevalidate }
|
|
72
|
+
: projected;
|
|
73
|
+
};
|
|
74
|
+
const routes = projection.routes.map((route) =>
|
|
75
|
+
withKeys(
|
|
76
|
+
route,
|
|
77
|
+
route.id,
|
|
78
|
+
definition.routes.find(({ id }) => id === route.id)?.loaders,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
const intercepts = projection.intercepts?.map((intercept, index) =>
|
|
82
|
+
withKeys(
|
|
83
|
+
intercept,
|
|
84
|
+
`intercept${index}`,
|
|
85
|
+
definition.intercepts[index]?.loaders,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
68
88
|
registerClientUrlRevalidators(fns);
|
|
69
|
-
return { ...projection, routes };
|
|
89
|
+
return { ...projection, routes, ...(intercepts && { intercepts }) };
|
|
70
90
|
}
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
* SEEDED directly into client context (see the `loaders` / `locationState` /
|
|
24
24
|
* `handles` / `outletPending` options) — nothing is executed on the server.
|
|
25
25
|
* This exercises the context read path, not the run path.
|
|
26
|
-
* - navigate()
|
|
27
|
-
*
|
|
28
|
-
* pending
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
26
|
+
* - navigate() drives the navigation lifecycle as a click does:
|
|
27
|
+
* useNavigation() reads loading from the call and idle with the commit,
|
|
28
|
+
* and useLinkStatus().pending is true for a link to the destination until
|
|
29
|
+
* the commit is handed to React. A `transition` spec plus a pending
|
|
30
|
+
* Promise seeded via navigate(url, { loaders }) commits through
|
|
31
|
+
* production's commitInTransition (browser/partial-update.ts), which React
|
|
32
|
+
* holds: useNavigation() stays loading and the held reader's
|
|
33
|
+
* useLoader().isLoading reads true until the data lands. Without
|
|
34
|
+
* `transition` the commit is urgent (production's same-structure hold is
|
|
35
|
+
* not modeled). useAction().state stays idle: no action runs.
|
|
33
36
|
* - A spec's transition({ when }) decides every commit with production's
|
|
34
37
|
* browser decision: navigate() and useRouter().push model kind "push",
|
|
35
38
|
* useRouter().replace kind "replace", refresh() kind "revalidate".
|
|
@@ -81,7 +84,10 @@ import type {
|
|
|
81
84
|
NavigationBridge,
|
|
82
85
|
UpdateSubscriber,
|
|
83
86
|
} from "../browser/types.js";
|
|
84
|
-
import type {
|
|
87
|
+
import type {
|
|
88
|
+
EventController,
|
|
89
|
+
NavigationHandle,
|
|
90
|
+
} from "../browser/event-controller.js";
|
|
85
91
|
import type { ResolvedSegment, RscMetadata } from "../browser/types.js";
|
|
86
92
|
import { NavigationProvider } from "../browser/react/NavigationProvider.js";
|
|
87
93
|
import {
|
|
@@ -407,9 +413,10 @@ export interface TestRouterHandle {
|
|
|
407
413
|
* pending Promise suspends the read like a streaming Flight chunk. When a
|
|
408
414
|
* spec in the chain has `transition`, the commit goes through production's
|
|
409
415
|
* commitInTransition: the reader stays on screen with
|
|
410
|
-
* `useLoader().isLoading === true
|
|
411
|
-
* inside RTL's `act()` to
|
|
412
|
-
*
|
|
416
|
+
* `useLoader().isLoading === true`, and `useNavigation().state` reads
|
|
417
|
+
* "loading", until the promise settles. Settle it inside RTL's `act()` to
|
|
418
|
+
* flush the commit. Without `transition` the commit is urgent and the read
|
|
419
|
+
* suspends to the nearest Suspense boundary.
|
|
413
420
|
*
|
|
414
421
|
* @example
|
|
415
422
|
* await router.navigate("/products/2", { loaders: [[ProductLoader, next]] });
|
|
@@ -429,10 +436,16 @@ export interface TestRouterHandle {
|
|
|
429
436
|
* location state as a production replace does). With neither, history is
|
|
430
437
|
* left alone and readers keep the state they have: `navigate(url)` and
|
|
431
438
|
* `navigate(url, { replace: false })` are the same call.
|
|
439
|
+
*
|
|
440
|
+
* `options.handles` are the destination's handle pushes (`[handle,
|
|
441
|
+
* values]`, attached to its leaf route as in `renderRoute`'s own
|
|
442
|
+
* `handles`). They arrive on the response's handle stream, so `useHandle`
|
|
443
|
+
* readers take them with the page's commit: while React holds the page,
|
|
444
|
+
* readers keep the values of the page being left.
|
|
432
445
|
*/
|
|
433
446
|
navigate(
|
|
434
447
|
url: string,
|
|
435
|
-
options?: Pick<RenderRouteOptions, "loaders"> &
|
|
448
|
+
options?: Pick<RenderRouteOptions, "loaders" | "handles"> &
|
|
436
449
|
Pick<NavigateOptions, "state" | "replace" | "transition">,
|
|
437
450
|
): Promise<void>;
|
|
438
451
|
/**
|
|
@@ -791,17 +804,36 @@ export async function renderRoute(
|
|
|
791
804
|
};
|
|
792
805
|
}
|
|
793
806
|
|
|
794
|
-
|
|
807
|
+
type NavigateCall = Pick<RenderRouteOptions, "loaders" | "handles"> & {
|
|
808
|
+
history?: Pick<NavigateOptionsInternal, "state" | "replace" | "transition">;
|
|
809
|
+
kind?: TransitionWhenKind;
|
|
810
|
+
transition?: boolean;
|
|
811
|
+
};
|
|
812
|
+
// The navigation's lifecycle, as navigation-transaction.ts drives it:
|
|
813
|
+
// useNavigation() reads loading from the call, delivered before the commit
|
|
814
|
+
// as a click's notification goes out while its response is in flight, and
|
|
815
|
+
// idle with the location at the commit, which React may hold.
|
|
795
816
|
const navigate = async (
|
|
796
817
|
target: string,
|
|
797
|
-
navOptions?:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
818
|
+
navOptions?: NavigateCall,
|
|
819
|
+
): Promise<void> => {
|
|
820
|
+
const handle = eventController.startNavigation(
|
|
821
|
+
new URL(target, TEST_ORIGIN).href,
|
|
822
|
+
);
|
|
823
|
+
// Synchronous, in the caller's task: a <Link> click's own act() scope
|
|
824
|
+
// takes it, and no act() of the caller's can overlap it.
|
|
825
|
+
act(() => eventController.flushRouteState());
|
|
826
|
+
try {
|
|
827
|
+
await commitNavigation(handle, target, navOptions);
|
|
828
|
+
} finally {
|
|
829
|
+
// Back to idle when the commit never came (a render that threw).
|
|
830
|
+
handle[Symbol.dispose]();
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
const commitNavigation = async (
|
|
834
|
+
handle: NavigationHandle,
|
|
835
|
+
target: string,
|
|
836
|
+
navOptions?: NavigateCall,
|
|
805
837
|
): Promise<void> => {
|
|
806
838
|
// A useRouter().push/replace or <Link> navigation (`history`) writes its
|
|
807
839
|
// entry the way production does (navigation-bridge.ts navigate ->
|
|
@@ -819,21 +851,6 @@ export async function renderRoute(
|
|
|
819
851
|
: undefined,
|
|
820
852
|
)
|
|
821
853
|
: undefined;
|
|
822
|
-
// No server fetch, so the navigation lifecycle never starts: the state
|
|
823
|
-
// useNavigation()/useLinkStatus()/useAction() read stays "idle" — asserting
|
|
824
|
-
// a pending/loading/submitting state here proves nothing. Warn once (per
|
|
825
|
-
// render) under the test runner so that false-confidence trap is loud.
|
|
826
|
-
if (isUnderTestRunner() && !warnedNavLifecycle) {
|
|
827
|
-
warnedNavLifecycle = true;
|
|
828
|
-
console.warn(
|
|
829
|
-
"renderRoute: navigate()/useRouter().push do NOT drive the navigation " +
|
|
830
|
-
"lifecycle. useNavigation().state, useLinkStatus().pending, and " +
|
|
831
|
-
'useAction().state stay "idle" here. Assert params/pathname/content ' +
|
|
832
|
-
"after navigate(); use renderServerTree or e2e to assert " +
|
|
833
|
-
"pending/loading/submitting transition states. A held " +
|
|
834
|
-
"useLoader().isLoading is modeled: see navigate(url, { loaders }).",
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
854
|
const nextUrl = new URL(target, TEST_ORIGIN);
|
|
838
855
|
const match = resolve(nextUrl.pathname);
|
|
839
856
|
const segments = buildSegments(
|
|
@@ -843,6 +860,23 @@ export async function renderRoute(
|
|
|
843
860
|
mount,
|
|
844
861
|
);
|
|
845
862
|
const metadata = makeMetadata(nextUrl.pathname, segments, match.params);
|
|
863
|
+
// The destination's handle pushes arrive on the response's handle
|
|
864
|
+
// stream, as production's do, behind the page's commit.
|
|
865
|
+
const navHandles = navOptions?.handles;
|
|
866
|
+
if (navHandles) {
|
|
867
|
+
const leafId =
|
|
868
|
+
[...segments].reverse().find((s) => s.type === "route")?.id ??
|
|
869
|
+
segments[segments.length - 1]?.id;
|
|
870
|
+
const pushed: HandleDataSeed = {};
|
|
871
|
+
for (const [h, values] of navHandles) {
|
|
872
|
+
if (leafId === undefined) continue;
|
|
873
|
+
const id = (h as unknown as { $$id: string }).$$id;
|
|
874
|
+
(pushed[id] ??= {})[leafId] = values;
|
|
875
|
+
}
|
|
876
|
+
metadata.handles = (async function* () {
|
|
877
|
+
yield pushed;
|
|
878
|
+
})();
|
|
879
|
+
}
|
|
846
880
|
// Production's browser-run transition({ when }) decision
|
|
847
881
|
// (browser/partial-update.ts): the committed location is the source.
|
|
848
882
|
const kind: TransitionWhenKind =
|
|
@@ -872,7 +906,10 @@ export async function renderRoute(
|
|
|
872
906
|
outletPending: options.outletPending,
|
|
873
907
|
transitionGatedOff: gatedOff,
|
|
874
908
|
});
|
|
875
|
-
|
|
909
|
+
// A newer navigate() started meanwhile: this one commits nothing, as
|
|
910
|
+
// production's (browser/partial-update.ts) returns on its aborted signal.
|
|
911
|
+
if (handle.signal.aborted) return;
|
|
912
|
+
handle.complete(nextUrl);
|
|
876
913
|
eventController.setParams(match.params);
|
|
877
914
|
store.setCurrentUrl(nextUrl.href);
|
|
878
915
|
store.setSegmentIds(segments.map((s) => s.id));
|
|
@@ -892,9 +929,14 @@ export async function renderRoute(
|
|
|
892
929
|
// same-structure / fully-prefetched / optimistic hold lanes are not
|
|
893
930
|
// modeled: without transition() the commit stays urgent.
|
|
894
931
|
if (shouldStartViewTransition(segments, gatedOff)) {
|
|
895
|
-
commitInTransition(
|
|
896
|
-
|
|
897
|
-
|
|
932
|
+
commitInTransition(
|
|
933
|
+
store.loaders,
|
|
934
|
+
emit,
|
|
935
|
+
segments,
|
|
936
|
+
{ root, metadata },
|
|
937
|
+
kind,
|
|
938
|
+
["navigation"],
|
|
939
|
+
);
|
|
898
940
|
} else {
|
|
899
941
|
emit({ root, metadata });
|
|
900
942
|
}
|
|
@@ -1024,6 +1066,7 @@ export async function renderRoute(
|
|
|
1024
1066
|
navigate: (target, navOptions) =>
|
|
1025
1067
|
navigate(target, {
|
|
1026
1068
|
loaders: navOptions?.loaders,
|
|
1069
|
+
handles: navOptions?.handles,
|
|
1027
1070
|
transition: navOptions?.transition,
|
|
1028
1071
|
history:
|
|
1029
1072
|
navOptions?.state !== undefined || navOptions?.replace
|