@tanstack/router-core 1.121.0-alpha.1 → 1.121.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/router.ts CHANGED
@@ -433,8 +433,9 @@ export interface BuildNextOptions {
433
433
  unmaskOnReload?: boolean
434
434
  }
435
435
  from?: string
436
- _fromLocation?: ParsedLocation
437
436
  href?: string
437
+ _fromLocation?: ParsedLocation
438
+ unsafeRelative?: 'path'
438
439
  }
439
440
 
440
441
  type NavigationEventInfo = {
@@ -521,11 +522,6 @@ export interface RouterErrorSerializer<TSerializedError> {
521
522
  deserialize: (err: TSerializedError) => unknown
522
523
  }
523
524
 
524
- export interface MatchedRoutesResult {
525
- matchedRoutes: Array<AnyRoute>
526
- routeParams: Record<string, string>
527
- }
528
-
529
525
  export type PreloadRouteFn<
530
526
  TRouteTree extends AnyRoute,
531
527
  TTrailingSlashOption extends TrailingSlashOption,
@@ -1071,9 +1067,9 @@ export class RouterCore<
1071
1067
  } as ParsedLocation,
1072
1068
  opts,
1073
1069
  )
1074
- } else {
1075
- return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
1076
1070
  }
1071
+
1072
+ return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
1077
1073
  }
1078
1074
 
1079
1075
  private matchRoutesInternal(
@@ -1334,7 +1330,8 @@ export class RouterCore<
1334
1330
  const route = this.looseRoutesById[match.routeId]!
1335
1331
  const existingMatch = this.getMatch(match.id)
1336
1332
 
1337
- // only execute `context` if we are not just building a location
1333
+ // only execute `context` if we are not calling from router.buildLocation
1334
+
1338
1335
  if (!existingMatch && opts?._buildLocation !== true) {
1339
1336
  const parentMatch = matches[index - 1]
1340
1337
  const parentContext = getParentContext(parentMatch)
@@ -1403,75 +1400,67 @@ export class RouterCore<
1403
1400
  dest: BuildNextOptions & {
1404
1401
  unmaskOnReload?: boolean
1405
1402
  } = {},
1406
- matchedRoutesResult?: MatchedRoutesResult,
1407
1403
  ): ParsedLocation => {
1408
- const fromMatches = dest._fromLocation
1409
- ? this.matchRoutes(dest._fromLocation, { _buildLocation: true })
1410
- : this.state.matches
1411
-
1412
- const fromMatch =
1413
- dest.from != null
1414
- ? fromMatches.find((d) =>
1415
- matchPathname(this.basepath, trimPathRight(d.pathname), {
1416
- to: dest.from,
1417
- caseSensitive: false,
1418
- fuzzy: false,
1419
- }),
1420
- )
1421
- : undefined
1422
-
1423
- const fromPath = fromMatch?.pathname || this.latestLocation.pathname
1404
+ // We allow the caller to override the current location
1405
+ const currentLocation = dest._fromLocation || this.latestLocation
1424
1406
 
1425
- invariant(
1426
- dest.from == null || fromMatch != null,
1427
- 'Could not find match for from: ' + dest.from,
1428
- )
1407
+ const allFromMatches = this.matchRoutes(currentLocation, {
1408
+ _buildLocation: true,
1409
+ })
1429
1410
 
1430
- const fromSearch = this.state.pendingMatches?.length
1431
- ? last(this.state.pendingMatches)?.search
1432
- : last(fromMatches)?.search || this.latestLocation.search
1411
+ const lastMatch = last(allFromMatches)!
1412
+
1413
+ // First let's find the starting pathname
1414
+ // By default, start with the current location
1415
+ let fromPath = lastMatch.fullPath
1416
+
1417
+ // If there is a to, it means we are changing the path in some way
1418
+ // So we need to find the relative fromPath
1419
+ if (dest.unsafeRelative === 'path') {
1420
+ fromPath = currentLocation.pathname
1421
+ } else if (dest.to && dest.from) {
1422
+ fromPath = dest.from
1423
+ const existingFrom = [...allFromMatches].reverse().find((d) => {
1424
+ return (
1425
+ d.fullPath === fromPath || d.fullPath === joinPaths([fromPath, '/'])
1426
+ )
1427
+ })
1433
1428
 
1434
- const stayingMatches = matchedRoutesResult?.matchedRoutes.filter((d) =>
1435
- fromMatches.find((e) => e.routeId === d.id),
1436
- )
1437
- let pathname: string
1438
- if (dest.to) {
1439
- const resolvePathTo =
1440
- fromMatch?.fullPath ||
1441
- last(fromMatches)?.fullPath ||
1442
- this.latestLocation.pathname
1443
- pathname = this.resolvePathWithBase(resolvePathTo, `${dest.to}`)
1444
- } else {
1445
- const fromRouteByFromPathRouteId =
1446
- this.routesById[
1447
- stayingMatches?.find((route) => {
1448
- const interpolatedPath = interpolatePath({
1449
- path: route.fullPath,
1450
- params: matchedRoutesResult?.routeParams ?? {},
1451
- decodeCharMap: this.pathParamsDecodeCharMap,
1452
- }).interpolatedPath
1453
- const pathname = joinPaths([this.basepath, interpolatedPath])
1454
- return pathname === fromPath
1455
- })?.id as keyof this['routesById']
1456
- ]
1457
- pathname = this.resolvePathWithBase(
1458
- fromPath,
1459
- fromRouteByFromPathRouteId?.to ?? fromPath,
1460
- )
1429
+ if (!existingFrom) {
1430
+ console.warn(`Could not find match for from: ${dest.from}`)
1431
+ }
1461
1432
  }
1462
1433
 
1463
- const prevParams = { ...last(fromMatches)?.params }
1434
+ // From search should always use the current location
1435
+ const fromSearch = lastMatch.search
1436
+ // Same with params. It can't hurt to provide as many as possible
1437
+ const fromParams = { ...lastMatch.params }
1464
1438
 
1439
+ // Resolve the next to
1440
+ const nextTo = dest.to
1441
+ ? this.resolvePathWithBase(fromPath, `${dest.to}`)
1442
+ : fromPath
1443
+
1444
+ // Resolve the next params
1465
1445
  let nextParams =
1466
1446
  (dest.params ?? true) === true
1467
- ? prevParams
1447
+ ? fromParams
1468
1448
  : {
1469
- ...prevParams,
1470
- ...functionalUpdate(dest.params as any, prevParams),
1449
+ ...fromParams,
1450
+ ...functionalUpdate(dest.params as any, fromParams),
1471
1451
  }
1472
1452
 
1453
+ const destRoutes = this.matchRoutes(
1454
+ nextTo,
1455
+ {},
1456
+ {
1457
+ _buildLocation: true,
1458
+ },
1459
+ ).map((d) => this.looseRoutesById[d.routeId]!)
1460
+
1461
+ // If there are any params, we need to stringify them
1473
1462
  if (Object.keys(nextParams).length > 0) {
1474
- matchedRoutesResult?.matchedRoutes
1463
+ destRoutes
1475
1464
  .map((route) => {
1476
1465
  return (
1477
1466
  route.options.params?.stringify ?? route.options.stringifyParams
@@ -1483,25 +1472,27 @@ export class RouterCore<
1483
1472
  })
1484
1473
  }
1485
1474
 
1486
- pathname = interpolatePath({
1487
- path: pathname,
1475
+ // Interpolate the next to into the next pathname
1476
+ const nextPathname = interpolatePath({
1477
+ path: nextTo,
1488
1478
  params: nextParams ?? {},
1489
1479
  leaveWildcards: false,
1490
1480
  leaveParams: opts.leaveParams,
1491
1481
  decodeCharMap: this.pathParamsDecodeCharMap,
1492
1482
  }).interpolatedPath
1493
1483
 
1494
- let search = fromSearch
1484
+ // Resolve the next search
1485
+ let nextSearch = fromSearch
1495
1486
  if (opts._includeValidateSearch && this.options.search?.strict) {
1496
1487
  let validatedSearch = {}
1497
- matchedRoutesResult?.matchedRoutes.forEach((route) => {
1488
+ destRoutes.forEach((route) => {
1498
1489
  try {
1499
1490
  if (route.options.validateSearch) {
1500
1491
  validatedSearch = {
1501
1492
  ...validatedSearch,
1502
1493
  ...(validateSearch(route.options.validateSearch, {
1503
1494
  ...validatedSearch,
1504
- ...search,
1495
+ ...nextSearch,
1505
1496
  }) ?? {}),
1506
1497
  }
1507
1498
  }
@@ -1509,137 +1500,52 @@ export class RouterCore<
1509
1500
  // ignore errors here because they are already handled in matchRoutes
1510
1501
  }
1511
1502
  })
1512
- search = validatedSearch
1503
+ nextSearch = validatedSearch
1513
1504
  }
1514
1505
 
1515
- const applyMiddlewares = (search: any) => {
1516
- const allMiddlewares =
1517
- matchedRoutesResult?.matchedRoutes.reduce(
1518
- (acc, route) => {
1519
- const middlewares: Array<SearchMiddleware<any>> = []
1520
- if ('search' in route.options) {
1521
- if (route.options.search?.middlewares) {
1522
- middlewares.push(...route.options.search.middlewares)
1523
- }
1524
- }
1525
- // TODO remove preSearchFilters and postSearchFilters in v2
1526
- else if (
1527
- route.options.preSearchFilters ||
1528
- route.options.postSearchFilters
1529
- ) {
1530
- const legacyMiddleware: SearchMiddleware<any> = ({
1531
- search,
1532
- next,
1533
- }) => {
1534
- let nextSearch = search
1535
- if (
1536
- 'preSearchFilters' in route.options &&
1537
- route.options.preSearchFilters
1538
- ) {
1539
- nextSearch = route.options.preSearchFilters.reduce(
1540
- (prev, next) => next(prev),
1541
- search,
1542
- )
1543
- }
1544
- const result = next(nextSearch)
1545
- if (
1546
- 'postSearchFilters' in route.options &&
1547
- route.options.postSearchFilters
1548
- ) {
1549
- return route.options.postSearchFilters.reduce(
1550
- (prev, next) => next(prev),
1551
- result,
1552
- )
1553
- }
1554
- return result
1555
- }
1556
- middlewares.push(legacyMiddleware)
1557
- }
1558
- if (opts._includeValidateSearch && route.options.validateSearch) {
1559
- const validate: SearchMiddleware<any> = ({ search, next }) => {
1560
- const result = next(search)
1561
- try {
1562
- const validatedSearch = {
1563
- ...result,
1564
- ...(validateSearch(
1565
- route.options.validateSearch,
1566
- result,
1567
- ) ?? {}),
1568
- }
1569
- return validatedSearch
1570
- } catch {
1571
- // ignore errors here because they are already handled in matchRoutes
1572
- return result
1573
- }
1574
- }
1575
- middlewares.push(validate)
1576
- }
1577
- return acc.concat(middlewares)
1578
- },
1579
- [] as Array<SearchMiddleware<any>>,
1580
- ) ?? []
1581
-
1582
- // the chain ends here since `next` is not called
1583
- const final: SearchMiddleware<any> = ({ search }) => {
1584
- if (!dest.search) {
1585
- return {}
1586
- }
1587
- if (dest.search === true) {
1588
- return search
1589
- }
1590
- return functionalUpdate(dest.search, search)
1591
- }
1592
- allMiddlewares.push(final)
1593
-
1594
- const applyNext = (index: number, currentSearch: any): any => {
1595
- // no more middlewares left, return the current search
1596
- if (index >= allMiddlewares.length) {
1597
- return currentSearch
1598
- }
1599
-
1600
- const middleware = allMiddlewares[index]!
1601
-
1602
- const next = (newSearch: any): any => {
1603
- return applyNext(index + 1, newSearch)
1604
- }
1605
-
1606
- return middleware({ search: currentSearch, next })
1607
- }
1608
-
1609
- // Start applying middlewares
1610
- return applyNext(0, search)
1611
- }
1506
+ nextSearch = applySearchMiddleware({
1507
+ search: nextSearch,
1508
+ dest,
1509
+ destRoutes,
1510
+ _includeValidateSearch: opts._includeValidateSearch,
1511
+ })
1612
1512
 
1613
- search = applyMiddlewares(search)
1513
+ // Replace the equal deep
1514
+ nextSearch = replaceEqualDeep(fromSearch, nextSearch)
1614
1515
 
1615
- search = replaceEqualDeep(fromSearch, search)
1616
- const searchStr = this.options.stringifySearch(search)
1516
+ // Stringify the next search
1517
+ const searchStr = this.options.stringifySearch(nextSearch)
1617
1518
 
1519
+ // Resolve the next hash
1618
1520
  const hash =
1619
1521
  dest.hash === true
1620
- ? this.latestLocation.hash
1522
+ ? currentLocation.hash
1621
1523
  : dest.hash
1622
- ? functionalUpdate(dest.hash, this.latestLocation.hash)
1524
+ ? functionalUpdate(dest.hash, currentLocation.hash)
1623
1525
  : undefined
1624
1526
 
1527
+ // Resolve the next hash string
1625
1528
  const hashStr = hash ? `#${hash}` : ''
1626
1529
 
1530
+ // Resolve the next state
1627
1531
  let nextState =
1628
1532
  dest.state === true
1629
- ? this.latestLocation.state
1533
+ ? currentLocation.state
1630
1534
  : dest.state
1631
- ? functionalUpdate(dest.state, this.latestLocation.state)
1535
+ ? functionalUpdate(dest.state, currentLocation.state)
1632
1536
  : {}
1633
1537
 
1634
- nextState = replaceEqualDeep(this.latestLocation.state, nextState)
1538
+ // Replace the equal deep
1539
+ nextState = replaceEqualDeep(currentLocation.state, nextState)
1635
1540
 
1541
+ // Return the next location
1636
1542
  return {
1637
- pathname,
1638
- search,
1543
+ pathname: nextPathname,
1544
+ search: nextSearch,
1639
1545
  searchStr,
1640
1546
  state: nextState as any,
1641
1547
  hash: hash ?? '',
1642
- href: `${pathname}${searchStr}${hashStr}`,
1548
+ href: `${nextPathname}${searchStr}${hashStr}`,
1643
1549
  unmaskOnReload: dest.unmaskOnReload,
1644
1550
  }
1645
1551
  }
@@ -1649,6 +1555,7 @@ export class RouterCore<
1649
1555
  maskedDest?: BuildNextOptions,
1650
1556
  ) => {
1651
1557
  const next = build(dest)
1558
+
1652
1559
  let maskedNext = maskedDest ? build(maskedDest) : undefined
1653
1560
 
1654
1561
  if (!maskedNext) {
@@ -1680,22 +1587,12 @@ export class RouterCore<
1680
1587
  }
1681
1588
  }
1682
1589
 
1683
- const nextMatches = this.getMatchedRoutes(
1684
- next.pathname,
1685
- dest.to as string,
1686
- )
1687
- const final = build(dest, nextMatches)
1688
-
1689
1590
  if (maskedNext) {
1690
- const maskedMatches = this.getMatchedRoutes(
1691
- maskedNext.pathname,
1692
- maskedDest?.to as string,
1693
- )
1694
- const maskedFinal = build(maskedDest, maskedMatches)
1695
- final.maskedLocation = maskedFinal
1591
+ const maskedFinal = build(maskedDest)
1592
+ next.maskedLocation = maskedFinal
1696
1593
  }
1697
1594
 
1698
- return final
1595
+ return next
1699
1596
  }
1700
1597
 
1701
1598
  if (opts.mask) {
@@ -2488,7 +2385,7 @@ export class RouterCore<
2488
2385
  !this.state.matches.find((d) => d.id === matchId),
2489
2386
  }))
2490
2387
 
2491
- const executeHead = () => {
2388
+ const executeHead = async () => {
2492
2389
  const match = this.getMatch(matchId)
2493
2390
  // in case of a redirecting match during preload, the match does not exist
2494
2391
  if (!match) {
@@ -2500,21 +2397,17 @@ export class RouterCore<
2500
2397
  params: match.params,
2501
2398
  loaderData: match.loaderData,
2502
2399
  }
2503
- const headFnContent = route.options.head?.(assetContext)
2400
+ const headFnContent =
2401
+ await route.options.head?.(assetContext)
2504
2402
  const meta = headFnContent?.meta
2505
2403
  const links = headFnContent?.links
2506
2404
  const headScripts = headFnContent?.scripts
2507
2405
 
2508
- const scripts = route.options.scripts?.(assetContext)
2509
- const headers = route.options.headers?.(assetContext)
2510
- updateMatch(matchId, (prev) => ({
2511
- ...prev,
2512
- meta,
2513
- links,
2514
- headScripts,
2515
- headers,
2516
- scripts,
2517
- }))
2406
+ const scripts =
2407
+ await route.options.scripts?.(assetContext)
2408
+ const headers =
2409
+ await route.options.headers?.(assetContext)
2410
+ return { meta, links, headScripts, headers, scripts }
2518
2411
  }
2519
2412
 
2520
2413
  const runLoader = async () => {
@@ -2561,17 +2454,19 @@ export class RouterCore<
2561
2454
  // to be preloaded before we resolve the match
2562
2455
  await route._componentsPromise
2563
2456
 
2564
- batch(() => {
2565
- updateMatch(matchId, (prev) => ({
2566
- ...prev,
2567
- error: undefined,
2568
- status: 'success',
2569
- isFetching: false,
2570
- updatedAt: Date.now(),
2571
- loaderData,
2572
- }))
2573
- executeHead()
2574
- })
2457
+ updateMatch(matchId, (prev) => ({
2458
+ ...prev,
2459
+ error: undefined,
2460
+ status: 'success',
2461
+ isFetching: false,
2462
+ updatedAt: Date.now(),
2463
+ loaderData,
2464
+ }))
2465
+ const head = await executeHead()
2466
+ updateMatch(matchId, (prev) => ({
2467
+ ...prev,
2468
+ ...head,
2469
+ }))
2575
2470
  } catch (e) {
2576
2471
  let error = e
2577
2472
 
@@ -2588,16 +2483,14 @@ export class RouterCore<
2588
2483
  onErrorError,
2589
2484
  )
2590
2485
  }
2591
-
2592
- batch(() => {
2593
- updateMatch(matchId, (prev) => ({
2594
- ...prev,
2595
- error,
2596
- status: 'error',
2597
- isFetching: false,
2598
- }))
2599
- executeHead()
2600
- })
2486
+ const head = await executeHead()
2487
+ updateMatch(matchId, (prev) => ({
2488
+ ...prev,
2489
+ error,
2490
+ status: 'error',
2491
+ isFetching: false,
2492
+ ...head,
2493
+ }))
2601
2494
  }
2602
2495
 
2603
2496
  this.serverSsr?.onMatchSettled({
@@ -2605,13 +2498,13 @@ export class RouterCore<
2605
2498
  match: this.getMatch(matchId)!,
2606
2499
  })
2607
2500
  } catch (err) {
2608
- batch(() => {
2609
- updateMatch(matchId, (prev) => ({
2610
- ...prev,
2611
- loaderPromise: undefined,
2612
- }))
2613
- executeHead()
2614
- })
2501
+ const head = await executeHead()
2502
+
2503
+ updateMatch(matchId, (prev) => ({
2504
+ ...prev,
2505
+ loaderPromise: undefined,
2506
+ ...head,
2507
+ }))
2615
2508
  handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2616
2509
  }
2617
2510
  }
@@ -2651,7 +2544,11 @@ export class RouterCore<
2651
2544
  // if the loader did not run, still update head.
2652
2545
  // reason: parent's beforeLoad may have changed the route context
2653
2546
  // and only now do we know the route context (and that the loader would not run)
2654
- executeHead()
2547
+ const head = await executeHead()
2548
+ updateMatch(matchId, (prev) => ({
2549
+ ...prev,
2550
+ ...head,
2551
+ }))
2655
2552
  }
2656
2553
  }
2657
2554
  if (!loaderIsRunningAsync) {
@@ -2873,6 +2770,7 @@ export class RouterCore<
2873
2770
  if (err.options.reloadDocument) {
2874
2771
  return undefined
2875
2772
  }
2773
+
2876
2774
  return await this.preloadRoute({
2877
2775
  ...err.options,
2878
2776
  _fromLocation: next,
@@ -3329,3 +3227,117 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
3329
3227
 
3330
3228
  return { matchedRoutes, routeParams, foundRoute }
3331
3229
  }
3230
+
3231
+ function applySearchMiddleware({
3232
+ search,
3233
+ dest,
3234
+ destRoutes,
3235
+ _includeValidateSearch,
3236
+ }: {
3237
+ search: any
3238
+ dest: BuildNextOptions
3239
+ destRoutes: Array<AnyRoute>
3240
+ _includeValidateSearch: boolean | undefined
3241
+ }) {
3242
+ const allMiddlewares =
3243
+ destRoutes.reduce(
3244
+ (acc, route) => {
3245
+ const middlewares: Array<SearchMiddleware<any>> = []
3246
+
3247
+ if ('search' in route.options) {
3248
+ if (route.options.search?.middlewares) {
3249
+ middlewares.push(...route.options.search.middlewares)
3250
+ }
3251
+ }
3252
+ // TODO remove preSearchFilters and postSearchFilters in v2
3253
+ else if (
3254
+ route.options.preSearchFilters ||
3255
+ route.options.postSearchFilters
3256
+ ) {
3257
+ const legacyMiddleware: SearchMiddleware<any> = ({
3258
+ search,
3259
+ next,
3260
+ }) => {
3261
+ let nextSearch = search
3262
+
3263
+ if (
3264
+ 'preSearchFilters' in route.options &&
3265
+ route.options.preSearchFilters
3266
+ ) {
3267
+ nextSearch = route.options.preSearchFilters.reduce(
3268
+ (prev, next) => next(prev),
3269
+ search,
3270
+ )
3271
+ }
3272
+
3273
+ const result = next(nextSearch)
3274
+
3275
+ if (
3276
+ 'postSearchFilters' in route.options &&
3277
+ route.options.postSearchFilters
3278
+ ) {
3279
+ return route.options.postSearchFilters.reduce(
3280
+ (prev, next) => next(prev),
3281
+ result,
3282
+ )
3283
+ }
3284
+
3285
+ return result
3286
+ }
3287
+ middlewares.push(legacyMiddleware)
3288
+ }
3289
+
3290
+ if (_includeValidateSearch && route.options.validateSearch) {
3291
+ const validate: SearchMiddleware<any> = ({ search, next }) => {
3292
+ const result = next(search)
3293
+ try {
3294
+ const validatedSearch = {
3295
+ ...result,
3296
+ ...(validateSearch(route.options.validateSearch, result) ?? {}),
3297
+ }
3298
+ return validatedSearch
3299
+ } catch {
3300
+ // ignore errors here because they are already handled in matchRoutes
3301
+ return result
3302
+ }
3303
+ }
3304
+
3305
+ middlewares.push(validate)
3306
+ }
3307
+
3308
+ return acc.concat(middlewares)
3309
+ },
3310
+ [] as Array<SearchMiddleware<any>>,
3311
+ ) ?? []
3312
+
3313
+ // the chain ends here since `next` is not called
3314
+ const final: SearchMiddleware<any> = ({ search }) => {
3315
+ if (!dest.search) {
3316
+ return {}
3317
+ }
3318
+ if (dest.search === true) {
3319
+ return search
3320
+ }
3321
+ return functionalUpdate(dest.search, search)
3322
+ }
3323
+
3324
+ allMiddlewares.push(final)
3325
+
3326
+ const applyNext = (index: number, currentSearch: any): any => {
3327
+ // no more middlewares left, return the current search
3328
+ if (index >= allMiddlewares.length) {
3329
+ return currentSearch
3330
+ }
3331
+
3332
+ const middleware = allMiddlewares[index]!
3333
+
3334
+ const next = (newSearch: any): any => {
3335
+ return applyNext(index + 1, newSearch)
3336
+ }
3337
+
3338
+ return middleware({ search: currentSearch, next })
3339
+ }
3340
+
3341
+ // Start applying middlewares
3342
+ return applyNext(0, search)
3343
+ }
package/src/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { RouteIds } from './routeInfo'
2
2
  import type { AnyRouter } from './router'
3
3
 
4
+ export type Awaitable<T> = T | Promise<T>
4
5
  export type NoInfer<T> = [T][T extends any ? 0 : never]
5
6
  export type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue
6
7
  ? TYesResult