all repos — kitten @ e8d6d7fac30bb733d714a36350d2a3edcfd2e41b

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]
	>
>;