@tanstack/router-core 1.171.16-pre.0 → 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/link.cjs.map +1 -1
- package/dist/cjs/link.d.cts +5 -3
- package/dist/cjs/load-client.cjs +169 -246
- package/dist/cjs/load-client.cjs.map +1 -1
- package/dist/cjs/load-client.d.cts +10 -39
- package/dist/cjs/router.cjs +42 -22
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +14 -10
- package/dist/esm/link.d.ts +5 -3
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/load-client.d.ts +10 -39
- package/dist/esm/load-client.js +171 -247
- package/dist/esm/load-client.js.map +1 -1
- package/dist/esm/router.d.ts +14 -10
- package/dist/esm/router.js +44 -23
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- 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/link.ts +5 -3
- package/src/load-client.ts +582 -632
- package/src/router.ts +87 -44
package/src/load-client.ts
CHANGED
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
// can rewrite relative imports for both ESM and CJS.
|
|
3
3
|
import { isNotFound } from './not-found'
|
|
4
4
|
import { isRedirect } from './redirect'
|
|
5
|
-
import {
|
|
6
|
-
_getUserHistoryState,
|
|
7
|
-
getLocationChangeInfo,
|
|
8
|
-
runRouteLifecycle,
|
|
9
|
-
} from './router'
|
|
10
|
-
import { deepEqual } from './utils'
|
|
5
|
+
import { getLocationChangeInfo, runRouteLifecycle } from './router'
|
|
11
6
|
import { hydrateSsrMatchId } from './ssr/ssr-match-id'
|
|
12
7
|
import type { GLOBAL_SEROVAL, GLOBAL_TSR } from './ssr/constants'
|
|
13
8
|
import type { AnySerializationAdapter } from './ssr/serializer/transformer'
|
|
@@ -46,26 +41,35 @@ function preloadComponent(
|
|
|
46
41
|
return (route.options[type] as any)?.preload?.()
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
function loadComponents(
|
|
44
|
+
function loadComponents(
|
|
45
|
+
route: AnyRoute,
|
|
46
|
+
onPendingReady?: () => void,
|
|
47
|
+
): Promise<void> | undefined {
|
|
50
48
|
const component = preloadComponent(route, 'component')
|
|
51
49
|
const pending = preloadComponent(route, 'pendingComponent')
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const pendingReady =
|
|
51
|
+
onPendingReady && pending ? pending.then(onPendingReady) : pending
|
|
52
|
+
if (onPendingReady && !pending) {
|
|
53
|
+
onPendingReady()
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
if (component && pendingReady) {
|
|
56
|
+
return Promise.all([component, pendingReady]).then(() => {})
|
|
57
|
+
}
|
|
58
|
+
return component ?? pendingReady
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export function loadRouteChunk(
|
|
59
62
|
route: AnyRoute,
|
|
60
63
|
// `false` waits only for lazy route options, before a boundary is selected.
|
|
61
64
|
componentType?: 'errorComponent' | 'notFoundComponent' | false,
|
|
65
|
+
onPendingReady?: () => void,
|
|
62
66
|
): Promise<void> | undefined {
|
|
63
67
|
const afterLazy = () =>
|
|
64
68
|
componentType === false
|
|
65
69
|
? undefined
|
|
66
70
|
: componentType
|
|
67
71
|
? preloadComponent(route, componentType)
|
|
68
|
-
: loadComponents(route)
|
|
72
|
+
: loadComponents(route, onPendingReady)
|
|
69
73
|
const current = route._lazy
|
|
70
74
|
if (current) {
|
|
71
75
|
return current === true ? afterLazy() : current.then(afterLazy)
|
|
@@ -161,11 +165,11 @@ const REDIRECTED = 3
|
|
|
161
165
|
const CANCELED = 4
|
|
162
166
|
|
|
163
167
|
type LoaderOutcome =
|
|
164
|
-
| [typeof SUCCESS, data: unknown]
|
|
165
|
-
| [typeof ERROR, error: unknown]
|
|
166
|
-
| [typeof NOT_FOUND, error: NotFoundError]
|
|
167
|
-
| [typeof REDIRECTED, redirect: AnyRedirect]
|
|
168
|
-
| [typeof CANCELED]
|
|
168
|
+
| [kind: typeof SUCCESS, data: unknown]
|
|
169
|
+
| [kind: typeof ERROR, error: unknown]
|
|
170
|
+
| [kind: typeof NOT_FOUND, error: NotFoundError]
|
|
171
|
+
| [kind: typeof REDIRECTED, redirect: AnyRedirect]
|
|
172
|
+
| [kind: typeof CANCELED]
|
|
169
173
|
|
|
170
174
|
type IndexedOutcome = [index: number, outcome: LoaderOutcome, boundary?: number]
|
|
171
175
|
|
|
@@ -190,31 +194,6 @@ declare const matchPhase: unique symbol
|
|
|
190
194
|
*/
|
|
191
195
|
type SettledMatch = WorkMatch & { readonly [matchPhase]: 'settled' }
|
|
192
196
|
|
|
193
|
-
export type LaneInputs = [
|
|
194
|
-
routeTree: AnyRoute,
|
|
195
|
-
context: unknown,
|
|
196
|
-
additionalContext: unknown,
|
|
197
|
-
state: object,
|
|
198
|
-
search: object,
|
|
199
|
-
maskedLocation:
|
|
200
|
-
| [
|
|
201
|
-
href: string,
|
|
202
|
-
state: object,
|
|
203
|
-
search: object,
|
|
204
|
-
unmaskOnReload: boolean | undefined,
|
|
205
|
-
]
|
|
206
|
-
| undefined,
|
|
207
|
-
]
|
|
208
|
-
|
|
209
|
-
export type ActivePreload = [
|
|
210
|
-
matches: Array<AnyRouteMatch>,
|
|
211
|
-
controller: AbortController,
|
|
212
|
-
result: Promise<LaneResult>,
|
|
213
|
-
semanticOwner: Array<AnyRouteMatch>,
|
|
214
|
-
inputs: LaneInputs,
|
|
215
|
-
redirects: number,
|
|
216
|
-
]
|
|
217
|
-
|
|
218
197
|
export type LoadTransaction = [
|
|
219
198
|
controller: AbortController,
|
|
220
199
|
redirects: number,
|
|
@@ -224,12 +203,14 @@ export type LoadTransaction = [
|
|
|
224
203
|
done: Promise<void>,
|
|
225
204
|
/**
|
|
226
205
|
* Dev-only HMR refresh mode. Presence is the mode flag; a refresh always
|
|
227
|
-
* carries the presentation it started from
|
|
228
|
-
*
|
|
206
|
+
* carries the presentation it started from and its optional hydration
|
|
207
|
+
* handoff. While a publication awaits acknowledgement, its rollback lives
|
|
208
|
+
* with the transaction that owns the publication.
|
|
229
209
|
*/
|
|
230
210
|
refresh?: [
|
|
231
211
|
presentation: Array<AnyRouteMatch>,
|
|
232
212
|
handoff: NonNullable<AnyRouter['_handoff']> | undefined,
|
|
213
|
+
rollback?: () => boolean,
|
|
233
214
|
],
|
|
234
215
|
]
|
|
235
216
|
|
|
@@ -244,10 +225,9 @@ export type PendingSession = [
|
|
|
244
225
|
]
|
|
245
226
|
|
|
246
227
|
type CoordinatorRouter = AnyRouter & {
|
|
247
|
-
/**
|
|
248
|
-
_preloads?: Map<
|
|
228
|
+
/** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */
|
|
229
|
+
_preloads?: Map<AbortController, Array<AnyRouteMatch>>
|
|
249
230
|
_refreshNextLoad?: boolean
|
|
250
|
-
_rollbackRefresh?: () => void
|
|
251
231
|
_cancelTransition?: () => void
|
|
252
232
|
}
|
|
253
233
|
|
|
@@ -262,14 +242,14 @@ type PublicationCheckpoint = {
|
|
|
262
242
|
type LoaderTask = [
|
|
263
243
|
index: number,
|
|
264
244
|
outcome: Promise<LoaderOutcome>,
|
|
265
|
-
|
|
245
|
+
chunkFailure: Promise<IndexedOutcome | undefined>,
|
|
266
246
|
candidate?: WorkMatch,
|
|
267
247
|
]
|
|
268
248
|
|
|
269
249
|
type BackgroundLoaderTask = [
|
|
270
250
|
index: number,
|
|
271
251
|
outcome: Promise<LoaderOutcome>,
|
|
272
|
-
|
|
252
|
+
chunkFailure: Promise<IndexedOutcome | undefined>,
|
|
273
253
|
candidate: WorkMatch,
|
|
274
254
|
]
|
|
275
255
|
|
|
@@ -286,15 +266,15 @@ type ExecuteLaneOptions = [
|
|
|
286
266
|
]
|
|
287
267
|
|
|
288
268
|
type ControlOutcome =
|
|
289
|
-
| [typeof REDIRECTED, redirect: AnyRedirect]
|
|
290
|
-
| [typeof CANCELED]
|
|
269
|
+
| [kind: typeof REDIRECTED, redirect: AnyRedirect]
|
|
270
|
+
| [kind: typeof CANCELED]
|
|
291
271
|
|
|
292
272
|
type LaneResult = ProjectedLane | ControlOutcome
|
|
293
273
|
|
|
294
274
|
function isControl(
|
|
295
275
|
result: Lane<any> | ControlOutcome,
|
|
296
276
|
): result is ControlOutcome {
|
|
297
|
-
return typeof result[0] === 'number'
|
|
277
|
+
return typeof result[0 /* location or kind */] === 'number'
|
|
298
278
|
}
|
|
299
279
|
|
|
300
280
|
export function waitFor<T>(
|
|
@@ -337,11 +317,11 @@ function normalize(
|
|
|
337
317
|
|
|
338
318
|
function normalizeError(route: AnyRoute, cause: unknown): LoaderOutcome {
|
|
339
319
|
let outcome = normalize(cause, true, route.id)
|
|
340
|
-
if (outcome[0] !== ERROR) {
|
|
320
|
+
if (outcome[0 /* kind */] !== ERROR) {
|
|
341
321
|
return outcome
|
|
342
322
|
}
|
|
343
323
|
try {
|
|
344
|
-
route.options.onError?.(outcome[1])
|
|
324
|
+
route.options.onError?.(outcome[1 /* error */])
|
|
345
325
|
} catch (onErrorCause) {
|
|
346
326
|
outcome = normalize(onErrorCause, true, route.id)
|
|
347
327
|
}
|
|
@@ -353,8 +333,11 @@ function normalizeLaneError(
|
|
|
353
333
|
cause: unknown,
|
|
354
334
|
options: ExecuteLaneOptions,
|
|
355
335
|
): LoaderOutcome {
|
|
356
|
-
if (
|
|
357
|
-
options[0].
|
|
336
|
+
if (
|
|
337
|
+
options[0 /* controller */].signal.aborted ||
|
|
338
|
+
!options[2 /* isCurrent */]()
|
|
339
|
+
) {
|
|
340
|
+
options[0 /* controller */].abort()
|
|
358
341
|
return [CANCELED]
|
|
359
342
|
}
|
|
360
343
|
return normalizeError(route, cause)
|
|
@@ -373,15 +356,16 @@ async function contextualize(
|
|
|
373
356
|
lane: MatchedLane,
|
|
374
357
|
options: ExecuteLaneOptions,
|
|
375
358
|
end: number,
|
|
359
|
+
planSuccessfulLane: () => void,
|
|
376
360
|
): Promise<IndexedOutcome | undefined> {
|
|
377
361
|
const [location, matches] = lane
|
|
378
|
-
const signal = options[0].signal
|
|
379
|
-
const preload = !!options[4]
|
|
380
|
-
for (let index = options[7] ?? 0; index < end; index++) {
|
|
362
|
+
const signal = options[0 /* controller */].signal
|
|
363
|
+
const preload = !!options[4 /* preload */]
|
|
364
|
+
for (let index = options[7 /* resolvedPrefix */] ?? 0; index < end; index++) {
|
|
381
365
|
const match = matches[index]!
|
|
382
366
|
const route = getRoute(router, match)
|
|
383
367
|
|
|
384
|
-
match.abortController = options[0]
|
|
368
|
+
match.abortController = options[0 /* controller */]
|
|
385
369
|
// Contextualization is serial, so the previous match already contains the
|
|
386
370
|
// complete parent context for this route.
|
|
387
371
|
const parentContext =
|
|
@@ -392,7 +376,7 @@ async function contextualize(
|
|
|
392
376
|
navigate: navigateFrom(router, location),
|
|
393
377
|
buildLocation: router.buildLocation,
|
|
394
378
|
cause: preload ? ('preload' as const) : match.cause,
|
|
395
|
-
abortController: options[0],
|
|
379
|
+
abortController: options[0 /* controller */],
|
|
396
380
|
preload,
|
|
397
381
|
matches,
|
|
398
382
|
routeId: route.id,
|
|
@@ -417,8 +401,8 @@ async function contextualize(
|
|
|
417
401
|
releaseFlight(router, match)
|
|
418
402
|
return [index, normalizeLaneError(route, cause, options)]
|
|
419
403
|
}
|
|
420
|
-
if (signal.aborted || !options[2]()) {
|
|
421
|
-
options[0].abort()
|
|
404
|
+
if (signal.aborted || !options[2 /* isCurrent */]()) {
|
|
405
|
+
options[0 /* controller */].abort()
|
|
422
406
|
return [index, [CANCELED]]
|
|
423
407
|
}
|
|
424
408
|
const validationError = match.paramsError ?? match.searchError
|
|
@@ -452,16 +436,16 @@ async function contextualize(
|
|
|
452
436
|
if (previousStatus === 'success') {
|
|
453
437
|
match.status = 'pending'
|
|
454
438
|
}
|
|
455
|
-
options[8]?.()
|
|
439
|
+
options[8 /* onReady */]?.()
|
|
456
440
|
try {
|
|
457
|
-
setFetching(router, match, 'beforeLoad', options[0])
|
|
441
|
+
setFetching(router, match, 'beforeLoad', options[0 /* controller */])
|
|
458
442
|
const result = await waitFor(beforeLoad(beforeLoadContext), signal)
|
|
459
|
-
if (!options[2]()) {
|
|
460
|
-
options[0].abort()
|
|
443
|
+
if (!options[2 /* isCurrent */]()) {
|
|
444
|
+
options[0 /* controller */].abort()
|
|
461
445
|
return [index, [CANCELED]]
|
|
462
446
|
}
|
|
463
447
|
const outcome = normalize(result, false, route.id)
|
|
464
|
-
if (outcome[0] !== SUCCESS) {
|
|
448
|
+
if (outcome[0 /* kind */] !== SUCCESS) {
|
|
465
449
|
releaseFlight(router, match)
|
|
466
450
|
return [index, outcome]
|
|
467
451
|
}
|
|
@@ -476,73 +460,54 @@ async function contextualize(
|
|
|
476
460
|
if (previousStatus === 'success' && match.status === 'pending') {
|
|
477
461
|
match.status = 'success'
|
|
478
462
|
}
|
|
479
|
-
setFetching(router, match, false, options[0])
|
|
463
|
+
setFetching(router, match, false, options[0 /* controller */])
|
|
480
464
|
}
|
|
481
465
|
}
|
|
482
466
|
|
|
467
|
+
// Let a synchronous lane claim predecessor flights before this frame yields.
|
|
468
|
+
planSuccessfulLane()
|
|
483
469
|
return
|
|
484
470
|
}
|
|
485
471
|
|
|
486
472
|
function releaseOwnedFlight(
|
|
487
473
|
router: AnyRouter,
|
|
488
|
-
|
|
474
|
+
match: WorkMatch,
|
|
489
475
|
flight?: LoaderFlight,
|
|
490
476
|
): AbortController | undefined {
|
|
491
|
-
if (!flight || --flight[2]) {
|
|
477
|
+
if (!flight || --flight[2 /* leases */]) {
|
|
492
478
|
return
|
|
493
479
|
}
|
|
494
|
-
if (router._flights?.get(id) === flight) {
|
|
495
|
-
router.
|
|
480
|
+
if (router._flights?.get(match.id) === flight) {
|
|
481
|
+
const current = router._tx
|
|
482
|
+
if (
|
|
483
|
+
current &&
|
|
484
|
+
!current[0 /* controller */].signal.aborted &&
|
|
485
|
+
!(process.env.NODE_ENV !== 'production' && current[6 /* refresh */]) &&
|
|
486
|
+
!current[3 /* matches */].includes(match) &&
|
|
487
|
+
current[3 /* matches */].some((candidate) => candidate.id === match.id) &&
|
|
488
|
+
current[3 /* matches */].some(
|
|
489
|
+
(candidate) => candidate.isFetching === 'beforeLoad',
|
|
490
|
+
)
|
|
491
|
+
) {
|
|
492
|
+
// Keep work discoverable only while the current lane is still running
|
|
493
|
+
// beforeLoad. Loader planning performs the matching zero-owner sweep.
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
router._flights.delete(match.id)
|
|
496
497
|
}
|
|
497
|
-
return flight[1]
|
|
498
|
+
return flight[1 /* controller */]
|
|
498
499
|
}
|
|
499
500
|
|
|
500
501
|
function releaseFlight(router: AnyRouter, match: WorkMatch): void {
|
|
501
502
|
const flight = match._flight
|
|
502
503
|
match._flight = undefined
|
|
503
|
-
releaseOwnedFlight(router, match
|
|
504
|
-
}
|
|
505
|
-
export function laneInputs(
|
|
506
|
-
router: AnyRouter,
|
|
507
|
-
location: ParsedLocation,
|
|
508
|
-
): LaneInputs {
|
|
509
|
-
const masked = location.maskedLocation
|
|
510
|
-
return [
|
|
511
|
-
router.routeTree,
|
|
512
|
-
router.options.context ?? {},
|
|
513
|
-
router.options.additionalContext,
|
|
514
|
-
_getUserHistoryState(location.state),
|
|
515
|
-
location.search,
|
|
516
|
-
masked && [
|
|
517
|
-
masked.href,
|
|
518
|
-
_getUserHistoryState(masked.state),
|
|
519
|
-
masked.search,
|
|
520
|
-
masked.unmaskOnReload,
|
|
521
|
-
],
|
|
522
|
-
]
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function samePreloadLane(
|
|
526
|
-
preload: ActivePreload,
|
|
527
|
-
router: AnyRouter,
|
|
528
|
-
location: ParsedLocation,
|
|
529
|
-
redirects: number,
|
|
530
|
-
): boolean {
|
|
531
|
-
return (
|
|
532
|
-
preload[3] === router._committed &&
|
|
533
|
-
preload[5] === redirects &&
|
|
534
|
-
deepEqual(preload[4], laneInputs(router, location)) &&
|
|
535
|
-
!preload[0].some(
|
|
536
|
-
(match) => getRoute(router, match as WorkMatch).options.preload === false,
|
|
537
|
-
)
|
|
538
|
-
)
|
|
504
|
+
releaseOwnedFlight(router, match, flight)?.abort()
|
|
539
505
|
}
|
|
540
|
-
|
|
541
506
|
/**
|
|
542
507
|
* Not passing in a `next` ownership recipient
|
|
543
508
|
* is equivalent to discarding the match resources
|
|
544
509
|
*/
|
|
545
|
-
|
|
510
|
+
function transferMatchResources(
|
|
546
511
|
router: AnyRouter,
|
|
547
512
|
previous: Array<AnyRouteMatch>,
|
|
548
513
|
next?: Array<AnyRouteMatch>,
|
|
@@ -552,7 +517,7 @@ export function transferMatchResources(
|
|
|
552
517
|
if (!next?.includes(match)) {
|
|
553
518
|
const flight = match._flight
|
|
554
519
|
match._flight = undefined
|
|
555
|
-
const controller = releaseOwnedFlight(router, match
|
|
520
|
+
const controller = releaseOwnedFlight(router, match, flight)
|
|
556
521
|
if (controller) {
|
|
557
522
|
abort.push(controller)
|
|
558
523
|
}
|
|
@@ -563,16 +528,57 @@ export function transferMatchResources(
|
|
|
563
528
|
}
|
|
564
529
|
}
|
|
565
530
|
|
|
566
|
-
function
|
|
567
|
-
|
|
568
|
-
|
|
531
|
+
function transferPredecessorResources(
|
|
532
|
+
router: AnyRouter,
|
|
533
|
+
previous: Array<AnyRouteMatch>,
|
|
534
|
+
next: Array<AnyRouteMatch>,
|
|
535
|
+
): void {
|
|
536
|
+
const abort: Array<AbortController> = []
|
|
537
|
+
for (const match of previous as Array<WorkMatch>) {
|
|
538
|
+
if (!next.includes(match)) {
|
|
539
|
+
const flight = match._flight
|
|
540
|
+
match._flight = undefined
|
|
541
|
+
if (
|
|
542
|
+
flight?.[2 /* leases */] === 1 &&
|
|
543
|
+
router._flights?.get(match.id) === flight &&
|
|
544
|
+
!(
|
|
545
|
+
process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */]
|
|
546
|
+
) &&
|
|
547
|
+
next.some((candidate) => candidate.id === match.id)
|
|
548
|
+
) {
|
|
549
|
+
// The successor has not made its same-ID reload decision yet.
|
|
550
|
+
flight[2 /* leases */] = 0
|
|
551
|
+
} else {
|
|
552
|
+
const controller = releaseOwnedFlight(router, match, flight)
|
|
553
|
+
if (controller) {
|
|
554
|
+
abort.push(controller)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
for (const controller of abort) {
|
|
560
|
+
controller.abort()
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function releaseUnownedFlights(router: AnyRouter): void {
|
|
565
|
+
const abort: Array<AbortController> = []
|
|
566
|
+
for (const [id, flight] of router._flights ?? []) {
|
|
567
|
+
if (!flight[2 /* leases */]) {
|
|
568
|
+
router._flights!.delete(id)
|
|
569
|
+
abort.push(flight[1 /* controller */])
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
for (const controller of abort) {
|
|
573
|
+
controller.abort()
|
|
574
|
+
}
|
|
569
575
|
}
|
|
570
576
|
|
|
571
577
|
function acquireMatchResources(matches: Array<AnyRouteMatch>): void {
|
|
572
578
|
for (const match of matches as Array<WorkMatch>) {
|
|
573
579
|
const flight = match._flight
|
|
574
580
|
if (flight) {
|
|
575
|
-
flight[2]++
|
|
581
|
+
flight[2 /* leases */]++
|
|
576
582
|
}
|
|
577
583
|
}
|
|
578
584
|
}
|
|
@@ -584,7 +590,7 @@ function setFetching(
|
|
|
584
590
|
owner?: AbortController,
|
|
585
591
|
): void {
|
|
586
592
|
match.isFetching = value
|
|
587
|
-
if (owner && router._tx?.[0] !== owner) {
|
|
593
|
+
if (owner && router._tx?.[0 /* controller */] !== owner) {
|
|
588
594
|
return
|
|
589
595
|
}
|
|
590
596
|
const store = router.stores.byRoute.get(match.routeId)
|
|
@@ -603,7 +609,7 @@ function getLoaderContext(
|
|
|
603
609
|
parentMatchPromise: Promise<WorkMatch> | undefined,
|
|
604
610
|
preload: boolean,
|
|
605
611
|
): LoaderFnContext {
|
|
606
|
-
const location = lane[0]
|
|
612
|
+
const location = lane[0 /* location */]
|
|
607
613
|
return {
|
|
608
614
|
params: match.params,
|
|
609
615
|
location,
|
|
@@ -638,13 +644,12 @@ async function loadResource(
|
|
|
638
644
|
}
|
|
639
645
|
|
|
640
646
|
let flight = match._flight
|
|
641
|
-
let joined = !!flight
|
|
642
647
|
setFetching(router, match, 'loader', owner)
|
|
643
648
|
try {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
649
|
+
if (!flight) {
|
|
650
|
+
const controller = new AbortController()
|
|
651
|
+
flight = [
|
|
652
|
+
Promise.resolve()
|
|
648
653
|
.then(() =>
|
|
649
654
|
loader(
|
|
650
655
|
getLoaderContext(
|
|
@@ -663,34 +668,35 @@ async function loadResource(
|
|
|
663
668
|
(cause) => normalize(cause, true, route.id),
|
|
664
669
|
)
|
|
665
670
|
.then((result): LoaderOutcome => {
|
|
666
|
-
|
|
667
|
-
|
|
671
|
+
// The registry controls discovery; leases keep current consumers
|
|
672
|
+
// sharing the same terminal outcome.
|
|
673
|
+
if (
|
|
674
|
+
result[0 /* kind */] !== SUCCESS &&
|
|
675
|
+
router._flights?.get(match.id) === flight
|
|
676
|
+
) {
|
|
677
|
+
router._flights!.delete(match.id)
|
|
678
|
+
if (!flight![2 /* leases */]) {
|
|
679
|
+
controller.abort()
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return result[0 /* kind */] === ERROR && flight![2 /* leases */]
|
|
683
|
+
? normalizeError(route, result[1 /* error */])
|
|
668
684
|
: result
|
|
669
|
-
})
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
match.
|
|
674
|
-
match.abortController = flight[1]
|
|
675
|
-
try {
|
|
676
|
-
const outcome = await waitFor(flight[0], signal)
|
|
677
|
-
if (!joined || outcome[0] === SUCCESS || outcome[0] === REDIRECTED) {
|
|
678
|
-
return outcome
|
|
679
|
-
}
|
|
680
|
-
} catch (cause) {
|
|
681
|
-
if (cause === signal) {
|
|
682
|
-
releaseFlight(router, match)
|
|
683
|
-
return [CANCELED]
|
|
684
|
-
}
|
|
685
|
-
throw cause
|
|
686
|
-
}
|
|
687
|
-
releaseFlight(router, match)
|
|
688
|
-
if (signal.aborted) {
|
|
689
|
-
return [CANCELED]
|
|
690
|
-
}
|
|
691
|
-
flight = undefined
|
|
692
|
-
joined = false
|
|
685
|
+
}),
|
|
686
|
+
controller,
|
|
687
|
+
1,
|
|
688
|
+
]
|
|
689
|
+
;(router._flights ??= new Map()).set(match.id, flight)
|
|
693
690
|
}
|
|
691
|
+
match._flight = flight
|
|
692
|
+
match.abortController = flight[1 /* controller */]
|
|
693
|
+
return await waitFor(flight[0 /* outcome */], signal)
|
|
694
|
+
} catch (cause) {
|
|
695
|
+
if (cause !== signal) {
|
|
696
|
+
throw cause
|
|
697
|
+
}
|
|
698
|
+
releaseFlight(router, match)
|
|
699
|
+
return [CANCELED]
|
|
694
700
|
} finally {
|
|
695
701
|
setFetching(router, match, false, owner)
|
|
696
702
|
}
|
|
@@ -701,14 +707,14 @@ function settleInto(
|
|
|
701
707
|
result: LoaderOutcome,
|
|
702
708
|
preload: boolean,
|
|
703
709
|
): asserts match is SettledMatch {
|
|
704
|
-
if (result[0] === SUCCESS) {
|
|
705
|
-
match.loaderData = result[1]
|
|
710
|
+
if (result[0 /* kind */] === SUCCESS) {
|
|
711
|
+
match.loaderData = result[1 /* data */]
|
|
706
712
|
match.error = undefined
|
|
707
713
|
match.status = 'success'
|
|
708
714
|
match.invalid = false
|
|
709
715
|
match.updatedAt = Date.now()
|
|
710
716
|
match.preload = preload
|
|
711
|
-
} else if (result[0] !== REDIRECTED) {
|
|
717
|
+
} else if (result[0 /* kind */] !== REDIRECTED) {
|
|
712
718
|
// Reduction installs only the selected terminal failure. Every other
|
|
713
719
|
// settled attempt remains a renderable, stale match in that lane.
|
|
714
720
|
match.status = 'success'
|
|
@@ -739,7 +745,7 @@ export function cacheLoaderMatch(
|
|
|
739
745
|
context: {},
|
|
740
746
|
} as WorkMatch
|
|
741
747
|
if (cached._flight) {
|
|
742
|
-
cached._flight[2]++
|
|
748
|
+
cached._flight[2 /* leases */]++
|
|
743
749
|
}
|
|
744
750
|
router._cache.set(match.id, cached)
|
|
745
751
|
if (current) {
|
|
@@ -751,11 +757,11 @@ function getParentSnapshot(
|
|
|
751
757
|
match: WorkMatch,
|
|
752
758
|
outcome: LoaderOutcome,
|
|
753
759
|
): WorkMatch {
|
|
754
|
-
if (outcome[0] === ERROR || outcome[0] === NOT_FOUND) {
|
|
760
|
+
if (outcome[0 /* kind */] === ERROR || outcome[0 /* kind */] === NOT_FOUND) {
|
|
755
761
|
return {
|
|
756
762
|
...match,
|
|
757
|
-
status: outcome[0] === ERROR ? 'error' : 'notFound',
|
|
758
|
-
error: outcome[1],
|
|
763
|
+
status: outcome[0 /* kind */] === ERROR ? 'error' : 'notFound',
|
|
764
|
+
error: outcome[1 /* error */],
|
|
759
765
|
_flight: undefined,
|
|
760
766
|
}
|
|
761
767
|
}
|
|
@@ -770,14 +776,14 @@ function createLoaderTask(
|
|
|
770
776
|
semanticParent: Promise<WorkMatch> | undefined,
|
|
771
777
|
options: ExecuteLaneOptions,
|
|
772
778
|
): Promise<WorkMatch> {
|
|
773
|
-
const match = lane[1][index]!
|
|
779
|
+
const match = lane[1 /* matches */][index]!
|
|
774
780
|
const route = getRoute(router, match)
|
|
775
|
-
const preload = !!options[4]
|
|
781
|
+
const preload = !!options[4 /* preload */]
|
|
776
782
|
const plannedCacheMatch = preload ? router._cache.get(match.id) : undefined
|
|
783
|
+
let configured
|
|
777
784
|
let reload = false
|
|
778
785
|
let reloadFailure: LoaderOutcome | undefined
|
|
779
786
|
try {
|
|
780
|
-
let configured
|
|
781
787
|
if (match.status === 'success') {
|
|
782
788
|
configured = route.options.shouldReload
|
|
783
789
|
if (typeof configured === 'function') {
|
|
@@ -787,14 +793,14 @@ function createLoaderTask(
|
|
|
787
793
|
lane,
|
|
788
794
|
match,
|
|
789
795
|
route,
|
|
790
|
-
options[0],
|
|
796
|
+
options[0 /* controller */],
|
|
791
797
|
semanticParent,
|
|
792
798
|
preload,
|
|
793
799
|
),
|
|
794
800
|
)
|
|
795
801
|
}
|
|
796
|
-
if (!options[2]()) {
|
|
797
|
-
options[0].abort()
|
|
802
|
+
if (!options[2 /* isCurrent */]()) {
|
|
803
|
+
options[0 /* controller */].abort()
|
|
798
804
|
reloadFailure = [CANCELED]
|
|
799
805
|
}
|
|
800
806
|
}
|
|
@@ -803,7 +809,7 @@ function createLoaderTask(
|
|
|
803
809
|
reload = true
|
|
804
810
|
} else {
|
|
805
811
|
const staleAge =
|
|
806
|
-
options[4] || match.preload
|
|
812
|
+
options[4 /* preload */] || match.preload
|
|
807
813
|
? (route.options.preloadStaleTime ??
|
|
808
814
|
router.options.defaultPreloadStaleTime ??
|
|
809
815
|
30_000)
|
|
@@ -813,9 +819,9 @@ function createLoaderTask(
|
|
|
813
819
|
configured ||
|
|
814
820
|
(configured === undefined &&
|
|
815
821
|
Date.now() - match.updatedAt >= staleAge &&
|
|
816
|
-
(options[6] ||
|
|
822
|
+
(options[6 /* forceStaleReload */] ||
|
|
817
823
|
match.cause === 'enter' ||
|
|
818
|
-
options[3].some(
|
|
824
|
+
options[3 /* base */].some(
|
|
819
825
|
(candidate) =>
|
|
820
826
|
candidate.routeId === match.routeId &&
|
|
821
827
|
candidate.id !== match.id,
|
|
@@ -831,12 +837,27 @@ function createLoaderTask(
|
|
|
831
837
|
const routeLoader = route.options.loader
|
|
832
838
|
const loader =
|
|
833
839
|
typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler
|
|
840
|
+
let donor =
|
|
841
|
+
(!preload || route.options.preload !== false) &&
|
|
842
|
+
routeLoader &&
|
|
843
|
+
!(process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */])
|
|
844
|
+
? router._flights?.get(match.id)
|
|
845
|
+
: undefined
|
|
846
|
+
if (donor === match._flight || reloadFailure) {
|
|
847
|
+
donor = undefined
|
|
848
|
+
} else if (donor && !reload && !preload && configured === undefined) {
|
|
849
|
+
// Normal cache policy accepts an already-running generation even when this
|
|
850
|
+
// lane itself would not have started another loader.
|
|
851
|
+
reload = true
|
|
852
|
+
} else if (!reload) {
|
|
853
|
+
donor = undefined
|
|
854
|
+
}
|
|
834
855
|
const background = !!(
|
|
835
856
|
routeLoader &&
|
|
836
857
|
reload &&
|
|
837
858
|
match.status === 'success' &&
|
|
838
859
|
!preload &&
|
|
839
|
-
!options[5] &&
|
|
860
|
+
!options[5 /* sync */] &&
|
|
840
861
|
((typeof routeLoader === 'function'
|
|
841
862
|
? undefined
|
|
842
863
|
: routeLoader?.staleReloadMode) ??
|
|
@@ -845,20 +866,19 @@ function createLoaderTask(
|
|
|
845
866
|
const loaded = reload && (!preload || route.options.preload !== false)
|
|
846
867
|
const blocking =
|
|
847
868
|
loaded && !background && (match.status !== 'success' || !!routeLoader)
|
|
869
|
+
const onLazyReady =
|
|
870
|
+
route.lazyFn && route._lazy !== true ? options[8 /* onReady */] : undefined
|
|
848
871
|
if (loaded && !routeLoader) {
|
|
849
872
|
match.invalid = false
|
|
850
873
|
match.updatedAt = Date.now()
|
|
851
874
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
donor = undefined
|
|
855
|
-
} else if (donor) {
|
|
856
|
-
donor[2]++
|
|
875
|
+
if (donor) {
|
|
876
|
+
donor[2 /* leases */]++
|
|
857
877
|
}
|
|
858
878
|
if (blocking) {
|
|
859
879
|
const acceptedFlight = match._flight
|
|
860
880
|
match._flight = donor
|
|
861
|
-
releaseOwnedFlight(router, match
|
|
881
|
+
releaseOwnedFlight(router, match, acceptedFlight)?.abort()
|
|
862
882
|
// A successful route without a loader has no blocking work to present. It
|
|
863
883
|
// still gets a task so its chunk and derived assets participate in the
|
|
864
884
|
// lane, but putting it back into pending would hide an already-rendered
|
|
@@ -866,7 +886,7 @@ function createLoaderTask(
|
|
|
866
886
|
if (match.status === 'success') {
|
|
867
887
|
match.status = 'pending'
|
|
868
888
|
}
|
|
869
|
-
options[8]?.()
|
|
889
|
+
options[8 /* onReady */]?.()
|
|
870
890
|
}
|
|
871
891
|
if (!loaded) {
|
|
872
892
|
match.isFetching = false
|
|
@@ -883,13 +903,17 @@ function createLoaderTask(
|
|
|
883
903
|
loader,
|
|
884
904
|
semanticParent,
|
|
885
905
|
preload,
|
|
886
|
-
options[0],
|
|
906
|
+
options[0 /* controller */],
|
|
887
907
|
)
|
|
888
908
|
const outcome = rawOutcome.then((result) => {
|
|
889
909
|
if (blocking) {
|
|
890
910
|
settleInto(match, result, preload)
|
|
891
|
-
if (result[0] === SUCCESS) {
|
|
892
|
-
if (
|
|
911
|
+
if (result[0 /* kind */] === SUCCESS) {
|
|
912
|
+
if (
|
|
913
|
+
preload &&
|
|
914
|
+
routeLoader &&
|
|
915
|
+
!options[0 /* controller */].signal.aborted
|
|
916
|
+
) {
|
|
893
917
|
cacheLoaderMatch(router, match, plannedCacheMatch)
|
|
894
918
|
}
|
|
895
919
|
// A route is renderable only after both its data and normal component
|
|
@@ -901,13 +925,10 @@ function createLoaderTask(
|
|
|
901
925
|
})
|
|
902
926
|
|
|
903
927
|
const rawChunkFailure = waitFor(
|
|
904
|
-
Promise.resolve().then(() => loadRouteChunk(route)),
|
|
905
|
-
options[0].signal,
|
|
928
|
+
Promise.resolve().then(() => loadRouteChunk(route, undefined, onLazyReady)),
|
|
929
|
+
options[0 /* controller */].signal,
|
|
906
930
|
).then(
|
|
907
|
-
() =>
|
|
908
|
-
options[8]?.()
|
|
909
|
-
return undefined
|
|
910
|
-
},
|
|
931
|
+
() => undefined,
|
|
911
932
|
(cause): IndexedOutcome => [
|
|
912
933
|
index,
|
|
913
934
|
normalizeLaneError(route, cause, options),
|
|
@@ -918,12 +939,12 @@ function createLoaderTask(
|
|
|
918
939
|
if (
|
|
919
940
|
blocking &&
|
|
920
941
|
!failure &&
|
|
921
|
-
result[0] === SUCCESS &&
|
|
942
|
+
result[0 /* kind */] === SUCCESS &&
|
|
922
943
|
match.status === 'pending' &&
|
|
923
|
-
options[2]()
|
|
944
|
+
options[2 /* isCurrent */]()
|
|
924
945
|
) {
|
|
925
946
|
match.status = 'success'
|
|
926
|
-
options[8]?.()
|
|
947
|
+
options[8 /* onReady */]?.()
|
|
927
948
|
}
|
|
928
949
|
return failure
|
|
929
950
|
}),
|
|
@@ -948,13 +969,18 @@ function createLoaderTask(
|
|
|
948
969
|
loader,
|
|
949
970
|
semanticParent,
|
|
950
971
|
false,
|
|
951
|
-
options[0],
|
|
972
|
+
options[0 /* controller */],
|
|
952
973
|
).then((result) => {
|
|
953
974
|
match.isFetching = false
|
|
954
975
|
settleInto(candidate, result, false)
|
|
955
976
|
return result
|
|
956
977
|
})
|
|
957
|
-
;(lane[2] ??= []).push([
|
|
978
|
+
;(lane[2 /* background */] ??= []).push([
|
|
979
|
+
index,
|
|
980
|
+
backgroundOutcome,
|
|
981
|
+
chunkFailure,
|
|
982
|
+
candidate,
|
|
983
|
+
])
|
|
958
984
|
return backgroundOutcome.then((result) =>
|
|
959
985
|
getParentSnapshot(candidate, result),
|
|
960
986
|
)
|
|
@@ -967,10 +993,12 @@ async function getNotFoundBoundary(
|
|
|
967
993
|
signal: AbortSignal,
|
|
968
994
|
fallback = 0,
|
|
969
995
|
): Promise<number> {
|
|
970
|
-
const cause = indexed?.[1][1
|
|
996
|
+
const cause = indexed?.[1 /* outcome */][1 /* error or redirect */] as
|
|
997
|
+
| NotFoundError
|
|
998
|
+
| undefined
|
|
971
999
|
let index = cause?.routeId
|
|
972
1000
|
? matches.findIndex((match) => match.routeId === cause.routeId)
|
|
973
|
-
: (indexed?.[0] ?? matches.length - 1)
|
|
1001
|
+
: (indexed?.[0 /* index */] ?? matches.length - 1)
|
|
974
1002
|
if (index < 0) {
|
|
975
1003
|
index = 0
|
|
976
1004
|
}
|
|
@@ -994,12 +1022,12 @@ async function getNotFoundBoundary(
|
|
|
994
1022
|
}
|
|
995
1023
|
|
|
996
1024
|
function discardBackground(router: AnyRouter, lane: Lane<any>): void {
|
|
997
|
-
if (lane[2]) {
|
|
1025
|
+
if (lane[2 /* background */]) {
|
|
998
1026
|
transferMatchResources(
|
|
999
1027
|
router,
|
|
1000
|
-
lane[2].map((task) => task[3]),
|
|
1028
|
+
lane[2 /* background */].map((task) => task[3 /* candidate */]),
|
|
1001
1029
|
)
|
|
1002
|
-
lane[2] = undefined
|
|
1030
|
+
lane[2 /* background */] = undefined
|
|
1003
1031
|
}
|
|
1004
1032
|
}
|
|
1005
1033
|
|
|
@@ -1014,26 +1042,29 @@ async function settleTasks(
|
|
|
1014
1042
|
try {
|
|
1015
1043
|
await Promise.all(
|
|
1016
1044
|
tasks.map((task) =>
|
|
1017
|
-
task[1].then(async (outcome) => {
|
|
1018
|
-
const taskIndex = task[0]
|
|
1045
|
+
task[1 /* outcome */].then(async (outcome) => {
|
|
1046
|
+
const taskIndex = task[0 /* index */]
|
|
1019
1047
|
if (gate && taskIndex >= (await gate)) {
|
|
1020
1048
|
return
|
|
1021
1049
|
}
|
|
1022
|
-
if (outcome[0] >= REDIRECTED) {
|
|
1050
|
+
if (outcome[0 /* kind */] >= REDIRECTED) {
|
|
1023
1051
|
throw [taskIndex, outcome] as IndexedOutcome
|
|
1024
1052
|
}
|
|
1025
|
-
if (!loaderFailure && outcome[0] !== SUCCESS) {
|
|
1053
|
+
if (!loaderFailure && outcome[0 /* kind */] !== SUCCESS) {
|
|
1026
1054
|
loaderFailure = [taskIndex, outcome]
|
|
1027
1055
|
// Every started descendant must settle before an ordinary failure
|
|
1028
1056
|
// wins because a redirect from any of them remains control flow.
|
|
1029
1057
|
await Promise.all(
|
|
1030
1058
|
(redirectTasks ?? []).map((nextTask) => {
|
|
1031
|
-
if (nextTask[0] <= taskIndex) {
|
|
1059
|
+
if (nextTask[0 /* index */] <= taskIndex) {
|
|
1032
1060
|
return
|
|
1033
1061
|
}
|
|
1034
|
-
return nextTask[1].then((nextOutcome) => {
|
|
1035
|
-
if (nextOutcome[0] === REDIRECTED) {
|
|
1036
|
-
throw [
|
|
1062
|
+
return nextTask[1 /* outcome */].then((nextOutcome) => {
|
|
1063
|
+
if (nextOutcome[0 /* kind */] === REDIRECTED) {
|
|
1064
|
+
throw [
|
|
1065
|
+
nextTask[0 /* index */],
|
|
1066
|
+
nextOutcome,
|
|
1067
|
+
] as IndexedOutcome
|
|
1037
1068
|
}
|
|
1038
1069
|
})
|
|
1039
1070
|
}),
|
|
@@ -1057,43 +1088,43 @@ async function reduceLane(
|
|
|
1057
1088
|
settlement: Promise<IndexedOutcome | undefined>,
|
|
1058
1089
|
onReady?: () => void,
|
|
1059
1090
|
): Promise<ReducedLane | ControlOutcome> {
|
|
1060
|
-
const matches = lane[1]
|
|
1091
|
+
const matches = lane[1 /* matches */]
|
|
1061
1092
|
let failure = await settlement
|
|
1062
1093
|
let redirectLimitExceeded = false
|
|
1063
1094
|
const plannedBoundary = matches.findIndex((match) => match._notFound)
|
|
1064
1095
|
const boundaryOf = (found: IndexedOutcome) =>
|
|
1065
|
-
found[1][0] === NOT_FOUND
|
|
1096
|
+
found[1 /* outcome */][0 /* kind */] === NOT_FOUND
|
|
1066
1097
|
? getNotFoundBoundary(router, matches, found, controller.signal)
|
|
1067
|
-
: found[0]
|
|
1098
|
+
: found[0 /* index */]
|
|
1068
1099
|
let readinessEnd = plannedBoundary < 0 ? matches.length : plannedBoundary
|
|
1069
1100
|
|
|
1070
|
-
if ((failure?.[1][0] ?? 0) >= REDIRECTED) {
|
|
1101
|
+
if ((failure?.[1 /* outcome */][0 /* kind */] ?? 0) >= REDIRECTED) {
|
|
1071
1102
|
readinessEnd = 0
|
|
1072
1103
|
} else if (failure) {
|
|
1073
|
-
readinessEnd = failure[2] ??= await boundaryOf(failure)
|
|
1104
|
+
readinessEnd = failure[2 /* boundary */] ??= await boundaryOf(failure)
|
|
1074
1105
|
for (const task of tasks) {
|
|
1075
|
-
if (task[0] >= readinessEnd) {
|
|
1106
|
+
if (task[0 /* index */] >= readinessEnd) {
|
|
1076
1107
|
break
|
|
1077
1108
|
}
|
|
1078
|
-
const outcome = await task[1]
|
|
1109
|
+
const outcome = await task[1 /* outcome */]
|
|
1079
1110
|
// Presence means a loader previously succeeded, even with `undefined`.
|
|
1080
1111
|
if (
|
|
1081
|
-
outcome[0] !== SUCCESS &&
|
|
1082
|
-
outcome[0] < REDIRECTED &&
|
|
1083
|
-
!('loaderData' in matches[task[0]]!)
|
|
1112
|
+
outcome[0 /* kind */] !== SUCCESS &&
|
|
1113
|
+
outcome[0 /* kind */] < REDIRECTED &&
|
|
1114
|
+
!('loaderData' in matches[task[0 /* index */]]!)
|
|
1084
1115
|
) {
|
|
1085
|
-
failure = [task[0], outcome]
|
|
1086
|
-
readinessEnd = failure[2] = await boundaryOf(failure)
|
|
1116
|
+
failure = [task[0 /* index */], outcome]
|
|
1117
|
+
readinessEnd = failure[2 /* boundary */] = await boundaryOf(failure)
|
|
1087
1118
|
break
|
|
1088
1119
|
}
|
|
1089
1120
|
}
|
|
1090
1121
|
}
|
|
1091
1122
|
|
|
1092
1123
|
for (const task of tasks) {
|
|
1093
|
-
if (task[0] >= readinessEnd) {
|
|
1124
|
+
if (task[0 /* index */] >= readinessEnd) {
|
|
1094
1125
|
break
|
|
1095
1126
|
}
|
|
1096
|
-
const chunkFailure = await task[2]
|
|
1127
|
+
const chunkFailure = await task[2 /* chunkFailure */]
|
|
1097
1128
|
if (!chunkFailure) {
|
|
1098
1129
|
continue
|
|
1099
1130
|
}
|
|
@@ -1101,11 +1132,11 @@ async function reduceLane(
|
|
|
1101
1132
|
break
|
|
1102
1133
|
}
|
|
1103
1134
|
|
|
1104
|
-
if ((failure?.[1][0] ?? 0) >= REDIRECTED) {
|
|
1105
|
-
const outcome = failure![1]
|
|
1135
|
+
if ((failure?.[1 /* outcome */][0 /* kind */] ?? 0) >= REDIRECTED) {
|
|
1136
|
+
const outcome = failure![1 /* outcome */]
|
|
1106
1137
|
if (
|
|
1107
|
-
outcome[0] !== REDIRECTED ||
|
|
1108
|
-
outcome[1].options.reloadDocument ||
|
|
1138
|
+
outcome[0 /* kind */] !== REDIRECTED ||
|
|
1139
|
+
outcome[1 /* redirect */].options.reloadDocument ||
|
|
1109
1140
|
redirects < 20
|
|
1110
1141
|
) {
|
|
1111
1142
|
discardBackground(router, lane)
|
|
@@ -1116,13 +1147,13 @@ async function reduceLane(
|
|
|
1116
1147
|
}
|
|
1117
1148
|
|
|
1118
1149
|
const boundary = failure
|
|
1119
|
-
? (failure[2] ?? (await boundaryOf(failure)))
|
|
1150
|
+
? (failure[2 /* boundary */] ?? (await boundaryOf(failure)))
|
|
1120
1151
|
: plannedBoundary
|
|
1121
1152
|
if (boundary >= 0) {
|
|
1122
|
-
const outcome = failure?.[1]
|
|
1123
|
-
const kind = outcome?.[0]
|
|
1153
|
+
const outcome = failure?.[1 /* outcome */]
|
|
1154
|
+
const kind = outcome?.[0 /* kind */]
|
|
1124
1155
|
const match = matches[boundary]!
|
|
1125
|
-
const cause = outcome?.[1]
|
|
1156
|
+
const cause = outcome?.[1 /* error or redirect */]
|
|
1126
1157
|
const install = () => {
|
|
1127
1158
|
if (outcome) {
|
|
1128
1159
|
match._notFound = undefined
|
|
@@ -1169,9 +1200,11 @@ async function reduceLane(
|
|
|
1169
1200
|
} else if (redirectLimitExceeded) {
|
|
1170
1201
|
controller.abort()
|
|
1171
1202
|
await Promise.all([
|
|
1172
|
-
...tasks.map((task) => task[1]),
|
|
1173
|
-
...tasks.map((task) => task[2]),
|
|
1174
|
-
...(lane[2] ?? []).map(
|
|
1203
|
+
...tasks.map((task) => task[1 /* outcome */]),
|
|
1204
|
+
...tasks.map((task) => task[2 /* chunkFailure */]),
|
|
1205
|
+
...(lane[2 /* background */] ?? []).map(
|
|
1206
|
+
(task) => task[1 /* outcome */],
|
|
1207
|
+
),
|
|
1175
1208
|
])
|
|
1176
1209
|
discardBackground(router, lane)
|
|
1177
1210
|
transferMatchResources(router, matches)
|
|
@@ -1187,9 +1220,9 @@ export async function projectLane(
|
|
|
1187
1220
|
lane: ReducedLane,
|
|
1188
1221
|
signal: AbortSignal,
|
|
1189
1222
|
start = 0,
|
|
1190
|
-
end = lane[1].length,
|
|
1223
|
+
end = lane[1 /* matches */].length,
|
|
1191
1224
|
): Promise<ProjectedLane> {
|
|
1192
|
-
const matches = lane[1]
|
|
1225
|
+
const matches = lane[1 /* matches */]
|
|
1193
1226
|
for (let index = start; index < end; index++) {
|
|
1194
1227
|
const match = matches[index]!
|
|
1195
1228
|
const routeOptions = getRoute(router, match).options
|
|
@@ -1239,9 +1272,9 @@ async function executeClientLane(
|
|
|
1239
1272
|
if (router.options.notFoundMode !== 'root' && plannedBoundary >= 0) {
|
|
1240
1273
|
const boundary = await getNotFoundBoundary(
|
|
1241
1274
|
router,
|
|
1242
|
-
matched[1],
|
|
1275
|
+
matched[1 /* matches */],
|
|
1243
1276
|
undefined,
|
|
1244
|
-
options[0].signal,
|
|
1277
|
+
options[0 /* controller */].signal,
|
|
1245
1278
|
plannedBoundary,
|
|
1246
1279
|
)
|
|
1247
1280
|
if (boundary !== plannedBoundary) {
|
|
@@ -1251,42 +1284,54 @@ async function executeClientLane(
|
|
|
1251
1284
|
plannedBoundary = boundary
|
|
1252
1285
|
}
|
|
1253
1286
|
let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1
|
|
1254
|
-
// From here on `matched` is contextualized: `contextualize` communicates
|
|
1255
|
-
// through mutation plus a failure return, so the phase brand is asserted at
|
|
1256
|
-
// the two use sites below rather than granted by a (byte-costing) return.
|
|
1257
|
-
const failure = await contextualize(router, matched, options, end)
|
|
1258
|
-
if (failure) {
|
|
1259
|
-
options[5] = true
|
|
1260
|
-
}
|
|
1261
1287
|
const tasks: Array<LoaderTask> = []
|
|
1262
|
-
const start = options[7] ?? 0
|
|
1288
|
+
const start = options[7 /* resolvedPrefix */] ?? 0
|
|
1263
1289
|
let semanticParent = start
|
|
1264
|
-
? Promise.resolve(matched[1][start - 1]!)
|
|
1290
|
+
? Promise.resolve(matched[1 /* matches */][start - 1]!)
|
|
1265
1291
|
: undefined
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1292
|
+
const planSuccessfulLane = () => {
|
|
1293
|
+
for (let index = start; index < end; index++) {
|
|
1294
|
+
if (options[0 /* controller */].signal.aborted) {
|
|
1295
|
+
break
|
|
1296
|
+
}
|
|
1297
|
+
semanticParent = createLoaderTask(
|
|
1298
|
+
router,
|
|
1299
|
+
matched as ContextualizedLane,
|
|
1300
|
+
index,
|
|
1301
|
+
tasks,
|
|
1302
|
+
semanticParent,
|
|
1303
|
+
options,
|
|
1304
|
+
)
|
|
1305
|
+
}
|
|
1277
1306
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1307
|
+
// From here on `matched` is contextualized: `contextualize` communicates
|
|
1308
|
+
// through mutation plus a failure return, so the phase brand is asserted at
|
|
1309
|
+
// the two use sites below rather than granted by a (byte-costing) return.
|
|
1310
|
+
const failure = await contextualize(
|
|
1311
|
+
router,
|
|
1312
|
+
matched,
|
|
1313
|
+
options,
|
|
1314
|
+
end,
|
|
1315
|
+
planSuccessfulLane,
|
|
1316
|
+
)
|
|
1317
|
+
if (failure) {
|
|
1318
|
+
options[5 /* sync */] = true
|
|
1319
|
+
end = failure[0 /* index */]
|
|
1320
|
+
if (failure[1 /* outcome */][0 /* kind */] === NOT_FOUND) {
|
|
1321
|
+
failure[2 /* boundary */] = await getNotFoundBoundary(
|
|
1322
|
+
router,
|
|
1323
|
+
matched[1 /* matches */],
|
|
1324
|
+
failure,
|
|
1325
|
+
options[0 /* controller */].signal,
|
|
1326
|
+
)
|
|
1327
|
+
end = Math.min(end, failure[2 /* boundary */] + 1)
|
|
1328
|
+
} else if (failure[1 /* outcome */][0 /* kind */] >= REDIRECTED) {
|
|
1329
|
+
end = 0
|
|
1281
1330
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
tasks,
|
|
1287
|
-
semanticParent,
|
|
1288
|
-
options,
|
|
1289
|
-
)
|
|
1331
|
+
planSuccessfulLane()
|
|
1332
|
+
}
|
|
1333
|
+
if (options[2 /* isCurrent */]() && !options[4 /* preload */]) {
|
|
1334
|
+
releaseUnownedFlights(router)
|
|
1290
1335
|
}
|
|
1291
1336
|
let reduced: ReducedLane | ControlOutcome
|
|
1292
1337
|
try {
|
|
@@ -1294,21 +1339,21 @@ async function executeClientLane(
|
|
|
1294
1339
|
router,
|
|
1295
1340
|
matched as ContextualizedLane,
|
|
1296
1341
|
tasks,
|
|
1297
|
-
options[0],
|
|
1298
|
-
options[1],
|
|
1299
|
-
settleTasks(tasks, failure, matched[2]),
|
|
1300
|
-
options[8],
|
|
1342
|
+
options[0 /* controller */],
|
|
1343
|
+
options[1 /* redirects */],
|
|
1344
|
+
settleTasks(tasks, failure, matched[2 /* background */]),
|
|
1345
|
+
options[8 /* onReady */],
|
|
1301
1346
|
)
|
|
1302
|
-
if (matched[2]?.length) {
|
|
1303
|
-
matched[3] = settleTasks(
|
|
1304
|
-
matched[2],
|
|
1347
|
+
if (matched[2 /* background */]?.length) {
|
|
1348
|
+
matched[3 /* backgroundSettlement */] = settleTasks(
|
|
1349
|
+
matched[2 /* background */],
|
|
1305
1350
|
undefined,
|
|
1306
1351
|
undefined,
|
|
1307
1352
|
reduction.then(
|
|
1308
1353
|
(foreground) =>
|
|
1309
1354
|
isControl(foreground)
|
|
1310
1355
|
? 0
|
|
1311
|
-
: _getRenderedMatches(foreground[1]).length,
|
|
1356
|
+
: _getRenderedMatches(foreground[1 /* matches */]).length,
|
|
1312
1357
|
() => 0,
|
|
1313
1358
|
),
|
|
1314
1359
|
)
|
|
@@ -1324,8 +1369,10 @@ async function executeClientLane(
|
|
|
1324
1369
|
return projectLane(
|
|
1325
1370
|
router,
|
|
1326
1371
|
reduced,
|
|
1327
|
-
options[0].signal,
|
|
1328
|
-
options[7] === reduced[1].length
|
|
1372
|
+
options[0 /* controller */].signal,
|
|
1373
|
+
options[7 /* resolvedPrefix */] === reduced[1 /* matches */].length
|
|
1374
|
+
? options[7 /* resolvedPrefix */]
|
|
1375
|
+
: 0,
|
|
1329
1376
|
)
|
|
1330
1377
|
}
|
|
1331
1378
|
|
|
@@ -1384,71 +1431,83 @@ function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
|
1384
1431
|
}
|
|
1385
1432
|
let session = router._pending
|
|
1386
1433
|
let tookOver = false
|
|
1387
|
-
const sessionMatchId =
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1434
|
+
const sessionMatchId =
|
|
1435
|
+
session?.[0 /* owner */][3 /* matches */][session[1 /* boundary */]]?.id
|
|
1436
|
+
if (session?.[0 /* owner */] !== tx) {
|
|
1437
|
+
if (
|
|
1438
|
+
session &&
|
|
1439
|
+
tx[3 /* matches */][session[1 /* boundary */]]?.id === sessionMatchId
|
|
1440
|
+
) {
|
|
1441
|
+
session[0 /* owner */] = tx
|
|
1391
1442
|
tookOver = true
|
|
1392
1443
|
} else {
|
|
1393
|
-
clearTimeout(session?.[3])
|
|
1444
|
+
clearTimeout(session?.[3 /* timer */])
|
|
1394
1445
|
router._pending = session = undefined
|
|
1395
1446
|
}
|
|
1396
1447
|
}
|
|
1397
|
-
const config = pendingConfig(router, tx[3])
|
|
1448
|
+
const config = pendingConfig(router, tx[3 /* matches */])
|
|
1398
1449
|
if (!config) {
|
|
1399
1450
|
return
|
|
1400
1451
|
}
|
|
1401
1452
|
const [delay, boundary, min, component] = config
|
|
1402
|
-
const matchId = tx[3][boundary]!.id
|
|
1403
|
-
if (
|
|
1453
|
+
const matchId = tx[3 /* matches */][boundary]!.id
|
|
1454
|
+
if (
|
|
1455
|
+
!session ||
|
|
1456
|
+
session[1 /* boundary */] !== boundary ||
|
|
1457
|
+
sessionMatchId !== matchId
|
|
1458
|
+
) {
|
|
1404
1459
|
// Hydration and redirects can preserve pending presentation without a session.
|
|
1405
1460
|
// Do not delay it again; conservatively start pendingMinMs from now.
|
|
1406
|
-
clearTimeout(session?.[3])
|
|
1461
|
+
clearTimeout(session?.[3 /* timer */])
|
|
1407
1462
|
const presented = router.stores.matches.get()[boundary]
|
|
1408
1463
|
const visible = presented?.id === matchId && presented.status === 'pending'
|
|
1409
1464
|
router._pending = session = [
|
|
1410
1465
|
tx,
|
|
1411
1466
|
boundary,
|
|
1412
|
-
visible ? Date.now() + min : tx[4] + delay,
|
|
1467
|
+
visible ? Date.now() + min : tx[4 /* startedAt */] + delay,
|
|
1413
1468
|
undefined,
|
|
1414
1469
|
visible ? Promise.resolve(true) : undefined,
|
|
1415
1470
|
component,
|
|
1416
1471
|
]
|
|
1417
1472
|
}
|
|
1418
|
-
if (
|
|
1473
|
+
if (
|
|
1474
|
+
session[4 /* ack */] &&
|
|
1475
|
+
!tookOver &&
|
|
1476
|
+
session[5 /* component */] === component
|
|
1477
|
+
) {
|
|
1419
1478
|
return
|
|
1420
1479
|
}
|
|
1421
|
-
session[5] = component
|
|
1422
|
-
if (!session[4]) {
|
|
1423
|
-
clearTimeout(session[3])
|
|
1424
|
-
const remaining = session[2] - Date.now()
|
|
1480
|
+
session[5 /* component */] = component
|
|
1481
|
+
if (!session[4 /* ack */]) {
|
|
1482
|
+
clearTimeout(session[3 /* timer */])
|
|
1483
|
+
const remaining = session[2 /* deadline */] - Date.now()
|
|
1425
1484
|
if (remaining > 0) {
|
|
1426
|
-
session[3] = setTimeout(() => {
|
|
1485
|
+
session[3 /* timer */] = setTimeout(() => {
|
|
1427
1486
|
offerPending(router, tx)
|
|
1428
1487
|
}, remaining)
|
|
1429
1488
|
return
|
|
1430
1489
|
}
|
|
1431
|
-
session[2] = 0
|
|
1490
|
+
session[2 /* deadline */] = 0
|
|
1432
1491
|
}
|
|
1433
|
-
const offered = tx[3].map((match) => ({
|
|
1492
|
+
const offered = tx[3 /* matches */].map((match) => ({
|
|
1434
1493
|
...match,
|
|
1435
1494
|
_flight: undefined,
|
|
1436
1495
|
}))
|
|
1437
1496
|
offered[boundary]!.status = 'pending'
|
|
1438
1497
|
const ack = router
|
|
1439
|
-
.startTransition(() => router.stores.setMatches(offered), offered
|
|
1498
|
+
.startTransition(() => router.stores.setMatches(offered), offered)
|
|
1440
1499
|
.then((rendered) => {
|
|
1441
1500
|
if (
|
|
1442
1501
|
rendered &&
|
|
1443
1502
|
router._pending === session &&
|
|
1444
|
-
session[4] === ack &&
|
|
1445
|
-
!session[2]
|
|
1503
|
+
session[4 /* ack */] === ack &&
|
|
1504
|
+
!session[2 /* deadline */]
|
|
1446
1505
|
) {
|
|
1447
|
-
session[2] = Date.now() + min
|
|
1506
|
+
session[2 /* deadline */] = Date.now() + min
|
|
1448
1507
|
}
|
|
1449
1508
|
return rendered
|
|
1450
1509
|
})
|
|
1451
|
-
session[4] = ack
|
|
1510
|
+
session[4 /* ack */] = ack
|
|
1452
1511
|
}
|
|
1453
1512
|
|
|
1454
1513
|
/**
|
|
@@ -1457,8 +1516,8 @@ function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
|
1457
1516
|
*/
|
|
1458
1517
|
function finishPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
1459
1518
|
const session = router._pending
|
|
1460
|
-
if (session?.[0] === tx) {
|
|
1461
|
-
clearTimeout(session[3])
|
|
1519
|
+
if (session?.[0 /* owner */] === tx) {
|
|
1520
|
+
clearTimeout(session[3 /* timer */])
|
|
1462
1521
|
router._pending = undefined
|
|
1463
1522
|
}
|
|
1464
1523
|
}
|
|
@@ -1472,7 +1531,7 @@ function publishMatches(
|
|
|
1472
1531
|
}
|
|
1473
1532
|
|
|
1474
1533
|
function discardLane(router: AnyRouter, lane: ProjectedLane): void {
|
|
1475
|
-
transferMatchResources(router, lane[1])
|
|
1534
|
+
transferMatchResources(router, lane[1 /* matches */])
|
|
1476
1535
|
discardBackground(router, lane)
|
|
1477
1536
|
}
|
|
1478
1537
|
|
|
@@ -1534,7 +1593,7 @@ function commitMatches(
|
|
|
1534
1593
|
)
|
|
1535
1594
|
}
|
|
1536
1595
|
// The lane becomes committed before publication can synchronously reenter.
|
|
1537
|
-
tx[3] = []
|
|
1596
|
+
tx[3 /* matches */] = []
|
|
1538
1597
|
router._cache = cached
|
|
1539
1598
|
publishMatches(router, matches)
|
|
1540
1599
|
transferMatchResources(
|
|
@@ -1559,7 +1618,7 @@ function commitRefreshMatches(
|
|
|
1559
1618
|
const cached = new Map<string, AnyRouteMatch>()
|
|
1560
1619
|
// Delay releasing the previous owners until the HMR render is acknowledged.
|
|
1561
1620
|
// Old generations must not become reusable cache entries after refresh.
|
|
1562
|
-
tx[3] = []
|
|
1621
|
+
tx[3 /* matches */] = []
|
|
1563
1622
|
router._cache = cached
|
|
1564
1623
|
checkpoint.previousMatches = previous
|
|
1565
1624
|
checkpoint.previousCache = previousCached
|
|
@@ -1595,7 +1654,7 @@ function rollbackPublication(
|
|
|
1595
1654
|
if (
|
|
1596
1655
|
!checkpoint.published ||
|
|
1597
1656
|
router._tx !== tx ||
|
|
1598
|
-
router._committed !== lane[1]
|
|
1657
|
+
router._committed !== lane[1 /* matches */]
|
|
1599
1658
|
) {
|
|
1600
1659
|
settlePublication(router, checkpoint)
|
|
1601
1660
|
return false
|
|
@@ -1625,7 +1684,7 @@ function rollbackPublication(
|
|
|
1625
1684
|
router.stores.status.set('idle')
|
|
1626
1685
|
router.stores.setMatches(checkpoint.previousPresentation)
|
|
1627
1686
|
})
|
|
1628
|
-
tx[0].abort()
|
|
1687
|
+
tx[0 /* controller */].abort()
|
|
1629
1688
|
transferMatchResources(router, discarded, restored)
|
|
1630
1689
|
discardBackground(router, lane)
|
|
1631
1690
|
if (router._tx === tx && router._commitPromise === checkpoint.commitPromise) {
|
|
@@ -1641,17 +1700,18 @@ async function transitionRefresh(
|
|
|
1641
1700
|
lane: ProjectedLane,
|
|
1642
1701
|
changeInfo: ReturnType<typeof getLocationChangeInfo>,
|
|
1643
1702
|
): Promise<boolean | undefined> {
|
|
1703
|
+
const refresh = tx[6 /* refresh */]!
|
|
1644
1704
|
const checkpoint: PublicationCheckpoint = {
|
|
1645
1705
|
previousMatches: router._committed,
|
|
1646
|
-
previousPresentation:
|
|
1706
|
+
previousPresentation: refresh[0 /* presentation */],
|
|
1647
1707
|
previousCache: router._cache,
|
|
1648
1708
|
commitPromise: router._commitPromise,
|
|
1649
1709
|
published: false,
|
|
1650
1710
|
}
|
|
1651
1711
|
const commit = () => {
|
|
1652
1712
|
finishPending(router, tx)
|
|
1653
|
-
|
|
1654
|
-
commitRefreshMatches(router, tx, lane[1], checkpoint)
|
|
1713
|
+
refresh[2 /* rollback */] = rollback
|
|
1714
|
+
commitRefreshMatches(router, tx, lane[1 /* matches */], checkpoint)
|
|
1655
1715
|
if (!checkpoint.published || router._tx !== tx) {
|
|
1656
1716
|
return
|
|
1657
1717
|
}
|
|
@@ -1661,25 +1721,25 @@ async function transitionRefresh(
|
|
|
1661
1721
|
}
|
|
1662
1722
|
}
|
|
1663
1723
|
const rollback = () => {
|
|
1664
|
-
if (
|
|
1665
|
-
|
|
1724
|
+
if (refresh[2 /* rollback */] === rollback) {
|
|
1725
|
+
refresh[2 /* rollback */] = undefined
|
|
1666
1726
|
}
|
|
1667
1727
|
const restored = rollbackPublication(router, tx, lane, checkpoint)
|
|
1668
1728
|
router._cancelTransition?.()
|
|
1669
1729
|
return restored
|
|
1670
1730
|
}
|
|
1671
1731
|
try {
|
|
1672
|
-
const rendered = await router.startTransition(commit, lane[1])
|
|
1673
|
-
if (
|
|
1674
|
-
|
|
1732
|
+
const rendered = await router.startTransition(commit, lane[1 /* matches */])
|
|
1733
|
+
if (refresh[2 /* rollback */] === rollback) {
|
|
1734
|
+
refresh[2 /* rollback */] = undefined
|
|
1675
1735
|
}
|
|
1676
1736
|
if (checkpoint.published) {
|
|
1677
|
-
const handoff =
|
|
1737
|
+
const handoff = refresh[1 /* handoff */]
|
|
1678
1738
|
if (handoff && router._handoff === handoff) {
|
|
1679
|
-
handoff[1]()
|
|
1739
|
+
handoff[1 /* finish */]()
|
|
1680
1740
|
}
|
|
1681
1741
|
if (router._tx === tx) {
|
|
1682
|
-
tx[6] = undefined
|
|
1742
|
+
tx[6 /* refresh */] = undefined
|
|
1683
1743
|
}
|
|
1684
1744
|
}
|
|
1685
1745
|
settlePublication(router, checkpoint)
|
|
@@ -1698,7 +1758,7 @@ async function awaitCurrent(
|
|
|
1698
1758
|
): Promise<void> {
|
|
1699
1759
|
let current = router._tx
|
|
1700
1760
|
while (current && current !== owner) {
|
|
1701
|
-
await current[5]
|
|
1761
|
+
await current[5 /* done */]
|
|
1702
1762
|
if (router._tx === current) {
|
|
1703
1763
|
return
|
|
1704
1764
|
}
|
|
@@ -1715,7 +1775,7 @@ async function followRedirect(
|
|
|
1715
1775
|
...redirect.options,
|
|
1716
1776
|
replace: true,
|
|
1717
1777
|
ignoreBlocker: true,
|
|
1718
|
-
_redirects: tx[1] + 1,
|
|
1778
|
+
_redirects: tx[1 /* redirects */] + 1,
|
|
1719
1779
|
} as any)
|
|
1720
1780
|
}
|
|
1721
1781
|
|
|
@@ -1724,9 +1784,9 @@ function restoreCommitted(
|
|
|
1724
1784
|
tx: LoadTransaction,
|
|
1725
1785
|
): void {
|
|
1726
1786
|
finishPending(router, tx)
|
|
1727
|
-
tx[0].abort()
|
|
1728
|
-
transferMatchResources(router, tx[3])
|
|
1729
|
-
tx[3] = []
|
|
1787
|
+
tx[0 /* controller */].abort()
|
|
1788
|
+
transferMatchResources(router, tx[3 /* matches */])
|
|
1789
|
+
tx[3 /* matches */] = []
|
|
1730
1790
|
if (router._tx !== tx) {
|
|
1731
1791
|
return
|
|
1732
1792
|
}
|
|
@@ -1750,15 +1810,22 @@ async function runBackground(
|
|
|
1750
1810
|
const next = base.map((match) => ({ ...match }))
|
|
1751
1811
|
acquireMatchResources(next)
|
|
1752
1812
|
for (const task of tasks) {
|
|
1753
|
-
releaseFlight(router, next[task[0]]!)
|
|
1754
|
-
next[task[0]] = task[3]
|
|
1813
|
+
releaseFlight(router, next[task[0 /* index */]]!)
|
|
1814
|
+
next[task[0 /* index */]] = task[3 /* candidate */]
|
|
1755
1815
|
}
|
|
1756
1816
|
// Phase jump: the clones inherit beforeLoad context from the committed
|
|
1757
1817
|
// foreground lane, which already ran `contextualize` for these matches.
|
|
1758
|
-
const lane = [tx[2], next] as ContextualizedLane
|
|
1818
|
+
const lane = [tx[2 /* location */], next] as ContextualizedLane
|
|
1759
1819
|
let reduced: ReducedLane | ControlOutcome
|
|
1760
1820
|
try {
|
|
1761
|
-
reduced = await reduceLane(
|
|
1821
|
+
reduced = await reduceLane(
|
|
1822
|
+
router,
|
|
1823
|
+
lane,
|
|
1824
|
+
tasks,
|
|
1825
|
+
tx[0 /* controller */],
|
|
1826
|
+
tx[1 /* redirects */],
|
|
1827
|
+
settlement,
|
|
1828
|
+
)
|
|
1762
1829
|
} catch (cause) {
|
|
1763
1830
|
transferMatchResources(router, next)
|
|
1764
1831
|
throw cause
|
|
@@ -1766,28 +1833,32 @@ async function runBackground(
|
|
|
1766
1833
|
if (isControl(reduced)) {
|
|
1767
1834
|
transferMatchResources(router, next)
|
|
1768
1835
|
if (
|
|
1769
|
-
reduced[0] === REDIRECTED &&
|
|
1836
|
+
reduced[0 /* kind */] === REDIRECTED &&
|
|
1770
1837
|
router._tx === tx &&
|
|
1771
1838
|
router._committed === base
|
|
1772
1839
|
) {
|
|
1773
|
-
await followRedirect(router, tx, reduced[1])
|
|
1840
|
+
await followRedirect(router, tx, reduced[1 /* redirect */])
|
|
1774
1841
|
}
|
|
1775
1842
|
return
|
|
1776
1843
|
}
|
|
1777
|
-
const projected = await projectLane(
|
|
1844
|
+
const projected = await projectLane(
|
|
1845
|
+
router,
|
|
1846
|
+
reduced,
|
|
1847
|
+
tx[0 /* controller */].signal,
|
|
1848
|
+
)
|
|
1778
1849
|
if (router._tx !== tx || router._committed !== base) {
|
|
1779
|
-
transferMatchResources(router, projected[1])
|
|
1850
|
+
transferMatchResources(router, projected[1 /* matches */])
|
|
1780
1851
|
return
|
|
1781
1852
|
}
|
|
1782
|
-
for (const match of projected[1] as Array<WorkMatch>) {
|
|
1853
|
+
for (const match of projected[1 /* matches */] as Array<WorkMatch>) {
|
|
1783
1854
|
const cached = router._cache.get(match.id) as WorkMatch | undefined
|
|
1784
1855
|
if (cached?._flight && cached._flight === match._flight) {
|
|
1785
1856
|
router._cache.delete(match.id)
|
|
1786
1857
|
releaseFlight(router, cached)
|
|
1787
1858
|
}
|
|
1788
1859
|
}
|
|
1789
|
-
publishMatches(router, projected[1])
|
|
1790
|
-
transferMatchResources(router, base, projected[1])
|
|
1860
|
+
publishMatches(router, projected[1 /* matches */])
|
|
1861
|
+
transferMatchResources(router, base, projected[1 /* matches */])
|
|
1791
1862
|
}
|
|
1792
1863
|
|
|
1793
1864
|
async function runClientTransaction(
|
|
@@ -1797,13 +1868,11 @@ async function runClientTransaction(
|
|
|
1797
1868
|
onReady?: () => void,
|
|
1798
1869
|
sync?: boolean,
|
|
1799
1870
|
resolvedPrefix?: number,
|
|
1800
|
-
adopted?: ActivePreload,
|
|
1801
|
-
retained?: ActivePreload,
|
|
1802
1871
|
): Promise<void> {
|
|
1803
1872
|
const options: ExecuteLaneOptions = [
|
|
1804
|
-
tx[0],
|
|
1805
|
-
tx[1],
|
|
1806
|
-
() => router._tx === tx && !!tx[3].length,
|
|
1873
|
+
tx[0 /* controller */],
|
|
1874
|
+
tx[1 /* redirects */],
|
|
1875
|
+
() => router._tx === tx && !!tx[3 /* matches */].length,
|
|
1807
1876
|
router._committed,
|
|
1808
1877
|
undefined,
|
|
1809
1878
|
sync,
|
|
@@ -1811,52 +1880,23 @@ async function runClientTransaction(
|
|
|
1811
1880
|
resolvedPrefix,
|
|
1812
1881
|
onReady,
|
|
1813
1882
|
]
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
if (retained) {
|
|
1821
|
-
discardPreload(router, retained)
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
if (
|
|
1825
|
-
adopted &&
|
|
1826
|
-
router._tx === tx &&
|
|
1827
|
-
((isControl(result) && result[0] === CANCELED) ||
|
|
1828
|
-
(!isControl(result) &&
|
|
1829
|
-
result[1].some(
|
|
1830
|
-
(match) => match.status !== 'success' || match._notFound,
|
|
1831
|
-
)))
|
|
1832
|
-
) {
|
|
1833
|
-
// Successful loaders already seeded the cache; retry only the guard lane.
|
|
1834
|
-
const donors = tx[3] as Array<WorkMatch>
|
|
1835
|
-
tx[3] = []
|
|
1836
|
-
transferMatchResources(router, donors)
|
|
1837
|
-
tx[0].abort()
|
|
1838
|
-
if (router._tx !== tx) {
|
|
1839
|
-
return
|
|
1840
|
-
}
|
|
1841
|
-
const controller = new AbortController()
|
|
1842
|
-
tx[0] = options[0] = controller
|
|
1843
|
-
tx[3] = router.matchRoutes(tx[2], {
|
|
1844
|
-
_controller: controller,
|
|
1845
|
-
})
|
|
1846
|
-
acquireMatchResources(tx[3])
|
|
1847
|
-
result = await executeClientLane(router, tx[2], tx[3], options)
|
|
1848
|
-
}
|
|
1883
|
+
const result = await executeClientLane(
|
|
1884
|
+
router,
|
|
1885
|
+
tx[2 /* location */],
|
|
1886
|
+
tx[3 /* matches */],
|
|
1887
|
+
options,
|
|
1888
|
+
)
|
|
1849
1889
|
|
|
1850
1890
|
if (isControl(result)) {
|
|
1851
|
-
if (result[0] === REDIRECTED && router._tx === tx) {
|
|
1891
|
+
if (result[0 /* kind */] === REDIRECTED && router._tx === tx) {
|
|
1852
1892
|
finishPending(router, tx)
|
|
1853
|
-
transferMatchResources(router, tx[3])
|
|
1854
|
-
tx[3] = []
|
|
1893
|
+
transferMatchResources(router, tx[3 /* matches */])
|
|
1894
|
+
tx[3 /* matches */] = []
|
|
1855
1895
|
if (router._tx === tx) {
|
|
1856
|
-
if (process.env.NODE_ENV !== 'production' && tx[6]) {
|
|
1896
|
+
if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) {
|
|
1857
1897
|
router._refreshNextLoad = true
|
|
1858
1898
|
}
|
|
1859
|
-
await followRedirect(router, tx, result[1])
|
|
1899
|
+
await followRedirect(router, tx, result[1 /* redirect */])
|
|
1860
1900
|
}
|
|
1861
1901
|
} else {
|
|
1862
1902
|
restoreCommitted(router, tx)
|
|
@@ -1864,36 +1904,40 @@ async function runClientTransaction(
|
|
|
1864
1904
|
return
|
|
1865
1905
|
}
|
|
1866
1906
|
const pending = router._pending
|
|
1867
|
-
if (pending?.[0] === tx) {
|
|
1907
|
+
if (pending?.[0 /* owner */] === tx) {
|
|
1868
1908
|
/**
|
|
1869
1909
|
* Loading finished, so cancel any pending reveal. If the fallback rendered,
|
|
1870
1910
|
* wait out the rest of `pendingMinMs` before replacing it. If it never
|
|
1871
1911
|
* rendered, there is no minimum wait; if another load took it over, that
|
|
1872
1912
|
* load owns the deadline.
|
|
1873
1913
|
*/
|
|
1874
|
-
clearTimeout(pending[3])
|
|
1875
|
-
if (pending[4]) {
|
|
1876
|
-
const signal = tx[0].signal
|
|
1914
|
+
clearTimeout(pending[3 /* timer */])
|
|
1915
|
+
if (pending[4 /* ack */]) {
|
|
1916
|
+
const signal = tx[0 /* controller */].signal
|
|
1877
1917
|
let rendered = false
|
|
1878
1918
|
try {
|
|
1879
|
-
rendered = await waitFor(pending[4], signal)
|
|
1919
|
+
rendered = await waitFor(pending[4 /* ack */], signal)
|
|
1880
1920
|
} catch (cause) {
|
|
1881
1921
|
if (cause !== signal) {
|
|
1882
1922
|
throw cause
|
|
1883
1923
|
}
|
|
1884
1924
|
}
|
|
1885
|
-
if (
|
|
1886
|
-
|
|
1925
|
+
if (
|
|
1926
|
+
rendered &&
|
|
1927
|
+
router._pending === pending &&
|
|
1928
|
+
pending[0 /* owner */] === tx
|
|
1929
|
+
) {
|
|
1930
|
+
const remaining = pending[2 /* deadline */] - Date.now()
|
|
1887
1931
|
if (remaining > 0) {
|
|
1888
1932
|
try {
|
|
1889
1933
|
await waitFor(
|
|
1890
1934
|
new Promise<void>((resolve) => {
|
|
1891
|
-
pending[3] = setTimeout(resolve, remaining)
|
|
1935
|
+
pending[3 /* timer */] = setTimeout(resolve, remaining)
|
|
1892
1936
|
}),
|
|
1893
1937
|
signal,
|
|
1894
1938
|
)
|
|
1895
1939
|
} catch {}
|
|
1896
|
-
clearTimeout(pending[3])
|
|
1940
|
+
clearTimeout(pending[3 /* timer */])
|
|
1897
1941
|
}
|
|
1898
1942
|
}
|
|
1899
1943
|
}
|
|
@@ -1903,12 +1947,12 @@ async function runClientTransaction(
|
|
|
1903
1947
|
discardLane(router, result)
|
|
1904
1948
|
return
|
|
1905
1949
|
}
|
|
1906
|
-
const toLocation = tx[2]
|
|
1950
|
+
const toLocation = tx[2 /* location */]
|
|
1907
1951
|
const changeInfo = getLocationChangeInfo(
|
|
1908
1952
|
toLocation,
|
|
1909
1953
|
router.stores.resolvedLocation.get(),
|
|
1910
1954
|
)
|
|
1911
|
-
const background = result[2]
|
|
1955
|
+
const background = result[2 /* background */]
|
|
1912
1956
|
await router.startViewTransition(async () => {
|
|
1913
1957
|
if (router._tx !== tx) {
|
|
1914
1958
|
discardLane(router, result)
|
|
@@ -1916,7 +1960,7 @@ async function runClientTransaction(
|
|
|
1916
1960
|
}
|
|
1917
1961
|
const commit = () => {
|
|
1918
1962
|
finishPending(router, tx)
|
|
1919
|
-
commitMatches(router, tx, result[1], resolvedPrefix)
|
|
1963
|
+
commitMatches(router, tx, result[1 /* matches */], resolvedPrefix)
|
|
1920
1964
|
if (router._tx !== tx) {
|
|
1921
1965
|
return
|
|
1922
1966
|
}
|
|
@@ -1926,12 +1970,12 @@ async function runClientTransaction(
|
|
|
1926
1970
|
}
|
|
1927
1971
|
}
|
|
1928
1972
|
const rendered =
|
|
1929
|
-
process.env.NODE_ENV !== 'production' && tx[6]
|
|
1973
|
+
process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]
|
|
1930
1974
|
? await transitionRefresh(router, tx, result, changeInfo)
|
|
1931
|
-
: await router.startTransition(commit, result[1])
|
|
1975
|
+
: await router.startTransition(commit, result[1 /* matches */])
|
|
1932
1976
|
if (
|
|
1933
1977
|
process.env.NODE_ENV !== 'production' &&
|
|
1934
|
-
tx[6] &&
|
|
1978
|
+
tx[6 /* refresh */] &&
|
|
1935
1979
|
rendered === undefined
|
|
1936
1980
|
) {
|
|
1937
1981
|
return
|
|
@@ -1944,9 +1988,13 @@ async function runClientTransaction(
|
|
|
1944
1988
|
// Publish refreshes only after the foreground render acknowledgement.
|
|
1945
1989
|
// Otherwise a fast refresh can replace the acknowledged generation
|
|
1946
1990
|
// before the framework commits it and strand the navigation.
|
|
1947
|
-
runBackground(
|
|
1948
|
-
|
|
1949
|
-
|
|
1991
|
+
runBackground(
|
|
1992
|
+
router,
|
|
1993
|
+
tx,
|
|
1994
|
+
result[1 /* matches */],
|
|
1995
|
+
background,
|
|
1996
|
+
result[3 /* backgroundSettlement */]!,
|
|
1997
|
+
).catch(console.error)
|
|
1950
1998
|
}
|
|
1951
1999
|
router.batch(() => {
|
|
1952
2000
|
router.stores.resolvedLocation.set(toLocation)
|
|
@@ -1968,12 +2016,12 @@ async function runClientTransaction(
|
|
|
1968
2016
|
|
|
1969
2017
|
export async function loadClientRoute(
|
|
1970
2018
|
router: CoordinatorRouter,
|
|
1971
|
-
opts?: { sync?: boolean
|
|
2019
|
+
opts?: { sync?: boolean },
|
|
1972
2020
|
): Promise<void> {
|
|
1973
2021
|
let rematerialize = false
|
|
1974
2022
|
if (process.env.NODE_ENV !== 'production') {
|
|
1975
|
-
router.
|
|
1976
|
-
rematerialize = !!router._refreshNextLoad || !!router._tx?.[6]
|
|
2023
|
+
router._tx?.[6 /* refresh */]?.[2 /* rollback */]?.()
|
|
2024
|
+
rematerialize = !!router._refreshNextLoad || !!router._tx?.[6 /* refresh */]
|
|
1977
2025
|
}
|
|
1978
2026
|
const refreshPresentation = rematerialize
|
|
1979
2027
|
? router.stores.matches.get()
|
|
@@ -1989,121 +2037,73 @@ export async function loadClientRoute(
|
|
|
1989
2037
|
pendingLocation?.href === location.href
|
|
1990
2038
|
? (pendingLocation._redirects ?? 0)
|
|
1991
2039
|
: 0
|
|
1992
|
-
// A same-location navigation joins the transaction already loading it
|
|
1993
|
-
// instead of restarting its work. Reload requests never carry the flag,
|
|
1994
|
-
// and same-location redirects must restart the lane they came from.
|
|
1995
|
-
if (
|
|
1996
|
-
opts?._dedupe &&
|
|
1997
|
-
!redirects &&
|
|
1998
|
-
previousOwner &&
|
|
1999
|
-
!rematerialize &&
|
|
2000
|
-
previousOwner[2].href === location.href &&
|
|
2001
|
-
router.stores.status.get() === 'pending'
|
|
2002
|
-
) {
|
|
2003
|
-
await awaitCurrent(router)
|
|
2004
|
-
return
|
|
2005
|
-
}
|
|
2006
2040
|
const handoff = router._handoff
|
|
2007
|
-
const hydrationController = rematerialize
|
|
2041
|
+
const hydrationController = rematerialize
|
|
2042
|
+
? undefined
|
|
2043
|
+
: handoff?.[0 /* claim */]()
|
|
2008
2044
|
const preflight = new AbortController()
|
|
2009
2045
|
const previousPreflight = router._preflight
|
|
2010
2046
|
router._preflight = preflight
|
|
2011
2047
|
if (!rematerialize && !hydrationController) {
|
|
2012
|
-
handoff?.[1]()
|
|
2048
|
+
handoff?.[1 /* finish */]()
|
|
2013
2049
|
}
|
|
2014
2050
|
previousPreflight?.abort()
|
|
2015
|
-
|
|
2051
|
+
// The preflight controller is not exposed to route hooks. Every replacement
|
|
2052
|
+
// aborts its predecessor, so a live signal is the sole authority here.
|
|
2053
|
+
if (preflight.signal.aborted) {
|
|
2016
2054
|
await awaitCurrent(router, previousOwner)
|
|
2017
2055
|
return
|
|
2018
2056
|
}
|
|
2019
2057
|
|
|
2020
2058
|
const changeInfo = getLocationChangeInfo(location, resolvedLocation)
|
|
2021
2059
|
router.emit({ type: 'onBeforeNavigate', ...changeInfo })
|
|
2022
|
-
if (!preflight.signal.aborted
|
|
2060
|
+
if (!preflight.signal.aborted) {
|
|
2023
2061
|
router.emit({ type: 'onBeforeLoad', ...changeInfo })
|
|
2024
2062
|
}
|
|
2025
|
-
if (preflight.signal.aborted
|
|
2026
|
-
preflight.abort()
|
|
2063
|
+
if (preflight.signal.aborted) {
|
|
2027
2064
|
await awaitCurrent(router, previousOwner)
|
|
2028
2065
|
return
|
|
2029
2066
|
}
|
|
2030
2067
|
const sameHref = previousLocation.href === location.href
|
|
2031
|
-
let adopted = router._preloads?.get(location.href)
|
|
2032
|
-
let retained: ActivePreload | undefined
|
|
2033
|
-
if (rematerialize && adopted) {
|
|
2034
|
-
router._preloads!.delete(location.href)
|
|
2035
|
-
discardPreload(router, adopted)
|
|
2036
|
-
adopted = undefined
|
|
2037
|
-
if (preflight.signal.aborted || router._tx !== previousOwner) {
|
|
2038
|
-
preflight.abort()
|
|
2039
|
-
await awaitCurrent(router, previousOwner)
|
|
2040
|
-
return
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
if (
|
|
2044
|
-
adopted &&
|
|
2045
|
-
(hydrationController ||
|
|
2046
|
-
!samePreloadLane(
|
|
2047
|
-
adopted,
|
|
2048
|
-
router,
|
|
2049
|
-
pendingLocation?.href === location.href ? pendingLocation : location,
|
|
2050
|
-
redirects,
|
|
2051
|
-
))
|
|
2052
|
-
) {
|
|
2053
|
-
router._preloads!.delete(location.href)
|
|
2054
|
-
// Keep incompatible loader flights alive through the real lane's reload
|
|
2055
|
-
// decisions so matching generations can still donate their work.
|
|
2056
|
-
retained = adopted
|
|
2057
|
-
adopted = undefined
|
|
2058
|
-
}
|
|
2059
2068
|
let matches: Array<AnyRouteMatch>
|
|
2060
2069
|
let controller = preflight
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
acquireMatchResources(matches)
|
|
2076
|
-
} catch (cause) {
|
|
2077
|
-
preflight.abort()
|
|
2078
|
-
if (retained) {
|
|
2079
|
-
discardPreload(router, retained)
|
|
2080
|
-
}
|
|
2081
|
-
if (!isRedirect(cause)) {
|
|
2082
|
-
if (process.env.NODE_ENV !== 'production' && rematerialize) {
|
|
2083
|
-
router._refreshNextLoad = undefined
|
|
2084
|
-
}
|
|
2085
|
-
await awaitCurrent(router)
|
|
2086
|
-
router._commitPromise?.resolve()
|
|
2087
|
-
router._commitPromise = undefined
|
|
2088
|
-
return
|
|
2070
|
+
try {
|
|
2071
|
+
matches =
|
|
2072
|
+
process.env.NODE_ENV !== 'production' && rematerialize
|
|
2073
|
+
? router.matchRoutes(location, {
|
|
2074
|
+
_controller: preflight,
|
|
2075
|
+
_rematerialize: true,
|
|
2076
|
+
})
|
|
2077
|
+
: router.matchRoutes(location, { _controller: preflight })
|
|
2078
|
+
acquireMatchResources(matches)
|
|
2079
|
+
} catch (cause) {
|
|
2080
|
+
preflight.abort()
|
|
2081
|
+
if (!isRedirect(cause)) {
|
|
2082
|
+
if (process.env.NODE_ENV !== 'production' && rematerialize) {
|
|
2083
|
+
router._refreshNextLoad = undefined
|
|
2089
2084
|
}
|
|
2090
|
-
await router
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
ignoreBlocker: true,
|
|
2094
|
-
})
|
|
2095
|
-
await awaitCurrent(router, previousOwner)
|
|
2085
|
+
await awaitCurrent(router)
|
|
2086
|
+
router._commitPromise?.resolve()
|
|
2087
|
+
router._commitPromise = undefined
|
|
2096
2088
|
return
|
|
2097
2089
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2090
|
+
await router.navigate({
|
|
2091
|
+
...cause.options,
|
|
2092
|
+
replace: true,
|
|
2093
|
+
ignoreBlocker: true,
|
|
2094
|
+
})
|
|
2095
|
+
await awaitCurrent(router, previousOwner)
|
|
2096
|
+
return
|
|
2104
2097
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2098
|
+
const resolvedPrefix = hydrationController
|
|
2099
|
+
? handoff
|
|
2100
|
+
: undefined
|
|
2101
|
+
if (resolvedPrefix) {
|
|
2102
|
+
controller = hydrationController!
|
|
2103
|
+
} else {
|
|
2104
|
+
hydrationController?.abort()
|
|
2105
|
+
}
|
|
2106
|
+
if (preflight.signal.aborted) {
|
|
2107
2107
|
transferMatchResources(router, matches)
|
|
2108
2108
|
await awaitCurrent(router, previousOwner)
|
|
2109
2109
|
return
|
|
@@ -2125,8 +2125,6 @@ export async function loadClientRoute(
|
|
|
2125
2125
|
() => offerPending(router, tx),
|
|
2126
2126
|
opts?.sync,
|
|
2127
2127
|
resolvedPrefix,
|
|
2128
|
-
adopted,
|
|
2129
|
-
retained,
|
|
2130
2128
|
),
|
|
2131
2129
|
)
|
|
2132
2130
|
.catch(() => {
|
|
@@ -2137,13 +2135,10 @@ export async function loadClientRoute(
|
|
|
2137
2135
|
]
|
|
2138
2136
|
if (process.env.NODE_ENV !== 'production' && rematerialize) {
|
|
2139
2137
|
// `refreshPresentation` is always captured when `rematerialize` is set.
|
|
2140
|
-
tx[6] = [refreshPresentation!, handoff]
|
|
2138
|
+
tx[6 /* refresh */] = [refreshPresentation!, handoff]
|
|
2141
2139
|
router._refreshNextLoad = undefined
|
|
2142
2140
|
}
|
|
2143
2141
|
router._tx = tx
|
|
2144
|
-
if (!rematerialize && router._handoff === handoff) {
|
|
2145
|
-
router._handoff = undefined
|
|
2146
|
-
}
|
|
2147
2142
|
if (previousOwner) {
|
|
2148
2143
|
for (const match of router.stores.matches.get() as Array<WorkMatch>) {
|
|
2149
2144
|
if (router._tx !== tx) {
|
|
@@ -2153,23 +2148,26 @@ export async function loadClientRoute(
|
|
|
2153
2148
|
setFetching(router, match, false)
|
|
2154
2149
|
}
|
|
2155
2150
|
}
|
|
2156
|
-
previousOwner[0].abort()
|
|
2157
|
-
|
|
2151
|
+
previousOwner[0 /* controller */].abort()
|
|
2152
|
+
transferPredecessorResources(
|
|
2153
|
+
router,
|
|
2154
|
+
previousOwner[3 /* matches */],
|
|
2155
|
+
tx[3 /* matches */],
|
|
2156
|
+
)
|
|
2158
2157
|
}
|
|
2159
2158
|
if (router._tx !== tx) {
|
|
2160
|
-
transferMatchResources(router, tx[3])
|
|
2161
|
-
tx[3] = []
|
|
2159
|
+
transferMatchResources(router, tx[3 /* matches */])
|
|
2160
|
+
tx[3 /* matches */] = []
|
|
2162
2161
|
await awaitCurrent(router, tx)
|
|
2163
2162
|
return
|
|
2164
2163
|
}
|
|
2165
|
-
|
|
2166
2164
|
router.batch(() => {
|
|
2167
2165
|
router.stores.status.set('pending')
|
|
2168
2166
|
router.stores.location.set(location)
|
|
2169
2167
|
})
|
|
2170
2168
|
offerPending(router, tx)
|
|
2171
2169
|
try {
|
|
2172
|
-
await tx[5]
|
|
2170
|
+
await tx[5 /* done */]
|
|
2173
2171
|
} finally {
|
|
2174
2172
|
await awaitCurrent(router, tx)
|
|
2175
2173
|
}
|
|
@@ -2178,10 +2176,14 @@ export async function loadClientRoute(
|
|
|
2178
2176
|
export async function refreshClientRoute(
|
|
2179
2177
|
router: CoordinatorRouter,
|
|
2180
2178
|
): Promise<void> {
|
|
2181
|
-
router.
|
|
2179
|
+
router._tx?.[6 /* refresh */]?.[2 /* rollback */]?.()
|
|
2182
2180
|
const pending = router._tx
|
|
2183
|
-
if (
|
|
2184
|
-
|
|
2181
|
+
if (
|
|
2182
|
+
pending &&
|
|
2183
|
+
!pending[6 /* refresh */] &&
|
|
2184
|
+
router.stores.status.get() === 'pending'
|
|
2185
|
+
) {
|
|
2186
|
+
await pending[5 /* done */]
|
|
2185
2187
|
if (router._tx !== pending) {
|
|
2186
2188
|
await awaitCurrent(router, pending)
|
|
2187
2189
|
}
|
|
@@ -2193,30 +2195,6 @@ export async function refreshClientRoute(
|
|
|
2193
2195
|
await loadClientRoute(router, { sync: true })
|
|
2194
2196
|
}
|
|
2195
2197
|
|
|
2196
|
-
function followPreloadRedirect(
|
|
2197
|
-
router: CoordinatorRouter,
|
|
2198
|
-
result: ControlOutcome,
|
|
2199
|
-
location: ParsedLocation,
|
|
2200
|
-
owner: LoadTransaction | undefined,
|
|
2201
|
-
redirects: number,
|
|
2202
|
-
): Promise<Array<AnyRouteMatch> | undefined> | undefined {
|
|
2203
|
-
if (
|
|
2204
|
-
result[0] === REDIRECTED &&
|
|
2205
|
-
!result[1].options.reloadDocument &&
|
|
2206
|
-
router._tx === owner
|
|
2207
|
-
) {
|
|
2208
|
-
return preloadClientRoute(
|
|
2209
|
-
router,
|
|
2210
|
-
{
|
|
2211
|
-
...result[1].options,
|
|
2212
|
-
_fromLocation: location,
|
|
2213
|
-
},
|
|
2214
|
-
redirects + 1,
|
|
2215
|
-
)
|
|
2216
|
-
}
|
|
2217
|
-
return
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
2198
|
export async function preloadClientRoute(
|
|
2221
2199
|
router: CoordinatorRouter,
|
|
2222
2200
|
opts: any,
|
|
@@ -2225,96 +2203,70 @@ export async function preloadClientRoute(
|
|
|
2225
2203
|
if (redirects > 20) {
|
|
2226
2204
|
return
|
|
2227
2205
|
}
|
|
2228
|
-
const owner = router._tx
|
|
2229
2206
|
if (
|
|
2230
2207
|
process.env.NODE_ENV !== 'production' &&
|
|
2231
|
-
(router._refreshNextLoad ||
|
|
2208
|
+
(router._refreshNextLoad || router._tx?.[6 /* refresh */])
|
|
2232
2209
|
) {
|
|
2233
2210
|
return
|
|
2234
2211
|
}
|
|
2235
2212
|
const location = opts._builtLocation ?? router.buildLocation(opts)
|
|
2236
2213
|
const base = router._committed
|
|
2237
2214
|
const controller = new AbortController()
|
|
2238
|
-
let matches: Array<AnyRouteMatch>
|
|
2239
|
-
let preload: ActivePreload | undefined
|
|
2240
|
-
let replaced: ActivePreload | undefined
|
|
2215
|
+
let matches: Array<AnyRouteMatch>
|
|
2241
2216
|
try {
|
|
2242
|
-
const pending = router._preloads?.get(location.href)
|
|
2243
|
-
if (pending) {
|
|
2244
|
-
if (samePreloadLane(pending, router, location, redirects)) {
|
|
2245
|
-
const result = await pending[2]
|
|
2246
|
-
return isControl(result)
|
|
2247
|
-
? followPreloadRedirect(router, result, location, owner, redirects)
|
|
2248
|
-
: result[1]
|
|
2249
|
-
}
|
|
2250
|
-
router._preloads!.delete(location.href)
|
|
2251
|
-
// Keep the superseded lane alive until this lane has made its reload
|
|
2252
|
-
// decisions. Its active flights are the synchronous donor authority.
|
|
2253
|
-
replaced = pending
|
|
2254
|
-
}
|
|
2255
2217
|
matches = router.matchRoutes(location, {
|
|
2256
2218
|
_controller: controller,
|
|
2257
2219
|
})
|
|
2258
2220
|
acquireMatchResources(matches)
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
redirects,
|
|
2264
|
-
// Preload lanes run to completion even when unrelated navigations
|
|
2265
|
-
// commit: finished work seeds the cache, and adoption safety is
|
|
2266
|
-
// enforced independently by samePreloadLane's base identity check.
|
|
2267
|
-
() => true,
|
|
2268
|
-
base,
|
|
2269
|
-
true,
|
|
2270
|
-
]),
|
|
2271
|
-
)
|
|
2272
|
-
.finally(() => {
|
|
2273
|
-
if (replaced) {
|
|
2274
|
-
discardPreload(router, replaced)
|
|
2275
|
-
}
|
|
2276
|
-
})
|
|
2277
|
-
preload = [
|
|
2278
|
-
matches,
|
|
2279
|
-
controller,
|
|
2280
|
-
promise,
|
|
2281
|
-
base,
|
|
2282
|
-
laneInputs(router, location),
|
|
2283
|
-
redirects,
|
|
2284
|
-
]
|
|
2285
|
-
;(router._preloads ??= new Map()).set(location.href, preload)
|
|
2286
|
-
const result = await promise
|
|
2287
|
-
if (router._preloads?.get(location.href) !== preload) {
|
|
2288
|
-
return isControl(result) ? undefined : result[1]
|
|
2221
|
+
} catch (cause) {
|
|
2222
|
+
controller.abort()
|
|
2223
|
+
if (!isNotFound(cause)) {
|
|
2224
|
+
console.error(cause)
|
|
2289
2225
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2226
|
+
return
|
|
2227
|
+
}
|
|
2228
|
+
;(router._preloads ??= new Map()).set(controller, matches)
|
|
2229
|
+
let active: boolean
|
|
2230
|
+
try {
|
|
2231
|
+
let result: LaneResult
|
|
2232
|
+
try {
|
|
2233
|
+
result = await executeClientLane(router, location, matches, [
|
|
2234
|
+
controller,
|
|
2235
|
+
redirects,
|
|
2236
|
+
// Preload lanes run to completion even when unrelated navigations commit:
|
|
2237
|
+
// finished work seeds the cache.
|
|
2238
|
+
() => true,
|
|
2239
|
+
base,
|
|
2240
|
+
true,
|
|
2241
|
+
])
|
|
2242
|
+
} finally {
|
|
2243
|
+
active = router._preloads.delete(controller)
|
|
2293
2244
|
transferMatchResources(router, matches)
|
|
2294
|
-
return followPreloadRedirect(router, result, location, owner, redirects)
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
transferMatchResources(router, result[1])
|
|
2298
|
-
controller.abort()
|
|
2299
|
-
return result[1]
|
|
2300
|
-
} catch (cause) {
|
|
2301
|
-
if (!preload || router._preloads?.get(location.href) === preload) {
|
|
2302
|
-
if (preload) {
|
|
2303
|
-
router._preloads!.delete(location.href)
|
|
2304
|
-
}
|
|
2305
2245
|
controller.abort()
|
|
2306
|
-
if (matches) {
|
|
2307
|
-
transferMatchResources(router, matches)
|
|
2308
|
-
}
|
|
2309
2246
|
}
|
|
2310
|
-
if (
|
|
2311
|
-
return
|
|
2247
|
+
if (!isControl(result)) {
|
|
2248
|
+
return result[1 /* matches */]
|
|
2249
|
+
}
|
|
2250
|
+
if (
|
|
2251
|
+
active &&
|
|
2252
|
+
result[0 /* kind */] === REDIRECTED &&
|
|
2253
|
+
!result[1 /* redirect */].options.reloadDocument
|
|
2254
|
+
) {
|
|
2255
|
+
return preloadClientRoute(
|
|
2256
|
+
router,
|
|
2257
|
+
{
|
|
2258
|
+
...result[1 /* redirect */].options,
|
|
2259
|
+
_fromLocation: location,
|
|
2260
|
+
},
|
|
2261
|
+
redirects + 1,
|
|
2262
|
+
)
|
|
2312
2263
|
}
|
|
2264
|
+
} catch (cause) {
|
|
2313
2265
|
if (!isNotFound(cause)) {
|
|
2314
2266
|
console.error(cause)
|
|
2315
2267
|
}
|
|
2316
|
-
return
|
|
2317
2268
|
}
|
|
2269
|
+
return
|
|
2318
2270
|
}
|
|
2319
2271
|
|
|
2320
2272
|
// --- SSR hydration (client entry via @tanstack/router-core/ssr/client) ---
|
|
@@ -2352,12 +2304,13 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2352
2304
|
)
|
|
2353
2305
|
}
|
|
2354
2306
|
router.ssr = { manifest: dehydratedRouter!.manifest }
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2307
|
+
router.options.ssr = {
|
|
2308
|
+
nonce: (
|
|
2309
|
+
document.querySelector('meta[property="csp-nonce"]') as
|
|
2310
|
+
| HTMLMetaElement
|
|
2311
|
+
| undefined
|
|
2312
|
+
)?.content,
|
|
2313
|
+
}
|
|
2361
2314
|
|
|
2362
2315
|
const dehydratedMatches = dehydratedRouter!.matches
|
|
2363
2316
|
|
|
@@ -2365,22 +2318,14 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2365
2318
|
const previousPreflight = router._preflight
|
|
2366
2319
|
router._preflight = controller
|
|
2367
2320
|
previousPreflight?.abort()
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
}
|
|
2372
|
-
controller.abort(cause)
|
|
2373
|
-
return false
|
|
2374
|
-
}
|
|
2375
|
-
const isCurrent = () =>
|
|
2376
|
-
(!router._tx &&
|
|
2377
|
-
router._preflight === controller &&
|
|
2378
|
-
!controller.signal.aborted) ||
|
|
2379
|
-
retire()
|
|
2321
|
+
// Route context can abort this controller itself. Only a new slot owner
|
|
2322
|
+
// supersedes hydration.
|
|
2323
|
+
const isCurrent = () => router._preflight === controller
|
|
2380
2324
|
|
|
2381
2325
|
let location!: AnyRouter['latestLocation']
|
|
2382
2326
|
let candidates!: Array<AnyRouteMatch>
|
|
2383
|
-
let
|
|
2327
|
+
let handoffHistoryHref!: string
|
|
2328
|
+
let handoffHistoryState: unknown
|
|
2384
2329
|
try {
|
|
2385
2330
|
await waitFor(
|
|
2386
2331
|
router.options.hydrate?.(dehydratedRouter!.dehydratedData),
|
|
@@ -2389,15 +2334,22 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2389
2334
|
if (!isCurrent()) {
|
|
2390
2335
|
return
|
|
2391
2336
|
}
|
|
2337
|
+
// Hydration trusts transported context and beforeLoad. The raw history
|
|
2338
|
+
// entry owns the handoff; route structure is verified after rematching.
|
|
2339
|
+
const historyLocation = router.history.location
|
|
2340
|
+
handoffHistoryHref = historyLocation.href
|
|
2341
|
+
handoffHistoryState = historyLocation.state
|
|
2392
2342
|
router.updateLatestLocation()
|
|
2393
2343
|
location = router.latestLocation
|
|
2394
2344
|
router.stores.location.set(location)
|
|
2395
|
-
handoffInputs = laneInputs(router, location)
|
|
2396
2345
|
candidates = router.matchRoutes(location, {
|
|
2397
2346
|
_controller: controller,
|
|
2398
2347
|
})
|
|
2399
2348
|
} catch (cause) {
|
|
2400
|
-
|
|
2349
|
+
if (isCurrent()) {
|
|
2350
|
+
router._preflight = undefined
|
|
2351
|
+
}
|
|
2352
|
+
controller.abort(cause)
|
|
2401
2353
|
if (cause !== controller.signal) {
|
|
2402
2354
|
throw cause
|
|
2403
2355
|
}
|
|
@@ -2492,8 +2444,6 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2492
2444
|
pendingBoundary ??= index
|
|
2493
2445
|
}
|
|
2494
2446
|
}
|
|
2495
|
-
let verifiedContextEnd = verifiedAssetEnd
|
|
2496
|
-
|
|
2497
2447
|
if (
|
|
2498
2448
|
!isTerminal &&
|
|
2499
2449
|
committed.length === shared &&
|
|
@@ -2536,14 +2486,12 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2536
2486
|
chunkFailure++
|
|
2537
2487
|
}
|
|
2538
2488
|
} catch {
|
|
2539
|
-
isCurrent()
|
|
2540
2489
|
return
|
|
2541
2490
|
}
|
|
2542
2491
|
if (!isCurrent()) {
|
|
2543
2492
|
return
|
|
2544
2493
|
}
|
|
2545
2494
|
if (chunkFailure < committed.length) {
|
|
2546
|
-
verifiedContextEnd = Math.min(verifiedContextEnd, chunkFailure)
|
|
2547
2495
|
retryFrom(chunkFailure)
|
|
2548
2496
|
}
|
|
2549
2497
|
|
|
@@ -2553,7 +2501,9 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2553
2501
|
pendingBoundary === committed.length
|
|
2554
2502
|
? committed.length + 1
|
|
2555
2503
|
: committed.length,
|
|
2556
|
-
|
|
2504
|
+
// `chunks.length` keeps the pre-retry committed length, so a smaller
|
|
2505
|
+
// `chunkFailure` is the exclusive bound of the verified context prefix.
|
|
2506
|
+
chunkFailure < chunks.length ? chunkFailure : verifiedAssetEnd,
|
|
2557
2507
|
)
|
|
2558
2508
|
for (let index = 0; index < contextEnd; index++) {
|
|
2559
2509
|
const match = candidates[index]!
|
|
@@ -2619,12 +2569,9 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2619
2569
|
let dataOnlyAssetEnd: number | undefined
|
|
2620
2570
|
if (needsClientLoad && pendingBoundary !== undefined) {
|
|
2621
2571
|
const boundary = presented[pendingBoundary]!
|
|
2572
|
+
// A verified descendant proves this data-only boundary was nonterminal.
|
|
2622
2573
|
dataOnlyAssetEnd =
|
|
2623
|
-
boundary.
|
|
2624
|
-
boundary.ssr === 'data-only' &&
|
|
2625
|
-
boundary.error === undefined &&
|
|
2626
|
-
!boundary._notFound &&
|
|
2627
|
-
verifiedAssetEnd > pendingBoundary + 1
|
|
2574
|
+
boundary.ssr === 'data-only' && verifiedAssetEnd > pendingBoundary + 1
|
|
2628
2575
|
? verifiedAssetEnd
|
|
2629
2576
|
: undefined
|
|
2630
2577
|
presented = presented.slice()
|
|
@@ -2636,29 +2583,33 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2636
2583
|
}
|
|
2637
2584
|
}
|
|
2638
2585
|
|
|
2639
|
-
const claim = () =>
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2586
|
+
const claim = () => {
|
|
2587
|
+
const historyLocation = router.history.location
|
|
2588
|
+
return needsClientLoad &&
|
|
2589
|
+
!router._tx &&
|
|
2590
|
+
historyLocation.href === handoffHistoryHref &&
|
|
2591
|
+
historyLocation.state === handoffHistoryState &&
|
|
2592
|
+
router._committed === committedMatches &&
|
|
2593
|
+
committedMatches.length &&
|
|
2594
|
+
!controller.signal.aborted
|
|
2647
2595
|
? controller
|
|
2648
2596
|
: undefined
|
|
2597
|
+
}
|
|
2649
2598
|
const handoff: NonNullable<AnyRouter['_handoff']> = [
|
|
2650
2599
|
claim,
|
|
2651
2600
|
(matches) => {
|
|
2652
2601
|
if (router._handoff !== handoff) {
|
|
2653
2602
|
return
|
|
2654
2603
|
}
|
|
2604
|
+
// `finish` is single-use. Consume the slot before validating or moving
|
|
2605
|
+
// resources so reentrant work cannot claim the same handoff.
|
|
2606
|
+
router._handoff = undefined
|
|
2655
2607
|
const prefix = committedMatches.length
|
|
2656
2608
|
if (
|
|
2657
2609
|
!matches ||
|
|
2658
2610
|
!claim() ||
|
|
2659
2611
|
committedMatches.some((match, index) => match.id !== matches[index]?.id)
|
|
2660
2612
|
) {
|
|
2661
|
-
router._handoff = undefined
|
|
2662
2613
|
controller.abort()
|
|
2663
2614
|
return
|
|
2664
2615
|
}
|
|
@@ -2666,8 +2617,7 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2666
2617
|
if (handoffAssetEnd !== undefined) {
|
|
2667
2618
|
for (let index = prefix; index < handoffAssetEnd; index++) {
|
|
2668
2619
|
if (candidates[index]?.id !== matches[index]?.id) {
|
|
2669
|
-
handoffAssetEnd =
|
|
2670
|
-
index > (pendingBoundary ?? -1) + 1 ? index : undefined
|
|
2620
|
+
handoffAssetEnd = index > pendingBoundary! + 1 ? index : undefined
|
|
2671
2621
|
break
|
|
2672
2622
|
}
|
|
2673
2623
|
}
|