v5.0.0
Major Changes:
The most significant change in this version is that path parameters can no longer be optional. This update enforces stricter type checks for parameters and allows for intuitive and type-safe query parameter definitions.
Previous Issues:
We had two main issues with the previous version of this package:
-
Ensuring Required Path Parameters Are Set
Consider the following code example:
const routeConfig = { userPosts: { path: '/users/:userid/posts/:postid', } } as const satisfies RouteConfig; // ...create link function type UserPostsRouteData = ExtractRouteData<typeof flatRouteConfig>['userPosts']; /** * UserPostsRouteData = { * path: 'userPosts' // Incorrect type inference has also been fixed. * params: Record<'userid', DefaultParamValue> | Record<'postid', DefaultParamValue> * search: never; * } */ const userPostsLink = link('userPosts', { userid: '123' }); // No type errors occurred even if not all required parameters were set when generating a path. // => '/users/123/posts'
In situations with multiple path parameters, the type of
paramswas a union type of the parameters, which did not trigger type errors if not all required parameters were set. This was not the desired behavior.In version 5, this has been improved by making
paramsand search parameters an intersection type of each path parameter. Now, a type error occurs if all required path parameters are not set.// Version 5: type UserPostsRouteData = ExtractRouteData<typeof flatRouteConfig>['userPosts']; /** * UserPostsRouteData = { * path: 'userPosts' // Incorrect type inference has been fixed. * params: Record<'userid', DefaultParamValue> & Record<'postid', DefaultParamValue> * search: never; * } */ const userPostsLink = link('userPosts', { userid: '123' }); // Type error, postid parameter must be set. const userPostsLink = link('userPosts', { userid: '123', postid: 1 }); // Type-safe π
-
Query Parameters
Previously, it was possible to make path parameters optional, but this led to ambiguity. Consider the following route configuration:
const routeConfig = { users: { path: '/users/:userid?' } } as const satisfies RouteConfig; // ...create link function const usersLink = link('users'); const userLink = link('users', { userid: '123' });
This can be confusing because a single route ID generates different paths. Instead, it should be defined like this:
const routeConfig = { users: { path: '/user', children: { user: { path: '/:userid' } } } } as const satisfies RouteConfig; // ...create link function const usersLink = link('users'); const userLink = link('users/user', { userid: '123' });
While this structure might seem nested and less elegant, it enforces the rule that each route ID generates a single path. This ensures type safety for path parameters and flexibility for any extensions beyond
/user.Consequently, optional path parameters have been deprecated. Moreover, this change eliminates the need to prefix query parameters with
/, making route definitions more intuitive without requiring specific rules.Now, if a property is not marked as optional, all its values must be set, otherwise, a type error will occur.
const routeConfig = { categories: { path: '/categories?size&color' } } as const satisfies RouteConfig; // ...create link function const categoriesLink = link('categories', undefined, { size: 'small' }); // Type error, please set the color parameter. const categoriesLink = link('categories', undefined, { size: 'small', color: 'red' }); // Type-safe π
If you want a parameter to be optional, just add a
?after the parameter, as in previous versions.const routeConfig = { categories: { path: '/categories?size&color?' } } as const satisfies RouteConfig; // ...create link function const categoriesLink = link('categories', undefined, { size: 'small' }); // Type-safe π
Modification Details:
Previously, the path property of the ExtractRouteData type inferred the route ID. This has been corrected to reflect the actual path value of the route.
const routeConfig = {
categories: {
path: '/categories?size&color'
}
} as const satisfies RouteConfig;
const flatConfig = flattenRouteConfig(routeConfig);
type CategoriesRouteData = ExtractRouteData<typeof flatConfig>['categories'];
/**
* CategoriesRouteData = {
* path: 'categories', // The path property should be the actual path value of that route!
* params: never;
* search: Record<'size', DefaultParamValue> & Record<'color', DefaultParamValue>
* }
*/
// Version 5:
type CategoriesRouteData = ExtractRouteData<typeof flatConfig>['categories'];
/**
* CategoriesRouteData = {
* path: '/categories?size&color', // Correct!
* params: never;
* search: Record<'size', DefaultParamValue> & Record<'color', DefaultParamValue>
* }
*/Other Breaking Changes:
The ParamValue type is no longer exported. Instead, path parameters are typed using DefaultParamValue, and search parameters use Partial<DefaultParamValue>.