My goal is to integrate "metadata" into a type for the purpose of developing a type-safe REST client. The concept involves using the type metadata within the link to automatically identify the correct endpoint schema for API calls. For example:
type Schema = {
users: {
GET: {
query: { userId: string };
};
};
posts: {
POST: {};
};
};
type User = {
self: Link<"users">;
};
const user: User = { self: "https://..." };
http(user.self, "GET", { userId: 1 });
I managed to achieve this using conditional types in a somewhat brute-force manner.
For instance:
type Routes = "users" | "posts";
type Verbs<R> = R extends "users" ? "GET" : never;
type Query<R, V> = R extends "users"
? V extends "GET"
? { queryId: string }
: never
: never;
However, this approach resulted in a normalized type model that would be cumbersome to input manually. Instead, I aim to utilize a de-normalized type structure, like so:
type Schema = {
users: {
GET: {
query: { userId: string };
};
};
posts: {
POST: {};
};
};
This can be implemented with types such as:
type Query<
S,
RN extends keyof S,
VN extends keyof S[RN]
> = OpQuery<S[RN][VN]>;
I have almost successfully executed all elements of this plan, except for one crucial aspect - deducing the route name from the link type:
type Schema = {
users: {
GET: {
query: { userId: string };
};
};
posts: {
POST: {};
};
};
type Link<R extends keyof Schema> = string;
type LinkRouteName<L> = L extends Link<infer R> ? R : never;
type name = LinkRouteName<Link<"users">>;
Expected outcome: name === "users"
Actual result: name === "users" | "posts"