@tanstack/router-core 1.171.24 → 1.171.25

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.
@@ -82,17 +82,51 @@ function normalize(value: unknown, rejected: boolean): LoaderOutcome {
82
82
  return rejected ? [ERROR, value] : [SUCCESS, value]
83
83
  }
84
84
 
85
- function normalizeError(route: AnyRoute, cause: unknown): LoaderOutcome {
85
+ function normalizeError(
86
+ router: AnyRouter,
87
+ lane: { location: ParsedLocation },
88
+ route: AnyRoute,
89
+ cause: unknown,
90
+ signal?: AbortSignal,
91
+ notify = true,
92
+ ): LoaderOutcome {
93
+ signal?.throwIfAborted()
86
94
  let outcome = normalize(cause, true)
87
95
  if (outcome[0] !== ERROR) {
88
- return outcome
96
+ return materializeRedirect(router, lane, route, outcome, signal, notify)
89
97
  }
90
98
  try {
91
99
  route.options.onError?.(outcome[1])
92
100
  } catch (onErrorCause) {
93
101
  outcome = normalize(onErrorCause, true)
94
102
  }
95
- return outcome
103
+ signal?.throwIfAborted()
104
+ return materializeRedirect(router, lane, route, outcome, signal, notify)
105
+ }
106
+
107
+ function materializeRedirect(
108
+ router: AnyRouter,
109
+ lane: { location: ParsedLocation },
110
+ route: AnyRoute,
111
+ outcome: LoaderOutcome,
112
+ signal?: AbortSignal,
113
+ notify = true,
114
+ ): LoaderOutcome {
115
+ if (outcome[0] !== REDIRECTED) {
116
+ return outcome
117
+ }
118
+ signal?.throwIfAborted()
119
+ try {
120
+ outcome[1].options._fromLocation = lane.location
121
+ router.resolveRedirect(outcome[1])
122
+ signal?.throwIfAborted()
123
+ return outcome
124
+ } catch (cause) {
125
+ signal?.throwIfAborted()
126
+ return notify
127
+ ? normalizeError(router, lane, route, cause, signal, false)
128
+ : [ERROR, cause]
129
+ }
96
130
  }
97
131
 
98
132
  function maybe<TValue>(
@@ -197,7 +231,13 @@ async function contextualize(
197
231
  match.ssr = await resolveSsr(router, lane, index)
198
232
  } catch (cause) {
199
233
  signal?.throwIfAborted()
200
- failure = [index, stampNotFound(match, normalizeError(route, cause))]
234
+ failure = [
235
+ index,
236
+ stampNotFound(
237
+ match,
238
+ normalizeError(router, lane, route, cause, signal),
239
+ ),
240
+ ]
201
241
  end = index
202
242
  }
203
243
  signal?.throwIfAborted()
@@ -239,7 +279,13 @@ async function contextualize(
239
279
  } catch (cause) {
240
280
  signal?.throwIfAborted()
241
281
  if (!failure) {
242
- failure = [index, stampNotFound(match, normalizeError(route, cause))]
282
+ failure = [
283
+ index,
284
+ stampNotFound(
285
+ match,
286
+ normalizeError(router, lane, route, cause, signal),
287
+ ),
288
+ ]
243
289
  }
244
290
  end = index
245
291
  break
@@ -252,7 +298,10 @@ async function contextualize(
252
298
  if (validationError !== undefined) {
253
299
  failure = [
254
300
  index,
255
- stampNotFound(match, normalizeError(route, validationError)),
301
+ stampNotFound(
302
+ match,
303
+ normalizeError(router, lane, route, validationError, signal),
304
+ ),
256
305
  ]
257
306
  end = index
258
307
  break
@@ -293,7 +342,16 @@ async function contextualize(
293
342
  try {
294
343
  const beforeLoadContext = await route.options.beforeLoad(options)
295
344
  signal?.throwIfAborted()
296
- const outcome = stampNotFound(match, normalize(beforeLoadContext, false))
345
+ const outcome = stampNotFound(
346
+ match,
347
+ materializeRedirect(
348
+ router,
349
+ lane,
350
+ route,
351
+ normalize(beforeLoadContext, false),
352
+ signal,
353
+ ),
354
+ )
297
355
  if (outcome[0] !== SUCCESS) {
298
356
  failure = [index, outcome]
299
357
  end = index
@@ -307,7 +365,13 @@ async function contextualize(
307
365
  parentContext = match.context
308
366
  } catch (cause) {
309
367
  signal?.throwIfAborted()
310
- failure = [index, stampNotFound(match, normalizeError(route, cause))]
368
+ failure = [
369
+ index,
370
+ stampNotFound(
371
+ match,
372
+ normalizeError(router, lane, route, cause, signal),
373
+ ),
374
+ ]
311
375
  end = index
312
376
  break
313
377
  }
@@ -373,14 +437,13 @@ function createLoaderTask(
373
437
  (cause) => normalize(cause, true),
374
438
  )
375
439
  .then((result): LoaderOutcome => {
376
- if (
377
- result[0] !== REDIRECTED &&
378
- (signal?.aborted || match.abortController.signal.reason === lane)
379
- ) {
440
+ if (signal?.aborted || match.abortController.signal.reason === lane) {
380
441
  return [SKIPPED]
381
442
  }
382
443
  if (result[0] === ERROR) {
383
- result = normalizeError(route, result[1])
444
+ result = normalizeError(router, lane, route, result[1], signal)
445
+ } else {
446
+ result = materializeRedirect(router, lane, route, result, signal)
384
447
  }
385
448
  return stampNotFound(match, result)
386
449
  })
@@ -424,13 +487,13 @@ async function getNotFoundBoundary(
424
487
  }
425
488
  for (let candidate = index; candidate >= 0; candidate--) {
426
489
  const route = getRoute(router, matches[candidate]!)
427
- const loading = loadRouteChunk(route, false)
428
- if (loading) {
429
- try {
490
+ try {
491
+ const loading = loadRouteChunk(route, false)
492
+ if (loading) {
430
493
  await loading
431
- } catch {
432
- signal?.throwIfAborted()
433
494
  }
495
+ } catch {
496
+ signal?.throwIfAborted()
434
497
  }
435
498
  signal?.throwIfAborted()
436
499
  if (route.options.notFoundComponent) {
@@ -450,15 +513,6 @@ function abortMatches(
450
513
  }
451
514
  }
452
515
 
453
- function resolveServerRedirect(
454
- router: AnyRouter,
455
- location: ParsedLocation,
456
- value: AnyRedirect,
457
- ): ServerLoadResult {
458
- value.options._fromLocation = location
459
- return { type: 'redirect', redirect: router.resolveRedirect(value) }
460
- }
461
-
462
516
  async function applyFailure(
463
517
  router: AnyRouter,
464
518
  lane: ContextualizedLane,
@@ -530,7 +584,10 @@ async function loadNormalChunks(
530
584
  signal?.throwIfAborted()
531
585
  return [
532
586
  index,
533
- stampNotFound(match, normalizeError(route, cause)),
587
+ stampNotFound(
588
+ match,
589
+ normalizeError(router, lane, route, cause, signal),
590
+ ),
534
591
  ] as IndexedOutcome
535
592
  },
536
593
  )
@@ -540,7 +597,13 @@ async function loadNormalChunks(
540
597
  }
541
598
  } catch (cause) {
542
599
  signal?.throwIfAborted()
543
- chunks.push([index, stampNotFound(match, normalizeError(route, cause))])
600
+ chunks.push([
601
+ index,
602
+ stampNotFound(
603
+ match,
604
+ normalizeError(router, lane, route, cause, signal),
605
+ ),
606
+ ])
544
607
  }
545
608
  }
546
609
  for (const chunk of chunks) {
@@ -698,7 +761,7 @@ async function executeServerLane(
698
761
 
699
762
  if (control?.[1][0] === REDIRECTED) {
700
763
  abortMatches(lane.matches, 0, lane)
701
- return resolveServerRedirect(router, location, control[1][1])
764
+ return { type: 'redirect', redirect: control[1][1] }
702
765
  }
703
766
 
704
767
  let failure = lane.failure ?? loaderFailure
@@ -742,7 +805,7 @@ async function executeServerLane(
742
805
  if (requiredFailure) {
743
806
  if (requiredFailure[1][0] === REDIRECTED) {
744
807
  abortMatches(lane.matches)
745
- return resolveServerRedirect(router, location, requiredFailure[1][1])
808
+ return { type: 'redirect', redirect: requiredFailure[1][1] }
746
809
  }
747
810
  failure = requiredFailure
748
811
  }
@@ -808,9 +871,7 @@ export async function loadServerRoute(
808
871
  })
809
872
  if (next.publicHref !== canonical.publicHref) {
810
873
  const href = canonical.publicHref || '/'
811
- throw canonical.external
812
- ? redirect({ href })
813
- : redirect({ href, _builtLocation: canonical })
874
+ throw redirect({ href })
814
875
  }
815
876
 
816
877
  const fromLocation = router.stores.resolvedLocation.get()
@@ -828,7 +889,8 @@ export async function loadServerRoute(
828
889
  if (!isRedirect(cause)) {
829
890
  throw cause
830
891
  }
831
- result = resolveServerRedirect(router, next, cause)
892
+ cause.options._fromLocation = next
893
+ result = { type: 'redirect', redirect: router.resolveRedirect(cause) }
832
894
  }
833
895
 
834
896
  router._serverResult = result
package/src/redirect.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { NavigateOptions } from './link'
2
2
  import type { AnyRouter, RegisteredRouter } from './router'
3
- import type { ParsedLocation } from './location'
4
3
 
5
4
  export type AnyRedirect = Redirect<any, any, any, any, any>
6
5
 
@@ -14,13 +13,7 @@ export type Redirect<
14
13
  TMaskFrom extends string = TFrom,
15
14
  TMaskTo extends string = '.',
16
15
  > = Response & {
17
- options: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & {
18
- /**
19
- * @internal
20
- * A **trusted** built location that can be used to redirect to.
21
- */
22
- _builtLocation?: ParsedLocation
23
- }
16
+ options: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
24
17
  }
25
18
 
26
19
  export type RedirectOptions<
@@ -50,11 +43,6 @@ export type RedirectOptions<
50
43
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#headers-property)
51
44
  */
52
45
  headers?: HeadersInit
53
- /**
54
- * @internal
55
- * A **trusted** built location that can be used to redirect to.
56
- */
57
- _builtLocation?: ParsedLocation
58
46
  } & NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
59
47
 
60
48
  export type ResolvedRedirect<
@@ -122,11 +110,7 @@ export function redirect<
122
110
  ): Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> {
123
111
  opts.statusCode = opts.statusCode || opts.code || 307
124
112
 
125
- if (
126
- !opts._builtLocation &&
127
- !opts.reloadDocument &&
128
- typeof opts.href === 'string'
129
- ) {
113
+ if (!opts.reloadDocument && typeof opts.href === 'string') {
130
114
  try {
131
115
  new URL(opts.href)
132
116
  opts.reloadDocument = true
package/src/router.ts CHANGED
@@ -692,13 +692,7 @@ export type PreloadRouteFn<
692
692
  TTo,
693
693
  TMaskFrom,
694
694
  TMaskTo
695
- > & {
696
- /**
697
- * @internal
698
- * A **trusted** built location that can be used to redirect to.
699
- */
700
- _builtLocation?: ParsedLocation
701
- },
695
+ >,
702
696
  ) => Promise<Array<AnyRouteMatch> | undefined>
703
697
 
704
698
  export type MatchRouteFn<
@@ -937,10 +931,10 @@ export function runRouteLifecycle(
937
931
  router: AnyRouter,
938
932
  previous: Array<AnyRouteMatch>,
939
933
  matches: Array<AnyRouteMatch>,
940
- isCurrent?: () => boolean,
934
+ owner?: LoadTransaction,
941
935
  ): void {
942
936
  for (const match of previous) {
943
- if (isCurrent?.() === false) {
937
+ if (owner && router._tx !== owner) {
944
938
  return
945
939
  }
946
940
  if (!matches.some((candidate) => candidate.routeId === match.routeId)) {
@@ -950,7 +944,7 @@ export function runRouteLifecycle(
950
944
  }
951
945
  }
952
946
  for (const match of matches) {
953
- if (isCurrent?.() === false) {
947
+ if (owner && router._tx !== owner) {
954
948
  return
955
949
  }
956
950
  const route = (router.routesById as Record<string, AnyRoute>)[
@@ -1846,6 +1840,19 @@ export class RouterCore<
1846
1840
  unmaskOnReload?: boolean
1847
1841
  } = {},
1848
1842
  ): ParsedLocation => {
1843
+ if (dest.href) {
1844
+ const parsed = parseHref(dest.href, {} as ParsedHistoryState)
1845
+ dest = {
1846
+ ...dest,
1847
+ to: executeRewriteInput(
1848
+ this.rewrite,
1849
+ new URL(parsed.pathname, this.origin),
1850
+ ).pathname,
1851
+ search: this.options.parseSearch(parsed.search),
1852
+ hash: parsed.hash.slice(1),
1853
+ }
1854
+ }
1855
+
1849
1856
  // We allow the caller to override the current location
1850
1857
  const currentLocation =
1851
1858
  dest._fromLocation || this._pendingLocation || this.latestLocation
@@ -2216,38 +2223,12 @@ export class RouterCore<
2216
2223
  hashScrollIntoView,
2217
2224
  viewTransition,
2218
2225
  ignoreBlocker,
2219
- _redirects,
2220
- href,
2221
2226
  ...rest
2222
- }: BuildNextOptions &
2223
- CommitLocationOptions & { _redirects?: number } = {}) => {
2224
- if (href) {
2225
- const currentIndex = this.history.location.state.__TSR_index
2226
-
2227
- const parsed = parseHref(href, {
2228
- __TSR_index: replace ? currentIndex : currentIndex + 1,
2229
- })
2230
-
2231
- // If the href contains the basepath, we need to strip it before setting `to`
2232
- // because `buildLocation` will add the basepath back when creating the final URL.
2233
- // Without this, hrefs like '/app/about' would become '/app/app/about'.
2234
- const hrefUrl = new URL(parsed.pathname, this.origin)
2235
- const rewrittenUrl = executeRewriteInput(this.rewrite, hrefUrl)
2236
-
2237
- rest.to = rewrittenUrl.pathname
2238
- rest.search = this.options.parseSearch(parsed.search)
2239
- // remove the leading `#` from the hash
2240
- rest.hash = parsed.hash.slice(1)
2241
- }
2242
-
2227
+ }: BuildNextOptions & CommitLocationOptions = {}) => {
2243
2228
  const location = this.buildLocation({
2244
2229
  ...(rest as any),
2245
2230
  _includeValidateSearch: true,
2246
2231
  })
2247
- if (_redirects) {
2248
- ;(location as typeof location & { _redirects?: number })._redirects =
2249
- _redirects
2250
- }
2251
2232
 
2252
2233
  this._pendingLocation = location as ParsedLocation<
2253
2234
  FullSearchSchema<TRouteTree>
@@ -2514,9 +2495,8 @@ export class RouterCore<
2514
2495
  resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
2515
2496
  const locationHeader = redirect.headers.get('Location')
2516
2497
 
2517
- if (!redirect.options.href || redirect.options._builtLocation) {
2518
- const location =
2519
- redirect.options._builtLocation ?? this.buildLocation(redirect.options)
2498
+ if (!redirect.options.href) {
2499
+ const location = this.buildLocation(redirect.options)
2520
2500
  const href = location.publicHref || '/'
2521
2501
  redirect.options.href = href
2522
2502
  redirect.headers.set('Location', href)
@@ -2535,7 +2515,6 @@ export class RouterCore<
2535
2515
 
2536
2516
  if (
2537
2517
  redirect.options.href &&
2538
- !redirect.options._builtLocation &&
2539
2518
  // Check for dangerous protocols before processing the redirect
2540
2519
  isDangerousProtocol(redirect.options.href, this.protocolAllowlist)
2541
2520
  ) {
@@ -2611,7 +2590,8 @@ export class RouterCore<
2611
2590
  TTrailingSlashOption,
2612
2591
  TDefaultStructuralSharingOption,
2613
2592
  TRouterHistory
2614
- > = (opts) => preloadClientRoute(this, opts)
2593
+ > = (opts: any, builtLocation?: ParsedLocation) =>
2594
+ preloadClientRoute(this, opts, 0, builtLocation)
2615
2595
 
2616
2596
  matchRoute: MatchRouteFn<
2617
2597
  TRouteTree,