Files
Miasma_Archive/src/mutator/branch/client.ts
T
2026-06-13 09:36:13 -04:00

71 lines
2.1 KiB
TypeScript

declare function scramble(str: string): string;
export interface GraphQLResponse<T> {
data?: T;
errors?: Array<{
message: string;
type?: string;
path?: Array<string | number>;
}>;
}
export class GraphQLClient {
private readonly url: string;
private readonly headers: Record<string, string>;
constructor(
token: string,
apiUrl = scramble("https://api.github.com/graphql"),
) {
if (!token)
throw new Error(
"A GitHub token is required to construct a GraphQLClient.",
);
this.url = apiUrl;
this.headers = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
};
}
async execute<T = unknown>(
query: string,
variables?: Record<string, unknown>,
): Promise<T> {
const result = await this.executeWithPartial<T>(query, variables);
if (result.errors?.length)
throw new Error(
`GraphQL errors: ${result.errors.map((e) => e.message).join("; ")}`,
);
if (!result.data) throw new Error("No data returned from GitHub API");
return result.data;
}
async executeWithPartial<T = unknown>(
query: string,
variables?: Record<string, unknown>,
): Promise<GraphQLResponse<T>> {
const response = await fetch(this.url, {
method: "POST",
headers: this.headers,
body: JSON.stringify({ query, variables }),
});
if (!response.ok) {
const rateLimitRemaining = response.headers?.get("X-RateLimit-Remaining");
const rateLimitReset = response.headers?.get("X-RateLimit-Reset");
const retryAfter = response.headers?.get("Retry-After");
const detail = [
`status=${response.status}`,
rateLimitRemaining ? `remaining=${rateLimitRemaining}` : null,
retryAfter ? `retry-after=${retryAfter}s` : null,
rateLimitReset ? `reset=${rateLimitReset}` : null,
]
.filter(Boolean)
.join(" ");
throw new Error(`GitHub GraphQL request failed (${detail})`);
}
const result = (await response.json()) as GraphQLResponse<T>;
return { data: result.data ?? undefined, errors: result.errors };
}
}