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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +3 -12
  3. package/dist/cjs/await-signal.cjs +21 -0
  4. package/dist/cjs/await-signal.cjs.map +1 -0
  5. package/dist/cjs/await-signal.d.cts +1 -0
  6. package/dist/cjs/index.cjs +3 -1
  7. package/dist/cjs/index.d.cts +3 -2
  8. package/dist/cjs/isServer/client.cjs +2 -0
  9. package/dist/cjs/isServer/client.cjs.map +1 -1
  10. package/dist/cjs/isServer/client.d.cts +1 -0
  11. package/dist/cjs/isServer/development.cjs +2 -0
  12. package/dist/cjs/isServer/development.cjs.map +1 -1
  13. package/dist/cjs/isServer/development.d.cts +1 -0
  14. package/dist/cjs/isServer/server.cjs +2 -0
  15. package/dist/cjs/isServer/server.cjs.map +1 -1
  16. package/dist/cjs/isServer/server.d.cts +1 -0
  17. package/dist/cjs/load-client.cjs +1470 -0
  18. package/dist/cjs/load-client.cjs.map +1 -0
  19. package/dist/cjs/load-client.d.cts +147 -0
  20. package/dist/cjs/load-server.cjs +565 -0
  21. package/dist/cjs/load-server.cjs.map +1 -0
  22. package/dist/cjs/load-server.d.cts +16 -0
  23. package/dist/cjs/redirect.cjs.map +1 -1
  24. package/dist/cjs/redirect.d.cts +0 -1
  25. package/dist/cjs/route.cjs.map +1 -1
  26. package/dist/cjs/router.cjs +187 -445
  27. package/dist/cjs/router.cjs.map +1 -1
  28. package/dist/cjs/router.d.cts +62 -52
  29. package/dist/cjs/scroll-restoration.cjs +27 -34
  30. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  31. package/dist/cjs/ssr/client.cjs +2 -2
  32. package/dist/cjs/ssr/createRequestHandler.cjs +60 -9
  33. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  34. package/dist/cjs/ssr/createRequestHandler.d.cts +1 -0
  35. package/dist/cjs/ssr/handlerCallback.cjs +49 -4
  36. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
  37. package/dist/cjs/ssr/handlerCallback.d.cts +3 -0
  38. package/dist/cjs/ssr/server.cjs +4 -0
  39. package/dist/cjs/ssr/server.d.cts +2 -2
  40. package/dist/cjs/ssr/ssr-client.d.cts +1 -10
  41. package/dist/cjs/ssr/ssr-match-id.cjs +2 -2
  42. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
  43. package/dist/cjs/ssr/ssr-server.cjs +6 -6
  44. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  45. package/dist/cjs/ssr/transformStreamWithRouter.cjs +27 -1
  46. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  47. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +2 -0
  48. package/dist/cjs/ssr/types.d.cts +0 -1
  49. package/dist/cjs/stores.cjs +30 -104
  50. package/dist/cjs/stores.cjs.map +1 -1
  51. package/dist/cjs/stores.d.cts +7 -32
  52. package/dist/cjs/utils.cjs +0 -4
  53. package/dist/cjs/utils.cjs.map +1 -1
  54. package/dist/esm/Matches.d.ts +3 -12
  55. package/dist/esm/Matches.js.map +1 -1
  56. package/dist/esm/await-signal.d.ts +1 -0
  57. package/dist/esm/await-signal.js +21 -0
  58. package/dist/esm/await-signal.js.map +1 -0
  59. package/dist/esm/index.d.ts +3 -2
  60. package/dist/esm/index.js +3 -2
  61. package/dist/esm/isServer/client.d.ts +1 -0
  62. package/dist/esm/isServer/client.js +2 -1
  63. package/dist/esm/isServer/client.js.map +1 -1
  64. package/dist/esm/isServer/development.d.ts +1 -0
  65. package/dist/esm/isServer/development.js +2 -1
  66. package/dist/esm/isServer/development.js.map +1 -1
  67. package/dist/esm/isServer/server.d.ts +1 -0
  68. package/dist/esm/isServer/server.js +2 -1
  69. package/dist/esm/isServer/server.js.map +1 -1
  70. package/dist/esm/load-client.d.ts +147 -0
  71. package/dist/esm/load-client.js +1462 -0
  72. package/dist/esm/load-client.js.map +1 -0
  73. package/dist/esm/load-server.d.ts +16 -0
  74. package/dist/esm/load-server.js +565 -0
  75. package/dist/esm/load-server.js.map +1 -0
  76. package/dist/esm/redirect.d.ts +0 -1
  77. package/dist/esm/redirect.js.map +1 -1
  78. package/dist/esm/route.js.map +1 -1
  79. package/dist/esm/router.d.ts +62 -52
  80. package/dist/esm/router.js +188 -446
  81. package/dist/esm/router.js.map +1 -1
  82. package/dist/esm/scroll-restoration.js +27 -34
  83. package/dist/esm/scroll-restoration.js.map +1 -1
  84. package/dist/esm/ssr/client.js +1 -1
  85. package/dist/esm/ssr/createRequestHandler.d.ts +1 -0
  86. package/dist/esm/ssr/createRequestHandler.js +61 -11
  87. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  88. package/dist/esm/ssr/handlerCallback.d.ts +3 -0
  89. package/dist/esm/ssr/handlerCallback.js +47 -5
  90. package/dist/esm/ssr/handlerCallback.js.map +1 -1
  91. package/dist/esm/ssr/server.d.ts +2 -2
  92. package/dist/esm/ssr/server.js +3 -3
  93. package/dist/esm/ssr/ssr-client.d.ts +1 -10
  94. package/dist/esm/ssr/ssr-match-id.js +2 -2
  95. package/dist/esm/ssr/ssr-match-id.js.map +1 -1
  96. package/dist/esm/ssr/ssr-server.js +6 -6
  97. package/dist/esm/ssr/ssr-server.js.map +1 -1
  98. package/dist/esm/ssr/transformStreamWithRouter.d.ts +2 -0
  99. package/dist/esm/ssr/transformStreamWithRouter.js +27 -1
  100. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  101. package/dist/esm/ssr/types.d.ts +0 -1
  102. package/dist/esm/stores.d.ts +7 -32
  103. package/dist/esm/stores.js +30 -104
  104. package/dist/esm/stores.js.map +1 -1
  105. package/dist/esm/utils.js +1 -4
  106. package/dist/esm/utils.js.map +1 -1
  107. package/package.json +3 -3
  108. package/src/Matches.ts +6 -21
  109. package/src/await-signal.ts +27 -0
  110. package/src/index.ts +1 -3
  111. package/src/isServer/client.ts +1 -0
  112. package/src/isServer/development.ts +1 -0
  113. package/src/isServer/server.ts +1 -0
  114. package/src/load-client.ts +2701 -0
  115. package/src/load-server.ts +849 -0
  116. package/src/redirect.ts +0 -1
  117. package/src/route.ts +2 -11
  118. package/src/router.ts +380 -774
  119. package/src/scroll-restoration.ts +40 -56
  120. package/src/ssr/createRequestHandler.ts +109 -16
  121. package/src/ssr/handlerCallback.ts +76 -10
  122. package/src/ssr/server.ts +4 -1
  123. package/src/ssr/ssr-client.ts +1 -310
  124. package/src/ssr/ssr-match-id.ts +11 -2
  125. package/src/ssr/ssr-server.ts +9 -8
  126. package/src/ssr/transformStreamWithRouter.ts +37 -1
  127. package/src/ssr/types.ts +0 -1
  128. package/src/stores.ts +46 -208
  129. package/dist/cjs/load-matches.cjs +0 -658
  130. package/dist/cjs/load-matches.cjs.map +0 -1
  131. package/dist/cjs/load-matches.d.cts +0 -18
  132. package/dist/cjs/ssr/ssr-client.cjs +0 -183
  133. package/dist/cjs/ssr/ssr-client.cjs.map +0 -1
  134. package/dist/esm/load-matches.d.ts +0 -18
  135. package/dist/esm/load-matches.js +0 -656
  136. package/dist/esm/load-matches.js.map +0 -1
  137. package/dist/esm/ssr/ssr-client.js +0 -183
  138. package/dist/esm/ssr/ssr-client.js.map +0 -1
  139. package/src/load-matches.ts +0 -1278
@@ -0,0 +1,849 @@
1
+ // Keep this filename free of a secondary extension so declaration generation
2
+ // can rewrite relative imports for both ESM and CJS.
3
+ import { isNotFound } from './not-found'
4
+ import { isRedirect, redirect } from './redirect'
5
+ import { rootRouteId } from './root'
6
+ import { loadRouteChunk } from './load-client'
7
+ import { waitForReason } from './await-signal'
8
+ import { getLocationChangeInfo, runRouteLifecycle } from './router'
9
+ import type { ParsedLocation } from './location'
10
+ import type { AnyRouteMatch } from './Matches'
11
+ import type { NotFoundError } from './not-found'
12
+ import type {
13
+ AnyRoute,
14
+ BeforeLoadContextOptions,
15
+ LoaderFnContext,
16
+ RouteContextOptions,
17
+ SsrContextOptions,
18
+ } from './route'
19
+ import type { AnyRedirect } from './redirect'
20
+ import type { AnyRouter, SSROption } from './router'
21
+
22
+ declare const serverLanePhase: unique symbol
23
+
24
+ type ServerLane<TPhase extends 'matched' | 'contextualized' | 'reduced'> = {
25
+ readonly [serverLanePhase]: TPhase
26
+ location: ParsedLocation
27
+ matches: Array<AnyRouteMatch>
28
+ }
29
+
30
+ type MatchedLane = ServerLane<'matched'>
31
+
32
+ type IndexedOutcome = [index: number, outcome: LoaderOutcome, boundary?: number]
33
+
34
+ type ContextualizedLane = ServerLane<'contextualized'> & {
35
+ end: number
36
+ failure?: IndexedOutcome
37
+ }
38
+
39
+ type ReducedLane = ServerLane<'reduced'>
40
+
41
+ const SUCCESS = 0
42
+ const ERROR = 1
43
+ const NOT_FOUND = 2
44
+ const REDIRECTED = 3
45
+ const SKIPPED = 4
46
+
47
+ type LoaderOutcome =
48
+ | [typeof SUCCESS, data: unknown]
49
+ | [typeof ERROR, error: unknown]
50
+ | [typeof NOT_FOUND, error: NotFoundError]
51
+ | [typeof REDIRECTED, redirect: AnyRedirect]
52
+ | [typeof SKIPPED]
53
+
54
+ type LoaderTask = {
55
+ index: number
56
+ outcome: Promise<LoaderOutcome>
57
+ match: Promise<AnyRouteMatch>
58
+ }
59
+
60
+ export type ServerLoadResult =
61
+ | {
62
+ type: 'render'
63
+ status: 200 | 404 | 500
64
+ matches: Array<AnyRouteMatch>
65
+ }
66
+ | { type: 'redirect'; redirect: AnyRedirect }
67
+
68
+ function getRoute(router: AnyRouter, match: AnyRouteMatch): AnyRoute {
69
+ return router.routesById[match.routeId]
70
+ }
71
+
72
+ function normalize(value: unknown, rejected: boolean): LoaderOutcome {
73
+ if (isRedirect(value)) {
74
+ return [REDIRECTED, value]
75
+ }
76
+ if (isNotFound(value)) {
77
+ return [NOT_FOUND, value]
78
+ }
79
+ if (rejected && typeof (value as any)?.then === 'function') {
80
+ value = new Error('A Promise was thrown', { cause: value })
81
+ }
82
+ return rejected ? [ERROR, value] : [SUCCESS, value]
83
+ }
84
+
85
+ function normalizeError(route: AnyRoute, cause: unknown): LoaderOutcome {
86
+ let outcome = normalize(cause, true)
87
+ if (outcome[0] !== ERROR) {
88
+ return outcome
89
+ }
90
+ try {
91
+ route.options.onError?.(outcome[1])
92
+ } catch (onErrorCause) {
93
+ outcome = normalize(onErrorCause, true)
94
+ }
95
+ return outcome
96
+ }
97
+
98
+ function maybe<TValue>(
99
+ value: TValue,
100
+ cause: unknown,
101
+ ): { status: 'success'; value: TValue } | { status: 'error'; error: unknown } {
102
+ if (cause !== undefined) {
103
+ return { status: 'error', error: cause }
104
+ }
105
+ return { status: 'success', value }
106
+ }
107
+
108
+ function navigateFrom(router: AnyRouter, location: ParsedLocation) {
109
+ return (options: any) =>
110
+ router.navigate({
111
+ ...options,
112
+ _fromLocation: location,
113
+ })
114
+ }
115
+
116
+ function waitFor<T>(value: Promise<T>, signal?: AbortSignal): Promise<T> {
117
+ return signal ? waitForReason(value, signal) : value
118
+ }
119
+
120
+ async function resolveSsr(
121
+ router: AnyRouter,
122
+ lane: MatchedLane,
123
+ index: number,
124
+ ): Promise<SSROption> {
125
+ const match = lane.matches[index]!
126
+ const route = getRoute(router, match)
127
+ const parentSsr = lane.matches[index - 1]?.ssr
128
+
129
+ if (router.isShell()) {
130
+ return route.id === rootRouteId
131
+ }
132
+ if (parentSsr === false) {
133
+ return false
134
+ }
135
+
136
+ const inherit = (value: SSROption): SSROption => {
137
+ return value === true && parentSsr === 'data-only' ? 'data-only' : value
138
+ }
139
+ const defaultSsr = router.options.defaultSsr ?? true
140
+ const inheritedDefault = inherit(defaultSsr)
141
+ // A functional override can fail. Establish the inherited policy first so
142
+ // the selected error boundary retains the route's actual renderability.
143
+ match.ssr = inheritedDefault
144
+ const option = route.options.ssr
145
+ if (option === undefined) {
146
+ return inheritedDefault
147
+ }
148
+ if (typeof option !== 'function') {
149
+ return inherit(option)
150
+ }
151
+
152
+ const context: SsrContextOptions<any, any, any> = {
153
+ search: maybe(match.search, match.searchError),
154
+ params: maybe(match.params, match.paramsError),
155
+ location: lane.location,
156
+ matches: lane.matches.map((candidate) => ({
157
+ index: candidate.index,
158
+ pathname: candidate.pathname,
159
+ fullPath: candidate.fullPath,
160
+ staticData: candidate.staticData,
161
+ id: candidate.id,
162
+ routeId: candidate.routeId,
163
+ search: maybe(candidate.search, candidate.searchError),
164
+ params: maybe(candidate.params, candidate.paramsError),
165
+ ssr: candidate.ssr,
166
+ })),
167
+ }
168
+ return inherit((await option(context)) ?? defaultSsr)
169
+ }
170
+
171
+ function stampNotFound(
172
+ match: AnyRouteMatch,
173
+ outcome: LoaderOutcome,
174
+ ): LoaderOutcome {
175
+ if (outcome[0] === NOT_FOUND && !outcome[1].routeId) {
176
+ outcome[1].routeId = match.routeId
177
+ }
178
+ return outcome
179
+ }
180
+
181
+ async function contextualize(
182
+ router: AnyRouter,
183
+ lane: MatchedLane,
184
+ signal?: AbortSignal,
185
+ ): Promise<ContextualizedLane> {
186
+ const globalBoundary = lane.matches.findIndex((match) => match._notFound)
187
+ let end = globalBoundary < 0 ? lane.matches.length : globalBoundary + 1
188
+ let failure: IndexedOutcome | undefined
189
+ let parentContext: Record<string, unknown> = {
190
+ ...(router.options.context ?? {}),
191
+ }
192
+
193
+ for (let index = 0; index < end; index++) {
194
+ const match = lane.matches[index]!
195
+ const route = getRoute(router, match)
196
+ try {
197
+ match.ssr = await resolveSsr(router, lane, index)
198
+ } catch (cause) {
199
+ signal?.throwIfAborted()
200
+ failure = [index, stampNotFound(match, normalizeError(route, cause))]
201
+ end = index
202
+ }
203
+ signal?.throwIfAborted()
204
+ if (failure?.[1][0] === REDIRECTED) {
205
+ break
206
+ }
207
+
208
+ match.__beforeLoadContext = undefined
209
+ let context = parentContext
210
+ try {
211
+ let routeContext
212
+ if (route.options.context) {
213
+ const routeContextOptions: RouteContextOptions<
214
+ any,
215
+ any,
216
+ any,
217
+ any,
218
+ any
219
+ > = {
220
+ deps: match.loaderDeps,
221
+ params: match.params,
222
+ context: parentContext,
223
+ location: lane.location,
224
+ navigate: navigateFrom(router, lane.location),
225
+ buildLocation: router.buildLocation,
226
+ cause: match.cause,
227
+ abortController: match.abortController,
228
+ preload: false,
229
+ matches: lane.matches,
230
+ routeId: route.id,
231
+ }
232
+ routeContext = route.options.context(routeContextOptions) ?? undefined
233
+ }
234
+ context = {
235
+ ...parentContext,
236
+ ...routeContext,
237
+ }
238
+ match.context = context
239
+ } catch (cause) {
240
+ signal?.throwIfAborted()
241
+ if (!failure) {
242
+ failure = [index, stampNotFound(match, normalizeError(route, cause))]
243
+ }
244
+ end = index
245
+ break
246
+ }
247
+ signal?.throwIfAborted()
248
+ if (failure) {
249
+ break
250
+ }
251
+ const validationError = match.paramsError ?? match.searchError
252
+ if (validationError !== undefined) {
253
+ failure = [
254
+ index,
255
+ stampNotFound(match, normalizeError(route, validationError)),
256
+ ]
257
+ end = index
258
+ break
259
+ }
260
+ signal?.throwIfAborted()
261
+
262
+ if (match.ssr === false || !route.options.beforeLoad) {
263
+ parentContext = context
264
+ continue
265
+ }
266
+
267
+ const abortController = match.abortController
268
+ const options: BeforeLoadContextOptions<
269
+ any,
270
+ any,
271
+ any,
272
+ any,
273
+ any,
274
+ any,
275
+ any,
276
+ any,
277
+ any
278
+ > = {
279
+ search: match.search,
280
+ abortController,
281
+ params: match.params,
282
+ preload: false,
283
+ context,
284
+ location: lane.location,
285
+ navigate: navigateFrom(router, lane.location),
286
+ buildLocation: router.buildLocation,
287
+ cause: match.cause,
288
+ matches: lane.matches,
289
+ routeId: route.id,
290
+ ...router.options.additionalContext,
291
+ }
292
+
293
+ try {
294
+ const beforeLoadContext = await route.options.beforeLoad(options)
295
+ signal?.throwIfAborted()
296
+ const outcome = stampNotFound(match, normalize(beforeLoadContext, false))
297
+ if (outcome[0] !== SUCCESS) {
298
+ failure = [index, outcome]
299
+ end = index
300
+ break
301
+ }
302
+ match.__beforeLoadContext = beforeLoadContext
303
+ match.context = {
304
+ ...context,
305
+ ...beforeLoadContext,
306
+ }
307
+ parentContext = match.context
308
+ } catch (cause) {
309
+ signal?.throwIfAborted()
310
+ failure = [index, stampNotFound(match, normalizeError(route, cause))]
311
+ end = index
312
+ break
313
+ }
314
+ }
315
+
316
+ return {
317
+ location: lane.location,
318
+ matches: lane.matches,
319
+ end,
320
+ failure,
321
+ } as ContextualizedLane
322
+ }
323
+
324
+ function getLoaderContext(
325
+ router: AnyRouter,
326
+ lane: ContextualizedLane,
327
+ match: AnyRouteMatch,
328
+ route: AnyRoute,
329
+ index: number,
330
+ tasks: Array<LoaderTask>,
331
+ ): LoaderFnContext {
332
+ return {
333
+ params: match.params,
334
+ deps: match.loaderDeps,
335
+ preload: false,
336
+ parentMatchPromise: tasks[index - 1]?.match,
337
+ abortController: match.abortController,
338
+ context: match.context,
339
+ location: lane.location,
340
+ navigate: navigateFrom(router, lane.location),
341
+ cause: match.cause,
342
+ route,
343
+ ...router.options.additionalContext,
344
+ }
345
+ }
346
+
347
+ function createLoaderTask(
348
+ router: AnyRouter,
349
+ lane: ContextualizedLane,
350
+ index: number,
351
+ tasks: Array<LoaderTask>,
352
+ signal?: AbortSignal,
353
+ ): LoaderTask {
354
+ const match = lane.matches[index]!
355
+ const route = getRoute(router, match)
356
+ let outcome: Promise<LoaderOutcome>
357
+
358
+ if (match.ssr === false) {
359
+ outcome = Promise.resolve<LoaderOutcome>([SKIPPED])
360
+ } else {
361
+ const routeLoader = route.options.loader
362
+ const loader =
363
+ typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler
364
+ if (!loader) {
365
+ outcome = Promise.resolve<LoaderOutcome>([SUCCESS, undefined])
366
+ } else {
367
+ outcome = Promise.resolve()
368
+ .then(() =>
369
+ loader(getLoaderContext(router, lane, match, route, index, tasks)),
370
+ )
371
+ .then(
372
+ (result) => normalize(result, false),
373
+ (cause) => normalize(cause, true),
374
+ )
375
+ .then((result): LoaderOutcome => {
376
+ if (
377
+ result[0] !== REDIRECTED &&
378
+ (signal?.aborted || match.abortController.signal.reason === lane)
379
+ ) {
380
+ return [SKIPPED]
381
+ }
382
+ if (result[0] === ERROR) {
383
+ result = normalizeError(route, result[1])
384
+ }
385
+ return stampNotFound(match, result)
386
+ })
387
+ }
388
+ }
389
+
390
+ const parentMatch = outcome.then((result) => {
391
+ const snapshot = { ...match }
392
+ if (result[0] === SUCCESS) {
393
+ snapshot.loaderData = result[1]
394
+ snapshot.status = 'success'
395
+ snapshot.error = undefined
396
+ snapshot.invalid = false
397
+ snapshot.isFetching = false
398
+ } else if (result[0] === ERROR) {
399
+ snapshot.status = 'error'
400
+ snapshot.error = result[1]
401
+ } else if (result[0] === NOT_FOUND) {
402
+ snapshot.status = 'notFound'
403
+ snapshot.error = result[1]
404
+ }
405
+ return snapshot
406
+ })
407
+
408
+ return { index, outcome, match: parentMatch }
409
+ }
410
+
411
+ async function getNotFoundBoundary(
412
+ router: AnyRouter,
413
+ matches: Array<AnyRouteMatch>,
414
+ indexed: IndexedOutcome | undefined,
415
+ signal?: AbortSignal,
416
+ fallback = 0,
417
+ ): Promise<number> {
418
+ const cause = indexed?.[1][1] as NotFoundError | undefined
419
+ let index = cause?.routeId
420
+ ? matches.findIndex((match) => match.routeId === cause.routeId)
421
+ : (indexed?.[0] ?? matches.length - 1)
422
+ if (index < 0) {
423
+ index = 0
424
+ }
425
+ for (let candidate = index; candidate >= 0; candidate--) {
426
+ const route = getRoute(router, matches[candidate]!)
427
+ const loading = loadRouteChunk(route, false)
428
+ if (loading) {
429
+ try {
430
+ await loading
431
+ } catch {
432
+ signal?.throwIfAborted()
433
+ }
434
+ }
435
+ signal?.throwIfAborted()
436
+ if (route.options.notFoundComponent) {
437
+ return candidate
438
+ }
439
+ }
440
+ return cause?.routeId ? index : fallback
441
+ }
442
+
443
+ function abortMatches(
444
+ matches: Array<AnyRouteMatch>,
445
+ start = 0,
446
+ reason?: unknown,
447
+ ): void {
448
+ for (let index = start; index < matches.length; index++) {
449
+ matches[index]!.abortController.abort(reason)
450
+ }
451
+ }
452
+
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
+ async function applyFailure(
463
+ router: AnyRouter,
464
+ lane: ContextualizedLane,
465
+ indexed: IndexedOutcome | undefined,
466
+ signal?: AbortSignal,
467
+ ): Promise<{ status: 200 | 404 | 500; boundary?: number; kind?: number }> {
468
+ if (!indexed) {
469
+ const boundary = lane.matches.findIndex((match) => match._notFound)
470
+ if (boundary >= 0) {
471
+ abortMatches(lane.matches, boundary + 1)
472
+ return { status: 404, boundary, kind: NOT_FOUND }
473
+ }
474
+ return { status: 200 }
475
+ }
476
+
477
+ const [index, outcome] = indexed
478
+ if (outcome[0] === ERROR) {
479
+ const match = lane.matches[index]!
480
+ match._notFound = undefined
481
+ match.status = 'error'
482
+ match.error = outcome[1]
483
+ match.isFetching = false
484
+ abortMatches(lane.matches, index + 1)
485
+ return { status: 500, boundary: index, kind: ERROR }
486
+ }
487
+
488
+ const boundary =
489
+ indexed[2] ??
490
+ (await getNotFoundBoundary(router, lane.matches, indexed, signal))
491
+ const match = lane.matches[boundary]!
492
+ const cause = outcome[1] as NotFoundError
493
+ cause.routeId = match.routeId
494
+ match._notFound = undefined
495
+ if (match.routeId === router.routeTree.id) {
496
+ match.status = 'success'
497
+ match._notFound = true
498
+ match.error = cause
499
+ } else {
500
+ match.status = 'notFound'
501
+ match.error = cause
502
+ }
503
+ match.isFetching = false
504
+ abortMatches(lane.matches, boundary + 1)
505
+ return { status: 404, boundary, kind: NOT_FOUND }
506
+ }
507
+
508
+ async function loadNormalChunks(
509
+ router: AnyRouter,
510
+ lane: ContextualizedLane,
511
+ end: number,
512
+ signal?: AbortSignal,
513
+ ): Promise<IndexedOutcome | undefined> {
514
+ const chunks: Array<IndexedOutcome | Promise<IndexedOutcome | undefined>> = []
515
+ for (let index = 0; index < lane.matches.length; index++) {
516
+ const match = lane.matches[index]!
517
+ if (index >= end || match.ssr !== true || match.status !== 'success') {
518
+ continue
519
+ }
520
+ const route = getRoute(router, match)
521
+ try {
522
+ const loading = loadRouteChunk(route)
523
+ if (loading) {
524
+ const chunk = loading.then(
525
+ () => {
526
+ signal?.throwIfAborted()
527
+ return undefined
528
+ },
529
+ (cause) => {
530
+ signal?.throwIfAborted()
531
+ return [
532
+ index,
533
+ stampNotFound(match, normalizeError(route, cause)),
534
+ ] as IndexedOutcome
535
+ },
536
+ )
537
+ // Route-order reduction can return before later chunks settle.
538
+ void chunk.catch(() => {})
539
+ chunks.push(chunk)
540
+ }
541
+ } catch (cause) {
542
+ signal?.throwIfAborted()
543
+ chunks.push([index, stampNotFound(match, normalizeError(route, cause))])
544
+ }
545
+ }
546
+ for (const chunk of chunks) {
547
+ const indexed = Array.isArray(chunk) ? chunk : await chunk
548
+ if (indexed) {
549
+ return indexed
550
+ }
551
+ }
552
+ return undefined
553
+ }
554
+
555
+ async function projectLane(
556
+ router: AnyRouter,
557
+ lane: ReducedLane,
558
+ signal?: AbortSignal,
559
+ ): Promise<void> {
560
+ for (const match of lane.matches) {
561
+ const routeOptions = getRoute(router, match).options
562
+ if (routeOptions.head || routeOptions.scripts || routeOptions.headers) {
563
+ const context = {
564
+ ssr: router.options.ssr,
565
+ matches: lane.matches,
566
+ match,
567
+ params: match.params,
568
+ loaderData: match.loaderData,
569
+ }
570
+ try {
571
+ const [head, scripts, headers] = await Promise.all([
572
+ routeOptions.head?.(context),
573
+ routeOptions.scripts?.(context),
574
+ routeOptions.headers?.(context),
575
+ ])
576
+ signal?.throwIfAborted()
577
+ match.meta = head?.meta
578
+ match.links = head?.links
579
+ match.headScripts = head?.scripts
580
+ match.styles = head?.styles
581
+ match.scripts = scripts
582
+ match.headers = headers
583
+ } catch (cause) {
584
+ signal?.throwIfAborted()
585
+ console.error(cause)
586
+ }
587
+ }
588
+ if (match.ssr === false || match.status !== 'success' || match._notFound) {
589
+ break
590
+ }
591
+ }
592
+ }
593
+
594
+ async function executeServerLane(
595
+ router: AnyRouter,
596
+ location: ParsedLocation,
597
+ matchedMatches: Array<AnyRouteMatch>,
598
+ signal?: AbortSignal,
599
+ ): Promise<ServerLoadResult> {
600
+ const matched = {
601
+ location,
602
+ matches: matchedMatches.map((match) => ({
603
+ ...match,
604
+ __beforeLoadContext: undefined,
605
+ context: {},
606
+ isFetching: false,
607
+ abortController: new AbortController(),
608
+ })),
609
+ } as MatchedLane
610
+ const abortLane = () => abortMatches(matched.matches, 0, signal?.reason)
611
+ if (signal?.aborted) {
612
+ abortLane()
613
+ signal.throwIfAborted()
614
+ }
615
+ signal?.addEventListener('abort', abortLane, { once: true })
616
+
617
+ try {
618
+ const plannedGlobalBoundary = matched.matches.findIndex(
619
+ (match) => match._notFound,
620
+ )
621
+ if (router.options.notFoundMode !== 'root' && plannedGlobalBoundary >= 0) {
622
+ const boundary = await getNotFoundBoundary(
623
+ router,
624
+ matched.matches,
625
+ undefined,
626
+ signal,
627
+ plannedGlobalBoundary,
628
+ )
629
+ if (boundary !== plannedGlobalBoundary) {
630
+ matched.matches[plannedGlobalBoundary]!._notFound = undefined
631
+ matched.matches[boundary]!._notFound = true
632
+ }
633
+ }
634
+ const lane = await contextualize(router, matched, signal)
635
+ signal?.throwIfAborted()
636
+
637
+ let loaderEnd = lane.end
638
+ if (lane.failure?.[1][0] === REDIRECTED) {
639
+ loaderEnd = 0
640
+ } else if (lane.failure?.[1][0] === NOT_FOUND) {
641
+ lane.failure[2] = await getNotFoundBoundary(
642
+ router,
643
+ lane.matches,
644
+ lane.failure,
645
+ signal,
646
+ )
647
+ loaderEnd = Math.min(loaderEnd, lane.failure[2] + 1)
648
+ }
649
+
650
+ const tasks: Array<LoaderTask> = []
651
+ for (let index = 0; index < loaderEnd; index++) {
652
+ const task = createLoaderTask(router, lane, index, tasks, signal)
653
+ tasks.push(task)
654
+ }
655
+
656
+ let loaderFailure: IndexedOutcome | undefined
657
+ let control = lane.failure?.[1][0] === REDIRECTED ? lane.failure : undefined
658
+ try {
659
+ await Promise.all(
660
+ tasks.map((task) =>
661
+ task.outcome.then((loadedOutcome) => {
662
+ const match = lane.matches[task.index]!
663
+ const outcome = loadedOutcome
664
+ if (outcome[0] === SUCCESS) {
665
+ match.loaderData = outcome[1]
666
+ match.status = 'success'
667
+ match.error = undefined
668
+ match.invalid = false
669
+ match.isFetching = false
670
+ match.updatedAt = Date.now()
671
+ } else if (outcome[0] === REDIRECTED) {
672
+ control = [task.index, outcome]
673
+ throw control
674
+ } else {
675
+ // A selective-SSR skip must stay pending for hydration. Every
676
+ // settled server attempt is otherwise renderable unless
677
+ // reduction selects it as the lane's terminal failure.
678
+ if (match.ssr !== false) {
679
+ match.status = 'success'
680
+ match.error = undefined
681
+ match.invalid = true
682
+ match.isFetching = false
683
+ }
684
+ if (!loaderFailure && outcome[0] !== SKIPPED) {
685
+ loaderFailure = [task.index, outcome]
686
+ }
687
+ }
688
+ }),
689
+ ),
690
+ )
691
+ } catch (cause) {
692
+ if (!Array.isArray(cause)) {
693
+ throw cause
694
+ }
695
+ control = cause as IndexedOutcome
696
+ }
697
+ signal?.throwIfAborted()
698
+
699
+ if (control?.[1][0] === REDIRECTED) {
700
+ abortMatches(lane.matches, 0, lane)
701
+ return resolveServerRedirect(router, location, control[1][1])
702
+ }
703
+
704
+ let failure = lane.failure ?? loaderFailure
705
+ const plannedBoundary = lane.matches.findIndex((match) => match._notFound)
706
+ let readinessEnd: number
707
+ if (failure) {
708
+ const outcomeEnd = (failure[2] ??=
709
+ failure[1][0] === NOT_FOUND
710
+ ? await getNotFoundBoundary(router, lane.matches, failure, signal)
711
+ : failure[0])
712
+ for (const task of tasks) {
713
+ if (task.index >= outcomeEnd) {
714
+ break
715
+ }
716
+ const outcome = await task.outcome
717
+ // Presence means a loader previously succeeded, even with `undefined`.
718
+ if (
719
+ outcome[0] !== SUCCESS &&
720
+ outcome[0] < REDIRECTED &&
721
+ !('loaderData' in lane.matches[task.index]!)
722
+ ) {
723
+ failure = [task.index, outcome]
724
+ failure[2] =
725
+ outcome[0] === NOT_FOUND
726
+ ? await getNotFoundBoundary(router, lane.matches, failure, signal)
727
+ : task.index
728
+ break
729
+ }
730
+ }
731
+ readinessEnd = failure[2]
732
+ } else {
733
+ readinessEnd = plannedBoundary < 0 ? lane.matches.length : plannedBoundary
734
+ }
735
+ const requiredFailure = await loadNormalChunks(
736
+ router,
737
+ lane,
738
+ readinessEnd,
739
+ signal,
740
+ )
741
+ signal?.throwIfAborted()
742
+ if (requiredFailure) {
743
+ if (requiredFailure[1][0] === REDIRECTED) {
744
+ abortMatches(lane.matches)
745
+ return resolveServerRedirect(router, location, requiredFailure[1][1])
746
+ }
747
+ failure = requiredFailure
748
+ }
749
+
750
+ const terminal = await applyFailure(router, lane, failure, signal)
751
+ if (terminal.boundary !== undefined) {
752
+ const match = lane.matches[terminal.boundary]!
753
+ if (match.ssr === true) {
754
+ const route = getRoute(router, match)
755
+ try {
756
+ if (terminal.kind === ERROR) {
757
+ await loadRouteChunk(route, 'errorComponent')
758
+ } else if (match._notFound) {
759
+ await Promise.all([
760
+ loadRouteChunk(route),
761
+ loadRouteChunk(route, 'notFoundComponent'),
762
+ ])
763
+ } else {
764
+ await loadRouteChunk(route, 'notFoundComponent')
765
+ }
766
+ } catch {}
767
+ signal?.throwIfAborted()
768
+ }
769
+ }
770
+
771
+ signal?.throwIfAborted()
772
+ await projectLane(
773
+ router,
774
+ {
775
+ location: lane.location,
776
+ matches: lane.matches,
777
+ } as ReducedLane,
778
+ signal,
779
+ )
780
+ signal?.throwIfAborted()
781
+ router.serverSsr?.onCleanup(abortLane)
782
+ return { type: 'render', status: terminal.status, matches: lane.matches }
783
+ } finally {
784
+ signal?.removeEventListener('abort', abortLane)
785
+ }
786
+ }
787
+
788
+ type ServerLoadOptions = NonNullable<Parameters<AnyRouter['load']>[0]> & {
789
+ _signal?: AbortSignal
790
+ }
791
+
792
+ export async function loadServerRoute(
793
+ router: AnyRouter,
794
+ opts?: ServerLoadOptions,
795
+ ): Promise<void> {
796
+ router.updateLatestLocation()
797
+ const next = router.latestLocation
798
+ const previous = router._committed
799
+ let result: ServerLoadResult
800
+ try {
801
+ const canonical = router.buildLocation({
802
+ to: next.pathname,
803
+ search: true,
804
+ params: true,
805
+ hash: true,
806
+ state: true,
807
+ _includeValidateSearch: true,
808
+ })
809
+ if (next.publicHref !== canonical.publicHref) {
810
+ const href = canonical.publicHref || '/'
811
+ throw canonical.external
812
+ ? redirect({ href })
813
+ : redirect({ href, _builtLocation: canonical })
814
+ }
815
+
816
+ const fromLocation = router.stores.resolvedLocation.get()
817
+ const changeInfo = getLocationChangeInfo(next, fromLocation)
818
+ router.emit({ type: 'onBeforeNavigate', ...changeInfo })
819
+ router.emit({ type: 'onBeforeLoad', ...changeInfo })
820
+ opts?._signal?.throwIfAborted()
821
+ result = await waitFor(
822
+ executeServerLane(router, next, router.matchRoutes(next), opts?._signal),
823
+ opts?._signal,
824
+ )
825
+ opts?._signal?.throwIfAborted()
826
+ } catch (cause) {
827
+ opts?._signal?.throwIfAborted()
828
+ if (!isRedirect(cause)) {
829
+ throw cause
830
+ }
831
+ result = resolveServerRedirect(router, next, cause)
832
+ }
833
+
834
+ router._serverResult = result
835
+ router.batch(() => {
836
+ router.stores.location.set(next)
837
+ router.stores.status.set('idle')
838
+ if (result.type === 'render') {
839
+ router.stores.setMatches(result.matches)
840
+ router.stores.resolvedLocation.set(next)
841
+ }
842
+ })
843
+ if (result.type === 'render') {
844
+ router._committed = result.matches
845
+ runRouteLifecycle(router, previous, result.matches)
846
+ }
847
+ router._commitPromise?.resolve()
848
+ router._commitPromise = undefined
849
+ }