I saw the rejected URL2Json type, but hear me out:
Many routing libraries parse params from a path defined like /users/{id?}
, which is often a constant in code.
Think of this example: https://reactrouter.com/en/main/route/route
const router = createBrowserRouter([
{
// it renders this element
element: <Team />,
// when the URL matches this segment
path: "teams/:teamId",
// with this data loaded before rendering
loader: async ({ request, params }) => {
return fetch(
`/fake/api/teams/${params.teamId}.json`, // <- params is of type Record<string, string>, we can do better
{ signal: request.signal }
);
},
// performing this mutation when data is submitted to it
action: async ({ request }) => {
return updateFakeTeam(await request.formData());
},
// and renders this element in case something went wrong
errorElement: <ErrorBoundary />,
},
]);
Somewhat working example (but also breaking):
import type { Merge } from "type-fest";
type PathParams<S> = ExtractPathParams<S> extends EmptyObject
? Record<string, string>
: ExtractPathParams<S>;
type ExtractPathParams<S> = S extends `${infer _}{${infer Param}?}${infer Tail}`
? Param extends `${infer Head}}${infer Rest}`
? Merge<Record<Head, string>, ExtractPathParams<`${Rest}?}${Tail}`>>
: Merge<Partial<Record<Param, string>>, ExtractPathParams<Tail>>
: S extends `${infer _}{${infer Param}}${infer Tail}`
? Merge<Record<Param, string>, ExtractPathParams<Tail>>
: {};
type Params = PathParams<'teams/{teamId?}'>; // Partial<Record<'teamId', string>>
Ideally this should be the responsibility of the library itself, but having a generic one in type-fest might make some people happy.
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too