// This recursively matches path segments and parameters in segments which // are surrounded by curly braces (i.e. `{foo}`) in these segments. // The results are joined to a superset of objects with their params // via the `&`. // In example, the path /{foo}/{bar} results in the type // `{ foo: string } & { bar: string }` type ParseUrlParamsRaw = Url extends `${infer Start}/${infer Rest}` ? ParseUrlParamsRaw & ParseUrlParamsRaw : Url extends `{${infer Param}}` ? { [P in Param]: string } : {}; // ParseUrlParamsRaw allows passing objects with any keys (i.e. `{name: "foo"}`) even though // an empty object is expected (`{}`). This type constraint enforces this by checking if // the result of ParseUrlParamsRaw is `{}`. `keyof {}` evaluates to `never`, so // when `{}` is evaluated an empty Record is expected, otherwise the result of // ParseUrlParamsRaw is expected. type ParseUrlParams = keyof ParseUrlParamsRaw extends never ? Record : ParseUrlParamsRaw; function navigate

(path: P, params: ParseUrlParams

) { // ... } function main3() { navigate("/hello", {}); navigate("/hello/{name}", { name: "foo" }); navigate("/book/{author}/{id}", { author: "foo", id: "bar" }); }