@tanstack/router-core 1.167.4 → 1.168.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/load-matches.cjs +14 -9
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/router.cjs +135 -151
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +16 -10
- package/dist/cjs/scroll-restoration.cjs +5 -4
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-client.cjs +14 -17
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +1 -1
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/stores.cjs +148 -0
- package/dist/cjs/stores.cjs.map +1 -0
- package/dist/cjs/stores.d.cts +70 -0
- package/dist/cjs/utils.cjs +7 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/load-matches.js +14 -9
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/router.d.ts +16 -10
- package/dist/esm/router.js +135 -151
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +5 -4
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.js +2 -2
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/ssr-client.js +14 -17
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +1 -1
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/stores.d.ts +70 -0
- package/dist/esm/stores.js +146 -0
- package/dist/esm/stores.js.map +1 -0
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +7 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +11 -0
- package/src/load-matches.ts +23 -11
- package/src/router.ts +238 -252
- package/src/scroll-restoration.ts +6 -5
- package/src/ssr/createRequestHandler.ts +5 -4
- package/src/ssr/ssr-client.ts +17 -18
- package/src/ssr/ssr-server.ts +1 -1
- package/src/stores.ts +342 -0
- package/src/utils.ts +9 -0
- package/dist/cjs/utils/batch.cjs +0 -16
- package/dist/cjs/utils/batch.cjs.map +0 -1
- package/dist/cjs/utils/batch.d.cts +0 -1
- package/dist/esm/utils/batch.d.ts +0 -1
- package/dist/esm/utils/batch.js +0 -15
- package/dist/esm/utils/batch.js.map +0 -1
- package/src/utils/batch.ts +0 -18
|
@@ -262,7 +262,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
262
262
|
// // console.log('mutation observer restoreScroll')
|
|
263
263
|
// restoreScroll(
|
|
264
264
|
// storageKey,
|
|
265
|
-
// getKey(router.
|
|
265
|
+
// getKey(router.stores.location.state),
|
|
266
266
|
// router.options.scrollRestorationBehavior,
|
|
267
267
|
// )
|
|
268
268
|
// })
|
|
@@ -306,7 +306,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
const restoreKey = getKey(router.
|
|
309
|
+
const restoreKey = getKey(router.stores.location.state)
|
|
310
310
|
|
|
311
311
|
scrollRestorationCache.set((state) => {
|
|
312
312
|
const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)
|
|
@@ -389,11 +389,12 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
389
389
|
*/
|
|
390
390
|
export function handleHashScroll(router: AnyRouter) {
|
|
391
391
|
if (typeof document !== 'undefined' && (document as any).querySelector) {
|
|
392
|
+
const location = router.stores.location.state
|
|
392
393
|
const hashScrollIntoViewOptions =
|
|
393
|
-
|
|
394
|
+
location.state.__hashScrollIntoViewOptions ?? true
|
|
394
395
|
|
|
395
|
-
if (hashScrollIntoViewOptions &&
|
|
396
|
-
const el = document.getElementById(
|
|
396
|
+
if (hashScrollIntoViewOptions && location.hash !== '') {
|
|
397
|
+
const el = document.getElementById(location.hash)
|
|
397
398
|
if (el) {
|
|
398
399
|
el.scrollIntoView(hashScrollIntoViewOptions)
|
|
399
400
|
}
|
|
@@ -78,12 +78,13 @@ export function createRequestHandler<TRouter extends AnyRouter>({
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function getRequestHeaders(opts: { router: AnyRouter }): Headers {
|
|
81
|
-
const matchHeaders =
|
|
82
|
-
(
|
|
83
|
-
|
|
81
|
+
const matchHeaders =
|
|
82
|
+
opts.router.stores.activeMatchesSnapshot.state.map<AnyHeaders>(
|
|
83
|
+
(match) => match.headers,
|
|
84
|
+
)
|
|
84
85
|
|
|
85
86
|
// Handle Redirects
|
|
86
|
-
const
|
|
87
|
+
const redirect = opts.router.stores.redirect.state
|
|
87
88
|
if (redirect) {
|
|
88
89
|
matchHeaders.push(redirect.headers)
|
|
89
90
|
}
|
package/src/ssr/ssr-client.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import invariant from 'tiny-invariant'
|
|
2
|
-
import { batch } from '../utils/batch'
|
|
3
2
|
import { isNotFound } from '../not-found'
|
|
4
3
|
import { createControlledPromise } from '../utils'
|
|
5
4
|
import { hydrateSsrMatchId } from './ssr-match-id'
|
|
@@ -86,7 +85,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
// Hydrate the router state
|
|
89
|
-
const matches = router.matchRoutes(router.
|
|
88
|
+
const matches = router.matchRoutes(router.stores.location.state)
|
|
90
89
|
|
|
91
90
|
// kick off loading the route chunks
|
|
92
91
|
const routeChunkPromise = Promise.all(
|
|
@@ -153,10 +152,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
|
|
|
153
152
|
}
|
|
154
153
|
})
|
|
155
154
|
|
|
156
|
-
router.
|
|
157
|
-
...s,
|
|
158
|
-
matches,
|
|
159
|
-
}))
|
|
155
|
+
router.stores.setActiveMatches(matches)
|
|
160
156
|
|
|
161
157
|
// Allow the user to handle custom hydration data
|
|
162
158
|
await router.options.hydrate?.(dehydratedData)
|
|
@@ -164,12 +160,14 @@ export async function hydrate(router: AnyRouter): Promise<any> {
|
|
|
164
160
|
// now that all necessary data is hydrated:
|
|
165
161
|
// 1) fully reconstruct the route context
|
|
166
162
|
// 2) execute `head()` and `scripts()` for each match
|
|
163
|
+
const activeMatches = router.stores.activeMatchesSnapshot.state
|
|
164
|
+
const location = router.stores.location.state
|
|
167
165
|
await Promise.all(
|
|
168
|
-
|
|
166
|
+
activeMatches.map(async (match) => {
|
|
169
167
|
try {
|
|
170
168
|
const route = router.looseRoutesById[match.routeId]!
|
|
171
169
|
|
|
172
|
-
const parentMatch =
|
|
170
|
+
const parentMatch = activeMatches[match.index - 1]
|
|
173
171
|
const parentContext = parentMatch?.context ?? router.options.context
|
|
174
172
|
|
|
175
173
|
// `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed
|
|
@@ -180,11 +178,11 @@ export async function hydrate(router: AnyRouter): Promise<any> {
|
|
|
180
178
|
deps: match.loaderDeps,
|
|
181
179
|
params: match.params,
|
|
182
180
|
context: parentContext ?? {},
|
|
183
|
-
location
|
|
181
|
+
location,
|
|
184
182
|
navigate: (opts: any) =>
|
|
185
183
|
router.navigate({
|
|
186
184
|
...opts,
|
|
187
|
-
_fromLocation:
|
|
185
|
+
_fromLocation: location,
|
|
188
186
|
}),
|
|
189
187
|
buildLocation: router.buildLocation,
|
|
190
188
|
cause: match.cause,
|
|
@@ -205,7 +203,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
|
|
|
205
203
|
|
|
206
204
|
const assetContext = {
|
|
207
205
|
ssr: router.options.ssr,
|
|
208
|
-
matches:
|
|
206
|
+
matches: activeMatches,
|
|
209
207
|
match,
|
|
210
208
|
params: match.params,
|
|
211
209
|
loaderData: match.loaderData,
|
|
@@ -270,16 +268,17 @@ export async function hydrate(router: AnyRouter): Promise<any> {
|
|
|
270
268
|
match._nonReactive.displayPendingPromise = loadPromise
|
|
271
269
|
|
|
272
270
|
loadPromise.then(() => {
|
|
273
|
-
batch(() => {
|
|
271
|
+
router.batch(() => {
|
|
274
272
|
// ensure router is not in status 'pending' anymore
|
|
275
273
|
// this usually happens in Transitioner but if loading synchronously resolves,
|
|
276
274
|
// Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false
|
|
277
|
-
if (router.
|
|
278
|
-
router.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
275
|
+
if (router.stores.status.state === 'pending') {
|
|
276
|
+
router.batch(() => {
|
|
277
|
+
router.stores.status.setState(() => 'idle')
|
|
278
|
+
router.stores.resolvedLocation.setState(
|
|
279
|
+
() => router.stores.location.state,
|
|
280
|
+
)
|
|
281
|
+
})
|
|
283
282
|
}
|
|
284
283
|
// hide the pending component once the load is finished
|
|
285
284
|
router.updateMatch(match.id, (prev) => ({
|
package/src/ssr/ssr-server.ts
CHANGED
|
@@ -202,7 +202,7 @@ export function attachRouterServerSsrUtils({
|
|
|
202
202
|
},
|
|
203
203
|
dehydrate: async () => {
|
|
204
204
|
invariant(!_dehydrated, 'router is already dehydrated!')
|
|
205
|
-
let matchesToDehydrate = router.state
|
|
205
|
+
let matchesToDehydrate = router.stores.activeMatchesSnapshot.state
|
|
206
206
|
if (router.isShell()) {
|
|
207
207
|
// In SPA mode we only want to dehydrate the root match
|
|
208
208
|
matchesToDehydrate = matchesToDehydrate.slice(0, 1)
|
package/src/stores.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { createLRUCache } from './lru-cache'
|
|
2
|
+
import { arraysEqual } from './utils'
|
|
3
|
+
|
|
4
|
+
import type { AnyRoute } from './route'
|
|
5
|
+
import type { RouterState } from './router'
|
|
6
|
+
import type { FullSearchSchema } from './routeInfo'
|
|
7
|
+
import type { ParsedLocation } from './location'
|
|
8
|
+
import type { AnyRedirect } from './redirect'
|
|
9
|
+
import type { AnyRouteMatch } from './Matches'
|
|
10
|
+
|
|
11
|
+
export interface RouterReadableStore<TValue> {
|
|
12
|
+
readonly state: TValue
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RouterWritableStore<
|
|
16
|
+
TValue,
|
|
17
|
+
> extends RouterReadableStore<TValue> {
|
|
18
|
+
setState: (updater: (prev: TValue) => TValue) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type RouterBatchFn = (fn: () => void) => void
|
|
22
|
+
|
|
23
|
+
export type MutableStoreFactory = <TValue>(
|
|
24
|
+
initialValue: TValue,
|
|
25
|
+
) => RouterWritableStore<TValue>
|
|
26
|
+
|
|
27
|
+
export type ReadonlyStoreFactory = <TValue>(
|
|
28
|
+
read: () => TValue,
|
|
29
|
+
) => RouterReadableStore<TValue>
|
|
30
|
+
|
|
31
|
+
export type GetStoreConfig = (opts: { isServer?: boolean }) => StoreConfig
|
|
32
|
+
|
|
33
|
+
export type StoreConfig = {
|
|
34
|
+
createMutableStore: MutableStoreFactory
|
|
35
|
+
createReadonlyStore: ReadonlyStoreFactory
|
|
36
|
+
batch: RouterBatchFn
|
|
37
|
+
init?: (stores: RouterStores<AnyRoute>) => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type MatchStore = RouterWritableStore<AnyRouteMatch> & {
|
|
41
|
+
routeId?: string
|
|
42
|
+
}
|
|
43
|
+
type ReadableStore<TValue> = RouterReadableStore<TValue>
|
|
44
|
+
|
|
45
|
+
/** SSR non-reactive createMutableStore */
|
|
46
|
+
export function createNonReactiveMutableStore<TValue>(
|
|
47
|
+
initialValue: TValue,
|
|
48
|
+
): RouterWritableStore<TValue> {
|
|
49
|
+
let value = initialValue
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
get state() {
|
|
53
|
+
return value
|
|
54
|
+
},
|
|
55
|
+
setState(updater: (prev: TValue) => TValue) {
|
|
56
|
+
value = updater(value)
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** SSR non-reactive createReadonlyStore */
|
|
62
|
+
export function createNonReactiveReadonlyStore<TValue>(
|
|
63
|
+
read: () => TValue,
|
|
64
|
+
): RouterReadableStore<TValue> {
|
|
65
|
+
return {
|
|
66
|
+
get state() {
|
|
67
|
+
return read()
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RouterStores<in out TRouteTree extends AnyRoute> {
|
|
73
|
+
status: RouterWritableStore<RouterState<TRouteTree>['status']>
|
|
74
|
+
loadedAt: RouterWritableStore<number>
|
|
75
|
+
isLoading: RouterWritableStore<boolean>
|
|
76
|
+
isTransitioning: RouterWritableStore<boolean>
|
|
77
|
+
location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>
|
|
78
|
+
resolvedLocation: RouterWritableStore<
|
|
79
|
+
ParsedLocation<FullSearchSchema<TRouteTree>> | undefined
|
|
80
|
+
>
|
|
81
|
+
statusCode: RouterWritableStore<number>
|
|
82
|
+
redirect: RouterWritableStore<AnyRedirect | undefined>
|
|
83
|
+
matchesId: RouterWritableStore<Array<string>>
|
|
84
|
+
pendingMatchesId: RouterWritableStore<Array<string>>
|
|
85
|
+
/** @internal */
|
|
86
|
+
cachedMatchesId: RouterWritableStore<Array<string>>
|
|
87
|
+
activeMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>
|
|
88
|
+
pendingMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>
|
|
89
|
+
cachedMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>
|
|
90
|
+
firstMatchId: ReadableStore<string | undefined>
|
|
91
|
+
hasPendingMatches: ReadableStore<boolean>
|
|
92
|
+
matchRouteReactivity: ReadableStore<{
|
|
93
|
+
locationHref: string
|
|
94
|
+
resolvedLocationHref: string | undefined
|
|
95
|
+
status: RouterState<TRouteTree>['status']
|
|
96
|
+
}>
|
|
97
|
+
__store: RouterReadableStore<RouterState<TRouteTree>>
|
|
98
|
+
|
|
99
|
+
activeMatchStoresById: Map<string, MatchStore>
|
|
100
|
+
pendingMatchStoresById: Map<string, MatchStore>
|
|
101
|
+
cachedMatchStoresById: Map<string, MatchStore>
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get a computed store that resolves a routeId to its current match state.
|
|
105
|
+
* Returns the same cached store instance for repeated calls with the same key.
|
|
106
|
+
* The computed depends on matchesId + the individual match store, so
|
|
107
|
+
* subscribers are only notified when the resolved match state changes.
|
|
108
|
+
*/
|
|
109
|
+
getMatchStoreByRouteId: (
|
|
110
|
+
routeId: string,
|
|
111
|
+
) => RouterReadableStore<AnyRouteMatch | undefined>
|
|
112
|
+
|
|
113
|
+
setActiveMatches: (nextMatches: Array<AnyRouteMatch>) => void
|
|
114
|
+
setPendingMatches: (nextMatches: Array<AnyRouteMatch>) => void
|
|
115
|
+
setCachedMatches: (nextMatches: Array<AnyRouteMatch>) => void
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createRouterStores<TRouteTree extends AnyRoute>(
|
|
119
|
+
initialState: RouterState<TRouteTree>,
|
|
120
|
+
config: StoreConfig,
|
|
121
|
+
): RouterStores<TRouteTree> {
|
|
122
|
+
const { createMutableStore, createReadonlyStore, batch, init } = config
|
|
123
|
+
|
|
124
|
+
// non reactive utilities
|
|
125
|
+
const activeMatchStoresById = new Map<string, MatchStore>()
|
|
126
|
+
const pendingMatchStoresById = new Map<string, MatchStore>()
|
|
127
|
+
const cachedMatchStoresById = new Map<string, MatchStore>()
|
|
128
|
+
|
|
129
|
+
// atoms
|
|
130
|
+
const status = createMutableStore(initialState.status)
|
|
131
|
+
const loadedAt = createMutableStore(initialState.loadedAt)
|
|
132
|
+
const isLoading = createMutableStore(initialState.isLoading)
|
|
133
|
+
const isTransitioning = createMutableStore(initialState.isTransitioning)
|
|
134
|
+
const location = createMutableStore(initialState.location)
|
|
135
|
+
const resolvedLocation = createMutableStore(initialState.resolvedLocation)
|
|
136
|
+
const statusCode = createMutableStore(initialState.statusCode)
|
|
137
|
+
const redirect = createMutableStore(initialState.redirect)
|
|
138
|
+
const matchesId = createMutableStore<Array<string>>([])
|
|
139
|
+
const pendingMatchesId = createMutableStore<Array<string>>([])
|
|
140
|
+
const cachedMatchesId = createMutableStore<Array<string>>([])
|
|
141
|
+
|
|
142
|
+
// 1st order derived stores
|
|
143
|
+
const activeMatchesSnapshot = createReadonlyStore(() =>
|
|
144
|
+
readPoolMatches(activeMatchStoresById, matchesId.state),
|
|
145
|
+
)
|
|
146
|
+
const pendingMatchesSnapshot = createReadonlyStore(() =>
|
|
147
|
+
readPoolMatches(pendingMatchStoresById, pendingMatchesId.state),
|
|
148
|
+
)
|
|
149
|
+
const cachedMatchesSnapshot = createReadonlyStore(() =>
|
|
150
|
+
readPoolMatches(cachedMatchStoresById, cachedMatchesId.state),
|
|
151
|
+
)
|
|
152
|
+
const firstMatchId = createReadonlyStore(() => matchesId.state[0])
|
|
153
|
+
const hasPendingMatches = createReadonlyStore(() =>
|
|
154
|
+
matchesId.state.some((matchId) => {
|
|
155
|
+
const store = activeMatchStoresById.get(matchId)
|
|
156
|
+
return store?.state.status === 'pending'
|
|
157
|
+
}),
|
|
158
|
+
)
|
|
159
|
+
const matchRouteReactivity = createReadonlyStore(() => ({
|
|
160
|
+
locationHref: location.state.href,
|
|
161
|
+
resolvedLocationHref: resolvedLocation.state?.href,
|
|
162
|
+
status: status.state,
|
|
163
|
+
}))
|
|
164
|
+
|
|
165
|
+
// compatibility "big" state store
|
|
166
|
+
const __store = createReadonlyStore(() => ({
|
|
167
|
+
status: status.state,
|
|
168
|
+
loadedAt: loadedAt.state,
|
|
169
|
+
isLoading: isLoading.state,
|
|
170
|
+
isTransitioning: isTransitioning.state,
|
|
171
|
+
matches: activeMatchesSnapshot.state,
|
|
172
|
+
location: location.state,
|
|
173
|
+
resolvedLocation: resolvedLocation.state,
|
|
174
|
+
statusCode: statusCode.state,
|
|
175
|
+
redirect: redirect.state,
|
|
176
|
+
}))
|
|
177
|
+
|
|
178
|
+
// Per-routeId computed store cache.
|
|
179
|
+
// Each entry resolves routeId → match state through the signal graph,
|
|
180
|
+
// giving consumers a single store to subscribe to instead of the
|
|
181
|
+
// two-level byRouteId → matchStore pattern.
|
|
182
|
+
//
|
|
183
|
+
// 64 max size is arbitrary, this is only for active matches anyway so
|
|
184
|
+
// it should be plenty. And we already have a 32 limit due to route
|
|
185
|
+
// matching bitmask anyway.
|
|
186
|
+
const matchStoreByRouteIdCache = createLRUCache<
|
|
187
|
+
string,
|
|
188
|
+
RouterReadableStore<AnyRouteMatch | undefined>
|
|
189
|
+
>(64)
|
|
190
|
+
|
|
191
|
+
function getMatchStoreByRouteId(
|
|
192
|
+
routeId: string,
|
|
193
|
+
): RouterReadableStore<AnyRouteMatch | undefined> {
|
|
194
|
+
let cached = matchStoreByRouteIdCache.get(routeId)
|
|
195
|
+
if (!cached) {
|
|
196
|
+
cached = createReadonlyStore(() => {
|
|
197
|
+
// Reading matchesId.state tracks it as a dependency.
|
|
198
|
+
// When matchesId changes (navigation), this computed re-evaluates.
|
|
199
|
+
const ids = matchesId.state
|
|
200
|
+
for (const id of ids) {
|
|
201
|
+
const matchStore = activeMatchStoresById.get(id)
|
|
202
|
+
if (matchStore && matchStore.routeId === routeId) {
|
|
203
|
+
// Reading matchStore.state tracks it as a dependency.
|
|
204
|
+
// When the match store's state changes, this re-evaluates.
|
|
205
|
+
return matchStore.state
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return undefined
|
|
209
|
+
})
|
|
210
|
+
matchStoreByRouteIdCache.set(routeId, cached)
|
|
211
|
+
}
|
|
212
|
+
return cached
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const store = {
|
|
216
|
+
// atoms
|
|
217
|
+
status,
|
|
218
|
+
loadedAt,
|
|
219
|
+
isLoading,
|
|
220
|
+
isTransitioning,
|
|
221
|
+
location,
|
|
222
|
+
resolvedLocation,
|
|
223
|
+
statusCode,
|
|
224
|
+
redirect,
|
|
225
|
+
matchesId,
|
|
226
|
+
pendingMatchesId,
|
|
227
|
+
cachedMatchesId,
|
|
228
|
+
|
|
229
|
+
// derived
|
|
230
|
+
activeMatchesSnapshot,
|
|
231
|
+
pendingMatchesSnapshot,
|
|
232
|
+
cachedMatchesSnapshot,
|
|
233
|
+
firstMatchId,
|
|
234
|
+
hasPendingMatches,
|
|
235
|
+
matchRouteReactivity,
|
|
236
|
+
|
|
237
|
+
// non-reactive state
|
|
238
|
+
activeMatchStoresById,
|
|
239
|
+
pendingMatchStoresById,
|
|
240
|
+
cachedMatchStoresById,
|
|
241
|
+
|
|
242
|
+
// compatibility "big" state
|
|
243
|
+
__store,
|
|
244
|
+
|
|
245
|
+
// per-key computed stores
|
|
246
|
+
getMatchStoreByRouteId,
|
|
247
|
+
|
|
248
|
+
// methods
|
|
249
|
+
setActiveMatches,
|
|
250
|
+
setPendingMatches,
|
|
251
|
+
setCachedMatches,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// initialize the active matches
|
|
255
|
+
setActiveMatches(initialState.matches as Array<AnyRouteMatch>)
|
|
256
|
+
init?.(store)
|
|
257
|
+
|
|
258
|
+
// setters to update non-reactive utilities in sync with the reactive stores
|
|
259
|
+
function setActiveMatches(nextMatches: Array<AnyRouteMatch>) {
|
|
260
|
+
reconcileMatchPool(
|
|
261
|
+
nextMatches,
|
|
262
|
+
activeMatchStoresById,
|
|
263
|
+
matchesId,
|
|
264
|
+
createMutableStore,
|
|
265
|
+
batch,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function setPendingMatches(nextMatches: Array<AnyRouteMatch>) {
|
|
270
|
+
reconcileMatchPool(
|
|
271
|
+
nextMatches,
|
|
272
|
+
pendingMatchStoresById,
|
|
273
|
+
pendingMatchesId,
|
|
274
|
+
createMutableStore,
|
|
275
|
+
batch,
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function setCachedMatches(nextMatches: Array<AnyRouteMatch>) {
|
|
280
|
+
reconcileMatchPool(
|
|
281
|
+
nextMatches,
|
|
282
|
+
cachedMatchStoresById,
|
|
283
|
+
cachedMatchesId,
|
|
284
|
+
createMutableStore,
|
|
285
|
+
batch,
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return store
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function readPoolMatches(
|
|
293
|
+
pool: Map<string, MatchStore>,
|
|
294
|
+
ids: Array<string>,
|
|
295
|
+
): Array<AnyRouteMatch> {
|
|
296
|
+
const matches: Array<AnyRouteMatch> = []
|
|
297
|
+
for (const id of ids) {
|
|
298
|
+
const matchStore = pool.get(id)
|
|
299
|
+
if (matchStore) {
|
|
300
|
+
matches.push(matchStore.state)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return matches
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function reconcileMatchPool(
|
|
307
|
+
nextMatches: Array<AnyRouteMatch>,
|
|
308
|
+
pool: Map<string, MatchStore>,
|
|
309
|
+
idStore: RouterWritableStore<Array<string>>,
|
|
310
|
+
createMutableStore: MutableStoreFactory,
|
|
311
|
+
batch: RouterBatchFn,
|
|
312
|
+
): void {
|
|
313
|
+
const nextIds = nextMatches.map((d) => d.id)
|
|
314
|
+
const nextIdSet = new Set(nextIds)
|
|
315
|
+
|
|
316
|
+
batch(() => {
|
|
317
|
+
for (const id of pool.keys()) {
|
|
318
|
+
if (!nextIdSet.has(id)) {
|
|
319
|
+
pool.delete(id)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
for (const nextMatch of nextMatches) {
|
|
324
|
+
const existing = pool.get(nextMatch.id)
|
|
325
|
+
if (!existing) {
|
|
326
|
+
const matchStore = createMutableStore(nextMatch) as MatchStore
|
|
327
|
+
matchStore.routeId = nextMatch.routeId
|
|
328
|
+
pool.set(nextMatch.id, matchStore)
|
|
329
|
+
continue
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
existing.routeId = nextMatch.routeId
|
|
333
|
+
if (existing.state !== nextMatch) {
|
|
334
|
+
existing.setState(() => nextMatch)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!arraysEqual(idStore.state, nextIds)) {
|
|
339
|
+
idStore.setState(() => nextIds)
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -697,3 +697,12 @@ export function buildDevStylesUrl(
|
|
|
697
697
|
const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`
|
|
698
698
|
return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`
|
|
699
699
|
}
|
|
700
|
+
|
|
701
|
+
export function arraysEqual<T>(a: Array<T>, b: Array<T>) {
|
|
702
|
+
if (a === b) return true
|
|
703
|
+
if (a.length !== b.length) return false
|
|
704
|
+
for (let i = 0; i < a.length; i++) {
|
|
705
|
+
if (a[i] !== b[i]) return false
|
|
706
|
+
}
|
|
707
|
+
return true
|
|
708
|
+
}
|
package/dist/cjs/utils/batch.cjs
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs");
|
|
2
|
-
let _tanstack_store = require("@tanstack/store");
|
|
3
|
-
let _tanstack_router_core_isServer = require("@tanstack/router-core/isServer");
|
|
4
|
-
//#region src/utils/batch.ts
|
|
5
|
-
function batch(fn) {
|
|
6
|
-
if (_tanstack_router_core_isServer.isServer) return fn();
|
|
7
|
-
let result;
|
|
8
|
-
(0, _tanstack_store.batch)(() => {
|
|
9
|
-
result = fn();
|
|
10
|
-
});
|
|
11
|
-
return result;
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
exports.batch = batch;
|
|
15
|
-
|
|
16
|
-
//# sourceMappingURL=batch.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"batch.cjs","names":[],"sources":["../../../src/utils/batch.ts"],"sourcesContent":["import { batch as storeBatch } from '@tanstack/store'\n\nimport { isServer } from '@tanstack/router-core/isServer'\n\n// `@tanstack/store`'s `batch` is for reactive notification batching.\n// On the server we don't subscribe/render reactively, so a lightweight\n// implementation that just executes is enough.\nexport function batch<T>(fn: () => T): T {\n if (isServer) {\n return fn()\n }\n\n let result!: T\n storeBatch(() => {\n result = fn()\n })\n return result\n}\n"],"mappings":";;;;AAOA,SAAgB,MAAS,IAAgB;AACvC,KAAI,+BAAA,SACF,QAAO,IAAI;CAGb,IAAI;AACJ,EAAA,GAAA,gBAAA,aAAiB;AACf,WAAS,IAAI;GACb;AACF,QAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function batch<T>(fn: () => T): T;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function batch<T>(fn: () => T): T;
|
package/dist/esm/utils/batch.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { batch } from "@tanstack/store";
|
|
2
|
-
import { isServer } from "@tanstack/router-core/isServer";
|
|
3
|
-
//#region src/utils/batch.ts
|
|
4
|
-
function batch$1(fn) {
|
|
5
|
-
if (isServer) return fn();
|
|
6
|
-
let result;
|
|
7
|
-
batch(() => {
|
|
8
|
-
result = fn();
|
|
9
|
-
});
|
|
10
|
-
return result;
|
|
11
|
-
}
|
|
12
|
-
//#endregion
|
|
13
|
-
export { batch$1 as batch };
|
|
14
|
-
|
|
15
|
-
//# sourceMappingURL=batch.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"batch.js","names":[],"sources":["../../../src/utils/batch.ts"],"sourcesContent":["import { batch as storeBatch } from '@tanstack/store'\n\nimport { isServer } from '@tanstack/router-core/isServer'\n\n// `@tanstack/store`'s `batch` is for reactive notification batching.\n// On the server we don't subscribe/render reactively, so a lightweight\n// implementation that just executes is enough.\nexport function batch<T>(fn: () => T): T {\n if (isServer) {\n return fn()\n }\n\n let result!: T\n storeBatch(() => {\n result = fn()\n })\n return result\n}\n"],"mappings":";;;AAOA,SAAgB,QAAS,IAAgB;AACvC,KAAI,SACF,QAAO,IAAI;CAGb,IAAI;AACJ,aAAiB;AACf,WAAS,IAAI;GACb;AACF,QAAO"}
|
package/src/utils/batch.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { batch as storeBatch } from '@tanstack/store'
|
|
2
|
-
|
|
3
|
-
import { isServer } from '@tanstack/router-core/isServer'
|
|
4
|
-
|
|
5
|
-
// `@tanstack/store`'s `batch` is for reactive notification batching.
|
|
6
|
-
// On the server we don't subscribe/render reactively, so a lightweight
|
|
7
|
-
// implementation that just executes is enough.
|
|
8
|
-
export function batch<T>(fn: () => T): T {
|
|
9
|
-
if (isServer) {
|
|
10
|
-
return fn()
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let result!: T
|
|
14
|
-
storeBatch(() => {
|
|
15
|
-
result = fn()
|
|
16
|
-
})
|
|
17
|
-
return result
|
|
18
|
-
}
|