pkgs/rest/src/types.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
import type { DiscordApiEndpoints } from "@purrkit/types";
type Split<
S extends string,
Delimiter extends string,
> = S extends `${infer Head}${Delimiter}${infer Tail}`
? [Head, ...Split<Tail, Delimiter>]
: S extends ""
? []
: [S];
type CleanPath<S extends string> = S extends `/${infer Rest}` ? Rest : S;
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
type GetSuccess<Op> = Op extends { response: infer R } ? R : undefined;
type GetQuery<Op> = Op extends { parameters: { query: infer Q } } ? Q : never;
type GetBody<Op> = Op extends { requestBody: infer B } ? B : never;
// TODO)) @purrkit/types keep hold of the non-200 response types
export interface DiscordError {
message: string;
code?: number;
errors?: Record<string, unknown>;
}
export type ApiResponse<TSuccess, TError = DiscordError> =
| { ok: true; data: TSuccess; status: number }
| { ok: false; data: TError; status: number };
type MapVerb<M> = M extends "post"
? "create" | "post"
: M extends "patch"
? "update" | "patch"
: M extends "delete"
? "delete"
: M;
type MethodFunc<Op, Method> = Method extends "get"
? (options?: { query?: GetQuery<Op> }) => Promise<ApiResponse<GetSuccess<Op>>>
: [GetBody<Op>] extends [never]
? (body?: undefined, options?: { query?: GetQuery<Op> }) => Promise<ApiResponse<GetSuccess<Op>>>
: (
body: GetBody<Op>,
options?: { query?: GetQuery<Op> },
) => Promise<ApiResponse<GetSuccess<Op>>>;
type MapOperations<Operations> = {
[Method in keyof Operations as MapVerb<Method>]: MethodFunc<Operations[Method], Method>;
} & (Operations extends { get: unknown }
? PromiseLike<ApiResponse<GetSuccess<Operations["get"]>>>
: unknown);
// strips '@' from any segment ie @me -> me
type MapSegment<S extends string> = S extends `@${infer Rest}` ? Rest : S;
type ParamKey = "$param";
type BuildNested<Segments extends string[], Operations> = Segments extends [
infer Head extends string,
...infer Tail extends string[],
]
? Head extends `{${string}}`
? { [K in ParamKey]: BuildNested<Tail, Operations> }
: { [K in MapSegment<Head>]: BuildNested<Tail, Operations> }
: MapOperations<Operations>;
type TransformTree<T> = T extends (...args: never) => unknown
? T // w want to keep functions intact
: T extends object
? ParamKey extends keyof T
? ((id: string) => TransformTree<T[ParamKey]>) & OmitTransform<T, ParamKey>
: { [K in keyof T]: TransformTree<T[K]> }
: T;
type OmitTransform<T, Key extends PropertyKey> = {
[K in Exclude<keyof T, Key>]: TransformTree<T[K]>;
};
export type DiscordRestClient = TransformTree<
UnionToIntersection<
{
[Path in keyof DiscordApiEndpoints]: BuildNested<
Split<CleanPath<Path & string>, "/">,
DiscordApiEndpoints[Path]
>;
}[keyof DiscordApiEndpoints]
>
>;
|