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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
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 GetQuery<Op> = Op extends { parameters: { query: infer Q } } ? Q : never;
type GetBody<Op> = Op extends { requestBody: infer B } ? B : never;
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 IsSuccessStatus<K> = K extends number | string
? `${K}` extends `2${string}`
? true
: false
: false;
type Discord2XX = 200 | 201 | 204;
type Discord3XX = 304;
type Discord4XX = 400 | 401 | 403 | 404 | 405 | 410 | 429;
type Discord5XX = 500 | 502 | 503 | 504;
type GetExplicitKeys<Responses> = {
[K in keyof Responses]: K extends number ? K : K extends `${infer N extends number}` ? N : never;
}[keyof Responses];
type MapStatusType<K, ExplicitKeys> = K extends number
? K
: K extends `${infer N extends number}`
? N
: K extends "2XX"
? Exclude<Discord2XX, ExplicitKeys>
: K extends "3XX"
? Exclude<Discord3XX, ExplicitKeys>
: K extends "4XX"
? Exclude<Discord4XX, ExplicitKeys>
: K extends "5XX"
? Exclude<Discord5XX, ExplicitKeys>
: K extends "default"
? Exclude<Discord4XX | Discord5XX, ExplicitKeys>
: number & {};
export type ResolveResponse<Op> = Op extends { responses: infer Responses }
? keyof Responses extends never
? ApiResponse<unknown>
: {
[K in keyof Responses]: {
ok: IsSuccessStatus<K>;
status: MapStatusType<K, GetExplicitKeys<Responses>>;
data: Responses[K];
};
}[keyof Responses]
: ApiResponse<unknown>;
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>;
headers?: Record<string, string>;
}) => Promise<ResolveResponse<Op>>
: [GetBody<Op>] extends [never]
? (
body?: undefined,
options?: { query?: GetQuery<Op>; headers?: Record<string, string> },
) => Promise<ResolveResponse<Op>>
: (
body: GetBody<Op> | FormData,
options?: { query?: GetQuery<Op>; headers?: Record<string, string> },
) => Promise<ResolveResponse<Op>>;
type MapOperations<Operations> = {
[Method in keyof Operations as MapVerb<Method>]: MethodFunc<Operations[Method], Method>;
} & (Operations extends { get: unknown }
? PromiseLike<ResolveResponse<Operations["get"]>>
: unknown);
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
: 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]
>
>;
|