declare function scramble(str: string): string; export interface GraphQLResponse { data?: T; errors?: Array<{ message: string; type?: string; path?: Array; }>; } export class GraphQLClient { private readonly url: string; private readonly headers: Record; 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( query: string, variables?: Record, ): Promise { const result = await this.executeWithPartial(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( query: string, variables?: Record, ): Promise> { 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; return { data: result.data ?? undefined, errors: result.errors }; } }