@tanstack/router-core 1.145.11 → 1.146.1

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.
@@ -460,8 +460,12 @@ export type InvalidateFn<TRouter extends AnyRouter> = (opts?: {
460
460
  export type ParseLocationFn<TRouteTree extends AnyRoute> = (locationToParse: HistoryLocation, previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>) => ParsedLocation<FullSearchSchema<TRouteTree>>;
461
461
  export type GetMatchRoutesFn = (pathname: string) => {
462
462
  matchedRoutes: ReadonlyArray<AnyRoute>;
463
+ /** exhaustive params, still in their string form */
463
464
  routeParams: Record<string, string>;
465
+ /** partial params, parsed from routeParams during matching */
466
+ parsedParams: Record<string, unknown> | undefined;
464
467
  foundRoute: AnyRoute | undefined;
468
+ parseError?: unknown;
465
469
  };
466
470
  export type EmitFn = (routerEvent: RouterEvent) => void;
467
471
  export type LoadFn = (opts?: {
@@ -693,5 +697,6 @@ export declare function getMatchedRoutes<TRouteLike extends RouteLike>({ pathnam
693
697
  matchedRoutes: readonly TRouteLike[];
694
698
  routeParams: Record<string, string>;
695
699
  foundRoute: TRouteLike | undefined;
700
+ parsedParams: Record<string, unknown> | undefined;
696
701
  };
697
702
  export {};
@@ -4,6 +4,7 @@ export declare const SEGMENT_TYPE_PARAM = 1;
4
4
  export declare const SEGMENT_TYPE_WILDCARD = 2;
5
5
  export declare const SEGMENT_TYPE_OPTIONAL_PARAM = 3;
6
6
  declare const SEGMENT_TYPE_INDEX = 4;
7
+ declare const SEGMENT_TYPE_PATHLESS = 5;
7
8
  /**
8
9
  * All the kinds of segments that can be present in a route path.
9
10
  */
@@ -11,7 +12,7 @@ export type SegmentKind = typeof SEGMENT_TYPE_PATHNAME | typeof SEGMENT_TYPE_PAR
11
12
  /**
12
13
  * All the kinds of segments that can be present in the segment tree.
13
14
  */
14
- type ExtendedSegmentKind = SegmentKind | typeof SEGMENT_TYPE_INDEX;
15
+ type ExtendedSegmentKind = SegmentKind | typeof SEGMENT_TYPE_INDEX | typeof SEGMENT_TYPE_PATHLESS;
15
16
  type ParsedSegment = Uint16Array & {
16
17
  /** segment type (0 = pathname, 1 = param, 2 = wildcard, 3 = optional param) */
17
18
  0: SegmentKind;
@@ -50,7 +51,7 @@ start: number,
50
51
  /** A Uint16Array (length: 6) to populate with the parsed segment data. */
51
52
  output?: Uint16Array): ParsedSegment;
52
53
  type StaticSegmentNode<T extends RouteLike> = SegmentNode<T> & {
53
- kind: typeof SEGMENT_TYPE_PATHNAME | typeof SEGMENT_TYPE_INDEX;
54
+ kind: typeof SEGMENT_TYPE_PATHNAME | typeof SEGMENT_TYPE_PATHLESS | typeof SEGMENT_TYPE_INDEX;
54
55
  };
55
56
  type DynamicSegmentNode<T extends RouteLike> = SegmentNode<T> & {
56
57
  kind: typeof SEGMENT_TYPE_PARAM | typeof SEGMENT_TYPE_WILDCARD | typeof SEGMENT_TYPE_OPTIONAL_PARAM;
@@ -61,6 +62,7 @@ type DynamicSegmentNode<T extends RouteLike> = SegmentNode<T> & {
61
62
  type AnySegmentNode<T extends RouteLike> = StaticSegmentNode<T> | DynamicSegmentNode<T>;
62
63
  type SegmentNode<T extends RouteLike> = {
63
64
  kind: ExtendedSegmentKind;
65
+ pathless: Array<StaticSegmentNode<T>> | null;
64
66
  /** Exact index segment (highest priority) */
65
67
  index: StaticSegmentNode<T> | null;
66
68
  /** Static segments (2nd priority) */
@@ -79,14 +81,28 @@ type SegmentNode<T extends RouteLike> = {
79
81
  fullPath: string;
80
82
  parent: AnySegmentNode<T> | null;
81
83
  depth: number;
84
+ /** route.options.params.parse function, set on the last node of the route */
85
+ parse: null | ((params: Record<string, string>) => any);
86
+ /** options.skipRouteOnParseError.params ?? false */
87
+ skipOnParamError: boolean;
88
+ /** options.skipRouteOnParseError.priority ?? 0 */
89
+ parsingPriority: number;
82
90
  };
83
91
  type RouteLike = {
92
+ id?: string;
84
93
  path?: string;
85
94
  children?: Array<RouteLike>;
86
95
  parentRoute?: RouteLike;
87
96
  isRoot?: boolean;
88
97
  options?: {
98
+ skipRouteOnParseError?: {
99
+ params?: boolean;
100
+ priority?: number;
101
+ };
89
102
  caseSensitive?: boolean;
103
+ params?: {
104
+ parse?: (params: Record<string, string>) => any;
105
+ };
90
106
  };
91
107
  } & ({
92
108
  fullPath: string;
@@ -127,7 +143,16 @@ path: string,
127
143
  /** The `processedTree` returned by the initial `processRouteTree` call. */
128
144
  processedTree: ProcessedTree<any, T, any>): {
129
145
  route: T;
130
- params: Record<string, string>;
146
+ /**
147
+ * The raw (unparsed) params extracted from the path.
148
+ * This will be the exhaustive list of all params defined in the route's path.
149
+ */
150
+ rawParams: Record<string, string>;
151
+ /**
152
+ * The accumlulated parsed params of each route in the branch that had `skipRouteOnParseError` enabled.
153
+ * Will not contain all params defined in the route's path. Those w/ a `params.parse` but no `skipRouteOnParseError` will need to be parsed separately.
154
+ */
155
+ parsedParams?: Record<string, unknown>;
131
156
  } | null;
132
157
  /**
133
158
  * @deprecated keep until v2 so that `router.matchRoute` can keep not caring about the actual route tree
@@ -138,13 +163,23 @@ export declare function findSingleMatch(from: string, caseSensitive: boolean, fu
138
163
  route: {
139
164
  from: string;
140
165
  };
141
- params: Record<string, string>;
166
+ /**
167
+ * The raw (unparsed) params extracted from the path.
168
+ * This will be the exhaustive list of all params defined in the route's path.
169
+ */
170
+ rawParams: Record<string, string>;
171
+ /**
172
+ * The accumlulated parsed params of each route in the branch that had `skipRouteOnParseError` enabled.
173
+ * Will not contain all params defined in the route's path. Those w/ a `params.parse` but no `skipRouteOnParseError` will need to be parsed separately.
174
+ */
175
+ parsedParams?: Record<string, unknown>;
142
176
  } | null;
143
177
  type RouteMatch<T extends Extract<RouteLike, {
144
178
  fullPath: string;
145
179
  }>> = {
146
180
  route: T;
147
- params: Record<string, string>;
181
+ rawParams: Record<string, string>;
182
+ parsedParams?: Record<string, unknown>;
148
183
  branch: ReadonlyArray<T>;
149
184
  };
150
185
  export declare function findRouteMatch<T extends Extract<RouteLike, {
@@ -182,6 +217,15 @@ initRoute?: (route: TRouteLike, index: number) => void): {
182
217
  };
183
218
  declare function findMatch<T extends RouteLike>(path: string, segmentTree: AnySegmentNode<T>, fuzzy?: boolean): {
184
219
  route: T;
185
- params: Record<string, string>;
220
+ /**
221
+ * The raw (unparsed) params extracted from the path.
222
+ * This will be the exhaustive list of all params defined in the route's path.
223
+ */
224
+ rawParams: Record<string, string>;
225
+ /**
226
+ * The accumlulated parsed params of each route in the branch that had `skipRouteOnParseError` enabled.
227
+ * Will not contain all params defined in the route's path. Those w/ a `params.parse` but no `skipRouteOnParseError` will need to be parsed separately.
228
+ */
229
+ parsedParams?: Record<string, unknown>;
186
230
  } | null;
187
231
  export {};
@@ -6,6 +6,7 @@ const SEGMENT_TYPE_PARAM = 1;
6
6
  const SEGMENT_TYPE_WILDCARD = 2;
7
7
  const SEGMENT_TYPE_OPTIONAL_PARAM = 3;
8
8
  const SEGMENT_TYPE_INDEX = 4;
9
+ const SEGMENT_TYPE_PATHLESS = 5;
9
10
  const PARAM_W_CURLY_BRACES_RE = /^([^{]*)\{\$([a-zA-Z_$][a-zA-Z0-9_$]*)\}([^}]*)$/;
10
11
  const OPTIONAL_PARAM_W_CURLY_BRACES_RE = /^([^{]*)\{-\$([a-zA-Z_$][a-zA-Z0-9_$]*)\}([^}]*)$/;
11
12
  const WILDCARD_W_CURLY_BRACES_RE = /^([^{]*)\{\$\}([^}]*)$/;
@@ -96,6 +97,7 @@ function parseSegments(defaultCaseSensitive, data, route, start, node, depth, on
96
97
  const path = route.fullPath ?? route.from;
97
98
  const length = path.length;
98
99
  const caseSensitive = route.options?.caseSensitive ?? defaultCaseSensitive;
100
+ const skipOnParamError = !!(route.options?.params?.parse && route.options?.skipRouteOnParseError?.params);
99
101
  while (cursor < length) {
100
102
  const segment = parseSegment(path, cursor, data);
101
103
  let nextNode;
@@ -145,8 +147,8 @@ function parseSegments(defaultCaseSensitive, data, route, start, node, depth, on
145
147
  const actuallyCaseSensitive = caseSensitive && !!(prefix_raw || suffix_raw);
146
148
  const prefix = !prefix_raw ? void 0 : actuallyCaseSensitive ? prefix_raw : prefix_raw.toLowerCase();
147
149
  const suffix = !suffix_raw ? void 0 : actuallyCaseSensitive ? suffix_raw : suffix_raw.toLowerCase();
148
- const existingNode = node.dynamic?.find(
149
- (s) => s.caseSensitive === actuallyCaseSensitive && s.prefix === prefix && s.suffix === suffix
150
+ const existingNode = !skipOnParamError && node.dynamic?.find(
151
+ (s) => !s.skipOnParamError && s.caseSensitive === actuallyCaseSensitive && s.prefix === prefix && s.suffix === suffix
150
152
  );
151
153
  if (existingNode) {
152
154
  nextNode = existingNode;
@@ -172,8 +174,8 @@ function parseSegments(defaultCaseSensitive, data, route, start, node, depth, on
172
174
  const actuallyCaseSensitive = caseSensitive && !!(prefix_raw || suffix_raw);
173
175
  const prefix = !prefix_raw ? void 0 : actuallyCaseSensitive ? prefix_raw : prefix_raw.toLowerCase();
174
176
  const suffix = !suffix_raw ? void 0 : actuallyCaseSensitive ? suffix_raw : suffix_raw.toLowerCase();
175
- const existingNode = node.optional?.find(
176
- (s) => s.caseSensitive === actuallyCaseSensitive && s.prefix === prefix && s.suffix === suffix
177
+ const existingNode = !skipOnParamError && node.optional?.find(
178
+ (s) => !s.skipOnParamError && s.caseSensitive === actuallyCaseSensitive && s.prefix === prefix && s.suffix === suffix
177
179
  );
178
180
  if (existingNode) {
179
181
  nextNode = existingNode;
@@ -215,6 +217,18 @@ function parseSegments(defaultCaseSensitive, data, route, start, node, depth, on
215
217
  }
216
218
  node = nextNode;
217
219
  }
220
+ if (skipOnParamError && route.children && !route.isRoot && route.id && route.id.charCodeAt(route.id.lastIndexOf("/") + 1) === 95) {
221
+ const pathlessNode = createStaticNode(
222
+ route.fullPath ?? route.from
223
+ );
224
+ pathlessNode.kind = SEGMENT_TYPE_PATHLESS;
225
+ pathlessNode.parent = node;
226
+ depth++;
227
+ pathlessNode.depth = depth;
228
+ node.pathless ??= [];
229
+ node.pathless.push(pathlessNode);
230
+ node = pathlessNode;
231
+ }
218
232
  const isLeaf = (route.path || !route.children) && !route.isRoot;
219
233
  if (isLeaf && path.endsWith("/")) {
220
234
  const indexNode = createStaticNode(
@@ -227,6 +241,9 @@ function parseSegments(defaultCaseSensitive, data, route, start, node, depth, on
227
241
  node.index = indexNode;
228
242
  node = indexNode;
229
243
  }
244
+ node.parse = route.options?.params?.parse ?? null;
245
+ node.skipOnParamError = skipOnParamError;
246
+ node.parsingPriority = route.options?.skipRouteOnParseError?.priority ?? 0;
230
247
  if (isLeaf && !node.route) {
231
248
  node.route = route;
232
249
  node.fullPath = route.fullPath ?? route.from;
@@ -246,6 +263,10 @@ function parseSegments(defaultCaseSensitive, data, route, start, node, depth, on
246
263
  }
247
264
  }
248
265
  function sortDynamic(a, b) {
266
+ if (a.skipOnParamError && !b.skipOnParamError) return -1;
267
+ if (!a.skipOnParamError && b.skipOnParamError) return 1;
268
+ if (a.skipOnParamError && b.skipOnParamError && (a.parsingPriority || b.parsingPriority))
269
+ return b.parsingPriority - a.parsingPriority;
249
270
  if (a.prefix && b.prefix && a.prefix !== b.prefix) {
250
271
  if (a.prefix.startsWith(b.prefix)) return -1;
251
272
  if (b.prefix.startsWith(a.prefix)) return 1;
@@ -263,6 +284,11 @@ function sortDynamic(a, b) {
263
284
  return 0;
264
285
  }
265
286
  function sortTreeNodes(node) {
287
+ if (node.pathless) {
288
+ for (const child of node.pathless) {
289
+ sortTreeNodes(child);
290
+ }
291
+ }
266
292
  if (node.static) {
267
293
  for (const child of node.static.values()) {
268
294
  sortTreeNodes(child);
@@ -296,6 +322,7 @@ function createStaticNode(fullPath) {
296
322
  return {
297
323
  kind: SEGMENT_TYPE_PATHNAME,
298
324
  depth: 0,
325
+ pathless: null,
299
326
  index: null,
300
327
  static: null,
301
328
  staticInsensitive: null,
@@ -304,13 +331,17 @@ function createStaticNode(fullPath) {
304
331
  wildcard: null,
305
332
  route: null,
306
333
  fullPath,
307
- parent: null
334
+ parent: null,
335
+ parse: null,
336
+ skipOnParamError: false,
337
+ parsingPriority: 0
308
338
  };
309
339
  }
310
340
  function createDynamicNode(kind, fullPath, caseSensitive, prefix, suffix) {
311
341
  return {
312
342
  kind,
313
343
  depth: 0,
344
+ pathless: null,
314
345
  index: null,
315
346
  static: null,
316
347
  staticInsensitive: null,
@@ -320,6 +351,9 @@ function createDynamicNode(kind, fullPath, caseSensitive, prefix, suffix) {
320
351
  route: null,
321
352
  fullPath,
322
353
  parent: null,
354
+ parse: null,
355
+ skipOnParamError: false,
356
+ parsingPriority: 0,
323
357
  caseSensitive,
324
358
  prefix,
325
359
  suffix
@@ -412,19 +446,21 @@ function findMatch(path, segmentTree, fuzzy = false) {
412
446
  const parts = path.split("/");
413
447
  const leaf = getNodeMatch(path, parts, segmentTree, fuzzy);
414
448
  if (!leaf) return null;
415
- const params = extractParams(path, parts, leaf);
416
- if ("**" in leaf) params["**"] = leaf["**"];
417
- const route = leaf.node.route;
449
+ const [rawParams] = extractParams(path, parts, leaf);
418
450
  return {
419
- route,
420
- params
451
+ route: leaf.node.route,
452
+ rawParams,
453
+ parsedParams: leaf.parsedParams
421
454
  };
422
455
  }
423
456
  function extractParams(path, parts, leaf) {
424
457
  const list = buildBranch(leaf.node);
425
458
  let nodeParts = null;
426
- const params = {};
427
- for (let partIndex = 0, nodeIndex = 0, pathIndex = 0; nodeIndex < list.length; partIndex++, nodeIndex++, pathIndex++) {
459
+ const rawParams = {};
460
+ let partIndex = leaf.extract?.part ?? 0;
461
+ let nodeIndex = leaf.extract?.node ?? 0;
462
+ let pathIndex = leaf.extract?.path ?? 0;
463
+ for (; nodeIndex < list.length; partIndex++, nodeIndex++, pathIndex++) {
428
464
  const node = list[nodeIndex];
429
465
  const part = parts[partIndex];
430
466
  const currentPathIndex = pathIndex;
@@ -441,10 +477,10 @@ function extractParams(path, parts, leaf) {
441
477
  nodePart.length - sufLength - 1
442
478
  );
443
479
  const value = part.substring(preLength, part.length - sufLength);
444
- params[name] = decodeURIComponent(value);
480
+ rawParams[name] = decodeURIComponent(value);
445
481
  } else {
446
482
  const name = nodePart.substring(1);
447
- params[name] = decodeURIComponent(part);
483
+ rawParams[name] = decodeURIComponent(part);
448
484
  }
449
485
  } else if (node.kind === SEGMENT_TYPE_OPTIONAL_PARAM) {
450
486
  if (leaf.skipped & 1 << nodeIndex) {
@@ -460,7 +496,7 @@ function extractParams(path, parts, leaf) {
460
496
  nodePart.length - sufLength - 1
461
497
  );
462
498
  const value = node.suffix || node.prefix ? part.substring(preLength, part.length - sufLength) : part;
463
- if (value) params[name] = decodeURIComponent(value);
499
+ if (value) rawParams[name] = decodeURIComponent(value);
464
500
  } else if (node.kind === SEGMENT_TYPE_WILDCARD) {
465
501
  const n = node;
466
502
  const value = path.substring(
@@ -468,12 +504,13 @@ function extractParams(path, parts, leaf) {
468
504
  path.length - (n.suffix?.length ?? 0)
469
505
  );
470
506
  const splat = decodeURIComponent(value);
471
- params["*"] = splat;
472
- params._splat = splat;
507
+ rawParams["*"] = splat;
508
+ rawParams._splat = splat;
473
509
  break;
474
510
  }
475
511
  }
476
- return params;
512
+ if (leaf.rawParams) Object.assign(rawParams, leaf.rawParams);
513
+ return [rawParams, { part: partIndex, node: nodeIndex, path: pathIndex }];
477
514
  }
478
515
  function buildRouteBranch(route) {
479
516
  const list = [route];
@@ -514,7 +551,15 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
514
551
  let bestMatch = null;
515
552
  while (stack.length) {
516
553
  const frame = stack.pop();
517
- let { node, index, skipped, depth, statics, dynamics, optionals } = frame;
554
+ const { node, index, skipped, depth, statics, dynamics, optionals } = frame;
555
+ let { extract, rawParams, parsedParams } = frame;
556
+ if (node.skipOnParamError) {
557
+ const result = validateMatchParams(path, parts, frame);
558
+ if (!result) continue;
559
+ rawParams = frame.rawParams;
560
+ extract = frame.extract;
561
+ parsedParams = frame.parsedParams;
562
+ }
518
563
  if (fuzzy && node.route && node.kind !== SEGMENT_TYPE_INDEX && isFrameMoreSpecific(bestFuzzy, frame)) {
519
564
  bestFuzzy = frame;
520
565
  }
@@ -523,7 +568,8 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
523
568
  if (node.route && !pathIsIndex && isFrameMoreSpecific(bestMatch, frame)) {
524
569
  bestMatch = frame;
525
570
  }
526
- if (!node.optional && !node.wildcard && !node.index) continue;
571
+ if (!node.optional && !node.wildcard && !node.index && !node.pathless)
572
+ continue;
527
573
  }
528
574
  const part = isBeyondPath ? void 0 : parts[index];
529
575
  let lowerPart;
@@ -535,8 +581,15 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
535
581
  depth: depth + 1,
536
582
  statics,
537
583
  dynamics,
538
- optionals
584
+ optionals,
585
+ extract,
586
+ rawParams,
587
+ parsedParams
539
588
  };
589
+ if (node.index.skipOnParamError) {
590
+ const result = validateMatchParams(path, parts, indexFrame);
591
+ if (!result) continue;
592
+ }
540
593
  if (statics === partsLength && !dynamics && !optionals && !skipped) {
541
594
  return indexFrame;
542
595
  }
@@ -558,15 +611,23 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
558
611
  const casePart = segment.caseSensitive ? end : end.toLowerCase();
559
612
  if (casePart !== suffix) continue;
560
613
  }
561
- wildcardMatch = {
614
+ const frame2 = {
562
615
  node: segment,
563
616
  index: partsLength,
564
617
  skipped,
565
618
  depth,
566
619
  statics,
567
620
  dynamics,
568
- optionals
621
+ optionals,
622
+ extract,
623
+ rawParams,
624
+ parsedParams
569
625
  };
626
+ if (segment.skipOnParamError) {
627
+ const result = validateMatchParams(path, parts, frame2);
628
+ if (!result) continue;
629
+ }
630
+ wildcardMatch = frame2;
570
631
  break;
571
632
  }
572
633
  }
@@ -582,7 +643,10 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
582
643
  depth: nextDepth,
583
644
  statics,
584
645
  dynamics,
585
- optionals
646
+ optionals,
647
+ extract,
648
+ rawParams,
649
+ parsedParams
586
650
  });
587
651
  }
588
652
  if (!isBeyondPath) {
@@ -601,7 +665,10 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
601
665
  depth: nextDepth,
602
666
  statics,
603
667
  dynamics,
604
- optionals: optionals + 1
668
+ optionals: optionals + 1,
669
+ extract,
670
+ rawParams,
671
+ parsedParams
605
672
  });
606
673
  }
607
674
  }
@@ -622,7 +689,10 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
622
689
  depth: depth + 1,
623
690
  statics,
624
691
  dynamics: dynamics + 1,
625
- optionals
692
+ optionals,
693
+ extract,
694
+ rawParams,
695
+ parsedParams
626
696
  });
627
697
  }
628
698
  }
@@ -638,7 +708,10 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
638
708
  depth: depth + 1,
639
709
  statics: statics + 1,
640
710
  dynamics,
641
- optionals
711
+ optionals,
712
+ extract,
713
+ rawParams,
714
+ parsedParams
642
715
  });
643
716
  }
644
717
  }
@@ -652,7 +725,28 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
652
725
  depth: depth + 1,
653
726
  statics: statics + 1,
654
727
  dynamics,
655
- optionals
728
+ optionals,
729
+ extract,
730
+ rawParams,
731
+ parsedParams
732
+ });
733
+ }
734
+ }
735
+ if (node.pathless) {
736
+ const nextDepth = depth + 1;
737
+ for (let i = node.pathless.length - 1; i >= 0; i--) {
738
+ const segment = node.pathless[i];
739
+ stack.push({
740
+ node: segment,
741
+ index,
742
+ skipped,
743
+ depth: nextDepth,
744
+ statics,
745
+ dynamics,
746
+ optionals,
747
+ extract,
748
+ rawParams,
749
+ parsedParams
656
750
  });
657
751
  }
658
752
  }
@@ -668,14 +762,24 @@ function getNodeMatch(path, parts, segmentTree, fuzzy) {
668
762
  sliceIndex += parts[i].length;
669
763
  }
670
764
  const splat = sliceIndex === path.length ? "/" : path.slice(sliceIndex);
671
- return {
672
- node: bestFuzzy.node,
673
- skipped: bestFuzzy.skipped,
674
- "**": decodeURIComponent(splat)
675
- };
765
+ bestFuzzy.rawParams ??= {};
766
+ bestFuzzy.rawParams["**"] = decodeURIComponent(splat);
767
+ return bestFuzzy;
676
768
  }
677
769
  return null;
678
770
  }
771
+ function validateMatchParams(path, parts, frame) {
772
+ try {
773
+ const [rawParams, state] = extractParams(path, parts, frame);
774
+ frame.rawParams = rawParams;
775
+ frame.extract = state;
776
+ const parsed = frame.node.parse(rawParams);
777
+ frame.parsedParams = Object.assign({}, frame.parsedParams, parsed);
778
+ return true;
779
+ } catch {
780
+ return null;
781
+ }
782
+ }
679
783
  function isFrameMoreSpecific(prev, next) {
680
784
  if (!prev) return true;
681
785
  return next.statics > prev.statics || next.statics === prev.statics && (next.dynamics > prev.dynamics || next.dynamics === prev.dynamics && (next.optionals > prev.optionals || next.optionals === prev.optionals && ((next.node.kind === SEGMENT_TYPE_INDEX) > (prev.node.kind === SEGMENT_TYPE_INDEX) || next.node.kind === SEGMENT_TYPE_INDEX === (prev.node.kind === SEGMENT_TYPE_INDEX) && next.depth > prev.depth)));