@tanstack/router-core 1.171.15 → 1.171.16
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/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +3 -12
- package/dist/cjs/await-signal.cjs +21 -0
- package/dist/cjs/await-signal.cjs.map +1 -0
- package/dist/cjs/await-signal.d.cts +1 -0
- package/dist/cjs/index.cjs +3 -1
- package/dist/cjs/index.d.cts +3 -2
- package/dist/cjs/isServer/client.cjs +2 -0
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +2 -0
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +2 -0
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/link.d.cts +5 -3
- package/dist/cjs/load-client.cjs +1393 -0
- package/dist/cjs/load-client.cjs.map +1 -0
- package/dist/cjs/load-client.d.cts +118 -0
- package/dist/cjs/load-server.cjs +565 -0
- package/dist/cjs/load-server.cjs.map +1 -0
- package/dist/cjs/load-server.d.cts +16 -0
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +0 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs +204 -439
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +68 -54
- package/dist/cjs/ssr/client.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs +60 -9
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.d.cts +1 -0
- package/dist/cjs/ssr/handlerCallback.cjs +49 -4
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
- package/dist/cjs/ssr/handlerCallback.d.cts +3 -0
- package/dist/cjs/ssr/server.cjs +4 -0
- package/dist/cjs/ssr/server.d.cts +2 -2
- package/dist/cjs/ssr/ssr-client.d.cts +1 -10
- package/dist/cjs/ssr/ssr-match-id.cjs +2 -2
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +6 -6
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +27 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +2 -0
- package/dist/cjs/ssr/types.d.cts +0 -1
- package/dist/cjs/stores.cjs +30 -104
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +7 -32
- package/dist/cjs/utils.cjs +0 -4
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/Matches.d.ts +3 -12
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/await-signal.d.ts +1 -0
- package/dist/esm/await-signal.js +21 -0
- package/dist/esm/await-signal.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +2 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +2 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +2 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/link.d.ts +5 -3
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/load-client.d.ts +118 -0
- package/dist/esm/load-client.js +1386 -0
- package/dist/esm/load-client.js.map +1 -0
- package/dist/esm/load-server.d.ts +16 -0
- package/dist/esm/load-server.js +565 -0
- package/dist/esm/load-server.js.map +1 -0
- package/dist/esm/redirect.d.ts +0 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +68 -54
- package/dist/esm/router.js +207 -442
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/ssr/client.js +1 -1
- package/dist/esm/ssr/createRequestHandler.d.ts +1 -0
- package/dist/esm/ssr/createRequestHandler.js +61 -11
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/handlerCallback.d.ts +3 -0
- package/dist/esm/ssr/handlerCallback.js +47 -5
- package/dist/esm/ssr/handlerCallback.js.map +1 -1
- package/dist/esm/ssr/server.d.ts +2 -2
- package/dist/esm/ssr/server.js +3 -3
- package/dist/esm/ssr/ssr-client.d.ts +1 -10
- package/dist/esm/ssr/ssr-match-id.js +2 -2
- package/dist/esm/ssr/ssr-match-id.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +6 -6
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +2 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +27 -1
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/ssr/types.d.ts +0 -1
- package/dist/esm/stores.d.ts +7 -32
- package/dist/esm/stores.js +30 -104
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.js +1 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/skills/router-core/SKILL.md +23 -4
- package/skills/router-core/auth-and-guards/SKILL.md +10 -10
- package/skills/router-core/code-splitting/SKILL.md +5 -4
- package/skills/router-core/data-loading/SKILL.md +30 -18
- package/skills/router-core/navigation/SKILL.md +5 -4
- package/skills/router-core/not-found-and-errors/SKILL.md +5 -4
- package/skills/router-core/path-params/SKILL.md +5 -4
- package/skills/router-core/search-params/SKILL.md +5 -4
- package/skills/router-core/ssr/SKILL.md +5 -4
- package/skills/router-core/type-safety/SKILL.md +11 -4
- package/src/Matches.ts +6 -21
- package/src/await-signal.ts +27 -0
- package/src/index.ts +1 -3
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +1 -0
- package/src/isServer/server.ts +1 -0
- package/src/link.ts +5 -3
- package/src/load-client.ts +2651 -0
- package/src/load-server.ts +849 -0
- package/src/redirect.ts +0 -1
- package/src/route.ts +2 -11
- package/src/router.ts +426 -773
- package/src/ssr/createRequestHandler.ts +109 -16
- package/src/ssr/handlerCallback.ts +76 -10
- package/src/ssr/server.ts +4 -1
- package/src/ssr/ssr-client.ts +1 -310
- package/src/ssr/ssr-match-id.ts +11 -2
- package/src/ssr/ssr-server.ts +9 -8
- package/src/ssr/transformStreamWithRouter.ts +37 -1
- package/src/ssr/types.ts +0 -1
- package/src/stores.ts +46 -208
- package/dist/cjs/load-matches.cjs +0 -658
- package/dist/cjs/load-matches.cjs.map +0 -1
- package/dist/cjs/load-matches.d.cts +0 -18
- package/dist/cjs/ssr/ssr-client.cjs +0 -183
- package/dist/cjs/ssr/ssr-client.cjs.map +0 -1
- package/dist/esm/load-matches.d.ts +0 -18
- package/dist/esm/load-matches.js +0 -656
- package/dist/esm/load-matches.js.map +0 -1
- package/dist/esm/ssr/ssr-client.js +0 -183
- package/dist/esm/ssr/ssr-client.js.map +0 -1
- package/src/load-matches.ts +0 -1278
package/src/router.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createBrowserHistory, parseHref } from '@tanstack/history'
|
|
2
|
-
import { isServer } from '@tanstack/router-core/isServer'
|
|
2
|
+
import { isServer, loadServerRoute } from '@tanstack/router-core/isServer'
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_PROTOCOL_ALLOWLIST,
|
|
5
|
-
createControlledPromise,
|
|
6
5
|
decodePath,
|
|
7
6
|
deepEqual,
|
|
8
7
|
encodePathLikeUrl,
|
|
@@ -35,8 +34,14 @@ import { isNotFound } from './not-found'
|
|
|
35
34
|
import { setupScrollRestoration } from './scroll-restoration'
|
|
36
35
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
37
36
|
import { rootRouteId } from './root'
|
|
38
|
-
import { isRedirect
|
|
39
|
-
import {
|
|
37
|
+
import { isRedirect } from './redirect'
|
|
38
|
+
import {
|
|
39
|
+
loadClientRoute,
|
|
40
|
+
loadRouteChunk,
|
|
41
|
+
preloadClientRoute,
|
|
42
|
+
refreshClientRoute,
|
|
43
|
+
replaceRouteChunk,
|
|
44
|
+
} from './load-client'
|
|
40
45
|
import {
|
|
41
46
|
composeRewrites,
|
|
42
47
|
executeRewriteInput,
|
|
@@ -51,6 +56,12 @@ import type {
|
|
|
51
56
|
} from './new-process-route-tree'
|
|
52
57
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
53
58
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
59
|
+
import type {
|
|
60
|
+
LoadTransaction,
|
|
61
|
+
LoaderFlight,
|
|
62
|
+
PendingSession,
|
|
63
|
+
} from './load-client'
|
|
64
|
+
import type { ServerLoadResult } from './load-server'
|
|
54
65
|
import type {
|
|
55
66
|
HistoryAction,
|
|
56
67
|
HistoryLocation,
|
|
@@ -62,7 +73,6 @@ import type {
|
|
|
62
73
|
import type {
|
|
63
74
|
Awaitable,
|
|
64
75
|
Constrain,
|
|
65
|
-
ControlledPromise,
|
|
66
76
|
NoInfer,
|
|
67
77
|
NonNullableUpdater,
|
|
68
78
|
PickAsRequired,
|
|
@@ -75,8 +85,6 @@ import type {
|
|
|
75
85
|
AnyRouteWithContext,
|
|
76
86
|
LoaderStaleReloadMode,
|
|
77
87
|
MakeRemountDepsOptionsUnion,
|
|
78
|
-
RouteContextOptions,
|
|
79
|
-
RouteLike,
|
|
80
88
|
RouteMask,
|
|
81
89
|
SearchMiddleware,
|
|
82
90
|
SearchMiddlewareMeta,
|
|
@@ -106,7 +114,6 @@ import type {
|
|
|
106
114
|
} from './manifest'
|
|
107
115
|
import type { AnySchema, AnyValidator } from './validators'
|
|
108
116
|
import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
|
|
109
|
-
import type { NotFoundError } from './not-found'
|
|
110
117
|
import type {
|
|
111
118
|
AnySerializationAdapter,
|
|
112
119
|
ValidateSerializableInput,
|
|
@@ -263,9 +270,9 @@ export interface RouterOptions<
|
|
|
263
270
|
*/
|
|
264
271
|
defaultPreloadStaleTime?: number
|
|
265
272
|
/**
|
|
266
|
-
* The default `
|
|
273
|
+
* The default `preloadGcTime` a route should use if none is provided.
|
|
267
274
|
*
|
|
268
|
-
* @default
|
|
275
|
+
* @default 300_000 `(5 minutes)`
|
|
269
276
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadgctime-property)
|
|
270
277
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
|
|
271
278
|
*/
|
|
@@ -297,7 +304,7 @@ export interface RouterOptions<
|
|
|
297
304
|
/**
|
|
298
305
|
* The default `gcTime` a route should use if no gcTime is provided.
|
|
299
306
|
*
|
|
300
|
-
* @default
|
|
307
|
+
* @default 300_000 `(5 minutes)`
|
|
301
308
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultgctime-property)
|
|
302
309
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
|
|
303
310
|
*/
|
|
@@ -544,14 +551,10 @@ export interface RouterState<
|
|
|
544
551
|
in out TRouteMatch = MakeRouteMatchUnion,
|
|
545
552
|
> {
|
|
546
553
|
status: 'pending' | 'idle'
|
|
547
|
-
loadedAt: number
|
|
548
554
|
isLoading: boolean
|
|
549
|
-
isTransitioning: boolean
|
|
550
555
|
matches: Array<TRouteMatch>
|
|
551
556
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
552
557
|
resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
553
|
-
statusCode: number
|
|
554
|
-
redirect?: AnyRedirect
|
|
555
558
|
}
|
|
556
559
|
|
|
557
560
|
export interface BuildNextOptions {
|
|
@@ -619,9 +622,21 @@ export type SubscribeFn = <TType extends keyof RouterEvents>(
|
|
|
619
622
|
) => () => void
|
|
620
623
|
|
|
621
624
|
export interface MatchRoutesOpts {
|
|
622
|
-
preload?: boolean
|
|
623
625
|
throwOnError?: boolean
|
|
624
|
-
|
|
626
|
+
/** @internal */
|
|
627
|
+
_controller?: AbortController
|
|
628
|
+
/** @internal */
|
|
629
|
+
_rematerialize?: boolean
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function routeNeedsLoad(route: AnyRoute): unknown {
|
|
633
|
+
return (
|
|
634
|
+
route.options.loader ||
|
|
635
|
+
route.options.beforeLoad ||
|
|
636
|
+
route.lazyFn ||
|
|
637
|
+
(route.options.component as any)?.preload ||
|
|
638
|
+
(route.options.pendingComponent as any)?.preload
|
|
639
|
+
)
|
|
625
640
|
}
|
|
626
641
|
|
|
627
642
|
export type InferRouterContext<TRouteTree extends AnyRoute> =
|
|
@@ -748,6 +763,7 @@ export type EmitFn = (routerEvent: RouterEvent) => void
|
|
|
748
763
|
export type LoadFn = (opts?: {
|
|
749
764
|
sync?: boolean
|
|
750
765
|
action?: { type: HistoryAction }
|
|
766
|
+
_signal?: AbortSignal
|
|
751
767
|
}) => Promise<void>
|
|
752
768
|
|
|
753
769
|
export type CommitLocationFn = ({
|
|
@@ -756,7 +772,10 @@ export type CommitLocationFn = ({
|
|
|
756
772
|
...next
|
|
757
773
|
}: ParsedLocation & CommitLocationOptions) => Promise<void>
|
|
758
774
|
|
|
759
|
-
export type StartTransitionFn = (
|
|
775
|
+
export type StartTransitionFn = (
|
|
776
|
+
fn: () => void,
|
|
777
|
+
expected: Array<AnyRouteMatch>,
|
|
778
|
+
) => Promise<boolean>
|
|
760
779
|
|
|
761
780
|
export interface MatchRoutesFn {
|
|
762
781
|
(
|
|
@@ -775,13 +794,6 @@ export interface MatchRoutesFn {
|
|
|
775
794
|
): Array<AnyRouteMatch>
|
|
776
795
|
}
|
|
777
796
|
|
|
778
|
-
export type GetMatchFn = (matchId: string) => AnyRouteMatch | undefined
|
|
779
|
-
|
|
780
|
-
export type UpdateMatchFn = (
|
|
781
|
-
id: string,
|
|
782
|
-
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
783
|
-
) => void
|
|
784
|
-
|
|
785
797
|
export type LoadRouteChunkFn = (route: AnyRoute) => Promise<Array<void>>
|
|
786
798
|
|
|
787
799
|
export type ResolveRedirect = (err: AnyRedirect) => ResolvedRedirect
|
|
@@ -896,12 +908,60 @@ export function getLocationChangeInfo(
|
|
|
896
908
|
location: ParsedLocation,
|
|
897
909
|
resolvedLocation?: ParsedLocation,
|
|
898
910
|
) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
911
|
+
return {
|
|
912
|
+
fromLocation: resolvedLocation,
|
|
913
|
+
toLocation: location,
|
|
914
|
+
pathChanged: resolvedLocation?.pathname !== location.pathname,
|
|
915
|
+
hrefChanged: resolvedLocation?.href !== location.href,
|
|
916
|
+
hashChanged: resolvedLocation?.hash !== location.hash,
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Return only state owned by the application, excluding volatile history
|
|
922
|
+
* bookkeeping. Mask payloads (`__tempLocation`/`__tempKey`) are kept: they
|
|
923
|
+
* distinguish otherwise-identical locations.
|
|
924
|
+
*/
|
|
925
|
+
export function _getUserHistoryState({
|
|
926
|
+
key: _key,
|
|
927
|
+
__TSR_key: _tsrKey,
|
|
928
|
+
__TSR_index: _tsrIndex,
|
|
929
|
+
__hashScrollIntoViewOptions: _hashScroll,
|
|
930
|
+
...state
|
|
931
|
+
}: ParsedHistoryState): HistoryState {
|
|
932
|
+
return state
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** Run route lifecycle callbacks in leave/enter/stay phases. */
|
|
936
|
+
export function runRouteLifecycle(
|
|
937
|
+
router: AnyRouter,
|
|
938
|
+
previous: Array<AnyRouteMatch>,
|
|
939
|
+
matches: Array<AnyRouteMatch>,
|
|
940
|
+
isCurrent?: () => boolean,
|
|
941
|
+
): void {
|
|
942
|
+
for (const match of previous) {
|
|
943
|
+
if (isCurrent?.() === false) {
|
|
944
|
+
return
|
|
945
|
+
}
|
|
946
|
+
if (!matches.some((candidate) => candidate.routeId === match.routeId)) {
|
|
947
|
+
;(router.routesById as Record<string, AnyRoute>)[
|
|
948
|
+
match.routeId
|
|
949
|
+
]!.options.onLeave?.(match)
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
for (const match of matches) {
|
|
953
|
+
if (isCurrent?.() === false) {
|
|
954
|
+
return
|
|
955
|
+
}
|
|
956
|
+
const route = (router.routesById as Record<string, AnyRoute>)[
|
|
957
|
+
match.routeId
|
|
958
|
+
]!
|
|
959
|
+
route.options[
|
|
960
|
+
previous.some((candidate) => candidate.routeId === match.routeId)
|
|
961
|
+
? 'onStay'
|
|
962
|
+
: 'onEnter'
|
|
963
|
+
]?.(match)
|
|
964
|
+
}
|
|
905
965
|
}
|
|
906
966
|
|
|
907
967
|
type LightweightRouteMatchResult = {
|
|
@@ -951,6 +1011,46 @@ declare global {
|
|
|
951
1011
|
| undefined
|
|
952
1012
|
}
|
|
953
1013
|
|
|
1014
|
+
export interface RouterCore<
|
|
1015
|
+
in out TRouteTree extends AnyRoute,
|
|
1016
|
+
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
1017
|
+
in out TDefaultStructuralSharingOption extends boolean,
|
|
1018
|
+
in out TRouterHistory extends RouterHistory = RouterHistory,
|
|
1019
|
+
in out TDehydrated extends Record<string, any> = Record<string, any>,
|
|
1020
|
+
> {
|
|
1021
|
+
shouldViewTransition?: boolean | ViewTransitionOptions
|
|
1022
|
+
/** Current client load transaction and owner of navigation writes. */
|
|
1023
|
+
_tx?: LoadTransaction
|
|
1024
|
+
/** Joinable in-flight loader generations keyed by match ID. */
|
|
1025
|
+
_flights?: Map<string, LoaderFlight>
|
|
1026
|
+
/** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */
|
|
1027
|
+
_preloads?: Map<AbortController, Array<AnyRouteMatch>>
|
|
1028
|
+
/** Owns cancellable work before a client transaction publishes. */
|
|
1029
|
+
_preflight?: AbortController
|
|
1030
|
+
/** Transfers one reconstructed SSR prefix into its initial client load. */
|
|
1031
|
+
_handoff?: HydrationHandoff
|
|
1032
|
+
/** Pending-boundary reveal and minimum-visible timing state. */
|
|
1033
|
+
_pending?: PendingSession
|
|
1034
|
+
/** Result of the latest server load, used to render or redirect. */
|
|
1035
|
+
_serverResult?: ServerLoadResult
|
|
1036
|
+
/** Framework publication waiting for an exact render acknowledgement. */
|
|
1037
|
+
_rendered?: [
|
|
1038
|
+
offered?: Array<AnyRouteMatch>,
|
|
1039
|
+
settle?: (rendered: boolean) => void,
|
|
1040
|
+
]
|
|
1041
|
+
/** Development-only HMR reload for a route and its descendants. */
|
|
1042
|
+
_refreshRoute: (() => Promise<void>) | undefined
|
|
1043
|
+
/** Development-only replacement for a route's lazy chunk owner. */
|
|
1044
|
+
_replaceRouteChunk:
|
|
1045
|
+
| ((route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void)
|
|
1046
|
+
| undefined
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export type HydrationHandoff = [
|
|
1050
|
+
claim: () => AbortController | undefined,
|
|
1051
|
+
finish: (matches?: Array<AnyRouteMatch>) => number | undefined,
|
|
1052
|
+
]
|
|
1053
|
+
|
|
954
1054
|
/**
|
|
955
1055
|
* Core, framework-agnostic router engine that powers TanStack Router.
|
|
956
1056
|
*
|
|
@@ -979,10 +1079,11 @@ export class RouterCore<
|
|
|
979
1079
|
restoration?: boolean
|
|
980
1080
|
reset?: boolean
|
|
981
1081
|
} = { next: true }
|
|
982
|
-
shouldViewTransition?: boolean | ViewTransitionOptions = undefined
|
|
983
|
-
isViewTransitionTypesSupported?: boolean = undefined
|
|
984
1082
|
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
985
|
-
|
|
1083
|
+
/** Accepted off-screen loader generations keyed by match ID. */
|
|
1084
|
+
_cache = new Map<string, AnyRouteMatch>()
|
|
1085
|
+
/** Accepted semantic lane, excluding temporary pending presentation. */
|
|
1086
|
+
_committed: Array<AnyRouteMatch> = []
|
|
986
1087
|
|
|
987
1088
|
// Must build in constructor
|
|
988
1089
|
stores!: RouterStores<TRouteTree>
|
|
@@ -1003,7 +1104,7 @@ export class RouterCore<
|
|
|
1003
1104
|
rewrite?: LocationRewrite
|
|
1004
1105
|
origin?: string
|
|
1005
1106
|
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
1006
|
-
|
|
1107
|
+
_pendingLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
1007
1108
|
basepath!: string
|
|
1008
1109
|
routeTree!: TRouteTree
|
|
1009
1110
|
routesById!: RoutesById<TRouteTree>
|
|
@@ -1048,24 +1149,19 @@ export class RouterCore<
|
|
|
1048
1149
|
options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST,
|
|
1049
1150
|
})
|
|
1050
1151
|
|
|
1051
|
-
if (typeof document
|
|
1152
|
+
if (!(isServer ?? typeof document === 'undefined')) {
|
|
1052
1153
|
self.__TSR_ROUTER__ = this
|
|
1053
1154
|
}
|
|
1054
1155
|
}
|
|
1055
1156
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1157
|
+
startTransition: StartTransitionFn = async (fn) => {
|
|
1158
|
+
fn()
|
|
1159
|
+
return false
|
|
1160
|
+
}
|
|
1061
1161
|
isShell() {
|
|
1062
1162
|
return !!this.options.isShell
|
|
1063
1163
|
}
|
|
1064
1164
|
|
|
1065
|
-
isPrerendering() {
|
|
1066
|
-
return !!this.options.isPrerendering
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
1165
|
update: UpdateFn<
|
|
1070
1166
|
TRouteTree,
|
|
1071
1167
|
TTrailingSlashOption,
|
|
@@ -1091,7 +1187,8 @@ export class RouterCore<
|
|
|
1091
1187
|
...newOptions,
|
|
1092
1188
|
}
|
|
1093
1189
|
|
|
1094
|
-
this.isServer =
|
|
1190
|
+
this.isServer =
|
|
1191
|
+
this.options.isServer ?? isServer ?? typeof document === 'undefined'
|
|
1095
1192
|
|
|
1096
1193
|
this.protocolAllowlist = new Set(this.options.protocolAllowlist)
|
|
1097
1194
|
|
|
@@ -1165,17 +1262,13 @@ export class RouterCore<
|
|
|
1165
1262
|
if (!this.stores && this.latestLocation) {
|
|
1166
1263
|
const config = this.getStoreConfig(this)
|
|
1167
1264
|
this.batch = config.batch
|
|
1168
|
-
this.stores = createRouterStores(
|
|
1169
|
-
getInitialRouterState(this.latestLocation),
|
|
1170
|
-
config,
|
|
1171
|
-
)
|
|
1265
|
+
this.stores = createRouterStores(this.latestLocation, config)
|
|
1172
1266
|
|
|
1173
1267
|
if (!(isServer ?? this.isServer)) {
|
|
1174
1268
|
setupScrollRestoration(this)
|
|
1175
1269
|
}
|
|
1176
1270
|
}
|
|
1177
1271
|
|
|
1178
|
-
let needsLocationUpdate = false
|
|
1179
1272
|
const nextBasepath = this.options.basepath ?? '/'
|
|
1180
1273
|
const nextRewriteOption = this.options.rewrite
|
|
1181
1274
|
const basepathChanged = basepathWasUnset || prevBasepath !== nextBasepath
|
|
@@ -1208,21 +1301,9 @@ export class RouterCore<
|
|
|
1208
1301
|
this.updateLatestLocation()
|
|
1209
1302
|
}
|
|
1210
1303
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if (needsLocationUpdate && this.stores) {
|
|
1215
|
-
this.stores.location.set(this.latestLocation)
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
if (
|
|
1219
|
-
typeof window !== 'undefined' &&
|
|
1220
|
-
'CSS' in window &&
|
|
1221
|
-
typeof window.CSS?.supports === 'function'
|
|
1222
|
-
) {
|
|
1223
|
-
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
1224
|
-
'selector(:active-view-transition-type(a))',
|
|
1225
|
-
)
|
|
1304
|
+
if (this.stores) {
|
|
1305
|
+
this.stores.location.set(this.latestLocation)
|
|
1306
|
+
}
|
|
1226
1307
|
}
|
|
1227
1308
|
}
|
|
1228
1309
|
|
|
@@ -1293,11 +1374,15 @@ export class RouterCore<
|
|
|
1293
1374
|
}
|
|
1294
1375
|
|
|
1295
1376
|
emit: EmitFn = (routerEvent) => {
|
|
1296
|
-
this.subscribers
|
|
1377
|
+
for (const listener of this.subscribers) {
|
|
1297
1378
|
if (listener.eventType === routerEvent.type) {
|
|
1298
|
-
|
|
1379
|
+
try {
|
|
1380
|
+
listener.fn(routerEvent)
|
|
1381
|
+
} catch (e) {
|
|
1382
|
+
console.error(e)
|
|
1383
|
+
}
|
|
1299
1384
|
}
|
|
1300
|
-
}
|
|
1385
|
+
}
|
|
1301
1386
|
}
|
|
1302
1387
|
|
|
1303
1388
|
/**
|
|
@@ -1405,10 +1490,6 @@ export class RouterCore<
|
|
|
1405
1490
|
return branch
|
|
1406
1491
|
}
|
|
1407
1492
|
|
|
1408
|
-
get looseRoutesById() {
|
|
1409
|
-
return this.routesById as Record<string, AnyRoute>
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
1493
|
matchRoutes: MatchRoutesFn = (
|
|
1413
1494
|
pathnameOrNext: string | ParsedLocation,
|
|
1414
1495
|
locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
|
|
@@ -1427,16 +1508,6 @@ export class RouterCore<
|
|
|
1427
1508
|
return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
|
|
1428
1509
|
}
|
|
1429
1510
|
|
|
1430
|
-
private getParentContext(parentMatch?: AnyRouteMatch) {
|
|
1431
|
-
const parentMatchId = parentMatch?.id
|
|
1432
|
-
|
|
1433
|
-
const parentContext = !parentMatchId
|
|
1434
|
-
? ((this.options.context as any) ?? undefined)
|
|
1435
|
-
: (parentMatch.context ?? this.options.context ?? undefined)
|
|
1436
|
-
|
|
1437
|
-
return parentContext
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
1511
|
private matchRoutesInternal(
|
|
1441
1512
|
next: ParsedLocation,
|
|
1442
1513
|
opts?: MatchRoutesOpts,
|
|
@@ -1463,18 +1534,19 @@ export class RouterCore<
|
|
|
1463
1534
|
}
|
|
1464
1535
|
}
|
|
1465
1536
|
|
|
1466
|
-
const
|
|
1537
|
+
const _notFoundRouteId = isGlobalNotFound
|
|
1467
1538
|
? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
|
|
1468
1539
|
: undefined
|
|
1469
1540
|
|
|
1470
1541
|
const matches = new Array<AnyRouteMatch>(matchedRoutes.length)
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1542
|
+
const committed = this._committed
|
|
1543
|
+
const previousAt = (route: AnyRoute, index: number) => {
|
|
1544
|
+
const match = committed[index]
|
|
1545
|
+
return match?.routeId === route.id
|
|
1546
|
+
? match
|
|
1547
|
+
: route === this.options.notFoundRoute
|
|
1548
|
+
? committed.find((candidate) => candidate.routeId === route.id)
|
|
1549
|
+
: undefined
|
|
1478
1550
|
}
|
|
1479
1551
|
|
|
1480
1552
|
for (let index = 0; index < matchedRoutes.length; index++) {
|
|
@@ -1506,7 +1578,6 @@ export class RouterCore<
|
|
|
1506
1578
|
...strictSearch,
|
|
1507
1579
|
}
|
|
1508
1580
|
strictMatchSearch = { ...parentStrictSearch, ...strictSearch }
|
|
1509
|
-
searchError = undefined
|
|
1510
1581
|
} catch (err: any) {
|
|
1511
1582
|
let searchParamError = err
|
|
1512
1583
|
if (!(err instanceof SearchParamError)) {
|
|
@@ -1524,19 +1595,25 @@ export class RouterCore<
|
|
|
1524
1595
|
searchError = searchParamError
|
|
1525
1596
|
}
|
|
1526
1597
|
}
|
|
1527
|
-
|
|
1528
1598
|
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1529
1599
|
// deps that the route's loader function might need to run. We need to do this
|
|
1530
1600
|
// before we create the match so that we can pass the deps to the route's
|
|
1531
1601
|
// potential key function which is used to uniquely identify the route match in state
|
|
1532
1602
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1603
|
+
let loaderDeps: any = ''
|
|
1604
|
+
let loaderDepsHash = ''
|
|
1605
|
+
try {
|
|
1606
|
+
loaderDeps =
|
|
1607
|
+
route.options.loaderDeps?.({
|
|
1608
|
+
search: preMatchSearch,
|
|
1609
|
+
}) ?? ''
|
|
1610
|
+
loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) || '' : ''
|
|
1611
|
+
} catch (cause) {
|
|
1612
|
+
if (opts?.throwOnError) {
|
|
1613
|
+
throw cause
|
|
1614
|
+
}
|
|
1615
|
+
searchError ??= cause
|
|
1616
|
+
}
|
|
1540
1617
|
const { interpolatedPath, usedParams } = interpolatePath({
|
|
1541
1618
|
path: route.fullPath,
|
|
1542
1619
|
params: routeParams,
|
|
@@ -1544,12 +1621,9 @@ export class RouterCore<
|
|
|
1544
1621
|
server: this.isServer,
|
|
1545
1622
|
})
|
|
1546
1623
|
|
|
1547
|
-
//
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
|
|
1551
|
-
// Existing matches are matches that are already loaded along with
|
|
1552
|
-
// pending matches that are still loading
|
|
1624
|
+
// Seed planning from the accepted same-ID cache generation first, then
|
|
1625
|
+
// from the committed generation for this route. Presentation stores are
|
|
1626
|
+
// deliberately not a semantic reuse authority.
|
|
1553
1627
|
const matchId =
|
|
1554
1628
|
// route.id for disambiguation
|
|
1555
1629
|
route.id +
|
|
@@ -1558,13 +1632,16 @@ export class RouterCore<
|
|
|
1558
1632
|
// explicit deps
|
|
1559
1633
|
loaderDepsHash
|
|
1560
1634
|
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1635
|
+
const previousMatch = previousAt(route, index)
|
|
1636
|
+
const existingMatch =
|
|
1637
|
+
process.env.NODE_ENV !== 'production' && opts?._rematerialize
|
|
1638
|
+
? undefined
|
|
1639
|
+
: (this._cache.get(matchId) ??
|
|
1640
|
+
(previousMatch?.id === matchId ? previousMatch : undefined))
|
|
1564
1641
|
|
|
1565
1642
|
const strictParams = existingMatch?._strictParams ?? usedParams
|
|
1566
1643
|
|
|
1567
|
-
let paramsError: unknown
|
|
1644
|
+
let paramsError: unknown
|
|
1568
1645
|
|
|
1569
1646
|
if (!existingMatch) {
|
|
1570
1647
|
try {
|
|
@@ -1600,15 +1677,10 @@ export class RouterCore<
|
|
|
1600
1677
|
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1601
1678
|
: nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
1602
1679
|
_strictSearch: strictMatchSearch,
|
|
1680
|
+
searchError,
|
|
1603
1681
|
}
|
|
1604
1682
|
} else {
|
|
1605
|
-
const status =
|
|
1606
|
-
route.options.loader ||
|
|
1607
|
-
route.options.beforeLoad ||
|
|
1608
|
-
route.lazyFn ||
|
|
1609
|
-
routeNeedsPreload(route)
|
|
1610
|
-
? 'pending'
|
|
1611
|
-
: 'success'
|
|
1683
|
+
const status = routeNeedsLoad(route) ? 'pending' : 'success'
|
|
1612
1684
|
|
|
1613
1685
|
match = {
|
|
1614
1686
|
id: matchId,
|
|
@@ -1623,96 +1695,42 @@ export class RouterCore<
|
|
|
1623
1695
|
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1624
1696
|
: preMatchSearch,
|
|
1625
1697
|
_strictSearch: strictMatchSearch,
|
|
1626
|
-
searchError
|
|
1698
|
+
searchError,
|
|
1627
1699
|
status,
|
|
1628
1700
|
isFetching: false,
|
|
1629
1701
|
error: undefined,
|
|
1630
1702
|
paramsError,
|
|
1631
|
-
__routeContext: undefined,
|
|
1632
|
-
_nonReactive: {
|
|
1633
|
-
loadPromise: createControlledPromise(),
|
|
1634
|
-
},
|
|
1635
|
-
__beforeLoadContext: undefined,
|
|
1636
1703
|
context: {},
|
|
1637
|
-
abortController: new AbortController(),
|
|
1638
|
-
fetchCount: 0,
|
|
1704
|
+
abortController: opts?._controller ?? new AbortController(),
|
|
1639
1705
|
cause,
|
|
1640
1706
|
loaderDeps: previousMatch
|
|
1641
1707
|
? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
|
|
1642
1708
|
: loaderDeps,
|
|
1643
1709
|
invalid: false,
|
|
1644
1710
|
preload: false,
|
|
1645
|
-
links: undefined,
|
|
1646
|
-
scripts: undefined,
|
|
1647
|
-
headScripts: undefined,
|
|
1648
|
-
meta: undefined,
|
|
1649
1711
|
staticData: route.options.staticData || {},
|
|
1650
1712
|
fullPath: route.fullPath,
|
|
1651
1713
|
}
|
|
1652
1714
|
}
|
|
1653
1715
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
// update the searchError if there is one
|
|
1660
|
-
match.searchError = searchError
|
|
1661
|
-
|
|
1662
|
-
const parentContext = this.getParentContext(parentMatch)
|
|
1663
|
-
|
|
1664
|
-
match.context = {
|
|
1665
|
-
...parentContext,
|
|
1666
|
-
...match.__routeContext,
|
|
1667
|
-
...match.__beforeLoadContext,
|
|
1716
|
+
// If we have a global not found, mark the right match as global not found
|
|
1717
|
+
const _notFound = _notFoundRouteId === route.id
|
|
1718
|
+
if (match._notFound && !_notFound) {
|
|
1719
|
+
match.error = undefined
|
|
1668
1720
|
}
|
|
1721
|
+
match._notFound = _notFound
|
|
1669
1722
|
|
|
1670
1723
|
matches[index] = match
|
|
1671
1724
|
}
|
|
1672
1725
|
|
|
1673
1726
|
for (let index = 0; index < matches.length; index++) {
|
|
1674
1727
|
const match = matches[index]!
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
? nullReplaceEqualDeep(previousMatch.params, routeParams)
|
|
1682
|
-
: routeParams
|
|
1683
|
-
|
|
1684
|
-
if (!existingMatch) {
|
|
1685
|
-
const parentMatch = matches[index - 1]
|
|
1686
|
-
const parentContext = this.getParentContext(parentMatch)
|
|
1687
|
-
|
|
1688
|
-
// Update the match's context
|
|
1689
|
-
|
|
1690
|
-
if (route.options.context) {
|
|
1691
|
-
const contextFnContext: RouteContextOptions<any, any, any, any, any> =
|
|
1692
|
-
{
|
|
1693
|
-
deps: match.loaderDeps,
|
|
1694
|
-
params: match.params,
|
|
1695
|
-
context: parentContext ?? {},
|
|
1696
|
-
location: next,
|
|
1697
|
-
navigate: (opts: any) =>
|
|
1698
|
-
this.navigate({ ...opts, _fromLocation: next }),
|
|
1699
|
-
buildLocation: this.buildLocation,
|
|
1700
|
-
cause: match.cause,
|
|
1701
|
-
abortController: match.abortController,
|
|
1702
|
-
preload: !!match.preload,
|
|
1703
|
-
matches,
|
|
1704
|
-
routeId: route.id,
|
|
1705
|
-
}
|
|
1706
|
-
// Get the route context
|
|
1707
|
-
match.__routeContext =
|
|
1708
|
-
route.options.context(contextFnContext) ?? undefined
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
match.context = {
|
|
1712
|
-
...parentContext,
|
|
1713
|
-
...match.__routeContext,
|
|
1714
|
-
...match.__beforeLoadContext,
|
|
1715
|
-
}
|
|
1728
|
+
match.params =
|
|
1729
|
+
match.cause === 'stay'
|
|
1730
|
+
? nullReplaceEqualDeep(match.params, routeParams)
|
|
1731
|
+
: routeParams
|
|
1732
|
+
if (opts?._controller) {
|
|
1733
|
+
match.context = {}
|
|
1716
1734
|
}
|
|
1717
1735
|
}
|
|
1718
1736
|
|
|
@@ -1720,25 +1738,38 @@ export class RouterCore<
|
|
|
1720
1738
|
}
|
|
1721
1739
|
|
|
1722
1740
|
getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1741
|
+
const routeParams: Record<string, string> = Object.create(null)
|
|
1742
|
+
const match = findRouteMatch(
|
|
1743
|
+
trimPathRight(pathname),
|
|
1744
|
+
this.processedTree,
|
|
1745
|
+
true,
|
|
1746
|
+
)
|
|
1747
|
+
if (match) {
|
|
1748
|
+
Object.assign(routeParams, match.rawParams)
|
|
1749
|
+
}
|
|
1750
|
+
return {
|
|
1751
|
+
matchedRoutes: match?.branch || [this.routesById[rootRouteId]!],
|
|
1752
|
+
routeParams,
|
|
1753
|
+
foundRoute: match?.route,
|
|
1754
|
+
}
|
|
1728
1755
|
}
|
|
1729
1756
|
|
|
1730
1757
|
/**
|
|
1731
1758
|
* Lightweight route matching for buildLocation.
|
|
1732
1759
|
* Only computes fullPath, accumulated search, and params - skipping expensive
|
|
1733
|
-
* operations like AbortController,
|
|
1760
|
+
* operations like AbortController, loaderDeps, and full match objects.
|
|
1734
1761
|
*/
|
|
1735
1762
|
private matchRoutesLightweight(
|
|
1736
1763
|
location: ParsedLocation,
|
|
1737
1764
|
): LightweightRouteMatchResult {
|
|
1738
|
-
const
|
|
1765
|
+
const lastRouteId = last(this.stores.ids.get())
|
|
1766
|
+
const lastStateMatch = lastRouteId
|
|
1767
|
+
? this.stores.byRoute.get(lastRouteId)!.get()
|
|
1768
|
+
: undefined
|
|
1769
|
+
const lastStateMatchId = lastStateMatch?.id
|
|
1739
1770
|
const cached = this.lightweightCache.get(location)
|
|
1740
|
-
if (cached && cached[0] === lastStateMatchId) {
|
|
1741
|
-
return cached[1]
|
|
1771
|
+
if (cached && cached[0 /* lastMatchId */] === lastStateMatchId) {
|
|
1772
|
+
return cached[1 /* result */]
|
|
1742
1773
|
}
|
|
1743
1774
|
|
|
1744
1775
|
const { matchedRoutes, routeParams } = this.getMatchedRoutes(
|
|
@@ -1769,8 +1800,6 @@ export class RouterCore<
|
|
|
1769
1800
|
}
|
|
1770
1801
|
|
|
1771
1802
|
// Determine params: reuse from state if possible, otherwise parse
|
|
1772
|
-
const lastStateMatch =
|
|
1773
|
-
lastStateMatchId && this.stores.matchStores.get(lastStateMatchId)?.get()
|
|
1774
1803
|
const canReuseParams =
|
|
1775
1804
|
lastStateMatch &&
|
|
1776
1805
|
lastStateMatch.routeId === lastRoute.id &&
|
|
@@ -1805,37 +1834,6 @@ export class RouterCore<
|
|
|
1805
1834
|
return result
|
|
1806
1835
|
}
|
|
1807
1836
|
|
|
1808
|
-
cancelMatch = (id: string) => {
|
|
1809
|
-
const match = this.getMatch(id)
|
|
1810
|
-
|
|
1811
|
-
if (!match) return
|
|
1812
|
-
|
|
1813
|
-
match.abortController.abort()
|
|
1814
|
-
clearTimeout(match._nonReactive.pendingTimeout)
|
|
1815
|
-
match._nonReactive.pendingTimeout = undefined
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
cancelMatches = () => {
|
|
1819
|
-
this.stores.pendingIds.get().forEach((matchId) => {
|
|
1820
|
-
this.cancelMatch(matchId)
|
|
1821
|
-
})
|
|
1822
|
-
|
|
1823
|
-
this.stores.matchesId.get().forEach((matchId) => {
|
|
1824
|
-
if (this.stores.pendingMatchStores.has(matchId)) {
|
|
1825
|
-
return
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
const match = this.stores.matchStores.get(matchId)?.get()
|
|
1829
|
-
if (!match) {
|
|
1830
|
-
return
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
if (match.status === 'pending' || match.isFetching === 'loader') {
|
|
1834
|
-
this.cancelMatch(matchId)
|
|
1835
|
-
}
|
|
1836
|
-
})
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
1837
|
/**
|
|
1840
1838
|
* Build the next ParsedLocation from navigation options without committing.
|
|
1841
1839
|
* Resolves `to`/`from`, params/search/hash/state, applies search validation
|
|
@@ -1851,7 +1849,7 @@ export class RouterCore<
|
|
|
1851
1849
|
): ParsedLocation => {
|
|
1852
1850
|
// We allow the caller to override the current location
|
|
1853
1851
|
const currentLocation =
|
|
1854
|
-
dest._fromLocation || this.
|
|
1852
|
+
dest._fromLocation || this._pendingLocation || this.latestLocation
|
|
1855
1853
|
|
|
1856
1854
|
// Use lightweight matching - only computes what buildLocation needs
|
|
1857
1855
|
// (fullPath, search, params) without creating full match objects
|
|
@@ -1906,15 +1904,7 @@ export class RouterCore<
|
|
|
1906
1904
|
: sourcePath
|
|
1907
1905
|
|
|
1908
1906
|
// Resolve the next params
|
|
1909
|
-
const nextParams =
|
|
1910
|
-
dest.params === false || dest.params === null
|
|
1911
|
-
? Object.create(null)
|
|
1912
|
-
: (dest.params ?? true) === true
|
|
1913
|
-
? fromParams
|
|
1914
|
-
: Object.assign(
|
|
1915
|
-
fromParams,
|
|
1916
|
-
functionalUpdate(dest.params as any, fromParams),
|
|
1917
|
-
)
|
|
1907
|
+
const nextParams = resolveNextParams(dest.params, fromParams)
|
|
1918
1908
|
|
|
1919
1909
|
const destRoute = this.routesByPath[
|
|
1920
1910
|
trimPathRight(nextTo) as keyof typeof this.routesByPath
|
|
@@ -2012,12 +2002,12 @@ export class RouterCore<
|
|
|
2012
2002
|
nextSearch = validatedSearch
|
|
2013
2003
|
}
|
|
2014
2004
|
|
|
2015
|
-
nextSearch = applySearchMiddleware(
|
|
2016
|
-
|
|
2005
|
+
nextSearch = applySearchMiddleware(
|
|
2006
|
+
nextSearch,
|
|
2017
2007
|
dest,
|
|
2018
2008
|
destRoutes,
|
|
2019
|
-
|
|
2020
|
-
|
|
2009
|
+
opts._includeValidateSearch,
|
|
2010
|
+
)
|
|
2021
2011
|
|
|
2022
2012
|
// Replace the equal deep
|
|
2023
2013
|
nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch)
|
|
@@ -2117,12 +2107,7 @@ export class RouterCore<
|
|
|
2117
2107
|
|
|
2118
2108
|
// If mask has a params function, call it with the matched params as context
|
|
2119
2109
|
// Otherwise, use the matched params or the provided params value
|
|
2120
|
-
const nextParams =
|
|
2121
|
-
maskParams === false || maskParams === null
|
|
2122
|
-
? Object.create(null)
|
|
2123
|
-
: (maskParams ?? true) === true
|
|
2124
|
-
? params
|
|
2125
|
-
: Object.assign(params, functionalUpdate(maskParams, params))
|
|
2110
|
+
const nextParams = resolveNextParams(maskParams, params)
|
|
2126
2111
|
|
|
2127
2112
|
maskedDest = {
|
|
2128
2113
|
from: opts.from,
|
|
@@ -2151,7 +2136,7 @@ export class RouterCore<
|
|
|
2151
2136
|
return buildWithMatches(opts)
|
|
2152
2137
|
}
|
|
2153
2138
|
|
|
2154
|
-
|
|
2139
|
+
_commitPromise: (Promise<void> & { resolve: () => void }) | undefined
|
|
2155
2140
|
|
|
2156
2141
|
/**
|
|
2157
2142
|
* Commit a previously built location to history (push/replace), optionally
|
|
@@ -2163,37 +2148,26 @@ export class RouterCore<
|
|
|
2163
2148
|
...next
|
|
2164
2149
|
}) => {
|
|
2165
2150
|
let historyAction: HistoryAction | undefined
|
|
2166
|
-
const
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
'__TSR_key',
|
|
2173
|
-
'__TSR_index',
|
|
2174
|
-
'__hashScrollIntoViewOptions',
|
|
2175
|
-
] as const
|
|
2176
|
-
ignoredProps.forEach((prop) => {
|
|
2177
|
-
;(next.state as any)[prop] = this.latestLocation.state[prop]
|
|
2178
|
-
})
|
|
2179
|
-
const isEqual = deepEqual(next.state, this.latestLocation.state)
|
|
2180
|
-
ignoredProps.forEach((prop) => {
|
|
2181
|
-
delete next.state[prop]
|
|
2182
|
-
})
|
|
2183
|
-
return isEqual
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
const isSameUrl =
|
|
2187
|
-
trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
|
|
2151
|
+
const isSameLocation =
|
|
2152
|
+
trimPathRight(this.latestLocation.href) === trimPathRight(next.href) &&
|
|
2153
|
+
deepEqual(
|
|
2154
|
+
_getUserHistoryState(next.state),
|
|
2155
|
+
_getUserHistoryState(this.latestLocation.state),
|
|
2156
|
+
)
|
|
2188
2157
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2158
|
+
const previousCommitPromise = this._commitPromise
|
|
2159
|
+
let resolve!: () => void
|
|
2160
|
+
const commitPromise = new Promise<void>((done) => {
|
|
2161
|
+
resolve = done
|
|
2162
|
+
}) as Promise<void> & { resolve: () => void }
|
|
2163
|
+
commitPromise.resolve = () => {
|
|
2164
|
+
resolve()
|
|
2191
2165
|
previousCommitPromise?.resolve()
|
|
2192
|
-
|
|
2193
|
-
|
|
2166
|
+
}
|
|
2167
|
+
this._commitPromise = commitPromise
|
|
2194
2168
|
|
|
2195
2169
|
// Don't commit to history if nothing changed
|
|
2196
|
-
if (
|
|
2170
|
+
if (isSameLocation) {
|
|
2197
2171
|
this.load()
|
|
2198
2172
|
} else {
|
|
2199
2173
|
let {
|
|
@@ -2245,21 +2219,17 @@ export class RouterCore<
|
|
|
2245
2219
|
nextHistory.state,
|
|
2246
2220
|
{ ignoreBlocker },
|
|
2247
2221
|
)
|
|
2222
|
+
|
|
2223
|
+
// Without a subscribed adapter the history commit cannot trigger the
|
|
2224
|
+
// load itself. The same-location branch already loads directly.
|
|
2225
|
+
if (!this.history.subscribers.size) {
|
|
2226
|
+
this.load({ action: { type: historyAction } })
|
|
2227
|
+
}
|
|
2248
2228
|
}
|
|
2249
2229
|
|
|
2250
2230
|
this._scroll.next = next.resetScroll ?? true
|
|
2251
2231
|
|
|
2252
|
-
|
|
2253
|
-
this.load(
|
|
2254
|
-
historyAction
|
|
2255
|
-
? {
|
|
2256
|
-
action: { type: historyAction },
|
|
2257
|
-
}
|
|
2258
|
-
: undefined,
|
|
2259
|
-
)
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
return this.commitLocationPromise
|
|
2232
|
+
return this._commitPromise
|
|
2263
2233
|
}
|
|
2264
2234
|
|
|
2265
2235
|
/** Convenience helper: build a location from options, then commit it. */
|
|
@@ -2269,9 +2239,11 @@ export class RouterCore<
|
|
|
2269
2239
|
hashScrollIntoView,
|
|
2270
2240
|
viewTransition,
|
|
2271
2241
|
ignoreBlocker,
|
|
2242
|
+
_redirects,
|
|
2272
2243
|
href,
|
|
2273
2244
|
...rest
|
|
2274
|
-
}: BuildNextOptions &
|
|
2245
|
+
}: BuildNextOptions &
|
|
2246
|
+
CommitLocationOptions & { _redirects?: number } = {}) => {
|
|
2275
2247
|
if (href) {
|
|
2276
2248
|
const currentIndex = this.history.location.state.__TSR_index
|
|
2277
2249
|
|
|
@@ -2295,8 +2267,12 @@ export class RouterCore<
|
|
|
2295
2267
|
...(rest as any),
|
|
2296
2268
|
_includeValidateSearch: true,
|
|
2297
2269
|
})
|
|
2270
|
+
if (_redirects) {
|
|
2271
|
+
;(location as typeof location & { _redirects?: number })._redirects =
|
|
2272
|
+
_redirects
|
|
2273
|
+
}
|
|
2298
2274
|
|
|
2299
|
-
this.
|
|
2275
|
+
this._pendingLocation = location as ParsedLocation<
|
|
2300
2276
|
FullSearchSchema<TRouteTree>
|
|
2301
2277
|
>
|
|
2302
2278
|
|
|
@@ -2312,8 +2288,8 @@ export class RouterCore<
|
|
|
2312
2288
|
// Clear pending location after commit starts
|
|
2313
2289
|
// We do this on next microtask to allow synchronous navigate calls to chain
|
|
2314
2290
|
queueMicrotask(() => {
|
|
2315
|
-
if (this.
|
|
2316
|
-
this.
|
|
2291
|
+
if (this._pendingLocation === location) {
|
|
2292
|
+
this._pendingLocation = undefined
|
|
2317
2293
|
}
|
|
2318
2294
|
})
|
|
2319
2295
|
|
|
@@ -2409,253 +2385,17 @@ export class RouterCore<
|
|
|
2409
2385
|
})
|
|
2410
2386
|
}
|
|
2411
2387
|
|
|
2412
|
-
latestLoadPromise: undefined | Promise<void>
|
|
2413
|
-
|
|
2414
|
-
beforeLoad = () => {
|
|
2415
|
-
// Cancel any pending matches
|
|
2416
|
-
this.cancelMatches()
|
|
2417
|
-
this.updateLatestLocation()
|
|
2418
|
-
|
|
2419
|
-
if (isServer ?? this.isServer) {
|
|
2420
|
-
// for SPAs on the initial load, this is handled by the Transitioner
|
|
2421
|
-
const nextLocation = this.buildLocation({
|
|
2422
|
-
to: this.latestLocation.pathname,
|
|
2423
|
-
search: true,
|
|
2424
|
-
params: true,
|
|
2425
|
-
hash: true,
|
|
2426
|
-
state: true,
|
|
2427
|
-
_includeValidateSearch: true,
|
|
2428
|
-
})
|
|
2429
|
-
|
|
2430
|
-
// Check if location changed - origin check is unnecessary since buildLocation
|
|
2431
|
-
// always uses this.origin when constructing URLs
|
|
2432
|
-
if (this.latestLocation.publicHref !== nextLocation.publicHref) {
|
|
2433
|
-
const href = this.getParsedLocationHref(nextLocation)
|
|
2434
|
-
if (nextLocation.external) {
|
|
2435
|
-
throw redirect({ href })
|
|
2436
|
-
} else {
|
|
2437
|
-
throw redirect({ href, _builtLocation: nextLocation })
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// Match the routes
|
|
2443
|
-
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
2444
|
-
|
|
2445
|
-
const nextCachedMatches = this.stores.cachedMatches
|
|
2446
|
-
.get()
|
|
2447
|
-
.filter((d) => !pendingMatches.some((e) => e.id === d.id))
|
|
2448
|
-
|
|
2449
|
-
// Ingest the new matches
|
|
2450
|
-
this.batch(() => {
|
|
2451
|
-
this.stores.status.set('pending')
|
|
2452
|
-
this.stores.statusCode.set(200)
|
|
2453
|
-
this.stores.isLoading.set(true)
|
|
2454
|
-
this.stores.location.set(this.latestLocation)
|
|
2455
|
-
this.stores.setPending(pendingMatches)
|
|
2456
|
-
// If a cached match moved to pending matches, remove it from cached matches
|
|
2457
|
-
this.stores.setCached(nextCachedMatches)
|
|
2458
|
-
})
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
2388
|
load: LoadFn = async (opts): Promise<void> => {
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
let notFound: NotFoundError | undefined
|
|
2465
|
-
let loadPromise: Promise<void>
|
|
2466
|
-
const previousLocation =
|
|
2467
|
-
this.stores.resolvedLocation.get() ?? this.stores.location.get()
|
|
2468
|
-
|
|
2469
|
-
// eslint-disable-next-line prefer-const
|
|
2470
|
-
loadPromise = new Promise<void>((resolve) => {
|
|
2471
|
-
this.startTransition(async () => {
|
|
2472
|
-
try {
|
|
2473
|
-
this.beforeLoad()
|
|
2474
|
-
if (historyAction) {
|
|
2475
|
-
this._scroll.hash =
|
|
2476
|
-
historyAction === 'PUSH' || historyAction === 'REPLACE'
|
|
2477
|
-
}
|
|
2478
|
-
const next = this.latestLocation
|
|
2479
|
-
const prevLocation = this.stores.resolvedLocation.get()
|
|
2480
|
-
const locationChangeInfo = getLocationChangeInfo(next, prevLocation)
|
|
2481
|
-
|
|
2482
|
-
if (!this.stores.redirect.get()) {
|
|
2483
|
-
this.emit({
|
|
2484
|
-
type: 'onBeforeNavigate',
|
|
2485
|
-
...locationChangeInfo,
|
|
2486
|
-
})
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
this.emit({
|
|
2490
|
-
type: 'onBeforeLoad',
|
|
2491
|
-
...locationChangeInfo,
|
|
2492
|
-
})
|
|
2493
|
-
|
|
2494
|
-
await loadMatches({
|
|
2495
|
-
router: this,
|
|
2496
|
-
sync: opts?.sync,
|
|
2497
|
-
forceStaleReload: previousLocation.href === next.href,
|
|
2498
|
-
matches: this.stores.pendingMatches.get(),
|
|
2499
|
-
location: next,
|
|
2500
|
-
updateMatch: this.updateMatch,
|
|
2501
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2502
|
-
onReady: async () => {
|
|
2503
|
-
// Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition)
|
|
2504
|
-
this.startTransition(() => {
|
|
2505
|
-
this.startViewTransition(async () => {
|
|
2506
|
-
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
2507
|
-
|
|
2508
|
-
// Commit the pending matches. If a previous match was
|
|
2509
|
-
// removed, place it in the cachedMatches
|
|
2510
|
-
//
|
|
2511
|
-
// exitingMatches uses match.id (routeId + params + loaderDeps) so
|
|
2512
|
-
// navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry.
|
|
2513
|
-
let exitingMatches: Array<AnyRouteMatch> | null = null
|
|
2514
|
-
|
|
2515
|
-
// Lifecycle-hook identity uses routeId only so that navigating between
|
|
2516
|
-
// different params/deps of the same route fires onStay (not onLeave+onEnter).
|
|
2517
|
-
let hookExitingMatches: Array<AnyRouteMatch> | null = null
|
|
2518
|
-
let hookEnteringMatches: Array<AnyRouteMatch> | null = null
|
|
2519
|
-
let hookStayingMatches: Array<AnyRouteMatch> | null = null
|
|
2520
|
-
|
|
2521
|
-
this.batch(() => {
|
|
2522
|
-
const pendingMatches = this.stores.pendingMatches.get()
|
|
2523
|
-
const mountPending = pendingMatches.length
|
|
2524
|
-
const currentMatches = this.stores.matches.get()
|
|
2525
|
-
|
|
2526
|
-
exitingMatches = mountPending
|
|
2527
|
-
? currentMatches.filter(
|
|
2528
|
-
(match) =>
|
|
2529
|
-
!this.stores.pendingMatchStores.has(match.id),
|
|
2530
|
-
)
|
|
2531
|
-
: null
|
|
2532
|
-
|
|
2533
|
-
// Lifecycle-hook identity: routeId only (route presence in tree)
|
|
2534
|
-
// Build routeId sets from pools to avoid derived stores.
|
|
2535
|
-
const pendingRouteIds = new Set<string>()
|
|
2536
|
-
for (const s of this.stores.pendingMatchStores.values()) {
|
|
2537
|
-
if (s.routeId) pendingRouteIds.add(s.routeId)
|
|
2538
|
-
}
|
|
2539
|
-
const activeRouteIds = new Set<string>()
|
|
2540
|
-
for (const s of this.stores.matchStores.values()) {
|
|
2541
|
-
if (s.routeId) activeRouteIds.add(s.routeId)
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
hookExitingMatches = mountPending
|
|
2545
|
-
? currentMatches.filter(
|
|
2546
|
-
(match) => !pendingRouteIds.has(match.routeId),
|
|
2547
|
-
)
|
|
2548
|
-
: null
|
|
2549
|
-
hookEnteringMatches = mountPending
|
|
2550
|
-
? pendingMatches.filter(
|
|
2551
|
-
(match) => !activeRouteIds.has(match.routeId),
|
|
2552
|
-
)
|
|
2553
|
-
: null
|
|
2554
|
-
hookStayingMatches = mountPending
|
|
2555
|
-
? pendingMatches.filter((match) =>
|
|
2556
|
-
activeRouteIds.has(match.routeId),
|
|
2557
|
-
)
|
|
2558
|
-
: currentMatches
|
|
2559
|
-
|
|
2560
|
-
this.stores.isLoading.set(false)
|
|
2561
|
-
this.stores.loadedAt.set(Date.now())
|
|
2562
|
-
/**
|
|
2563
|
-
* When committing new matches, cache any exiting matches that are still usable.
|
|
2564
|
-
* Routes that resolved with `status: 'error'` or `status: 'notFound'` are
|
|
2565
|
-
* deliberately excluded from `cachedMatches` so that subsequent invalidations
|
|
2566
|
-
* or reloads re-run their loaders instead of reusing the failed/not-found data.
|
|
2567
|
-
*/
|
|
2568
|
-
if (mountPending) {
|
|
2569
|
-
this.stores.setMatches(pendingMatches)
|
|
2570
|
-
this.stores.setPending([])
|
|
2571
|
-
this.stores.setCached([
|
|
2572
|
-
...this.stores.cachedMatches.get(),
|
|
2573
|
-
...exitingMatches!.filter(
|
|
2574
|
-
(d) =>
|
|
2575
|
-
d.status !== 'error' &&
|
|
2576
|
-
d.status !== 'notFound' &&
|
|
2577
|
-
d.status !== 'redirected',
|
|
2578
|
-
),
|
|
2579
|
-
])
|
|
2580
|
-
this.clearExpiredCache()
|
|
2581
|
-
}
|
|
2582
|
-
})
|
|
2583
|
-
|
|
2584
|
-
//
|
|
2585
|
-
for (const [matches, hook] of [
|
|
2586
|
-
[hookExitingMatches, 'onLeave'],
|
|
2587
|
-
[hookEnteringMatches, 'onEnter'],
|
|
2588
|
-
[hookStayingMatches, 'onStay'],
|
|
2589
|
-
] as const) {
|
|
2590
|
-
if (!matches) continue
|
|
2591
|
-
for (const match of matches as Array<AnyRouteMatch>) {
|
|
2592
|
-
this.looseRoutesById[match.routeId]!.options[hook]?.(
|
|
2593
|
-
match,
|
|
2594
|
-
)
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
})
|
|
2598
|
-
})
|
|
2599
|
-
},
|
|
2600
|
-
})
|
|
2601
|
-
} catch (err) {
|
|
2602
|
-
if (isRedirect(err)) {
|
|
2603
|
-
redirect = err
|
|
2604
|
-
if (!(isServer ?? this.isServer)) {
|
|
2605
|
-
this.navigate({
|
|
2606
|
-
...redirect.options,
|
|
2607
|
-
replace: true,
|
|
2608
|
-
ignoreBlocker: true,
|
|
2609
|
-
})
|
|
2610
|
-
}
|
|
2611
|
-
} else if (isNotFound(err)) {
|
|
2612
|
-
notFound = err
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2615
|
-
const nextStatusCode = redirect
|
|
2616
|
-
? redirect.status
|
|
2617
|
-
: notFound
|
|
2618
|
-
? 404
|
|
2619
|
-
: this.stores.matches.get().some((d) => d.status === 'error')
|
|
2620
|
-
? 500
|
|
2621
|
-
: 200
|
|
2622
|
-
|
|
2623
|
-
this.batch(() => {
|
|
2624
|
-
this.stores.statusCode.set(nextStatusCode)
|
|
2625
|
-
this.stores.redirect.set(redirect)
|
|
2626
|
-
})
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
if (this.latestLoadPromise === loadPromise) {
|
|
2630
|
-
this.commitLocationPromise?.resolve()
|
|
2631
|
-
this.latestLoadPromise = undefined
|
|
2632
|
-
this.commitLocationPromise = undefined
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
resolve()
|
|
2636
|
-
})
|
|
2637
|
-
})
|
|
2638
|
-
|
|
2639
|
-
this.latestLoadPromise = loadPromise
|
|
2640
|
-
|
|
2641
|
-
await loadPromise
|
|
2642
|
-
|
|
2643
|
-
while (
|
|
2644
|
-
(this.latestLoadPromise as any) &&
|
|
2645
|
-
loadPromise !== this.latestLoadPromise
|
|
2646
|
-
) {
|
|
2647
|
-
await this.latestLoadPromise
|
|
2389
|
+
if (isServer ?? this.isServer) {
|
|
2390
|
+
return loadServerRoute(this, opts)
|
|
2648
2391
|
}
|
|
2649
2392
|
|
|
2650
|
-
|
|
2651
|
-
if (
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
newStatusCode = 500
|
|
2655
|
-
}
|
|
2656
|
-
if (newStatusCode !== undefined) {
|
|
2657
|
-
this.stores.statusCode.set(newStatusCode)
|
|
2393
|
+
this.updateLatestLocation()
|
|
2394
|
+
if (opts?.action) {
|
|
2395
|
+
this._scroll.hash =
|
|
2396
|
+
opts.action.type === 'PUSH' || opts.action.type === 'REPLACE'
|
|
2658
2397
|
}
|
|
2398
|
+
await loadClientRoute(this, opts)
|
|
2659
2399
|
}
|
|
2660
2400
|
|
|
2661
2401
|
startViewTransition = (fn: () => Promise<void>) => {
|
|
@@ -2670,9 +2410,8 @@ export class RouterCore<
|
|
|
2670
2410
|
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
2671
2411
|
if (
|
|
2672
2412
|
shouldViewTransition &&
|
|
2673
|
-
typeof document
|
|
2674
|
-
|
|
2675
|
-
typeof document.startViewTransition === 'function'
|
|
2413
|
+
!(isServer ?? typeof document === 'undefined') &&
|
|
2414
|
+
typeof (document as any).startViewTransition === 'function'
|
|
2676
2415
|
) {
|
|
2677
2416
|
// lib.dom.ts doesn't support viewTransition types variant yet.
|
|
2678
2417
|
// TODO: Fix this when dom types are updated
|
|
@@ -2680,7 +2419,7 @@ export class RouterCore<
|
|
|
2680
2419
|
|
|
2681
2420
|
if (
|
|
2682
2421
|
typeof shouldViewTransition === 'object' &&
|
|
2683
|
-
|
|
2422
|
+
window.CSS?.supports?.('selector(:active-view-transition-type(a))')
|
|
2684
2423
|
) {
|
|
2685
2424
|
const next = this.latestLocation
|
|
2686
2425
|
const prevLocation = this.stores.resolvedLocation.get()
|
|
@@ -2693,8 +2432,7 @@ export class RouterCore<
|
|
|
2693
2432
|
: shouldViewTransition.types
|
|
2694
2433
|
|
|
2695
2434
|
if (resolvedViewTransitionTypes === false) {
|
|
2696
|
-
fn()
|
|
2697
|
-
return
|
|
2435
|
+
return fn()
|
|
2698
2436
|
}
|
|
2699
2437
|
|
|
2700
2438
|
startViewTransitionParams = {
|
|
@@ -2705,58 +2443,21 @@ export class RouterCore<
|
|
|
2705
2443
|
startViewTransitionParams = fn
|
|
2706
2444
|
}
|
|
2707
2445
|
|
|
2708
|
-
document.startViewTransition(startViewTransitionParams)
|
|
2709
|
-
|
|
2710
|
-
fn()
|
|
2446
|
+
return (document as any).startViewTransition(startViewTransitionParams)
|
|
2447
|
+
.updateCallbackDone
|
|
2711
2448
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
updateMatch: UpdateMatchFn = (id, updater) => {
|
|
2715
|
-
this.startTransition(() => {
|
|
2716
|
-
const pendingMatch = this.stores.pendingMatchStores.get(id)
|
|
2717
|
-
if (pendingMatch) {
|
|
2718
|
-
pendingMatch.set(updater)
|
|
2719
|
-
return
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
const activeMatch = this.stores.matchStores.get(id)
|
|
2723
|
-
if (activeMatch) {
|
|
2724
|
-
activeMatch.set(updater)
|
|
2725
|
-
return
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
const cachedMatch = this.stores.cachedMatchStores.get(id)
|
|
2729
|
-
if (cachedMatch) {
|
|
2730
|
-
const next = updater(cachedMatch.get())
|
|
2731
|
-
if (next.status === 'redirected') {
|
|
2732
|
-
const deleted = this.stores.cachedMatchStores.delete(id)
|
|
2733
|
-
if (deleted) {
|
|
2734
|
-
this.stores.cachedIds.set((prev) =>
|
|
2735
|
-
prev.filter((matchId) => matchId !== id),
|
|
2736
|
-
)
|
|
2737
|
-
}
|
|
2738
|
-
} else {
|
|
2739
|
-
cachedMatch.set(next)
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
})
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => {
|
|
2746
|
-
return (
|
|
2747
|
-
this.stores.cachedMatchStores.get(matchId)?.get() ??
|
|
2748
|
-
this.stores.pendingMatchStores.get(matchId)?.get() ??
|
|
2749
|
-
this.stores.matchStores.get(matchId)?.get()
|
|
2750
|
-
)
|
|
2449
|
+
return fn()
|
|
2751
2450
|
}
|
|
2752
2451
|
|
|
2753
2452
|
/**
|
|
2754
|
-
* Invalidate
|
|
2453
|
+
* Invalidate selected match generations and optionally force current matches
|
|
2454
|
+
* back into a pending state.
|
|
2455
|
+
*
|
|
2456
|
+
* - Marks committed and cached matches whose IDs are selected as invalid.
|
|
2457
|
+
* - Retires selected active preloads so older work cannot publish fresh data.
|
|
2755
2458
|
*
|
|
2756
|
-
*
|
|
2757
|
-
*
|
|
2758
|
-
* its status is reset to `'pending'` and its `error` cleared so that the loader is re-run
|
|
2759
|
-
* on the next `load()` call (eg. after HMR or a manual invalidation).
|
|
2459
|
+
* The next load decides when to publish pending UI, so invalidation does not
|
|
2460
|
+
* mutate the currently rendered status.
|
|
2760
2461
|
*/
|
|
2761
2462
|
invalidate: InvalidateFn<
|
|
2762
2463
|
RouterCore<
|
|
@@ -2767,44 +2468,79 @@ export class RouterCore<
|
|
|
2767
2468
|
TDehydrated
|
|
2768
2469
|
>
|
|
2769
2470
|
> = (opts) => {
|
|
2471
|
+
const committedMatches = this._committed
|
|
2472
|
+
const filter = opts?.filter
|
|
2473
|
+
const preloads = this._preloads
|
|
2474
|
+
const invalidIds = new Set(
|
|
2475
|
+
[
|
|
2476
|
+
...committedMatches,
|
|
2477
|
+
...this._cache.values(),
|
|
2478
|
+
...[...(preloads?.values() ?? [])].flat(),
|
|
2479
|
+
...(this._tx?.[3 /* matches */] ?? []),
|
|
2480
|
+
]
|
|
2481
|
+
.filter(
|
|
2482
|
+
(match) => !filter || filter(match as MakeRouteMatchUnion<this>),
|
|
2483
|
+
)
|
|
2484
|
+
.map((match) => match.id),
|
|
2485
|
+
)
|
|
2486
|
+
const discardedPreloads: Array<AbortController> = []
|
|
2487
|
+
for (const [controller, matches] of preloads ?? []) {
|
|
2488
|
+
if (matches.some((match) => invalidIds.has(match.id))) {
|
|
2489
|
+
preloads!.delete(controller)
|
|
2490
|
+
discardedPreloads.push(controller)
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2770
2493
|
const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2771
|
-
if (
|
|
2772
|
-
|
|
2494
|
+
if (invalidIds.has(d.id)) {
|
|
2495
|
+
const route = this.routesById[d.routeId] as AnyRoute
|
|
2496
|
+
const next = {
|
|
2773
2497
|
...d,
|
|
2774
2498
|
invalid: true,
|
|
2775
|
-
...(opts?.forcePending ||
|
|
2776
|
-
|
|
2777
|
-
|
|
2499
|
+
...((opts?.forcePending ||
|
|
2500
|
+
d.status === 'error' ||
|
|
2501
|
+
d.status === 'notFound') &&
|
|
2502
|
+
routeNeedsLoad(route)
|
|
2778
2503
|
? ({ status: 'pending', error: undefined } as const)
|
|
2779
2504
|
: undefined),
|
|
2780
2505
|
}
|
|
2506
|
+
// Invalidation replaces this owner; it does not create another lease.
|
|
2507
|
+
;(d as AnyRouteMatch & { _flight?: LoaderFlight })._flight = undefined
|
|
2508
|
+
return next
|
|
2781
2509
|
}
|
|
2782
2510
|
return d
|
|
2783
2511
|
}
|
|
2784
2512
|
|
|
2785
|
-
this.
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2513
|
+
this._committed = committedMatches.map(invalidate)
|
|
2514
|
+
// Cache entries are settled successes. Retiring matching active preload
|
|
2515
|
+
// owners makes an in-place stale mark sufficient while preserving data.
|
|
2516
|
+
for (const [id, match] of this._cache) {
|
|
2517
|
+
if (invalidIds.has(id)) {
|
|
2518
|
+
match.invalid = true
|
|
2519
|
+
if (opts?.forcePending) {
|
|
2520
|
+
match.status = 'pending'
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
// The superseding load must not discover any same-ID generation selected
|
|
2525
|
+
// for replacement. Existing owners release it in their normal order.
|
|
2526
|
+
for (const id of invalidIds) {
|
|
2527
|
+
this._flights?.delete(id)
|
|
2528
|
+
}
|
|
2529
|
+
for (const controller of discardedPreloads) {
|
|
2530
|
+
controller.abort()
|
|
2531
|
+
}
|
|
2790
2532
|
|
|
2791
2533
|
this.shouldViewTransition = false
|
|
2792
2534
|
return this.load({ sync: opts?.sync })
|
|
2793
2535
|
}
|
|
2794
2536
|
|
|
2795
|
-
getParsedLocationHref = (location: ParsedLocation) => {
|
|
2796
|
-
// For redirects and external use, we need publicHref (with rewrite output applied)
|
|
2797
|
-
// href is the internal path after rewrite input, publicHref is user-facing
|
|
2798
|
-
return location.publicHref || '/'
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
2537
|
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
|
|
2802
2538
|
const locationHeader = redirect.headers.get('Location')
|
|
2803
2539
|
|
|
2804
2540
|
if (!redirect.options.href || redirect.options._builtLocation) {
|
|
2805
2541
|
const location =
|
|
2806
2542
|
redirect.options._builtLocation ?? this.buildLocation(redirect.options)
|
|
2807
|
-
const href =
|
|
2543
|
+
const href = location.publicHref || '/'
|
|
2808
2544
|
redirect.options.href = href
|
|
2809
2545
|
redirect.headers.set('Location', href)
|
|
2810
2546
|
} else if (locationHeader) {
|
|
@@ -2841,115 +2577,64 @@ export class RouterCore<
|
|
|
2841
2577
|
}
|
|
2842
2578
|
|
|
2843
2579
|
clearCache: ClearCacheFn<this> = (opts) => {
|
|
2580
|
+
const cached = this._cache
|
|
2581
|
+
const preloads = this._preloads
|
|
2844
2582
|
const filter = opts?.filter
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
this.stores.setCached([])
|
|
2583
|
+
const discarded: Array<AnyRouteMatch> = []
|
|
2584
|
+
const discardedIds: Array<string> = []
|
|
2585
|
+
for (const [id, match] of cached) {
|
|
2586
|
+
if (!filter || filter(match as MakeRouteMatchUnion<this>)) {
|
|
2587
|
+
discardedIds.push(id)
|
|
2588
|
+
discarded.push(match)
|
|
2589
|
+
}
|
|
2853
2590
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
const filter = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2860
|
-
const route = this.looseRoutesById[d.routeId]!
|
|
2861
|
-
|
|
2862
|
-
if (!route.options.loader) {
|
|
2863
|
-
return true
|
|
2591
|
+
const abort: Array<AbortController> = []
|
|
2592
|
+
for (const [controller, matches] of preloads ?? []) {
|
|
2593
|
+
if (!filter || matches.some(filter as any)) {
|
|
2594
|
+
abort.push(controller)
|
|
2595
|
+
discarded.push(...matches)
|
|
2864
2596
|
}
|
|
2597
|
+
}
|
|
2865
2598
|
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2599
|
+
// Run every public filter before changing authority, then prune both maps
|
|
2600
|
+
// before releasing a public loader
|
|
2601
|
+
// signal, whose abort listeners can synchronously reenter the router.
|
|
2602
|
+
for (const id of discardedIds) {
|
|
2603
|
+
cached.delete(id)
|
|
2604
|
+
}
|
|
2605
|
+
for (const controller of abort) {
|
|
2606
|
+
preloads!.delete(controller)
|
|
2607
|
+
}
|
|
2608
|
+
// clearCache is a force-retirement boundary, so a final discarded lease
|
|
2609
|
+
// must not use the normal zero-owner handoff window.
|
|
2610
|
+
for (const match of discarded as Array<
|
|
2611
|
+
AnyRouteMatch & { _flight?: LoaderFlight }
|
|
2612
|
+
>) {
|
|
2613
|
+
const flight = match._flight
|
|
2614
|
+
match._flight = undefined
|
|
2615
|
+
if (flight && !--flight[2 /* leases */]) {
|
|
2616
|
+
if (this._flights?.get(match.id) === flight) {
|
|
2617
|
+
this._flights.delete(match.id)
|
|
2618
|
+
}
|
|
2619
|
+
abort.push(flight[1 /* controller */])
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
for (const controller of abort) {
|
|
2623
|
+
controller.abort()
|
|
2879
2624
|
}
|
|
2880
|
-
this.clearCache({ filter })
|
|
2881
2625
|
}
|
|
2882
2626
|
|
|
2883
|
-
loadRouteChunk
|
|
2627
|
+
loadRouteChunk: (
|
|
2628
|
+
route: AnyRoute,
|
|
2629
|
+
componentType?: 'errorComponent' | 'notFoundComponent' | false,
|
|
2630
|
+
) => Promise<void> | undefined = loadRouteChunk
|
|
2884
2631
|
|
|
2885
2632
|
preloadRoute: PreloadRouteFn<
|
|
2886
2633
|
TRouteTree,
|
|
2887
2634
|
TTrailingSlashOption,
|
|
2888
2635
|
TDefaultStructuralSharingOption,
|
|
2889
2636
|
TRouterHistory
|
|
2890
|
-
> =
|
|
2891
|
-
const next = opts._builtLocation ?? this.buildLocation(opts as any)
|
|
2892
|
-
|
|
2893
|
-
let matches = this.matchRoutes(next, {
|
|
2894
|
-
throwOnError: true,
|
|
2895
|
-
preload: true,
|
|
2896
|
-
dest: opts,
|
|
2897
|
-
})
|
|
2898
|
-
|
|
2899
|
-
const activeMatchIds = new Set([
|
|
2900
|
-
...this.stores.matchesId.get(),
|
|
2901
|
-
...this.stores.pendingIds.get(),
|
|
2902
|
-
])
|
|
2903
|
-
|
|
2904
|
-
const loadedMatchIds = new Set([
|
|
2905
|
-
...activeMatchIds,
|
|
2906
|
-
...this.stores.cachedIds.get(),
|
|
2907
|
-
])
|
|
2908
|
-
|
|
2909
|
-
// If the matches are already loaded, we need to add them to the cached matches.
|
|
2910
|
-
const matchesToCache = matches.filter(
|
|
2911
|
-
(match) => !loadedMatchIds.has(match.id),
|
|
2912
|
-
)
|
|
2913
|
-
if (matchesToCache.length) {
|
|
2914
|
-
const cachedMatches = this.stores.cachedMatches.get()
|
|
2915
|
-
this.stores.setCached([...cachedMatches, ...matchesToCache])
|
|
2916
|
-
}
|
|
2917
|
-
|
|
2918
|
-
try {
|
|
2919
|
-
matches = await loadMatches({
|
|
2920
|
-
router: this,
|
|
2921
|
-
matches,
|
|
2922
|
-
location: next,
|
|
2923
|
-
preload: true,
|
|
2924
|
-
updateMatch: (id, updater) => {
|
|
2925
|
-
// Don't update the match if it's currently loaded
|
|
2926
|
-
if (activeMatchIds.has(id)) {
|
|
2927
|
-
matches = matches.map((d) => (d.id === id ? updater(d) : d))
|
|
2928
|
-
} else {
|
|
2929
|
-
this.updateMatch(id, updater)
|
|
2930
|
-
}
|
|
2931
|
-
},
|
|
2932
|
-
})
|
|
2933
|
-
|
|
2934
|
-
return matches
|
|
2935
|
-
} catch (err) {
|
|
2936
|
-
if (isRedirect(err)) {
|
|
2937
|
-
if (err.options.reloadDocument) {
|
|
2938
|
-
return undefined
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
return await this.preloadRoute({
|
|
2942
|
-
...err.options,
|
|
2943
|
-
_fromLocation: next,
|
|
2944
|
-
})
|
|
2945
|
-
}
|
|
2946
|
-
if (!isNotFound(err)) {
|
|
2947
|
-
// Preload errors are not fatal, but we should still log them
|
|
2948
|
-
console.error(err)
|
|
2949
|
-
}
|
|
2950
|
-
return undefined
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2637
|
+
> = (opts) => preloadClientRoute(this, opts)
|
|
2953
2638
|
|
|
2954
2639
|
matchRoute: MatchRouteFn<
|
|
2955
2640
|
TRouteTree,
|
|
@@ -2967,12 +2652,12 @@ export class RouterCore<
|
|
|
2967
2652
|
}
|
|
2968
2653
|
const next = this.buildLocation(matchLocation as any)
|
|
2969
2654
|
|
|
2970
|
-
|
|
2655
|
+
const isPending = this.stores.status.get() === 'pending'
|
|
2656
|
+
if (opts?.pending && !isPending) {
|
|
2971
2657
|
return false
|
|
2972
2658
|
}
|
|
2973
2659
|
|
|
2974
|
-
const pending =
|
|
2975
|
-
opts?.pending === undefined ? !this.stores.isLoading.get() : opts.pending
|
|
2660
|
+
const pending = opts?.pending ?? !isPending
|
|
2976
2661
|
|
|
2977
2662
|
const baseLocation = pending
|
|
2978
2663
|
? this.latestLocation
|
|
@@ -3012,11 +2697,19 @@ export class RouterCore<
|
|
|
3012
2697
|
serverSsr?: ServerSsr
|
|
3013
2698
|
|
|
3014
2699
|
serverSsrLifecycle?: RouterSsrLifecycle
|
|
2700
|
+
}
|
|
3015
2701
|
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
2702
|
+
/**
|
|
2703
|
+
* In non-production environments,
|
|
2704
|
+
* augment the RouterCore class with a `_refreshRoute` method
|
|
2705
|
+
* dedicated to HMR.
|
|
2706
|
+
*/
|
|
2707
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2708
|
+
RouterCore.prototype._replaceRouteChunk = replaceRouteChunk
|
|
2709
|
+
RouterCore.prototype._refreshRoute = async function () {
|
|
2710
|
+
this._serverResult = undefined
|
|
2711
|
+
this.updateLatestLocation()
|
|
2712
|
+
await refreshClientRoute(this)
|
|
3020
2713
|
}
|
|
3021
2714
|
}
|
|
3022
2715
|
|
|
@@ -3053,14 +2746,11 @@ export function getInitialRouterState(
|
|
|
3053
2746
|
location: ParsedLocation,
|
|
3054
2747
|
): RouterState<any> {
|
|
3055
2748
|
return {
|
|
3056
|
-
loadedAt: 0,
|
|
3057
2749
|
isLoading: false,
|
|
3058
|
-
isTransitioning: false,
|
|
3059
2750
|
status: 'idle',
|
|
3060
2751
|
resolvedLocation: undefined,
|
|
3061
2752
|
location,
|
|
3062
2753
|
matches: [],
|
|
3063
|
-
statusCode: 200,
|
|
3064
2754
|
}
|
|
3065
2755
|
}
|
|
3066
2756
|
|
|
@@ -3092,56 +2782,12 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3092
2782
|
return {}
|
|
3093
2783
|
}
|
|
3094
2784
|
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
routesById,
|
|
3102
|
-
processedTree,
|
|
3103
|
-
}: {
|
|
3104
|
-
pathname: string
|
|
3105
|
-
routesById: Record<string, TRouteLike>
|
|
3106
|
-
processedTree: ProcessedTree<any, any, any>
|
|
3107
|
-
}) {
|
|
3108
|
-
const routeParams: Record<string, string> = Object.create(null)
|
|
3109
|
-
const trimmedPath = trimPathRight(pathname)
|
|
3110
|
-
|
|
3111
|
-
let foundRoute: TRouteLike | undefined = undefined
|
|
3112
|
-
const match = findRouteMatch<TRouteLike>(trimmedPath, processedTree, true)
|
|
3113
|
-
if (match) {
|
|
3114
|
-
foundRoute = match.route
|
|
3115
|
-
Object.assign(routeParams, match.rawParams) // Copy params, because they're cached
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
const matchedRoutes = match?.branch || [routesById[rootRouteId]!]
|
|
3119
|
-
|
|
3120
|
-
return { matchedRoutes, routeParams, foundRoute }
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
/**
|
|
3124
|
-
* TODO: once caches are persisted across requests on the server,
|
|
3125
|
-
* we can cache the built middleware chain using `last(destRoutes)` as the key
|
|
3126
|
-
*/
|
|
3127
|
-
function applySearchMiddleware({
|
|
3128
|
-
search,
|
|
3129
|
-
dest,
|
|
3130
|
-
destRoutes,
|
|
3131
|
-
_includeValidateSearch,
|
|
3132
|
-
}: {
|
|
3133
|
-
search: any
|
|
3134
|
-
dest: { search?: unknown }
|
|
3135
|
-
destRoutes: ReadonlyArray<AnyRoute>
|
|
3136
|
-
_includeValidateSearch: boolean | undefined
|
|
3137
|
-
}) {
|
|
3138
|
-
const middleware = buildMiddlewareChain(destRoutes)
|
|
3139
|
-
return middleware(search, dest, _includeValidateSearch ?? false)
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
3143
|
-
let dest: BuildNextOptions
|
|
3144
|
-
let includeValidateSearch: boolean | undefined
|
|
2785
|
+
function applySearchMiddleware(
|
|
2786
|
+
search: any,
|
|
2787
|
+
dest: BuildNextOptions,
|
|
2788
|
+
destRoutes: ReadonlyArray<AnyRoute>,
|
|
2789
|
+
includeValidateSearch: boolean | undefined,
|
|
2790
|
+
) {
|
|
3145
2791
|
const middlewares = [] as Array<SearchMiddleware<any>>
|
|
3146
2792
|
|
|
3147
2793
|
for (const route of destRoutes) {
|
|
@@ -3234,15 +2880,7 @@ function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
|
3234
2880
|
return (middlewares[index]! as any)({ search: currentSearch, next, meta })
|
|
3235
2881
|
}
|
|
3236
2882
|
|
|
3237
|
-
return
|
|
3238
|
-
search: any,
|
|
3239
|
-
nextDest: BuildNextOptions,
|
|
3240
|
-
_includeValidateSearch: boolean,
|
|
3241
|
-
) {
|
|
3242
|
-
dest = nextDest
|
|
3243
|
-
includeValidateSearch = _includeValidateSearch
|
|
3244
|
-
return applyNext(0, search)
|
|
3245
|
-
}
|
|
2883
|
+
return applyNext(0, search)
|
|
3246
2884
|
}
|
|
3247
2885
|
|
|
3248
2886
|
function findGlobalNotFoundRouteId(
|
|
@@ -3250,26 +2888,41 @@ function findGlobalNotFoundRouteId(
|
|
|
3250
2888
|
routes: ReadonlyArray<AnyRoute>,
|
|
3251
2889
|
) {
|
|
3252
2890
|
if (notFoundMode !== 'root') {
|
|
2891
|
+
let fallback
|
|
3253
2892
|
for (let i = routes.length - 1; i >= 0; i--) {
|
|
3254
2893
|
const route = routes[i]!
|
|
3255
|
-
if (route.
|
|
2894
|
+
if (route.options.notFoundComponent) {
|
|
3256
2895
|
return route.id
|
|
3257
2896
|
}
|
|
2897
|
+
fallback ||= route.children && route.id
|
|
2898
|
+
}
|
|
2899
|
+
if (fallback) {
|
|
2900
|
+
return fallback
|
|
3258
2901
|
}
|
|
3259
2902
|
}
|
|
3260
2903
|
return rootRouteId
|
|
3261
2904
|
}
|
|
3262
2905
|
|
|
2906
|
+
function resolveNextParams(
|
|
2907
|
+
spec: unknown,
|
|
2908
|
+
base: Record<string, unknown>,
|
|
2909
|
+
): Record<string, unknown> {
|
|
2910
|
+
return spec === false || spec === null
|
|
2911
|
+
? Object.create(null)
|
|
2912
|
+
: (spec ?? true) === true
|
|
2913
|
+
? base
|
|
2914
|
+
: Object.assign(base, functionalUpdate(spec as any, base))
|
|
2915
|
+
}
|
|
2916
|
+
|
|
3263
2917
|
function extractStrictParams(
|
|
3264
2918
|
route: AnyRoute,
|
|
3265
2919
|
accumulatedParams: Record<string, unknown>,
|
|
3266
2920
|
) {
|
|
3267
2921
|
const parseParams = route.options.params?.parse ?? route.options.parseParams
|
|
3268
2922
|
if (parseParams) {
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
Object.assign(accumulatedParams, result)
|
|
2923
|
+
Object.assign(
|
|
2924
|
+
accumulatedParams,
|
|
2925
|
+
parseParams(accumulatedParams as Record<string, string>),
|
|
2926
|
+
)
|
|
3274
2927
|
}
|
|
3275
2928
|
}
|