Check paths with types
A link’s path is a string, so a typo goes unnoticed on its own. This page covers turning a wrong pattern or param into a type error, and using the same check in your own code and in @k8ordo/state.
On this page
Params are checked with no setup
href, navigateTo and useParams derive the params’ type from the pattern string they are given. With no setup at all, a missing param or a misspelled one is a type error.
href('/products/:id', { id: '42' });
href('/products/:id');
No id: a type error
href('/products/:id', { productId: '42' });
productId is not in the pattern: a type errorCheck patterns against the route table
Unlike a param, a typo in the pattern itself passes with no setup. Register the route table on Register, and a pattern the table lacks is a type error too.
src/k8ordo-router.d.tsimport type { routes } from './routes';
declare module '@k8ordo/router' {
interface Register {
routes: typeof routes;
}
}href('/products/:id', { id: '42' });
href('/prodcuts/:id', { id: '42' });
Not a pattern in the table: a type errorBefore registering, any string starting with / passes. After it, href and navigateTo accept only the patterns a link can point at, and useParams only the table’s patterns. useMatch and matchPath accept the table’s patterns, and those followed by /*.
Register once, in the app. A library that registered would impose its route table on every app that uses it.
Note
Under @k8ordo/static and @k8ordo/server, this registration is generated into .k8ordo/register.gen.ts. Writing it by hand makes two of the same, so leave it out.
Use the pattern types in your own code
The types derived from the route table are yours to use too, for example to limit the destinations a navigation lists to pages the table has.
src/links.tsimport type { RegisteredNavigablePattern } from '@k8ordo/router';
export type SitePath = Exclude<
Extract<RegisteredNavigablePattern, `/:locale${string}`>,
`/:locale${string}:${string}`
>;This is this site’s SitePath: the linkable patterns that start with /:locale and have no param besides the locale. Naming a page the table lacks in the navigation data is a type error.
Four types are derived from the registered table.
RegisteredPattern: the pattern of every page in the table,/*patterns includedRegisteredNavigablePattern: the patterns a link can point at, which leaves out those with/*RegisteredParams<P>: the params a link to patternPtakesRegisteredPageParams<P>: the params the page at patternPreceives
Before registering, the first two are any string starting with /.
Note
Under the framework, a page with a paramsSchema has its params typed by the schema, for its links as much as for the page; see “Use it under the framework”.
Check @k8ordo/state’s links against the same table
@k8ordo/state’s Register takes the same line. With it, the paths handed to @k8ordo/state’s href are checked against the same route table this router matches against.
src/k8ordo.d.tsimport type { routes } from './routes';
declare module '@k8ordo/router' {
interface Register {
routes: typeof routes;
}
}
declare module '@k8ordo/state' {
interface Register {
routes: typeof routes;
}
}listState.href('/products', { q: 'lamp' }); // '/products?q=lamp'
listState.href('/prodcuts', { q: 'lamp' });
No pattern in the table matches: a type error@k8ordo/state’s href takes an actual path rather than a pattern, with values where the params go, as in /products/42. Whether it fits any pattern is checked segment by segment.
Type your own path-taking API
The check @k8ordo/state uses is a type called NavigablePath. Anything else that takes a path can use the same type.
import type { NavigablePath } from '@k8ordo/router';
type A = NavigablePath<typeof routes, '/products/42'>;
// '/products/42'
type B = NavigablePath<typeof routes, '/products/42/reviews'>;
// never
type C = NavigablePath<typeof routes, '/products/'>;
// neverNavigablePath gives back the path when it fits one of the linkable patterns, and never when none fits. Where a pattern has a param such as :id, any one non-empty segment fits.
A path with a trailing slash is refused. Matching treats /products/ as /products, but a path written into a link is kept to one spelling.
It does not build a type listing every path in the table to compare against, because a page such as /:locale would turn that type into /${string}, which takes every path there is.