sindresorhus / type-fest

A collection of essential TypeScript types
Creative Commons Zero v1.0 Universal
14.15k stars 535 forks source link

Proposal: URL Pattern API #516

Open PindaPixel opened 1 year ago

PindaPixel commented 1 year ago

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.

Upvote & Fund

Fund with Polar

sindresorhus commented 1 year ago

It could maybe make sense to support the patterns supported by the URL Pattern API (minus the regex pattern).