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
58import { event, isStaging } from '.';
const GRAPHQL_TIMEOUT_MS = 60_000;
const RETRY_DELAYS_MS = [1_000, 2_000];
export const graphqlQuery = async ({ query, variables = {} }) => {
const authorization = event?.headers?.authorization;
const role = event?.headers?.['x-hasura-role'];
const headers = {
'Content-Type': 'application/json',
...authorization ? { authorization } : { 'x-hasura-admin-secret': process.env.DB_PASSWORD },
...role && ({ 'x-hasura-role': role }),
};
const url = `https://graphql${isStaging ? '-staging' : ''}.equalifyapp.com/v1/graphql`;
const body = JSON.stringify({ query, variables });
let lastError: Error;
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
if (attempt > 0) {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAYS_MS[attempt - 1]));
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), GRAPHQL_TIMEOUT_MS);
try {
const res = await fetch(url, { method: 'POST', headers, body, signal: controller.signal });
clearTimeout(timer);
const contentType = res.headers.get('content-type') || '';
if (!res.ok || !contentType.includes('application/json')) {
const text = await res.text();
console.log(JSON.stringify({ graphqlError: { status: res.status, body: text.slice(0, 500), attempt } }));
// Retry on 5xx (transient server/gateway errors); surface 4xx immediately
if (res.status >= 500 && attempt < RETRY_DELAYS_MS.length) {
lastError = new Error(`GraphQL endpoint returned HTTP ${res.status}`);
continue;
}
throw new Error(`GraphQL endpoint returned HTTP ${res.status}`);
}
const response = await res.json();
if (!response?.data) {
console.log(JSON.stringify({ graphqlError: response }));
}
return response?.data;
} catch (err) {
clearTimeout(timer);
// Only retry actual network failures. Don't retry our own timeout (AbortError):
// it's the same query against the same DB — retrying just multiplies load and creates
// a stampede that makes everything worse.
const isRetryable = err.message?.includes('fetch failed');
console.log(JSON.stringify({ graphqlError: { message: err.message, attempt } }));
if (isRetryable && attempt < RETRY_DELAYS_MS.length) {
lastError = err;
continue;
}
throw err;
}
}
throw lastError!;
}