Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1import { createDPoPProof } from './auth/dpop';
2import { getValidAccessToken } from './auth/tokens';
3import { Storage } from './storage/storage';
4
5export interface GraphQLResponse<T = unknown> {
6 data?: T;
7 errors?: Array<{ message: string; path?: string[] }>;
8}
9
10/**
11 * Execute a GraphQL query or mutation
12 */
13export async function graphqlRequest<T = unknown>(
14 storage: Storage,
15 namespace: string,
16 graphqlUrl: string,
17 tokenUrl: string,
18 query: string,
19 variables: Record<string, unknown> = {},
20 requireAuth = false,
21 signal?: AbortSignal
22): Promise<T> {
23 const headers: Record<string, string> = {
24 'Content-Type': 'application/json',
25 };
26
27 if (requireAuth) {
28 const token = await getValidAccessToken(storage, namespace, tokenUrl);
29 if (!token) {
30 throw new Error('Not authenticated');
31 }
32
33 // Create DPoP proof bound to this request
34 const dpopProof = await createDPoPProof(namespace, 'POST', graphqlUrl, token);
35
36 headers['Authorization'] = `DPoP ${token}`;
37 headers['DPoP'] = dpopProof;
38 }
39
40 const response = await fetch(graphqlUrl, {
41 method: 'POST',
42 headers,
43 body: JSON.stringify({ query, variables }),
44 signal,
45 });
46
47 if (!response.ok) {
48 throw new Error(`GraphQL request failed: ${response.statusText}`);
49 }
50
51 const result: GraphQLResponse<T> = await response.json();
52
53 if (result.errors && result.errors.length > 0) {
54 throw new Error(`GraphQL error: ${result.errors[0].message}`);
55 }
56
57 return result.data as T;
58}