all repos — kitten @ 93ed15a9146fe5650eb8c207730a35b299ad116d

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