I have the following object ( this defines the Angular routing / pages. Some have params that can be replaced.):
const ROUTES = {
PAGE_NO_PARAMS: '/hello/page/two',
PAGE_R: '/about/:id',
PAGE_Z: '/page/page/:param/:id',
PAGE_N: '/who/:x/:y/:z/page',
} as const
Can I create a type of valid page types from this object that ignores the params (:xxx) in the path with some sort of wildcard?
Ie. create a type like below (but using the ROUTES
object). I have it already without the wildcard. However, paths with params resolved ie. /about/xxxxx
do not meet the type
check`
type ValidRoute = '/hello/page/two' | '/about/${string}' | '/page/page/${string}/${string}'| '/who/${string}/${string}/${string}/page'
In summary, I would like to map though each property in ROUTES
and create a type that replaces /:any string/
with /${string}
I hope this all makes sense. I'm trying to replace runtime errors with build errors.
CodePudding user response:
You can use some recursive conditional types to process the path. Wen we encounter :name
we can replace it with a $string}
type MakeValidRoute<T extends string, R extends string = ''> =
T extends `${infer Head}/:${infer Name}/${infer Tail}`?
MakeValidRoute<`/${Tail}`, `${R}${Head}/${string}`>:
T extends `${infer Head}/:${infer Name}`?
`${R}${Head}/${string}`:
`${R}${T}`
We use tail recursive conditional types to improve performance of these types in the compiler
CodePudding user response:
The other answer here is correct. For completeness, this is how I did it:
type SubstParamNames<T extends string, S extends string = string, A extends string = ""> =
T extends `${infer F}:${infer _}/${infer R}` ? SubstParamNames<R, S, `${A}${F}${S}/`>
: T extends `${infer F}:${infer _}` ? `${A}${F}${S}` : `${A}${T}`
The SubstParamNames<T, S>
type is a tail recursive conditional type that substitutes all colon-delimited path fragments (starts with a ":"
and ends with a "/"
or the end of the string) in T
with the value in S
, which defaults to just string
.
That gives the following output:
const ROUTES = {
PAGE_NO_PARAMS: '/hello/page/two',
PAGE_R: '/about/:id',
PAGE_Z: '/page/page/:param/:id',
PAGE_N: '/who/:x/:y/:z/page',
} as const
type Routes = SubstParamNames<typeof ROUTES[keyof typeof ROUTES]>
/* type RP = type Routes = "/hello/page/two" | `/about/${string}` |
`/page/page/${string}/${string}` | `/who/${string}/${string}/${string}/page`
*/
And if it matters, you could change string
to something else:
type IfItMatters = SubstParamNames<"/ay/:bee/cee/:dee/eee", "XXX">
// type IfItMatters = "/ay/XXX/cee/XXX/eee"