diff --git a/apps/studio/data/fetchers.ts b/apps/studio/data/fetchers.ts index 68338e47670..a4c4ad6f4cd 100644 --- a/apps/studio/data/fetchers.ts +++ b/apps/studio/data/fetchers.ts @@ -63,10 +63,12 @@ function pgMetaGuard(request: Request) { // If there is no valid `x-connection-encrypted`, pg-meta will necesseraly fail to connect to the target database // in such case, we save the hops and throw a 421 response instead if (!isValidConnString(request.headers.get('x-connection-encrypted'))) { + const retryAfterHeader = request.headers.get('Retry-After') throw new ResponseError( 'API Error: happened while trying to acquire connection to the database', 400, - request.headers.get('X-Request-Id') ?? undefined + request.headers.get('X-Request-Id') ?? undefined, + retryAfterHeader ? parseInt(retryAfterHeader) : undefined ) } } @@ -98,6 +100,8 @@ client.use( // add code field to body body.code = response.status body.requestId = request.headers.get('X-Request-Id') + const retryAfterHeader = response.headers.get('Retry-After') + body.retryAfter = retryAfterHeader ? parseInt(retryAfterHeader) : undefined return new Response(JSON.stringify(body), { headers: response.headers, @@ -136,9 +140,11 @@ export const handleError = (error: unknown): never => { const errorCode = 'code' in error && typeof error.code === 'number' ? error.code : undefined const requestId = 'requestId' in error && typeof error.requestId === 'string' ? error.requestId : undefined + const retryAfter = + 'retryAfter' in error && typeof error.retryAfter === 'number' ? error.retryAfter : undefined if (errorMessage) { - throw new ResponseError(errorMessage, errorCode, requestId) + throw new ResponseError(errorMessage, errorCode, requestId, retryAfter) } } diff --git a/apps/studio/data/query-client.ts b/apps/studio/data/query-client.ts index b206f55d0de..e2535c3bf5e 100644 --- a/apps/studio/data/query-client.ts +++ b/apps/studio/data/query-client.ts @@ -24,7 +24,9 @@ export function getQueryClient() { error instanceof ResponseError && error.code !== undefined && error.code >= 400 && - error.code < 500 + error.code < 500 && + // Still retry on 429s (rate limit) + error.code !== 429 ) { return false } @@ -35,6 +37,14 @@ export function getQueryClient() { return false }, + retryDelay(failureCount, error) { + if (error instanceof ResponseError && error.retryAfter) { + return error.retryAfter * 1000 + } + + // react-query default: doubles, starting at 1000ms, with each attempt, but will not exceed 30 seconds + return Math.min(1000 * 2 ** failureCount, 30000) + }, }, }, }) diff --git a/apps/studio/types/base.ts b/apps/studio/types/base.ts index 02fcc1fa064..4ab48786fcc 100644 --- a/apps/studio/types/base.ts +++ b/apps/studio/types/base.ts @@ -99,11 +99,13 @@ export type SupaResponse = T | ResponseFailure export class ResponseError extends Error { code?: number requestId?: string + retryAfter?: number - constructor(message: string | undefined, code?: number, requestId?: string) { + constructor(message: string | undefined, code?: number, requestId?: string, retryAfter?: number) { super(message || 'API error happened while trying to communicate with the server.') this.code = code this.requestId = requestId + this.retryAfter = retryAfter } }