@benjavicente/router-core 1.168.9 → 1.169.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Matches.cjs +1 -1
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/config.cjs +1 -1
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/defer.cjs +1 -1
- package/dist/cjs/defer.cjs.map +1 -1
- package/dist/cjs/hash-scroll.cjs +1 -1
- package/dist/cjs/hash-scroll.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.d.cts +3 -3
- package/dist/cjs/isServer/client.cjs +1 -1
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/development.cjs +1 -1
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/server.cjs +1 -1
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/link.cjs +1 -1
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/load-matches.cjs +26 -26
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/manifest.cjs +31 -0
- package/dist/cjs/manifest.cjs.map +1 -1
- package/dist/cjs/manifest.d.cts +8 -0
- package/dist/cjs/new-process-route-tree.cjs +55 -67
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.d.cts +4 -26
- package/dist/cjs/not-found.cjs +1 -1
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/path.cjs +1 -22
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/root.cjs +1 -1
- package/dist/cjs/root.cjs.map +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +6 -34
- package/dist/cjs/router.cjs +94 -80
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +6 -5
- package/dist/cjs/scroll-restoration-script/server.cjs +1 -1
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +7 -7
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +2 -2
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/constants.cjs +2 -2
- package/dist/cjs/ssr/constants.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.cjs +50 -41
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.d.cts +12 -4
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +2 -2
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -1
- package/dist/cjs/ssr/serializer/transformer.cjs +16 -14
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/transformer.d.cts +24 -23
- package/dist/cjs/ssr/ssr-client.cjs +9 -9
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +110 -22
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.d.cts +4 -2
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +10 -7
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/stores.cjs +57 -57
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +16 -16
- package/dist/cjs/utils.cjs +12 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/Matches.js +1 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/defer.js +1 -1
- package/dist/esm/defer.js.map +1 -1
- package/dist/esm/hash-scroll.js +1 -1
- package/dist/esm/hash-scroll.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -3
- package/dist/esm/isServer/client.js +1 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.js +1 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.js +1 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/link.js +1 -1
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/load-matches.js +26 -26
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/manifest.d.ts +8 -0
- package/dist/esm/manifest.js +28 -1
- package/dist/esm/manifest.js.map +1 -1
- package/dist/esm/new-process-route-tree.d.ts +4 -26
- package/dist/esm/new-process-route-tree.js +55 -68
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/not-found.js +1 -1
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/path.js +1 -22
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/root.js +1 -1
- package/dist/esm/root.js.map +1 -1
- package/dist/esm/route.d.ts +6 -34
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +6 -5
- package/dist/esm/router.js +96 -82
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration-script/server.js +1 -1
- package/dist/esm/scroll-restoration-script/server.js.map +1 -1
- package/dist/esm/scroll-restoration.js +7 -7
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +2 -2
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/constants.js +2 -2
- package/dist/esm/ssr/constants.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.js +2 -2
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/serializer/RawStream.d.ts +12 -4
- package/dist/esm/ssr/serializer/RawStream.js +50 -41
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +2 -2
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
- package/dist/esm/ssr/serializer/transformer.d.ts +24 -23
- package/dist/esm/ssr/serializer/transformer.js +16 -14
- package/dist/esm/ssr/serializer/transformer.js.map +1 -1
- package/dist/esm/ssr/ssr-client.js +9 -9
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.d.ts +4 -2
- package/dist/esm/ssr/ssr-server.js +110 -22
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.js +10 -7
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/stores.d.ts +16 -16
- package/dist/esm/stores.js +58 -58
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +12 -8
- package/dist/esm/utils.js.map +1 -1
- package/package.json +4 -8
- package/skills/router-core/auth-and-guards/SKILL.md +41 -0
- package/skills/router-core/ssr/SKILL.md +59 -5
- package/skills/router-core/type-safety/SKILL.md +37 -42
- package/src/hash-scroll.ts +1 -1
- package/src/index.ts +9 -2
- package/src/load-matches.ts +14 -15
- package/src/manifest.ts +46 -0
- package/src/new-process-route-tree.ts +93 -106
- package/src/not-found.ts +1 -1
- package/src/path.ts +1 -27
- package/src/route.ts +10 -36
- package/src/router.ts +151 -140
- package/src/scroll-restoration.ts +1 -1
- package/src/ssr/createRequestHandler.ts +4 -5
- package/src/ssr/serializer/RawStream.ts +65 -56
- package/src/ssr/serializer/ShallowErrorPlugin.ts +2 -2
- package/src/ssr/serializer/seroval-plugins.ts +2 -1
- package/src/ssr/serializer/transformer.ts +71 -76
- package/src/ssr/ssr-client.ts +8 -12
- package/src/ssr/ssr-server.ts +170 -14
- package/src/ssr/transformStreamWithRouter.ts +3 -0
- package/src/stores.ts +86 -86
- package/src/utils.ts +7 -0
- package/bin/intent.js +0 -25
|
@@ -207,10 +207,8 @@ function parseSegments<TRouteLike extends RouteLike>(
|
|
|
207
207
|
const path = route.fullPath ?? route.from
|
|
208
208
|
const length = path.length
|
|
209
209
|
const caseSensitive = route.options?.caseSensitive ?? defaultCaseSensitive
|
|
210
|
-
const
|
|
211
|
-
route.options?.params?.parse
|
|
212
|
-
route.options?.skipRouteOnParseError?.params
|
|
213
|
-
)
|
|
210
|
+
const parseParams =
|
|
211
|
+
route.options?.params?.parse ?? route.options?.parseParams
|
|
214
212
|
while (cursor < length) {
|
|
215
213
|
const segment = parseSegment(path, cursor, data)
|
|
216
214
|
let nextNode: AnySegmentNode<TRouteLike>
|
|
@@ -270,10 +268,10 @@ function parseSegments<TRouteLike extends RouteLike>(
|
|
|
270
268
|
? suffix_raw
|
|
271
269
|
: suffix_raw.toLowerCase()
|
|
272
270
|
const existingNode =
|
|
273
|
-
!
|
|
271
|
+
!parseParams &&
|
|
274
272
|
node.dynamic?.find(
|
|
275
273
|
(s) =>
|
|
276
|
-
!s.
|
|
274
|
+
!s.parse &&
|
|
277
275
|
s.caseSensitive === actuallyCaseSensitive &&
|
|
278
276
|
s.prefix === prefix &&
|
|
279
277
|
s.suffix === suffix,
|
|
@@ -312,10 +310,10 @@ function parseSegments<TRouteLike extends RouteLike>(
|
|
|
312
310
|
? suffix_raw
|
|
313
311
|
: suffix_raw.toLowerCase()
|
|
314
312
|
const existingNode =
|
|
315
|
-
!
|
|
313
|
+
!parseParams &&
|
|
316
314
|
node.optional?.find(
|
|
317
315
|
(s) =>
|
|
318
|
-
!s.
|
|
316
|
+
!s.parse &&
|
|
319
317
|
s.caseSensitive === actuallyCaseSensitive &&
|
|
320
318
|
s.prefix === prefix &&
|
|
321
319
|
s.suffix === suffix,
|
|
@@ -372,7 +370,7 @@ function parseSegments<TRouteLike extends RouteLike>(
|
|
|
372
370
|
|
|
373
371
|
// create pathless node
|
|
374
372
|
if (
|
|
375
|
-
|
|
373
|
+
parseParams &&
|
|
376
374
|
route.children &&
|
|
377
375
|
!route.isRoot &&
|
|
378
376
|
route.id &&
|
|
@@ -404,9 +402,7 @@ function parseSegments<TRouteLike extends RouteLike>(
|
|
|
404
402
|
node = indexNode
|
|
405
403
|
}
|
|
406
404
|
|
|
407
|
-
node.parse =
|
|
408
|
-
node.skipOnParamError = skipOnParamError
|
|
409
|
-
node.parsingPriority = route.options?.skipRouteOnParseError?.priority ?? 0
|
|
405
|
+
node.parse = parseParams ?? null
|
|
410
406
|
|
|
411
407
|
// make node "matchable"
|
|
412
408
|
if (isLeaf && !node.route) {
|
|
@@ -433,25 +429,17 @@ function sortDynamic(
|
|
|
433
429
|
prefix?: string
|
|
434
430
|
suffix?: string
|
|
435
431
|
caseSensitive: boolean
|
|
436
|
-
|
|
437
|
-
parsingPriority: number
|
|
432
|
+
parse: null | ((params: Record<string, string>) => unknown)
|
|
438
433
|
},
|
|
439
434
|
b: {
|
|
440
435
|
prefix?: string
|
|
441
436
|
suffix?: string
|
|
442
437
|
caseSensitive: boolean
|
|
443
|
-
|
|
444
|
-
parsingPriority: number
|
|
438
|
+
parse: null | ((params: Record<string, string>) => unknown)
|
|
445
439
|
},
|
|
446
440
|
) {
|
|
447
|
-
if (a.
|
|
448
|
-
if (!a.
|
|
449
|
-
if (
|
|
450
|
-
a.skipOnParamError &&
|
|
451
|
-
b.skipOnParamError &&
|
|
452
|
-
(a.parsingPriority || b.parsingPriority)
|
|
453
|
-
)
|
|
454
|
-
return b.parsingPriority - a.parsingPriority
|
|
441
|
+
if (a.parse && !b.parse) return -1
|
|
442
|
+
if (!a.parse && b.parse) return 1
|
|
455
443
|
if (a.prefix && b.prefix && a.prefix !== b.prefix) {
|
|
456
444
|
if (a.prefix.startsWith(b.prefix)) return -1
|
|
457
445
|
if (b.prefix.startsWith(a.prefix)) return 1
|
|
@@ -467,8 +455,7 @@ function sortDynamic(
|
|
|
467
455
|
if (a.caseSensitive && !b.caseSensitive) return -1
|
|
468
456
|
if (!a.caseSensitive && b.caseSensitive) return 1
|
|
469
457
|
|
|
470
|
-
//
|
|
471
|
-
// at this point the 2 nodes cannot conflict during matching
|
|
458
|
+
// Equal specificity preserves route declaration order through stable sort.
|
|
472
459
|
return 0
|
|
473
460
|
}
|
|
474
461
|
|
|
@@ -525,8 +512,6 @@ function createStaticNode<T extends RouteLike>(
|
|
|
525
512
|
fullPath,
|
|
526
513
|
parent: null,
|
|
527
514
|
parse: null,
|
|
528
|
-
skipOnParamError: false,
|
|
529
|
-
parsingPriority: 0,
|
|
530
515
|
}
|
|
531
516
|
}
|
|
532
517
|
|
|
@@ -558,8 +543,6 @@ function createDynamicNode<T extends RouteLike>(
|
|
|
558
543
|
fullPath,
|
|
559
544
|
parent: null,
|
|
560
545
|
parse: null,
|
|
561
|
-
skipOnParamError: false,
|
|
562
|
-
parsingPriority: 0,
|
|
563
546
|
caseSensitive,
|
|
564
547
|
prefix,
|
|
565
548
|
suffix,
|
|
@@ -621,13 +604,7 @@ type SegmentNode<T extends RouteLike> = {
|
|
|
621
604
|
depth: number
|
|
622
605
|
|
|
623
606
|
/** route.options.params.parse function, set on the last node of the route */
|
|
624
|
-
parse: null | ((params: Record<string, string>) =>
|
|
625
|
-
|
|
626
|
-
/** options.skipRouteOnParseError.params ?? false */
|
|
627
|
-
skipOnParamError: boolean
|
|
628
|
-
|
|
629
|
-
/** options.skipRouteOnParseError.priority ?? 0 */
|
|
630
|
-
parsingPriority: number
|
|
607
|
+
parse: null | ((params: Record<string, string>) => unknown)
|
|
631
608
|
}
|
|
632
609
|
|
|
633
610
|
type RouteLike = {
|
|
@@ -637,13 +614,10 @@ type RouteLike = {
|
|
|
637
614
|
parentRoute?: RouteLike // parent route,
|
|
638
615
|
isRoot?: boolean
|
|
639
616
|
options?: {
|
|
640
|
-
skipRouteOnParseError?: {
|
|
641
|
-
params?: boolean
|
|
642
|
-
priority?: number
|
|
643
|
-
}
|
|
644
617
|
caseSensitive?: boolean
|
|
618
|
+
parseParams?: (params: Record<string, string>) => unknown
|
|
645
619
|
params?: {
|
|
646
|
-
parse?: (params: Record<string, string>) =>
|
|
620
|
+
parse?: (params: Record<string, string>) => unknown
|
|
647
621
|
}
|
|
648
622
|
}
|
|
649
623
|
} &
|
|
@@ -734,7 +708,6 @@ export function findSingleMatch(
|
|
|
734
708
|
type RouteMatch<T extends Extract<RouteLike, { fullPath: string }>> = {
|
|
735
709
|
route: T
|
|
736
710
|
rawParams: Record<string, string>
|
|
737
|
-
parsedParams?: Record<string, unknown>
|
|
738
711
|
branch: ReadonlyArray<T>
|
|
739
712
|
}
|
|
740
713
|
|
|
@@ -858,11 +831,6 @@ function findMatch<T extends RouteLike>(
|
|
|
858
831
|
* This will be the exhaustive list of all params defined in the route's path.
|
|
859
832
|
*/
|
|
860
833
|
rawParams: Record<string, string>
|
|
861
|
-
/**
|
|
862
|
-
* The accumlulated parsed params of each route in the branch that had `skipRouteOnParseError` enabled.
|
|
863
|
-
* 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.
|
|
864
|
-
*/
|
|
865
|
-
parsedParams?: Record<string, unknown>
|
|
866
834
|
} | null {
|
|
867
835
|
const parts = path.split('/')
|
|
868
836
|
const leaf = getNodeMatch(path, parts, segmentTree, fuzzy)
|
|
@@ -871,7 +839,6 @@ function findMatch<T extends RouteLike>(
|
|
|
871
839
|
return {
|
|
872
840
|
route: leaf.node.route!,
|
|
873
841
|
rawParams,
|
|
874
|
-
parsedParams: leaf.parsedParams,
|
|
875
842
|
}
|
|
876
843
|
}
|
|
877
844
|
|
|
@@ -991,7 +958,7 @@ function extractParams<T extends RouteLike>(
|
|
|
991
958
|
]
|
|
992
959
|
}
|
|
993
960
|
|
|
994
|
-
function buildRouteBranch<T extends RouteLike>(route: T) {
|
|
961
|
+
export function buildRouteBranch<T extends RouteLike>(route: T) {
|
|
995
962
|
const list = [route]
|
|
996
963
|
while (route.parentRoute) {
|
|
997
964
|
route = route.parentRoute as T
|
|
@@ -1023,6 +990,7 @@ type MatchStackFrame<T extends RouteLike> = {
|
|
|
1023
990
|
* If we really really need to support more than 32 segments we can switch to using a `BigInt` here. It's about 2x slower in worst case scenarios.
|
|
1024
991
|
*/
|
|
1025
992
|
skipped: number
|
|
993
|
+
/** Positional bitmasks tracking which consumed URL segments matched each segment kind. */
|
|
1026
994
|
statics: number
|
|
1027
995
|
dynamics: number
|
|
1028
996
|
optionals: number
|
|
@@ -1030,7 +998,6 @@ type MatchStackFrame<T extends RouteLike> = {
|
|
|
1030
998
|
extract?: ParamExtractionState
|
|
1031
999
|
/** intermediary params from param extraction */
|
|
1032
1000
|
rawParams?: Record<string, string>
|
|
1033
|
-
parsedParams?: Record<string, unknown>
|
|
1034
1001
|
}
|
|
1035
1002
|
|
|
1036
1003
|
function getNodeMatch<T extends RouteLike>(
|
|
@@ -1044,7 +1011,7 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1044
1011
|
if (path === '/' && segmentTree.index)
|
|
1045
1012
|
return { node: segmentTree.index, skipped: 0 } as Pick<
|
|
1046
1013
|
Frame,
|
|
1047
|
-
'node' | 'skipped'
|
|
1014
|
+
'node' | 'skipped'
|
|
1048
1015
|
>
|
|
1049
1016
|
|
|
1050
1017
|
const trailingSlash = !last(parts)
|
|
@@ -1066,27 +1033,37 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1066
1033
|
index: 1,
|
|
1067
1034
|
skipped: 0,
|
|
1068
1035
|
depth: 1,
|
|
1069
|
-
statics:
|
|
1036
|
+
statics: 0,
|
|
1070
1037
|
dynamics: 0,
|
|
1071
1038
|
optionals: 0,
|
|
1072
1039
|
},
|
|
1073
1040
|
]
|
|
1074
1041
|
|
|
1075
|
-
let wildcardMatch: Frame | null = null
|
|
1076
1042
|
let bestFuzzy: Frame | null = null
|
|
1077
1043
|
let bestMatch: Frame | null = null
|
|
1078
1044
|
|
|
1079
1045
|
while (stack.length) {
|
|
1080
1046
|
const frame = stack.pop()!
|
|
1081
1047
|
const { node, index, skipped, depth, statics, dynamics, optionals } = frame
|
|
1082
|
-
let { extract, rawParams
|
|
1048
|
+
let { extract, rawParams } = frame
|
|
1083
1049
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1050
|
+
// Wildcard candidates are pushed speculatively as fallbacks in case a
|
|
1051
|
+
// higher-priority wildcard later fails params.parse. If a better wildcard
|
|
1052
|
+
// has already validated and become bestMatch, lower-priority wildcard
|
|
1053
|
+
// fallbacks cannot win anymore and should not run params.parse.
|
|
1054
|
+
if (
|
|
1055
|
+
node.kind === SEGMENT_TYPE_WILDCARD &&
|
|
1056
|
+
node.route &&
|
|
1057
|
+
!isFrameMoreSpecific(bestMatch, frame)
|
|
1058
|
+
) {
|
|
1059
|
+
continue
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (node.parse) {
|
|
1063
|
+
const result = validateParseParams(path, parts, frame)
|
|
1086
1064
|
if (!result) continue
|
|
1087
1065
|
rawParams = frame.rawParams
|
|
1088
1066
|
extract = frame.extract
|
|
1089
|
-
parsedParams = frame.parsedParams
|
|
1090
1067
|
}
|
|
1091
1068
|
|
|
1092
1069
|
// In fuzzy mode, track the best partial match we've found so far
|
|
@@ -1101,7 +1078,13 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1101
1078
|
|
|
1102
1079
|
const isBeyondPath = index === partsLength
|
|
1103
1080
|
if (isBeyondPath) {
|
|
1104
|
-
if (
|
|
1081
|
+
if (
|
|
1082
|
+
node.route &&
|
|
1083
|
+
(!pathIsIndex ||
|
|
1084
|
+
node.kind === SEGMENT_TYPE_INDEX ||
|
|
1085
|
+
node.kind === SEGMENT_TYPE_WILDCARD) &&
|
|
1086
|
+
isFrameMoreSpecific(bestMatch, frame)
|
|
1087
|
+
) {
|
|
1105
1088
|
bestMatch = frame
|
|
1106
1089
|
}
|
|
1107
1090
|
// beyond the length of the path parts, only some segment types can match
|
|
@@ -1124,17 +1107,21 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1124
1107
|
optionals,
|
|
1125
1108
|
extract,
|
|
1126
1109
|
rawParams,
|
|
1127
|
-
parsedParams,
|
|
1128
1110
|
}
|
|
1129
1111
|
let indexValid = true
|
|
1130
|
-
if (node.index.
|
|
1131
|
-
const result =
|
|
1112
|
+
if (node.index.parse) {
|
|
1113
|
+
const result = validateParseParams(path, parts, indexFrame)
|
|
1132
1114
|
if (!result) indexValid = false
|
|
1133
1115
|
}
|
|
1134
1116
|
if (indexValid) {
|
|
1135
1117
|
// perfect match, no need to continue
|
|
1136
1118
|
// this is an optimization, algorithm should work correctly without this block
|
|
1137
|
-
if (
|
|
1119
|
+
if (
|
|
1120
|
+
!dynamics &&
|
|
1121
|
+
!optionals &&
|
|
1122
|
+
!skipped &&
|
|
1123
|
+
isPerfectStaticMatch(statics, partsLength)
|
|
1124
|
+
) {
|
|
1138
1125
|
return indexFrame
|
|
1139
1126
|
}
|
|
1140
1127
|
if (isFrameMoreSpecific(bestMatch, indexFrame)) {
|
|
@@ -1145,8 +1132,9 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1145
1132
|
}
|
|
1146
1133
|
|
|
1147
1134
|
// 5. Try wildcard match
|
|
1148
|
-
if (node.wildcard
|
|
1149
|
-
for (
|
|
1135
|
+
if (node.wildcard) {
|
|
1136
|
+
for (let i = node.wildcard.length - 1; i >= 0; i--) {
|
|
1137
|
+
const segment = node.wildcard[i]!
|
|
1150
1138
|
const { prefix, suffix } = segment
|
|
1151
1139
|
if (prefix) {
|
|
1152
1140
|
if (isBeyondPath) continue
|
|
@@ -1161,26 +1149,18 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1161
1149
|
const casePart = segment.caseSensitive ? end : end.toLowerCase()
|
|
1162
1150
|
if (casePart !== suffix) continue
|
|
1163
1151
|
}
|
|
1164
|
-
//
|
|
1165
|
-
|
|
1166
|
-
const frame = {
|
|
1152
|
+
// wildcard matches consume the rest of the URL and cannot have children
|
|
1153
|
+
stack.push({
|
|
1167
1154
|
node: segment,
|
|
1168
1155
|
index: partsLength,
|
|
1169
1156
|
skipped,
|
|
1170
|
-
depth,
|
|
1157
|
+
depth: depth + 1,
|
|
1171
1158
|
statics,
|
|
1172
1159
|
dynamics,
|
|
1173
1160
|
optionals,
|
|
1174
1161
|
extract,
|
|
1175
1162
|
rawParams,
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1178
|
-
if (segment.skipOnParamError) {
|
|
1179
|
-
const result = validateMatchParams(path, parts, frame)
|
|
1180
|
-
if (!result) continue
|
|
1181
|
-
}
|
|
1182
|
-
wildcardMatch = frame
|
|
1183
|
-
break
|
|
1163
|
+
})
|
|
1184
1164
|
}
|
|
1185
1165
|
}
|
|
1186
1166
|
|
|
@@ -1201,7 +1181,6 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1201
1181
|
optionals,
|
|
1202
1182
|
extract,
|
|
1203
1183
|
rawParams,
|
|
1204
|
-
parsedParams,
|
|
1205
1184
|
}) // enqueue skipping the optional
|
|
1206
1185
|
}
|
|
1207
1186
|
if (!isBeyondPath) {
|
|
@@ -1222,10 +1201,9 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1222
1201
|
depth: nextDepth,
|
|
1223
1202
|
statics,
|
|
1224
1203
|
dynamics,
|
|
1225
|
-
optionals: optionals +
|
|
1204
|
+
optionals: optionals + segmentScore(partsLength, index),
|
|
1226
1205
|
extract,
|
|
1227
1206
|
rawParams,
|
|
1228
|
-
parsedParams,
|
|
1229
1207
|
})
|
|
1230
1208
|
}
|
|
1231
1209
|
}
|
|
@@ -1249,11 +1227,10 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1249
1227
|
skipped,
|
|
1250
1228
|
depth: depth + 1,
|
|
1251
1229
|
statics,
|
|
1252
|
-
dynamics: dynamics +
|
|
1230
|
+
dynamics: dynamics + segmentScore(partsLength, index),
|
|
1253
1231
|
optionals,
|
|
1254
1232
|
extract,
|
|
1255
1233
|
rawParams,
|
|
1256
|
-
parsedParams,
|
|
1257
1234
|
})
|
|
1258
1235
|
}
|
|
1259
1236
|
}
|
|
@@ -1269,12 +1246,11 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1269
1246
|
index: index + 1,
|
|
1270
1247
|
skipped,
|
|
1271
1248
|
depth: depth + 1,
|
|
1272
|
-
statics: statics +
|
|
1249
|
+
statics: statics + segmentScore(partsLength, index),
|
|
1273
1250
|
dynamics,
|
|
1274
1251
|
optionals,
|
|
1275
1252
|
extract,
|
|
1276
1253
|
rawParams,
|
|
1277
|
-
parsedParams,
|
|
1278
1254
|
})
|
|
1279
1255
|
}
|
|
1280
1256
|
}
|
|
@@ -1288,12 +1264,11 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1288
1264
|
index: index + 1,
|
|
1289
1265
|
skipped,
|
|
1290
1266
|
depth: depth + 1,
|
|
1291
|
-
statics: statics +
|
|
1267
|
+
statics: statics + segmentScore(partsLength, index),
|
|
1292
1268
|
dynamics,
|
|
1293
1269
|
optionals,
|
|
1294
1270
|
extract,
|
|
1295
1271
|
rawParams,
|
|
1296
|
-
parsedParams,
|
|
1297
1272
|
})
|
|
1298
1273
|
}
|
|
1299
1274
|
}
|
|
@@ -1313,22 +1288,13 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1313
1288
|
optionals,
|
|
1314
1289
|
extract,
|
|
1315
1290
|
rawParams,
|
|
1316
|
-
parsedParams,
|
|
1317
1291
|
})
|
|
1318
1292
|
}
|
|
1319
1293
|
}
|
|
1320
1294
|
}
|
|
1321
1295
|
|
|
1322
|
-
if (bestMatch && wildcardMatch) {
|
|
1323
|
-
return isFrameMoreSpecific(wildcardMatch, bestMatch)
|
|
1324
|
-
? bestMatch
|
|
1325
|
-
: wildcardMatch
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
1296
|
if (bestMatch) return bestMatch
|
|
1329
1297
|
|
|
1330
|
-
if (wildcardMatch) return wildcardMatch
|
|
1331
|
-
|
|
1332
1298
|
if (fuzzy && bestFuzzy) {
|
|
1333
1299
|
let sliceIndex = bestFuzzy.index
|
|
1334
1300
|
for (let i = 0; i < bestFuzzy.index; i++) {
|
|
@@ -1343,25 +1309,46 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1343
1309
|
return null
|
|
1344
1310
|
}
|
|
1345
1311
|
|
|
1346
|
-
function
|
|
1312
|
+
function segmentScore(partsLength: number, index: number): number {
|
|
1313
|
+
// The specificity scores are bitmasks over consumed URL segments. Earlier
|
|
1314
|
+
// URL segments should dominate later ones when comparing scores, so the
|
|
1315
|
+
// first real segment gets the highest bit and the last gets bit 0. Since
|
|
1316
|
+
// `parts[0]` is the empty string before the leading slash, real URL segments
|
|
1317
|
+
// are [1, partsLength), making this segment's bit `partsLength - index - 1`.
|
|
1318
|
+
return 2 ** (partsLength - index - 1)
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function isPerfectStaticMatch(statics: number, partsLength: number): boolean {
|
|
1322
|
+
return statics === 2 ** (partsLength - 1) - 1
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function validateParseParams<T extends RouteLike>(
|
|
1347
1326
|
path: string,
|
|
1348
1327
|
parts: Array<string>,
|
|
1349
1328
|
frame: MatchStackFrame<T>,
|
|
1350
1329
|
) {
|
|
1330
|
+
let rawParams: Record<string, string>
|
|
1331
|
+
let state: ParamExtractionState
|
|
1332
|
+
|
|
1351
1333
|
try {
|
|
1352
|
-
|
|
1353
|
-
frame.rawParams = rawParams
|
|
1354
|
-
frame.extract = state
|
|
1355
|
-
const parsed = frame.node.parse!(rawParams)
|
|
1356
|
-
frame.parsedParams = Object.assign(
|
|
1357
|
-
Object.create(null),
|
|
1358
|
-
frame.parsedParams,
|
|
1359
|
-
parsed,
|
|
1360
|
-
)
|
|
1361
|
-
return true
|
|
1334
|
+
;[rawParams, state] = extractParams(path, parts, frame)
|
|
1362
1335
|
} catch {
|
|
1363
1336
|
return null
|
|
1364
1337
|
}
|
|
1338
|
+
|
|
1339
|
+
frame.rawParams = rawParams
|
|
1340
|
+
frame.extract = state
|
|
1341
|
+
|
|
1342
|
+
if (!frame.node.parse) return true
|
|
1343
|
+
|
|
1344
|
+
try {
|
|
1345
|
+
if (frame.node.parse(rawParams) === false) return null
|
|
1346
|
+
} catch {
|
|
1347
|
+
// Thrown parse errors should be surfaced on the selected match by
|
|
1348
|
+
// extractStrictParams, not used as fallback route selection.
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
return true
|
|
1365
1352
|
}
|
|
1366
1353
|
|
|
1367
1354
|
function isFrameMoreSpecific(
|
package/src/not-found.ts
CHANGED
package/src/path.ts
CHANGED
|
@@ -167,33 +167,7 @@ export function resolvePath({
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
let joined = ''
|
|
172
|
-
for (let i = 0; i < baseSegments.length; i++) {
|
|
173
|
-
if (i > 0) joined += '/'
|
|
174
|
-
const part = baseSegments[i]!
|
|
175
|
-
if (!part) continue
|
|
176
|
-
segment = parseSegment(part, 0, segment)
|
|
177
|
-
const kind = segment[0]
|
|
178
|
-
if (kind === SEGMENT_TYPE_PATHNAME) {
|
|
179
|
-
joined += part
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
const end = segment[5]
|
|
183
|
-
const prefix = part.substring(0, segment[1])
|
|
184
|
-
const suffix = part.substring(segment[4], end)
|
|
185
|
-
const value = part.substring(segment[2], segment[3])
|
|
186
|
-
if (kind === SEGMENT_TYPE_PARAM) {
|
|
187
|
-
joined += prefix || suffix ? `${prefix}{$${value}}${suffix}` : `$${value}`
|
|
188
|
-
} else if (kind === SEGMENT_TYPE_WILDCARD) {
|
|
189
|
-
joined += prefix || suffix ? `${prefix}{$}${suffix}` : '$'
|
|
190
|
-
} else {
|
|
191
|
-
// SEGMENT_TYPE_OPTIONAL_PARAM
|
|
192
|
-
joined += `${prefix}{-$${value}}${suffix}`
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
joined = cleanPath(joined)
|
|
196
|
-
const result = joined || '/'
|
|
170
|
+
const result = cleanPath(baseSegments.join('/')) || '/'
|
|
197
171
|
if (key && cache) cache.set(key, result)
|
|
198
172
|
return result
|
|
199
173
|
}
|
package/src/route.ts
CHANGED
|
@@ -173,9 +173,13 @@ export type ResolveParams<
|
|
|
173
173
|
|
|
174
174
|
export type ParseParamsFn<in out TPath extends string, in out TParams> = (
|
|
175
175
|
rawParams: Expand<ResolveParams<TPath>>,
|
|
176
|
-
) => TParams
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
) => TParams | false
|
|
177
|
+
|
|
178
|
+
type ValidateParsedParams<TPath extends string, TParams> = [TParams] extends [
|
|
179
|
+
ResolveParams<TPath, any>,
|
|
180
|
+
]
|
|
181
|
+
? unknown
|
|
182
|
+
: never
|
|
179
183
|
|
|
180
184
|
export type StringifyParamsFn<in out TPath extends string, in out TParams> = (
|
|
181
185
|
params: TParams,
|
|
@@ -183,14 +187,15 @@ export type StringifyParamsFn<in out TPath extends string, in out TParams> = (
|
|
|
183
187
|
|
|
184
188
|
export type ParamsOptions<in out TPath extends string, in out TParams> = {
|
|
185
189
|
params?: {
|
|
186
|
-
parse?: ParseParamsFn<TPath, TParams>
|
|
190
|
+
parse?: ParseParamsFn<TPath, TParams> & ValidateParsedParams<TPath, TParams>
|
|
187
191
|
stringify?: StringifyParamsFn<TPath, TParams>
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
/**
|
|
191
195
|
@deprecated Use params.parse instead
|
|
192
196
|
*/
|
|
193
|
-
parseParams?: ParseParamsFn<TPath, TParams>
|
|
197
|
+
parseParams?: ParseParamsFn<TPath, TParams> &
|
|
198
|
+
ValidateParsedParams<TPath, TParams>
|
|
194
199
|
|
|
195
200
|
/**
|
|
196
201
|
@deprecated Use params.stringify instead
|
|
@@ -1293,37 +1298,6 @@ export interface UpdatableRouteOptions<
|
|
|
1293
1298
|
in out TBeforeLoadFn,
|
|
1294
1299
|
>
|
|
1295
1300
|
extends UpdatableStaticRouteOption, UpdatableRouteOptionsExtensions {
|
|
1296
|
-
/**
|
|
1297
|
-
* Options to control route matching behavior with runtime code.
|
|
1298
|
-
*
|
|
1299
|
-
* @experimental 🚧 this feature is subject to change
|
|
1300
|
-
*
|
|
1301
|
-
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/RouteOptionsType
|
|
1302
|
-
*/
|
|
1303
|
-
skipRouteOnParseError?: {
|
|
1304
|
-
/**
|
|
1305
|
-
* If `true`, skip this route during matching if `params.parse` fails.
|
|
1306
|
-
*
|
|
1307
|
-
* Without this option, a `/$param` route could match *any* value for `param`,
|
|
1308
|
-
* and only later during the route lifecycle would `params.parse` run and potentially
|
|
1309
|
-
* show the `errorComponent` if validation failed.
|
|
1310
|
-
*
|
|
1311
|
-
* With this option enabled, the route will only match if `params.parse` succeeds.
|
|
1312
|
-
* If it fails, the router will continue trying to match other routes, potentially
|
|
1313
|
-
* finding a different route that works, or ultimately showing the `notFoundComponent`.
|
|
1314
|
-
*
|
|
1315
|
-
* @default false
|
|
1316
|
-
*/
|
|
1317
|
-
params?: boolean
|
|
1318
|
-
/**
|
|
1319
|
-
* In cases where multiple routes would need to run `params.parse` during matching
|
|
1320
|
-
* to determine which route to pick, this priority number can be used as a tie-breaker
|
|
1321
|
-
* for which route to try first. Higher number = higher priority.
|
|
1322
|
-
*
|
|
1323
|
-
* @default 0
|
|
1324
|
-
*/
|
|
1325
|
-
priority?: number
|
|
1326
|
-
}
|
|
1327
1301
|
/**
|
|
1328
1302
|
* If true, this route will be matched as case-sensitive
|
|
1329
1303
|
*
|