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 |
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> }) => Promise<ResolveResponse<Op>>
: [GetBody<Op>] extends [never]
? (body?: undefined, options?: { query?: GetQuery<Op> }) => Promise<ResolveResponse<Op>>
: (body: GetBody<Op>, options?: { query?: GetQuery<Op> }) => 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]
>
>;
|