@tanstack/router-core 1.136.4 → 1.136.6

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 (48) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +2 -0
  3. package/dist/cjs/index.cjs +0 -5
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/index.d.cts +1 -4
  6. package/dist/cjs/lru-cache.cjs +5 -0
  7. package/dist/cjs/lru-cache.cjs.map +1 -1
  8. package/dist/cjs/lru-cache.d.cts +1 -0
  9. package/dist/cjs/new-process-route-tree.cjs +655 -0
  10. package/dist/cjs/new-process-route-tree.cjs.map +1 -0
  11. package/dist/cjs/new-process-route-tree.d.cts +177 -0
  12. package/dist/cjs/path.cjs +133 -434
  13. package/dist/cjs/path.cjs.map +1 -1
  14. package/dist/cjs/path.d.cts +3 -39
  15. package/dist/cjs/router.cjs +47 -98
  16. package/dist/cjs/router.cjs.map +1 -1
  17. package/dist/cjs/router.d.cts +6 -11
  18. package/dist/esm/Matches.d.ts +2 -0
  19. package/dist/esm/Matches.js.map +1 -1
  20. package/dist/esm/index.d.ts +1 -4
  21. package/dist/esm/index.js +1 -6
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/lru-cache.d.ts +1 -0
  24. package/dist/esm/lru-cache.js +5 -0
  25. package/dist/esm/lru-cache.js.map +1 -1
  26. package/dist/esm/new-process-route-tree.d.ts +177 -0
  27. package/dist/esm/new-process-route-tree.js +655 -0
  28. package/dist/esm/new-process-route-tree.js.map +1 -0
  29. package/dist/esm/path.d.ts +3 -39
  30. package/dist/esm/path.js +133 -434
  31. package/dist/esm/path.js.map +1 -1
  32. package/dist/esm/router.d.ts +6 -11
  33. package/dist/esm/router.js +48 -99
  34. package/dist/esm/router.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/Matches.ts +2 -0
  37. package/src/index.ts +0 -6
  38. package/src/lru-cache.ts +6 -0
  39. package/src/new-process-route-tree.ts +1036 -0
  40. package/src/path.ts +168 -639
  41. package/src/router.ts +57 -126
  42. package/dist/cjs/process-route-tree.cjs +0 -144
  43. package/dist/cjs/process-route-tree.cjs.map +0 -1
  44. package/dist/cjs/process-route-tree.d.cts +0 -18
  45. package/dist/esm/process-route-tree.d.ts +0 -18
  46. package/dist/esm/process-route-tree.js +0 -144
  47. package/dist/esm/process-route-tree.js.map +0 -1
  48. package/src/process-route-tree.ts +0 -241
package/src/router.ts CHANGED
@@ -9,21 +9,26 @@ import {
9
9
  last,
10
10
  replaceEqualDeep,
11
11
  } from './utils'
12
- import { processRouteTree } from './process-route-tree'
12
+ import {
13
+ findFlatMatch,
14
+ findRouteMatch,
15
+ findSingleMatch,
16
+ processRouteMasks,
17
+ processRouteTree,
18
+ } from './new-process-route-tree'
13
19
  import {
14
20
  cleanPath,
15
21
  interpolatePath,
16
- matchPathname,
17
22
  resolvePath,
18
23
  trimPath,
19
24
  trimPathRight,
20
25
  } from './path'
26
+ import { createLRUCache } from './lru-cache'
21
27
  import { isNotFound } from './not-found'
22
28
  import { setupScrollRestoration } from './scroll-restoration'
23
29
  import { defaultParseSearch, defaultStringifySearch } from './searchParams'
24
30
  import { rootRouteId } from './root'
25
31
  import { isRedirect, redirect } from './redirect'
26
- import { createLRUCache } from './lru-cache'
27
32
  import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
28
33
  import {
29
34
  composeRewrites,
@@ -31,7 +36,7 @@ import {
31
36
  executeRewriteOutput,
32
37
  rewriteBasepath,
33
38
  } from './rewrite'
34
- import type { ParsePathnameCache } from './path'
39
+ import type { ProcessedTree } from './new-process-route-tree'
35
40
  import type { SearchParser, SearchSerializer } from './searchParams'
36
41
  import type { AnyRedirect, ResolvedRedirect } from './redirect'
37
42
  import type {
@@ -691,10 +696,7 @@ export type ParseLocationFn<TRouteTree extends AnyRoute> = (
691
696
  previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
692
697
  ) => ParsedLocation<FullSearchSchema<TRouteTree>>
693
698
 
694
- export type GetMatchRoutesFn = (
695
- pathname: string,
696
- routePathname: string | undefined,
697
- ) => {
699
+ export type GetMatchRoutesFn = (pathname: string) => {
698
700
  matchedRoutes: Array<AnyRoute>
699
701
  routeParams: Record<string, string>
700
702
  foundRoute: AnyRoute | undefined
@@ -902,7 +904,7 @@ export class RouterCore<
902
904
  routeTree!: TRouteTree
903
905
  routesById!: RoutesById<TRouteTree>
904
906
  routesByPath!: RoutesByPath<TRouteTree>
905
- flatRoutes!: Array<AnyRoute>
907
+ processedTree!: ProcessedTree<TRouteTree, any, any>
906
908
  isServer!: boolean
907
909
  pathParamsDecodeCharMap?: Map<string, string>
908
910
 
@@ -1094,18 +1096,22 @@ export class RouterCore<
1094
1096
  }
1095
1097
 
1096
1098
  buildRouteTree = () => {
1097
- const { routesById, routesByPath, flatRoutes } = processRouteTree({
1098
- routeTree: this.routeTree,
1099
- initRoute: (route, i) => {
1099
+ const { routesById, routesByPath, processedTree } = processRouteTree(
1100
+ this.routeTree,
1101
+ this.options.caseSensitive,
1102
+ (route, i) => {
1100
1103
  route.init({
1101
1104
  originalIndex: i,
1102
1105
  })
1103
1106
  },
1104
- })
1107
+ )
1108
+ if (this.options.routeMasks) {
1109
+ processRouteMasks(this.options.routeMasks, processedTree)
1110
+ }
1105
1111
 
1106
1112
  this.routesById = routesById as RoutesById<TRouteTree>
1107
1113
  this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
1108
- this.flatRoutes = flatRoutes as Array<AnyRoute>
1114
+ this.processedTree = processedTree
1109
1115
 
1110
1116
  const notFoundRoute = this.options.notFoundRoute
1111
1117
 
@@ -1203,13 +1209,15 @@ export class RouterCore<
1203
1209
  return location
1204
1210
  }
1205
1211
 
1212
+ resolvePathCache = createLRUCache<string, string>(1000)
1213
+
1206
1214
  /** Resolve a path against the router basepath and trailing-slash policy. */
1207
1215
  resolvePathWithBase = (from: string, path: string) => {
1208
1216
  const resolvedPath = resolvePath({
1209
1217
  base: from,
1210
1218
  to: cleanPath(path),
1211
1219
  trailingSlash: this.options.trailingSlash,
1212
- parseCache: this.parsePathnameCache,
1220
+ cache: this.resolvePathCache,
1213
1221
  })
1214
1222
  return resolvedPath
1215
1223
  }
@@ -1242,7 +1250,6 @@ export class RouterCore<
1242
1250
  ): Array<AnyRouteMatch> {
1243
1251
  const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
1244
1252
  next.pathname,
1245
- opts?.dest?.to as string,
1246
1253
  )
1247
1254
  let isGlobalNotFound = false
1248
1255
 
@@ -1535,21 +1542,11 @@ export class RouterCore<
1535
1542
  return matches
1536
1543
  }
1537
1544
 
1538
- /** a cache for `parsePathname` */
1539
- private parsePathnameCache: ParsePathnameCache = createLRUCache(1000)
1540
-
1541
- getMatchedRoutes: GetMatchRoutesFn = (
1542
- pathname: string,
1543
- routePathname: string | undefined,
1544
- ) => {
1545
+ getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
1545
1546
  return getMatchedRoutes({
1546
1547
  pathname,
1547
- routePathname,
1548
- caseSensitive: this.options.caseSensitive,
1549
- routesByPath: this.routesByPath,
1550
1548
  routesById: this.routesById,
1551
- flatRoutes: this.flatRoutes,
1552
- parseCache: this.parsePathnameCache,
1549
+ processedTree: this.processedTree,
1553
1550
  })
1554
1551
  }
1555
1552
 
@@ -1612,10 +1609,7 @@ export class RouterCore<
1612
1609
  process.env.NODE_ENV !== 'production' &&
1613
1610
  dest._isNavigate
1614
1611
  ) {
1615
- const allFromMatches = this.getMatchedRoutes(
1616
- dest.from,
1617
- undefined,
1618
- ).matchedRoutes
1612
+ const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes
1619
1613
 
1620
1614
  const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
1621
1615
  return comparePaths(d.fullPath, dest.from!)
@@ -1666,7 +1660,6 @@ export class RouterCore<
1666
1660
  const interpolatedNextTo = interpolatePath({
1667
1661
  path: nextTo,
1668
1662
  params: nextParams,
1669
- parseCache: this.parsePathnameCache,
1670
1663
  }).interpolatedPath
1671
1664
 
1672
1665
  const destRoutes = this.matchRoutes(interpolatedNextTo, undefined, {
@@ -1693,7 +1686,6 @@ export class RouterCore<
1693
1686
  path: nextTo,
1694
1687
  params: nextParams,
1695
1688
  decodeCharMap: this.pathParamsDecodeCharMap,
1696
- parseCache: this.parsePathnameCache,
1697
1689
  }).interpolatedPath,
1698
1690
  )
1699
1691
 
@@ -1786,35 +1778,23 @@ export class RouterCore<
1786
1778
  let maskedNext = maskedDest ? build(maskedDest) : undefined
1787
1779
 
1788
1780
  if (!maskedNext) {
1789
- let params = {}
1781
+ const params = {}
1790
1782
 
1791
- const foundMask = this.options.routeMasks?.find((d) => {
1792
- const match = matchPathname(
1783
+ if (this.options.routeMasks) {
1784
+ const match = findFlatMatch<RouteMask<TRouteTree>>(
1793
1785
  next.pathname,
1794
- {
1795
- to: d.from,
1796
- caseSensitive: false,
1797
- fuzzy: false,
1798
- },
1799
- this.parsePathnameCache,
1786
+ this.processedTree,
1800
1787
  )
1801
-
1802
1788
  if (match) {
1803
- params = match
1804
- return true
1805
- }
1806
-
1807
- return false
1808
- })
1809
-
1810
- if (foundMask) {
1811
- const { from: _from, ...maskProps } = foundMask
1812
- maskedDest = {
1813
- from: opts.from,
1814
- ...maskProps,
1815
- params,
1789
+ Object.assign(params, match.params) // Copy params, because they're cached
1790
+ const { from: _from, ...maskProps } = match.route
1791
+ maskedDest = {
1792
+ from: opts.from,
1793
+ ...maskProps,
1794
+ params,
1795
+ }
1796
+ maskedNext = build(maskedDest)
1816
1797
  }
1817
- maskedNext = build(maskedDest)
1818
1798
  }
1819
1799
  }
1820
1800
 
@@ -2525,31 +2505,31 @@ export class RouterCore<
2525
2505
  ? this.latestLocation
2526
2506
  : this.state.resolvedLocation || this.state.location
2527
2507
 
2528
- const match = matchPathname(
2508
+ const match = findSingleMatch(
2509
+ next.pathname,
2510
+ opts?.caseSensitive ?? false,
2511
+ opts?.fuzzy ?? false,
2529
2512
  baseLocation.pathname,
2530
- {
2531
- ...opts,
2532
- to: next.pathname,
2533
- },
2534
- this.parsePathnameCache,
2535
- ) as any
2513
+ this.processedTree,
2514
+ )
2536
2515
 
2537
2516
  if (!match) {
2538
2517
  return false
2539
2518
  }
2519
+
2540
2520
  if (location.params) {
2541
- if (!deepEqual(match, location.params, { partial: true })) {
2521
+ if (!deepEqual(match.params, location.params, { partial: true })) {
2542
2522
  return false
2543
2523
  }
2544
2524
  }
2545
2525
 
2546
- if (match && (opts?.includeSearch ?? true)) {
2526
+ if (opts?.includeSearch ?? true) {
2547
2527
  return deepEqual(baseLocation.search, next.search, { partial: true })
2548
- ? match
2528
+ ? match.params
2549
2529
  : false
2550
2530
  }
2551
2531
 
2552
- return match
2532
+ return match.params
2553
2533
  }
2554
2534
 
2555
2535
  ssr?: {
@@ -2645,70 +2625,21 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
2645
2625
  */
2646
2626
  export function getMatchedRoutes<TRouteLike extends RouteLike>({
2647
2627
  pathname,
2648
- routePathname,
2649
- caseSensitive,
2650
- routesByPath,
2651
2628
  routesById,
2652
- flatRoutes,
2653
- parseCache,
2629
+ processedTree,
2654
2630
  }: {
2655
2631
  pathname: string
2656
- routePathname?: string
2657
- caseSensitive?: boolean
2658
- routesByPath: Record<string, TRouteLike>
2659
2632
  routesById: Record<string, TRouteLike>
2660
- flatRoutes: Array<TRouteLike>
2661
- parseCache?: ParsePathnameCache
2633
+ processedTree: ProcessedTree<any, any, any>
2662
2634
  }) {
2663
- let routeParams: Record<string, string> = {}
2635
+ const routeParams: Record<string, string> = {}
2664
2636
  const trimmedPath = trimPathRight(pathname)
2665
- const getMatchedParams = (route: TRouteLike) => {
2666
- const result = matchPathname(
2667
- trimmedPath,
2668
- {
2669
- to: route.fullPath,
2670
- caseSensitive: route.options?.caseSensitive ?? caseSensitive,
2671
- // we need fuzzy matching for `notFoundMode: 'fuzzy'`
2672
- fuzzy: true,
2673
- },
2674
- parseCache,
2675
- )
2676
- return result
2677
- }
2678
2637
 
2679
- let foundRoute: TRouteLike | undefined =
2680
- routePathname !== undefined ? routesByPath[routePathname] : undefined
2681
- if (foundRoute) {
2682
- routeParams = getMatchedParams(foundRoute)!
2683
- } else {
2684
- // iterate over flatRoutes to find the best match
2685
- // if we find a fuzzy matching route, keep looking for a perfect fit
2686
- let fuzzyMatch:
2687
- | { foundRoute: TRouteLike; routeParams: Record<string, string> }
2688
- | undefined = undefined
2689
- for (const route of flatRoutes) {
2690
- const matchedParams = getMatchedParams(route)
2691
-
2692
- if (matchedParams) {
2693
- if (
2694
- route.path !== '/' &&
2695
- (matchedParams as Record<string, string>)['**']
2696
- ) {
2697
- if (!fuzzyMatch) {
2698
- fuzzyMatch = { foundRoute: route, routeParams: matchedParams }
2699
- }
2700
- } else {
2701
- foundRoute = route
2702
- routeParams = matchedParams
2703
- break
2704
- }
2705
- }
2706
- }
2707
- // did not find a perfect fit, so take the fuzzy matching route if it exists
2708
- if (!foundRoute && fuzzyMatch) {
2709
- foundRoute = fuzzyMatch.foundRoute
2710
- routeParams = fuzzyMatch.routeParams
2711
- }
2638
+ let foundRoute: TRouteLike | undefined = undefined
2639
+ const match = findRouteMatch<TRouteLike>(trimmedPath, processedTree, true)
2640
+ if (match) {
2641
+ foundRoute = match.route
2642
+ Object.assign(routeParams, match.params) // Copy params, because they're cached
2712
2643
  }
2713
2644
 
2714
2645
  let routeCursor: TRouteLike = foundRoute || routesById[rootRouteId]!
@@ -1,144 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const invariant = require("tiny-invariant");
4
- const path = require("./path.cjs");
5
- const SLASH_SCORE = 0.75;
6
- const STATIC_SEGMENT_SCORE = 1;
7
- const REQUIRED_PARAM_BASE_SCORE = 0.5;
8
- const OPTIONAL_PARAM_BASE_SCORE = 0.4;
9
- const WILDCARD_PARAM_BASE_SCORE = 0.25;
10
- const STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2;
11
- const BOTH_PRESENCE_BASE_SCORE = 0.05;
12
- const PREFIX_PRESENCE_BASE_SCORE = 0.02;
13
- const SUFFIX_PRESENCE_BASE_SCORE = 0.01;
14
- const PREFIX_LENGTH_SCORE_MULTIPLIER = 2e-4;
15
- const SUFFIX_LENGTH_SCORE_MULTIPLIER = 1e-4;
16
- function handleParam(segment, baseScore) {
17
- if (segment.prefixSegment && segment.suffixSegment) {
18
- return baseScore + BOTH_PRESENCE_BASE_SCORE + PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length + SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length;
19
- }
20
- if (segment.prefixSegment) {
21
- return baseScore + PREFIX_PRESENCE_BASE_SCORE + PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length;
22
- }
23
- if (segment.suffixSegment) {
24
- return baseScore + SUFFIX_PRESENCE_BASE_SCORE + SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length;
25
- }
26
- return baseScore;
27
- }
28
- function sortRoutes(routes) {
29
- const scoredRoutes = [];
30
- routes.forEach((d, i) => {
31
- if (d.isRoot || !d.path) {
32
- return;
33
- }
34
- const trimmed = path.trimPathLeft(d.fullPath);
35
- let parsed = path.parsePathname(trimmed);
36
- let skip = 0;
37
- while (parsed.length > skip + 1 && parsed[skip]?.value === "/") {
38
- skip++;
39
- }
40
- if (skip > 0) parsed = parsed.slice(skip);
41
- let optionalParamCount = 0;
42
- let hasStaticAfter = false;
43
- const scores = parsed.map((segment, index) => {
44
- if (segment.value === "/") {
45
- return SLASH_SCORE;
46
- }
47
- if (segment.type === path.SEGMENT_TYPE_PATHNAME) {
48
- return STATIC_SEGMENT_SCORE;
49
- }
50
- let baseScore = void 0;
51
- if (segment.type === path.SEGMENT_TYPE_PARAM) {
52
- baseScore = REQUIRED_PARAM_BASE_SCORE;
53
- } else if (segment.type === path.SEGMENT_TYPE_OPTIONAL_PARAM) {
54
- baseScore = OPTIONAL_PARAM_BASE_SCORE;
55
- optionalParamCount++;
56
- } else {
57
- baseScore = WILDCARD_PARAM_BASE_SCORE;
58
- }
59
- for (let i2 = index + 1; i2 < parsed.length; i2++) {
60
- const nextSegment = parsed[i2];
61
- if (nextSegment.type === path.SEGMENT_TYPE_PATHNAME && nextSegment.value !== "/") {
62
- hasStaticAfter = true;
63
- return handleParam(
64
- segment,
65
- baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE
66
- );
67
- }
68
- }
69
- return handleParam(segment, baseScore);
70
- });
71
- scoredRoutes.push({
72
- child: d,
73
- trimmed,
74
- parsed,
75
- index: i,
76
- scores,
77
- optionalParamCount,
78
- hasStaticAfter
79
- });
80
- });
81
- const flatRoutes = scoredRoutes.sort((a, b) => {
82
- const minLength = Math.min(a.scores.length, b.scores.length);
83
- for (let i = 0; i < minLength; i++) {
84
- if (a.scores[i] !== b.scores[i]) {
85
- return b.scores[i] - a.scores[i];
86
- }
87
- }
88
- if (a.scores.length !== b.scores.length) {
89
- if (a.optionalParamCount !== b.optionalParamCount) {
90
- if (a.hasStaticAfter === b.hasStaticAfter) {
91
- return a.optionalParamCount - b.optionalParamCount;
92
- } else if (a.hasStaticAfter && !b.hasStaticAfter) {
93
- return -1;
94
- } else if (!a.hasStaticAfter && b.hasStaticAfter) {
95
- return 1;
96
- }
97
- }
98
- return b.scores.length - a.scores.length;
99
- }
100
- for (let i = 0; i < minLength; i++) {
101
- if (a.parsed[i].value !== b.parsed[i].value) {
102
- return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
103
- }
104
- }
105
- return a.index - b.index;
106
- }).map((d, i) => {
107
- d.child.rank = i;
108
- return d.child;
109
- });
110
- return flatRoutes;
111
- }
112
- function processRouteTree({
113
- routeTree,
114
- initRoute
115
- }) {
116
- const routesById = {};
117
- const routesByPath = {};
118
- const recurseRoutes = (childRoutes) => {
119
- childRoutes.forEach((childRoute, i) => {
120
- initRoute?.(childRoute, i);
121
- const existingRoute = routesById[childRoute.id];
122
- invariant(
123
- !existingRoute,
124
- `Duplicate routes found with id: ${String(childRoute.id)}`
125
- );
126
- routesById[childRoute.id] = childRoute;
127
- if (!childRoute.isRoot && childRoute.path) {
128
- const trimmedFullPath = path.trimPathRight(childRoute.fullPath);
129
- if (!routesByPath[trimmedFullPath] || childRoute.fullPath.endsWith("/")) {
130
- routesByPath[trimmedFullPath] = childRoute;
131
- }
132
- }
133
- const children = childRoute.children;
134
- if (children?.length) {
135
- recurseRoutes(children);
136
- }
137
- });
138
- };
139
- recurseRoutes([routeTree]);
140
- const flatRoutes = sortRoutes(Object.values(routesById));
141
- return { routesById, routesByPath, flatRoutes };
142
- }
143
- exports.processRouteTree = processRouteTree;
144
- //# sourceMappingURL=process-route-tree.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"process-route-tree.cjs","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parsePathname,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parsePathname(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["trimPathLeft","parsePathname","SEGMENT_TYPE_PATHNAME","SEGMENT_TYPE_PARAM","SEGMENT_TYPE_OPTIONAL_PARAM","i","trimPathRight"],"mappings":"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"}
@@ -1,18 +0,0 @@
1
- import { RouteLike } from './route.cjs';
2
- export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
3
- routesById: Record<string, TRouteLike>;
4
- routesByPath: Record<string, TRouteLike>;
5
- flatRoutes: Array<TRouteLike>;
6
- };
7
- /**
8
- * Build lookup maps and a specificity-sorted flat list from a route tree.
9
- * Returns `routesById`, `routesByPath`, and `flatRoutes`.
10
- */
11
- /**
12
- * Build lookup maps and a specificity-sorted flat list from a route tree.
13
- * Returns `routesById`, `routesByPath`, and `flatRoutes`.
14
- */
15
- export declare function processRouteTree<TRouteLike extends RouteLike>({ routeTree, initRoute, }: {
16
- routeTree: TRouteLike;
17
- initRoute?: (route: TRouteLike, index: number) => void;
18
- }): ProcessRouteTreeResult<TRouteLike>;
@@ -1,18 +0,0 @@
1
- import { RouteLike } from './route.js';
2
- export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
3
- routesById: Record<string, TRouteLike>;
4
- routesByPath: Record<string, TRouteLike>;
5
- flatRoutes: Array<TRouteLike>;
6
- };
7
- /**
8
- * Build lookup maps and a specificity-sorted flat list from a route tree.
9
- * Returns `routesById`, `routesByPath`, and `flatRoutes`.
10
- */
11
- /**
12
- * Build lookup maps and a specificity-sorted flat list from a route tree.
13
- * Returns `routesById`, `routesByPath`, and `flatRoutes`.
14
- */
15
- export declare function processRouteTree<TRouteLike extends RouteLike>({ routeTree, initRoute, }: {
16
- routeTree: TRouteLike;
17
- initRoute?: (route: TRouteLike, index: number) => void;
18
- }): ProcessRouteTreeResult<TRouteLike>;
@@ -1,144 +0,0 @@
1
- import invariant from "tiny-invariant";
2
- import { trimPathRight, trimPathLeft, parsePathname, SEGMENT_TYPE_PATHNAME, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM } from "./path.js";
3
- const SLASH_SCORE = 0.75;
4
- const STATIC_SEGMENT_SCORE = 1;
5
- const REQUIRED_PARAM_BASE_SCORE = 0.5;
6
- const OPTIONAL_PARAM_BASE_SCORE = 0.4;
7
- const WILDCARD_PARAM_BASE_SCORE = 0.25;
8
- const STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2;
9
- const BOTH_PRESENCE_BASE_SCORE = 0.05;
10
- const PREFIX_PRESENCE_BASE_SCORE = 0.02;
11
- const SUFFIX_PRESENCE_BASE_SCORE = 0.01;
12
- const PREFIX_LENGTH_SCORE_MULTIPLIER = 2e-4;
13
- const SUFFIX_LENGTH_SCORE_MULTIPLIER = 1e-4;
14
- function handleParam(segment, baseScore) {
15
- if (segment.prefixSegment && segment.suffixSegment) {
16
- return baseScore + BOTH_PRESENCE_BASE_SCORE + PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length + SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length;
17
- }
18
- if (segment.prefixSegment) {
19
- return baseScore + PREFIX_PRESENCE_BASE_SCORE + PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length;
20
- }
21
- if (segment.suffixSegment) {
22
- return baseScore + SUFFIX_PRESENCE_BASE_SCORE + SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length;
23
- }
24
- return baseScore;
25
- }
26
- function sortRoutes(routes) {
27
- const scoredRoutes = [];
28
- routes.forEach((d, i) => {
29
- if (d.isRoot || !d.path) {
30
- return;
31
- }
32
- const trimmed = trimPathLeft(d.fullPath);
33
- let parsed = parsePathname(trimmed);
34
- let skip = 0;
35
- while (parsed.length > skip + 1 && parsed[skip]?.value === "/") {
36
- skip++;
37
- }
38
- if (skip > 0) parsed = parsed.slice(skip);
39
- let optionalParamCount = 0;
40
- let hasStaticAfter = false;
41
- const scores = parsed.map((segment, index) => {
42
- if (segment.value === "/") {
43
- return SLASH_SCORE;
44
- }
45
- if (segment.type === SEGMENT_TYPE_PATHNAME) {
46
- return STATIC_SEGMENT_SCORE;
47
- }
48
- let baseScore = void 0;
49
- if (segment.type === SEGMENT_TYPE_PARAM) {
50
- baseScore = REQUIRED_PARAM_BASE_SCORE;
51
- } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
52
- baseScore = OPTIONAL_PARAM_BASE_SCORE;
53
- optionalParamCount++;
54
- } else {
55
- baseScore = WILDCARD_PARAM_BASE_SCORE;
56
- }
57
- for (let i2 = index + 1; i2 < parsed.length; i2++) {
58
- const nextSegment = parsed[i2];
59
- if (nextSegment.type === SEGMENT_TYPE_PATHNAME && nextSegment.value !== "/") {
60
- hasStaticAfter = true;
61
- return handleParam(
62
- segment,
63
- baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE
64
- );
65
- }
66
- }
67
- return handleParam(segment, baseScore);
68
- });
69
- scoredRoutes.push({
70
- child: d,
71
- trimmed,
72
- parsed,
73
- index: i,
74
- scores,
75
- optionalParamCount,
76
- hasStaticAfter
77
- });
78
- });
79
- const flatRoutes = scoredRoutes.sort((a, b) => {
80
- const minLength = Math.min(a.scores.length, b.scores.length);
81
- for (let i = 0; i < minLength; i++) {
82
- if (a.scores[i] !== b.scores[i]) {
83
- return b.scores[i] - a.scores[i];
84
- }
85
- }
86
- if (a.scores.length !== b.scores.length) {
87
- if (a.optionalParamCount !== b.optionalParamCount) {
88
- if (a.hasStaticAfter === b.hasStaticAfter) {
89
- return a.optionalParamCount - b.optionalParamCount;
90
- } else if (a.hasStaticAfter && !b.hasStaticAfter) {
91
- return -1;
92
- } else if (!a.hasStaticAfter && b.hasStaticAfter) {
93
- return 1;
94
- }
95
- }
96
- return b.scores.length - a.scores.length;
97
- }
98
- for (let i = 0; i < minLength; i++) {
99
- if (a.parsed[i].value !== b.parsed[i].value) {
100
- return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
101
- }
102
- }
103
- return a.index - b.index;
104
- }).map((d, i) => {
105
- d.child.rank = i;
106
- return d.child;
107
- });
108
- return flatRoutes;
109
- }
110
- function processRouteTree({
111
- routeTree,
112
- initRoute
113
- }) {
114
- const routesById = {};
115
- const routesByPath = {};
116
- const recurseRoutes = (childRoutes) => {
117
- childRoutes.forEach((childRoute, i) => {
118
- initRoute?.(childRoute, i);
119
- const existingRoute = routesById[childRoute.id];
120
- invariant(
121
- !existingRoute,
122
- `Duplicate routes found with id: ${String(childRoute.id)}`
123
- );
124
- routesById[childRoute.id] = childRoute;
125
- if (!childRoute.isRoot && childRoute.path) {
126
- const trimmedFullPath = trimPathRight(childRoute.fullPath);
127
- if (!routesByPath[trimmedFullPath] || childRoute.fullPath.endsWith("/")) {
128
- routesByPath[trimmedFullPath] = childRoute;
129
- }
130
- }
131
- const children = childRoute.children;
132
- if (children?.length) {
133
- recurseRoutes(children);
134
- }
135
- });
136
- };
137
- recurseRoutes([routeTree]);
138
- const flatRoutes = sortRoutes(Object.values(routesById));
139
- return { routesById, routesByPath, flatRoutes };
140
- }
141
- export {
142
- processRouteTree
143
- };
144
- //# sourceMappingURL=process-route-tree.js.map