@@ -788,28 +788,55 @@ type RegexMatchPlus<
788788 : never
789789 : never ;
790790
791- // Recursive helper for finding path parameters in the absence of wildcards
792- type _PathParam < Path extends string > =
791+ // Union of all path parameters - required and optional
792+ export type PathParam < Path extends string > =
793+ | RequiredPathParam < Path >
794+ | OptionalPathParam < Path > ;
795+
796+ // Recursive helper for finding required path parameters in the absence of wildcards
797+ type _RequiredPathParam < Path extends string > =
793798 // split path into individual path segments
794799 Path extends `${infer L } /${infer R } `
795- ? _PathParam < L > | _PathParam < R >
796- : // find params after `:`
797- Path extends `:${infer Param } `
798- ? Param extends `${infer Optional } ?${string } `
799- ? RegexMatchPlus < ParamChar , Optional >
800- : RegexMatchPlus < ParamChar , Param >
801- : // otherwise, there aren't any params present
800+ ? _RequiredPathParam < L > | _RequiredPathParam < R >
801+ : // ignore optional params
802+ Path extends `:${string } ?${string } `
803+ ? never
804+ : // find required params after `:`
805+ Path extends `:${infer Param } `
806+ ? RegexMatchPlus < ParamChar , Param >
807+ : // otherwise, there aren't any params present
808+ never ;
809+
810+ type RequiredPathParam < Path extends string > =
811+ // check if path is just a wildcard
812+ Path extends "*" | "/*"
813+ ? "*"
814+ : // look for wildcard at the end of the path
815+ Path extends `${infer Rest } /*`
816+ ? "*" | _RequiredPathParam < Rest >
817+ : // look for params in the absence of wildcards
818+ _RequiredPathParam < Path > ;
819+
820+ // Recursive helper for finding optional path parameters in the absence of wildcards
821+ type _OptionalPathParam < Path extends string > =
822+ // split path into individual path segments
823+ Path extends `${infer L } /${infer R } `
824+ ? _OptionalPathParam < L > | _OptionalPathParam < R >
825+ : // find optional params after `:`
826+ Path extends `:${infer Optional } ?${infer Rest } `
827+ ? RegexMatchPlus < ParamChar , Optional > | _OptionalPathParam < Rest >
828+ : // otherwise, there aren't any optional params present
802829 never ;
803830
804- export type PathParam < Path extends string > =
831+ type OptionalPathParam < Path extends string > =
805832 // check if path is just a wildcard
806833 Path extends "*" | "/*"
807834 ? "*"
808835 : // look for wildcard at the end of the path
809836 Path extends `${infer Rest } /*`
810- ? "*" | _PathParam < Rest >
837+ ? "*" | _OptionalPathParam < Rest >
811838 : // look for params in the absence of wildcards
812- _PathParam < Path > ;
839+ _OptionalPathParam < Path > ;
813840
814841// eslint-disable-next-line @typescript-eslint/no-unused-vars
815842type _tests = [
@@ -821,6 +848,32 @@ type _tests = [
821848 Expect < Equal < PathParam < "/:a/b/:c/*" > , "a" | "c" | "*" > > ,
822849 Expect < Equal < PathParam < "/:lang.xml" > , "lang" > > ,
823850 Expect < Equal < PathParam < "/:lang?.xml" > , "lang" > > ,
851+ Expect < Equal < RequiredPathParam < "/a/b/*" > , "*" > > ,
852+ Expect < Equal < RequiredPathParam < ":a" > , "a" > > ,
853+ Expect < Equal < RequiredPathParam < "/a/:b" > , "b" > > ,
854+ Expect < Equal < RequiredPathParam < "/a/blahblahblah:b" > , never > > ,
855+ Expect < Equal < RequiredPathParam < "/:a/:b" > , "a" | "b" > > ,
856+ Expect < Equal < RequiredPathParam < "/:a/b/:c/*" > , "a" | "c" | "*" > > ,
857+ Expect < Equal < RequiredPathParam < "/:lang.xml" > , "lang" > > ,
858+ Expect < Equal < OptionalPathParam < "/a/b/*" > , "*" > > ,
859+ Expect < Equal < OptionalPathParam < ":a?" > , "a" > > ,
860+ Expect < Equal < OptionalPathParam < "/a/:b?" > , "b" > > ,
861+ Expect < Equal < OptionalPathParam < "/a/blahblahblah:b?" > , never > > ,
862+ Expect < Equal < OptionalPathParam < "/:a?/:b?" > , "a" | "b" > > ,
863+ Expect < Equal < OptionalPathParam < "/:a?/b/:c?/*" > , "a" | "c" | "*" > > ,
864+ Expect < Equal < OptionalPathParam < "/:lang?.xml" > , "lang" > > ,
865+ Expect < Equal < PathParam < "/:a?/:b" > , "a" | "b" > > ,
866+ Expect < Equal < RequiredPathParam < "/:a?/:b" > , "b" > > ,
867+ Expect < Equal < OptionalPathParam < "/:a?/:b" > , "a" > > ,
868+ Expect < Equal < PathParam < "/:a?/b/:c" > , "a" | "c" > > ,
869+ Expect < Equal < RequiredPathParam < "/:a?/b/:c" > , "c" > > ,
870+ Expect < Equal < OptionalPathParam < "/:a?/b/:c" > , "a" > > ,
871+ Expect < Equal < PathParam < "/:a/:b" > , "a" | "b" > > ,
872+ Expect < Equal < RequiredPathParam < "/:a/:b" > , "a" | "b" > > ,
873+ Expect < Equal < OptionalPathParam < "/:a/:b" > , never > > ,
874+ Expect < Equal < PathParam < "/:a?/:b?" > , "a" | "b" > > ,
875+ Expect < Equal < RequiredPathParam < "/:a?/:b?" > , never > > ,
876+ Expect < Equal < OptionalPathParam < "/:a?/:b?" > , "a" | "b" > > ,
824877] ;
825878
826879// Attempt to parse the given string segment. If it fails, then just return the
@@ -1366,7 +1419,9 @@ function matchRouteBranch<
13661419export function generatePath < Path extends string > (
13671420 originalPath : Path ,
13681421 params : {
1369- [ key in PathParam < Path > ] : string | null ;
1422+ [ key in RequiredPathParam < Path > ] : string ;
1423+ } & {
1424+ [ key in OptionalPathParam < Path > ] ?: string | null | undefined ;
13701425 } = { } as any ,
13711426) : string {
13721427 let path : string = originalPath ;
0 commit comments