@tanstack/router-core 1.171.15 → 1.171.16-pre.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.
Files changed (134) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +3 -12
  3. package/dist/cjs/await-signal.cjs +21 -0
  4. package/dist/cjs/await-signal.cjs.map +1 -0
  5. package/dist/cjs/await-signal.d.cts +1 -0
  6. package/dist/cjs/index.cjs +3 -1
  7. package/dist/cjs/index.d.cts +3 -2
  8. package/dist/cjs/isServer/client.cjs +2 -0
  9. package/dist/cjs/isServer/client.cjs.map +1 -1
  10. package/dist/cjs/isServer/client.d.cts +1 -0
  11. package/dist/cjs/isServer/development.cjs +2 -0
  12. package/dist/cjs/isServer/development.cjs.map +1 -1
  13. package/dist/cjs/isServer/development.d.cts +1 -0
  14. package/dist/cjs/isServer/server.cjs +2 -0
  15. package/dist/cjs/isServer/server.cjs.map +1 -1
  16. package/dist/cjs/isServer/server.d.cts +1 -0
  17. package/dist/cjs/load-client.cjs +1470 -0
  18. package/dist/cjs/load-client.cjs.map +1 -0
  19. package/dist/cjs/load-client.d.cts +147 -0
  20. package/dist/cjs/load-server.cjs +565 -0
  21. package/dist/cjs/load-server.cjs.map +1 -0
  22. package/dist/cjs/load-server.d.cts +16 -0
  23. package/dist/cjs/redirect.cjs.map +1 -1
  24. package/dist/cjs/redirect.d.cts +0 -1
  25. package/dist/cjs/route.cjs.map +1 -1
  26. package/dist/cjs/router.cjs +187 -442
  27. package/dist/cjs/router.cjs.map +1 -1
  28. package/dist/cjs/router.d.cts +61 -51
  29. package/dist/cjs/ssr/client.cjs +2 -2
  30. package/dist/cjs/ssr/createRequestHandler.cjs +60 -9
  31. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  32. package/dist/cjs/ssr/createRequestHandler.d.cts +1 -0
  33. package/dist/cjs/ssr/handlerCallback.cjs +49 -4
  34. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
  35. package/dist/cjs/ssr/handlerCallback.d.cts +3 -0
  36. package/dist/cjs/ssr/server.cjs +4 -0
  37. package/dist/cjs/ssr/server.d.cts +2 -2
  38. package/dist/cjs/ssr/ssr-client.d.cts +1 -10
  39. package/dist/cjs/ssr/ssr-match-id.cjs +2 -2
  40. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
  41. package/dist/cjs/ssr/ssr-server.cjs +6 -6
  42. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  43. package/dist/cjs/ssr/transformStreamWithRouter.cjs +27 -1
  44. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  45. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +2 -0
  46. package/dist/cjs/ssr/types.d.cts +0 -1
  47. package/dist/cjs/stores.cjs +30 -104
  48. package/dist/cjs/stores.cjs.map +1 -1
  49. package/dist/cjs/stores.d.cts +7 -32
  50. package/dist/cjs/utils.cjs +0 -4
  51. package/dist/cjs/utils.cjs.map +1 -1
  52. package/dist/esm/Matches.d.ts +3 -12
  53. package/dist/esm/Matches.js.map +1 -1
  54. package/dist/esm/await-signal.d.ts +1 -0
  55. package/dist/esm/await-signal.js +21 -0
  56. package/dist/esm/await-signal.js.map +1 -0
  57. package/dist/esm/index.d.ts +3 -2
  58. package/dist/esm/index.js +3 -2
  59. package/dist/esm/isServer/client.d.ts +1 -0
  60. package/dist/esm/isServer/client.js +2 -1
  61. package/dist/esm/isServer/client.js.map +1 -1
  62. package/dist/esm/isServer/development.d.ts +1 -0
  63. package/dist/esm/isServer/development.js +2 -1
  64. package/dist/esm/isServer/development.js.map +1 -1
  65. package/dist/esm/isServer/server.d.ts +1 -0
  66. package/dist/esm/isServer/server.js +2 -1
  67. package/dist/esm/isServer/server.js.map +1 -1
  68. package/dist/esm/load-client.d.ts +147 -0
  69. package/dist/esm/load-client.js +1462 -0
  70. package/dist/esm/load-client.js.map +1 -0
  71. package/dist/esm/load-server.d.ts +16 -0
  72. package/dist/esm/load-server.js +565 -0
  73. package/dist/esm/load-server.js.map +1 -0
  74. package/dist/esm/redirect.d.ts +0 -1
  75. package/dist/esm/redirect.js.map +1 -1
  76. package/dist/esm/route.js.map +1 -1
  77. package/dist/esm/router.d.ts +61 -51
  78. package/dist/esm/router.js +188 -444
  79. package/dist/esm/router.js.map +1 -1
  80. package/dist/esm/ssr/client.js +1 -1
  81. package/dist/esm/ssr/createRequestHandler.d.ts +1 -0
  82. package/dist/esm/ssr/createRequestHandler.js +61 -11
  83. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  84. package/dist/esm/ssr/handlerCallback.d.ts +3 -0
  85. package/dist/esm/ssr/handlerCallback.js +47 -5
  86. package/dist/esm/ssr/handlerCallback.js.map +1 -1
  87. package/dist/esm/ssr/server.d.ts +2 -2
  88. package/dist/esm/ssr/server.js +3 -3
  89. package/dist/esm/ssr/ssr-client.d.ts +1 -10
  90. package/dist/esm/ssr/ssr-match-id.js +2 -2
  91. package/dist/esm/ssr/ssr-match-id.js.map +1 -1
  92. package/dist/esm/ssr/ssr-server.js +6 -6
  93. package/dist/esm/ssr/ssr-server.js.map +1 -1
  94. package/dist/esm/ssr/transformStreamWithRouter.d.ts +2 -0
  95. package/dist/esm/ssr/transformStreamWithRouter.js +27 -1
  96. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  97. package/dist/esm/ssr/types.d.ts +0 -1
  98. package/dist/esm/stores.d.ts +7 -32
  99. package/dist/esm/stores.js +30 -104
  100. package/dist/esm/stores.js.map +1 -1
  101. package/dist/esm/utils.js +1 -4
  102. package/dist/esm/utils.js.map +1 -1
  103. package/package.json +3 -3
  104. package/src/Matches.ts +6 -21
  105. package/src/await-signal.ts +27 -0
  106. package/src/index.ts +1 -3
  107. package/src/isServer/client.ts +1 -0
  108. package/src/isServer/development.ts +1 -0
  109. package/src/isServer/server.ts +1 -0
  110. package/src/load-client.ts +2701 -0
  111. package/src/load-server.ts +849 -0
  112. package/src/redirect.ts +0 -1
  113. package/src/route.ts +2 -11
  114. package/src/router.ts +379 -769
  115. package/src/ssr/createRequestHandler.ts +109 -16
  116. package/src/ssr/handlerCallback.ts +76 -10
  117. package/src/ssr/server.ts +4 -1
  118. package/src/ssr/ssr-client.ts +1 -310
  119. package/src/ssr/ssr-match-id.ts +11 -2
  120. package/src/ssr/ssr-server.ts +9 -8
  121. package/src/ssr/transformStreamWithRouter.ts +37 -1
  122. package/src/ssr/types.ts +0 -1
  123. package/src/stores.ts +46 -208
  124. package/dist/cjs/load-matches.cjs +0 -658
  125. package/dist/cjs/load-matches.cjs.map +0 -1
  126. package/dist/cjs/load-matches.d.cts +0 -18
  127. package/dist/cjs/ssr/ssr-client.cjs +0 -183
  128. package/dist/cjs/ssr/ssr-client.cjs.map +0 -1
  129. package/dist/esm/load-matches.d.ts +0 -18
  130. package/dist/esm/load-matches.js +0 -656
  131. package/dist/esm/load-matches.js.map +0 -1
  132. package/dist/esm/ssr/ssr-client.js +0 -183
  133. package/dist/esm/ssr/ssr-client.js.map +0 -1
  134. package/src/load-matches.ts +0 -1278
@@ -4,6 +4,8 @@ import { TSR_SCRIPT_BARRIER_ID } from './constants'
4
4
  import type { AnyRouter } from '../router'
5
5
 
6
6
  export type TransformStreamWithRouterOptions = {
7
+ /** The request lifetime that owns this response stream. */
8
+ signal?: AbortSignal
7
9
  /** Timeout for serialization to complete after app render finishes (default: 60000ms) */
8
10
  timeoutMs?: number
9
11
  /** Maximum lifetime of the stream transform (default: 120000ms). Safety net for cleanup. */
@@ -196,6 +198,22 @@ function createAbortNotifier(opts?: TransformStreamWithRouterOptions) {
196
198
  }
197
199
  }
198
200
 
201
+ function listenToAbort(
202
+ signal: AbortSignal | undefined,
203
+ onAbort: (reason?: unknown) => void,
204
+ ) {
205
+ if (!signal) {
206
+ return
207
+ }
208
+ if (signal.aborted) {
209
+ onAbort(signal.reason)
210
+ return
211
+ }
212
+ const listener = () => onAbort(signal.reason)
213
+ signal.addEventListener('abort', listener, { once: true })
214
+ return () => signal.removeEventListener('abort', listener)
215
+ }
216
+
199
217
  export function transformStreamWithRouter(
200
218
  router: AnyRouter,
201
219
  appStream: ReadableStream,
@@ -224,6 +242,7 @@ function makeFastPathStream(
224
242
  let controller: ReadableStreamDefaultController<Uint8Array> | undefined
225
243
  let state: MergeState = MergeState.ReadingBody
226
244
  let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined
245
+ let stopListeningToAbort: (() => void) | undefined
227
246
  let stopListeningToInjectedHtml: (() => void) | undefined
228
247
  const readerState = createReaderState(appStream)
229
248
  const notifyAbort = createAbortNotifier(opts)
@@ -251,6 +270,8 @@ function makeFastPathStream(
251
270
  clearTimeout(lifetimeTimeoutHandle)
252
271
  lifetimeTimeoutHandle = undefined
253
272
  }
273
+ stopListeningToAbort?.()
274
+ stopListeningToAbort = undefined
254
275
  try {
255
276
  stopListeningToInjectedHtml?.()
256
277
  } catch {
@@ -360,6 +381,11 @@ function makeFastPathStream(
360
381
  },
361
382
  })
362
383
 
384
+ stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {
385
+ safeError(reason)
386
+ cleanup(reason)
387
+ })
388
+
363
389
  return stream
364
390
  }
365
391
 
@@ -380,6 +406,7 @@ function makeMainStream(
380
406
  let stopListeningToSerializationFinished: (() => void) | undefined
381
407
  let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined
382
408
  let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined
409
+ let stopListeningToAbort: (() => void) | undefined
383
410
  let cleanedUp = false
384
411
 
385
412
  let controller: ReadableStreamDefaultController<Uint8Array> | undefined
@@ -509,6 +536,8 @@ function makeMainStream(
509
536
  }
510
537
  stopListeningToInjectedHtml = undefined
511
538
  stopListeningToSerializationFinished = undefined
539
+ stopListeningToAbort?.()
540
+ stopListeningToAbort = undefined
512
541
 
513
542
  if (serializationTimeoutHandle !== undefined) {
514
543
  clearTimeout(serializationTimeoutHandle)
@@ -763,7 +792,14 @@ function makeMainStream(
763
792
  if (cleanedUp || isDone()) return stream
764
793
  }
765
794
 
766
- // Transform the appStream
795
+ stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {
796
+ safeError(reason)
797
+ cleanup(reason)
798
+ })
799
+ if (cleanedUp || isDone())
800
+ return stream
801
+
802
+ // Transform the appStream
767
803
  ;(async () => {
768
804
  try {
769
805
  while (true) {
package/src/ssr/types.ts CHANGED
@@ -15,7 +15,6 @@ export interface DehydratedMatch {
15
15
  export interface DehydratedRouter {
16
16
  manifest: Manifest | undefined
17
17
  dehydratedData?: any
18
- lastMatchId?: string
19
18
  matches: Array<DehydratedMatch>
20
19
  }
21
20
 
package/src/stores.ts CHANGED
@@ -1,11 +1,9 @@
1
- import { createLRUCache } from './lru-cache'
2
1
  import { arraysEqual, functionalUpdate } from './utils'
3
2
 
4
3
  import type { AnyRoute } from './route'
5
4
  import type { RouterState } from './router'
6
5
  import type { FullSearchSchema } from './routeInfo'
7
6
  import type { ParsedLocation } from './location'
8
- import type { AnyRedirect } from './redirect'
9
7
  import type { AnyRouteMatch } from './Matches'
10
8
 
11
9
  export interface RouterReadableStore<TValue> {
@@ -34,12 +32,9 @@ export type StoreConfig = {
34
32
  createMutableStore: MutableStoreFactory
35
33
  createReadonlyStore: ReadonlyStoreFactory
36
34
  batch: RouterBatchFn
37
- init?: (stores: RouterStores<AnyRoute>) => void
38
35
  }
39
36
 
40
- type MatchStore = RouterWritableStore<AnyRouteMatch> & {
41
- routeId?: string
42
- }
37
+ type MatchStore = RouterWritableStore<AnyRouteMatch | undefined>
43
38
  type ReadableStore<TValue> = RouterReadableStore<TValue>
44
39
 
45
40
  /** SSR non-reactive createMutableStore */
@@ -71,272 +66,115 @@ export function createNonReactiveReadonlyStore<TValue>(
71
66
 
72
67
  export interface RouterStores<in out TRouteTree extends AnyRoute> {
73
68
  status: RouterWritableStore<RouterState<TRouteTree>['status']>
74
- loadedAt: RouterWritableStore<number>
75
- isLoading: RouterWritableStore<boolean>
76
- isTransitioning: RouterWritableStore<boolean>
77
69
  location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>
78
70
  resolvedLocation: RouterWritableStore<
79
71
  ParsedLocation<FullSearchSchema<TRouteTree>> | undefined
80
72
  >
81
- statusCode: RouterWritableStore<number>
82
- redirect: RouterWritableStore<AnyRedirect | undefined>
83
- matchesId: RouterWritableStore<Array<string>>
84
- pendingIds: RouterWritableStore<Array<string>>
85
- /** @internal */
86
- cachedIds: RouterWritableStore<Array<string>>
73
+ ids: RouterWritableStore<Array<string>>
87
74
  matches: ReadableStore<Array<AnyRouteMatch>>
88
- pendingMatches: ReadableStore<Array<AnyRouteMatch>>
89
- cachedMatches: ReadableStore<Array<AnyRouteMatch>>
90
- firstId: ReadableStore<string | undefined>
91
- hasPending: ReadableStore<boolean>
92
- matchRouteDeps: ReadableStore<{
93
- locationHref: string
94
- resolvedLocationHref: string | undefined
95
- status: RouterState<TRouteTree>['status']
96
- }>
97
75
  __store: RouterReadableStore<RouterState<TRouteTree>>
98
76
 
99
- matchStores: Map<string, MatchStore>
100
- pendingMatchStores: Map<string, MatchStore>
101
- cachedMatchStores: Map<string, MatchStore>
77
+ byRoute: Map<string, MatchStore>
102
78
 
103
79
  /**
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.
80
+ * Get the stable atom for a route's presented match. The atom remains in the
81
+ * pool when the route leaves and contains `undefined` until it re-enters.
108
82
  */
109
- getRouteMatchStore: (
83
+ getMatchStore: (
110
84
  routeId: string,
111
85
  ) => RouterReadableStore<AnyRouteMatch | undefined>
112
86
 
113
87
  setMatches: (nextMatches: Array<AnyRouteMatch>) => void
114
- setPending: (nextMatches: Array<AnyRouteMatch>) => void
115
- setCached: (nextMatches: Array<AnyRouteMatch>) => void
116
88
  }
117
89
 
118
90
  export function createRouterStores<TRouteTree extends AnyRoute>(
119
- initialState: RouterState<TRouteTree>,
91
+ initialLocation: RouterState<TRouteTree>['location'],
120
92
  config: StoreConfig,
121
93
  ): RouterStores<TRouteTree> {
122
- const { createMutableStore, createReadonlyStore, batch, init } = config
94
+ const { createMutableStore, createReadonlyStore, batch } = config
123
95
 
124
96
  // non reactive utilities
125
- const matchStores = new Map<string, MatchStore>()
126
- const pendingMatchStores = new Map<string, MatchStore>()
127
- const cachedMatchStores = new Map<string, MatchStore>()
97
+ const byRoute = new Map<string, MatchStore>()
128
98
 
129
99
  // 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 pendingIds = createMutableStore<Array<string>>([])
140
- const cachedIds = createMutableStore<Array<string>>([])
100
+ const status = createMutableStore<RouterState<TRouteTree>['status']>('idle')
101
+ const location = createMutableStore(initialLocation)
102
+ const resolvedLocation =
103
+ createMutableStore<RouterState<TRouteTree>['resolvedLocation']>(undefined)
104
+ const ids = createMutableStore<Array<string>>([])
141
105
 
142
106
  // 1st order derived stores
143
107
  const matches = createReadonlyStore(() =>
144
- readPoolMatches(matchStores, matchesId.get()),
145
- )
146
- const pendingMatches = createReadonlyStore(() =>
147
- readPoolMatches(pendingMatchStores, pendingIds.get()),
148
- )
149
- const cachedMatches = createReadonlyStore(() =>
150
- readPoolMatches(cachedMatchStores, cachedIds.get()),
151
- )
152
- const firstId = createReadonlyStore(() => matchesId.get()[0])
153
- const hasPending = createReadonlyStore(() =>
154
- matchesId.get().some((matchId) => {
155
- const store = matchStores.get(matchId)
156
- return store?.get().status === 'pending'
157
- }),
108
+ ids.get().map((id) => byRoute.get(id)!.get()!),
158
109
  )
159
- const matchRouteDeps = createReadonlyStore(() => ({
160
- locationHref: location.get().href,
161
- resolvedLocationHref: resolvedLocation.get()?.href,
162
- status: status.get(),
163
- }))
164
110
 
165
111
  // compatibility "big" state store
166
112
  const __store = createReadonlyStore(() => ({
167
113
  status: status.get(),
168
- loadedAt: loadedAt.get(),
169
- isLoading: isLoading.get(),
170
- isTransitioning: isTransitioning.get(),
114
+ isLoading: status.get() === 'pending',
171
115
  matches: matches.get(),
172
116
  location: location.get(),
173
117
  resolvedLocation: resolvedLocation.get(),
174
- statusCode: statusCode.get(),
175
- redirect: redirect.get(),
176
118
  }))
177
119
 
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 getRouteMatchStore(
192
- routeId: string,
193
- ): RouterReadableStore<AnyRouteMatch | undefined> {
194
- let cached = matchStoreByRouteIdCache.get(routeId)
195
- if (!cached) {
196
- cached = createReadonlyStore(() => {
197
- // Reading matchesId.get() tracks it as a dependency.
198
- // When matchesId changes (navigation), this computed re-evaluates.
199
- const ids = matchesId.get()
200
- for (const id of ids) {
201
- const matchStore = matchStores.get(id)
202
- if (matchStore && matchStore.routeId === routeId) {
203
- // Reading matchStore.get() tracks it as a dependency.
204
- // When the match store's state changes, this re-evaluates.
205
- return matchStore.get()
206
- }
207
- }
208
- return undefined
209
- })
210
- matchStoreByRouteIdCache.set(routeId, cached)
120
+ function getMatchStore(routeId: string): MatchStore {
121
+ let matchStore = byRoute.get(routeId)
122
+ if (!matchStore) {
123
+ matchStore = createMutableStore<AnyRouteMatch | undefined>(undefined)
124
+ byRoute.set(routeId, matchStore)
211
125
  }
212
- return cached
126
+ return matchStore
213
127
  }
214
128
 
215
129
  const store = {
216
130
  // atoms
217
131
  status,
218
- loadedAt,
219
- isLoading,
220
- isTransitioning,
221
132
  location,
222
133
  resolvedLocation,
223
- statusCode,
224
- redirect,
225
- matchesId,
226
- pendingIds,
227
- cachedIds,
134
+ ids,
228
135
 
229
136
  // derived
230
137
  matches,
231
- pendingMatches,
232
- cachedMatches,
233
- firstId,
234
- hasPending,
235
- matchRouteDeps,
236
138
 
237
139
  // non-reactive state
238
- matchStores,
239
- pendingMatchStores,
240
- cachedMatchStores,
140
+ byRoute,
241
141
 
242
142
  // compatibility "big" state
243
143
  __store,
244
144
 
245
- // per-key computed stores
246
- getRouteMatchStore,
145
+ // stable per-route presentation atoms
146
+ getMatchStore,
247
147
 
248
148
  // methods
249
149
  setMatches,
250
- setPending,
251
- setCached,
252
150
  }
253
151
 
254
- // initialize the active matches
255
- setMatches(initialState.matches as Array<AnyRouteMatch>)
256
- init?.(store)
257
-
258
152
  // setters to update non-reactive utilities in sync with the reactive stores
259
153
  function setMatches(nextMatches: Array<AnyRouteMatch>) {
260
- reconcileMatchPool(
261
- nextMatches,
262
- matchStores,
263
- matchesId,
264
- createMutableStore,
265
- batch,
266
- )
267
- }
268
-
269
- function setPending(nextMatches: Array<AnyRouteMatch>) {
270
- reconcileMatchPool(
271
- nextMatches,
272
- pendingMatchStores,
273
- pendingIds,
274
- createMutableStore,
275
- batch,
276
- )
277
- }
278
-
279
- function setCached(nextMatches: Array<AnyRouteMatch>) {
280
- reconcileMatchPool(
281
- nextMatches,
282
- cachedMatchStores,
283
- cachedIds,
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.get())
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)
154
+ const previousIds = ids.get()
155
+ const nextIds = nextMatches.map((match) => match.routeId)
156
+
157
+ batch(() => {
158
+ // Publish lane membership first so framework trees reconcile departures
159
+ // before observers of a leaving route receive its tombstone.
160
+ if (!arraysEqual(previousIds, nextIds)) {
161
+ ids.set(nextIds)
320
162
  }
321
- }
322
163
 
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
164
+ for (const id of previousIds) {
165
+ if (!nextIds.includes(id)) {
166
+ byRoute.get(id)!.set(() => undefined)
167
+ }
330
168
  }
331
169
 
332
- existing.routeId = nextMatch.routeId
333
- if (existing.get() !== nextMatch) {
334
- existing.set(nextMatch)
170
+ for (const nextMatch of nextMatches) {
171
+ const matchStore = getMatchStore(nextMatch.routeId)
172
+ if (matchStore.get() !== nextMatch) {
173
+ matchStore.set(nextMatch)
174
+ }
335
175
  }
336
- }
176
+ })
177
+ }
337
178
 
338
- if (!arraysEqual(idStore.get(), nextIds)) {
339
- idStore.set(nextIds)
340
- }
341
- })
179
+ return store
342
180
  }