@tanstack/router-core 1.171.16-pre.0 → 1.171.17

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/src/router.ts CHANGED
@@ -41,7 +41,6 @@ import {
41
41
  preloadClientRoute,
42
42
  refreshClientRoute,
43
43
  replaceRouteChunk,
44
- transferMatchResources,
45
44
  } from './load-client'
46
45
  import {
47
46
  composeRewrites,
@@ -58,7 +57,6 @@ import type {
58
57
  import type { SearchParser, SearchSerializer } from './searchParams'
59
58
  import type { AnyRedirect, ResolvedRedirect } from './redirect'
60
59
  import type {
61
- ActivePreload,
62
60
  LoadTransaction,
63
61
  LoaderFlight,
64
62
  PendingSession,
@@ -766,7 +764,6 @@ export type LoadFn = (opts?: {
766
764
  sync?: boolean
767
765
  action?: { type: HistoryAction }
768
766
  _signal?: AbortSignal
769
- _dedupe?: boolean
770
767
  }) => Promise<void>
771
768
 
772
769
  export type CommitLocationFn = ({
@@ -778,7 +775,6 @@ export type CommitLocationFn = ({
778
775
  export type StartTransitionFn = (
779
776
  fn: () => void,
780
777
  expected: Array<AnyRouteMatch>,
781
- urgent?: boolean,
782
778
  ) => Promise<boolean>
783
779
 
784
780
  export interface MatchRoutesFn {
@@ -1027,8 +1023,8 @@ export interface RouterCore<
1027
1023
  _tx?: LoadTransaction
1028
1024
  /** Joinable in-flight loader generations keyed by match ID. */
1029
1025
  _flights?: Map<string, LoaderFlight>
1030
- /** Whole speculative lanes that an identical navigation may adopt. */
1031
- _preloads?: Map<string, ActivePreload>
1026
+ /** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */
1027
+ _preloads?: Map<AbortController, Array<AnyRouteMatch>>
1032
1028
  /** Owns cancellable work before a client transaction publishes. */
1033
1029
  _preflight?: AbortController
1034
1030
  /** Transfers one reconstructed SSR prefix into its initial client load. */
@@ -1037,8 +1033,11 @@ export interface RouterCore<
1037
1033
  _pending?: PendingSession
1038
1034
  /** Result of the latest server load, used to render or redirect. */
1039
1035
  _serverResult?: ServerLoadResult
1040
- /** Framework callback that acknowledges an exact matches publication. */
1041
- _rendered?: (matches: Array<AnyRouteMatch>) => void
1036
+ /** Framework publication waiting for an exact render acknowledgement. */
1037
+ _rendered?: [
1038
+ offered?: Array<AnyRouteMatch>,
1039
+ settle?: (rendered: boolean) => void,
1040
+ ]
1042
1041
  /** Development-only HMR reload for a route and its descendants. */
1043
1042
  _refreshRoute: (() => Promise<void>) | undefined
1044
1043
  /** Development-only replacement for a route's lazy chunk owner. */
@@ -1769,8 +1768,8 @@ export class RouterCore<
1769
1768
  : undefined
1770
1769
  const lastStateMatchId = lastStateMatch?.id
1771
1770
  const cached = this.lightweightCache.get(location)
1772
- if (cached && cached[0] === lastStateMatchId) {
1773
- return cached[1]
1771
+ if (cached && cached[0 /* lastMatchId */] === lastStateMatchId) {
1772
+ return cached[1 /* result */]
1774
1773
  }
1775
1774
 
1776
1775
  const { matchedRoutes, routeParams } = this.getMatchedRoutes(
@@ -2169,7 +2168,7 @@ export class RouterCore<
2169
2168
 
2170
2169
  // Don't commit to history if nothing changed
2171
2170
  if (isSameLocation) {
2172
- this.load({ _dedupe: true })
2171
+ this.load()
2173
2172
  } else {
2174
2173
  let {
2175
2174
  // eslint-disable-next-line prefer-const
@@ -2451,9 +2450,11 @@ export class RouterCore<
2451
2450
  }
2452
2451
 
2453
2452
  /**
2454
- * Invalidate the current matches and optionally force them back into a pending state.
2453
+ * Invalidate selected match generations and optionally force current matches
2454
+ * back into a pending state.
2455
2455
  *
2456
- * - Marks all matches that pass the optional `filter` as `invalid: true`.
2456
+ * - Marks committed and cached matches whose IDs are selected as invalid.
2457
+ * - Retires selected active preloads so older work cannot publish fresh data.
2457
2458
  *
2458
2459
  * The next load decides when to publish pending UI, so invalidation does not
2459
2460
  * mutate the currently rendered status.
@@ -2469,15 +2470,28 @@ export class RouterCore<
2469
2470
  > = (opts) => {
2470
2471
  const committedMatches = this._committed
2471
2472
  const filter = opts?.filter
2472
- const invalidIds = filter
2473
- ? new Set(
2474
- [...committedMatches, ...this._cache.values()]
2475
- .filter((match) => filter(match as MakeRouteMatchUnion<this>))
2476
- .map((match) => match.id),
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>),
2477
2483
  )
2478
- : undefined
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
+ }
2479
2493
  const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
2480
- if (!invalidIds || invalidIds.has(d.id)) {
2494
+ if (invalidIds.has(d.id)) {
2481
2495
  const route = this.routesById[d.routeId] as AnyRoute
2482
2496
  const next = {
2483
2497
  ...d,
@@ -2496,13 +2510,25 @@ export class RouterCore<
2496
2510
  return d
2497
2511
  }
2498
2512
 
2499
- const committed = committedMatches.map(invalidate)
2500
- this._committed = committed
2501
- const cache = new Map<string, AnyRouteMatch>()
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.
2502
2516
  for (const [id, match] of this._cache) {
2503
- cache.set(id, invalidate(match))
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()
2504
2531
  }
2505
- this._cache = cache
2506
2532
 
2507
2533
  this.shouldViewTransition = false
2508
2534
  return this.load({ sync: opts?.sync })
@@ -2554,37 +2580,54 @@ export class RouterCore<
2554
2580
  const cached = this._cache
2555
2581
  const preloads = this._preloads
2556
2582
  const filter = opts?.filter
2557
- const retained = new Map<string, AnyRouteMatch>()
2558
2583
  const discarded: Array<AnyRouteMatch> = []
2584
+ const discardedIds: Array<string> = []
2559
2585
  for (const [id, match] of cached) {
2560
- if (filter && !filter(match as MakeRouteMatchUnion<this>)) {
2561
- retained.set(id, match)
2562
- } else {
2586
+ if (!filter || filter(match as MakeRouteMatchUnion<this>)) {
2587
+ discardedIds.push(id)
2563
2588
  discarded.push(match)
2564
2589
  }
2565
2590
  }
2566
- const retainedPreloads = new Map<string, ActivePreload>()
2567
- const discardedPreloads: Array<ActivePreload> = []
2568
- for (const [href, preload] of preloads ?? []) {
2569
- if (!filter || preload[0].some(filter as any)) {
2570
- discardedPreloads.push(preload)
2571
- discarded.push(...preload[0])
2572
- } else {
2573
- retainedPreloads.set(href, preload)
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)
2574
2596
  }
2575
2597
  }
2576
2598
 
2577
- // Install both replacement authorities before releasing a public loader
2599
+ // Run every public filter before changing authority, then prune both maps
2600
+ // before releasing a public loader
2578
2601
  // signal, whose abort listeners can synchronously reenter the router.
2579
- this._cache = retained
2580
- this._preloads = retainedPreloads
2581
- transferMatchResources(this, discarded)
2582
- for (const preload of discardedPreloads) {
2583
- preload[1].abort()
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()
2584
2624
  }
2585
2625
  }
2586
2626
 
2587
- loadRouteChunk = loadRouteChunk
2627
+ loadRouteChunk: (
2628
+ route: AnyRoute,
2629
+ componentType?: 'errorComponent' | 'notFoundComponent' | false,
2630
+ ) => Promise<void> | undefined = loadRouteChunk
2588
2631
 
2589
2632
  preloadRoute: PreloadRouteFn<
2590
2633
  TRouteTree,