Skip to content

Commit

Permalink
feat: router with as const type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
ismanapa committed Jul 27, 2023
1 parent c8867f3 commit 5c21adb
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/const-type-parameter/basic-example.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// Example with "as const"
export type User = { name: string; age: number };

Expand Down
65 changes: 65 additions & 0 deletions src/const-type-parameter/router/router-with-extract-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
type ExtractRouteParams<T> = T extends `:${infer P}/${infer Rest}`
? P | ExtractRouteParams<`${Rest}`>
: T extends `:${infer P}`
? P
: T extends `${infer _Start}/${infer Rest}`
? ExtractRouteParams<Rest>
: never;

type RouteParamsObject<T extends string> = {
[key in T]: string;
};

export type Route = {
name: string;
path: string;
};

function buildRouter<const T extends Route>(routes: T[]) {
const getRoute = <
RouteName extends T["name"],
RouteParams extends RouteParamsObject<
ExtractRouteParams<Extract<T, { name: RouteName }>["path"]>
>
>(
name: RouteName,
params: RouteParams
): string => {
const route = routes.find((n) => n.name && n.name === name)!.path;

Object.entries<string>(params).forEach(([key, value]) => {
route.replace(`:${key}`, value);
});

return route;
};
return { getRoute };
}

// Ejemplo de narrowing que explicando el porque funciona la firma anterior
type GeneratedRoutesType =
| { name: "login"; path: "/login"; params: [] }
| { name: "courses"; path: "/courses/:category/:year"; params: ["category"] };

type RouteNames = GeneratedRoutesType["name"];
// ^?

type RouteParams = Extract<GeneratedRoutesType, { name: "courses" }>["params"];
// ^?

type RoutePath = Extract<GeneratedRoutesType, { name: "courses" }>["path"];
// ^?

const { getRoute } = buildRouter([
{
name: "login",
path: "/login",
},
{
name: "courses",
path: "/courses/:category",
},
]);

const route = getRoute("courses", { category: "TypeScript" });
51 changes: 51 additions & 0 deletions src/const-type-parameter/router/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
export type Route = {
name: string;
path: string;
params: readonly string[];
};

function buildRouter<const T extends Route>(routes: T[]) {
const getRoute = <
RouteName extends T["name"],
RouteParams extends Extract<T, { name: RouteName }>["params"]
>(
name: RouteName,
params: { [key in RouteParams[number]]: string }
): string => {
const route = routes.find((n) => n.name && n.name === name)!.path;

Object.entries<string>(params).forEach(([key, value]) => {
route.replace(`:${key}`, value);
});

return route;
};
return { getRoute };
}

// Ejemplo de narrowing que explicando el porque funciona la firma anterior
type GeneratedRoutesType =
| { name: "login"; path: "/login"; params: [] }
| { name: "courses"; path: "/courses"; params: ["category"] };

type RouteNames = GeneratedRoutesType["name"];
// ^?

type RouteParams = Extract<GeneratedRoutesType, { name: "courses" }>["params"];
// ^?

const { getRoute } = buildRouter([
{
name: "login",
path: "/login",
params: [],
},
{
name: "courses",
path: "/courses/:category",
params: ["category"],
},
]);

const route = getRoute("courses", { category: "typescript" });

0 comments on commit 5c21adb

Please sign in to comment.