Skip to content

Commit ece2c4e

Browse files
committed
only retry mutations when safe to do so
1 parent eeeea26 commit ece2c4e

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

src/GraphQL/Client/Query.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Error codes where the request definitely never reached the server
2+
const noConnectionErrorCodes = new Set([
3+
"ECONNREFUSED",
4+
"ENETUNREACH",
5+
]);
6+
7+
// Error codes where the connection may have been established
8+
// before failing, so the server may have received the request
9+
const ambiguousConnectionErrorCodes = new Set([
10+
"ETIMEDOUT",
11+
"ECONNRESET",
12+
]);
13+
14+
const getCode = (error) => {
15+
const code = error?.cause?.code;
16+
return typeof code === "string" ? code : "";
17+
};
18+
19+
export const isNoConnectionError = (error) =>
20+
noConnectionErrorCodes.has(getCode(error));
21+
22+
export const isConnectionError = (error) => {
23+
const code = getCode(error);
24+
return noConnectionErrorCodes.has(code) || ambiguousConnectionErrorCodes.has(code);
25+
};

src/GraphQL/Client/Query.purs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ import GraphQL.Client.Types (class GqlQuery, class QueryClient, Client(..), GqlR
4343
import GraphQL.Client.Variables (class VarsTypeChecked, getVarsJson, getVarsTypeNames)
4444
import Type.Proxy (Proxy(..))
4545

46+
foreign import isConnectionError :: Error -> Boolean
47+
foreign import isNoConnectionError :: Error -> Boolean
48+
4649
-- | Run a graphQL query with a custom decoder and custom options
4750
queryOptsWithDecoder
4851
:: forall client directives schema query returns queryOpts mutationOpts sr
@@ -199,8 +202,12 @@ runQuery
199202
-> Aff returns
200203
runQuery decodeFn opts client _ queryNameUnsafe q =
201204
addErrorInfo (Proxy @schema) queryName q do
202-
json <- clientQuery opts client queryName (getVarsTypeNames (Proxy :: _ schema) q <> toGqlQueryString q)
203-
(getVarsJson (Proxy :: _ schema) q)
205+
let doQuery = clientQuery opts client queryName (getVarsTypeNames (Proxy :: _ schema) q <> toGqlQueryString q)
206+
(getVarsJson (Proxy :: _ schema) q)
207+
json <- doQuery `catchError` \err ->
208+
-- Retry once on connection-level errors (ETIMEDOUT, ECONNREFUSED, etc.)
209+
if isConnectionError err then doQuery
210+
else throwError err
204211
decodeJsonData decodeFn json
205212
where
206213
queryName = safeQueryName queryNameUnsafe
@@ -218,8 +225,12 @@ runMutation
218225
-> Aff returns
219226
runMutation decodeFn opts client _ queryNameUnsafe q =
220227
addErrorInfo (Proxy @schema) queryName q do
221-
json <- clientMutation opts client queryName (getVarsTypeNames (Proxy :: _ schema) q <> toGqlQueryString q)
222-
(getVarsJson (Proxy :: _ schema) q)
228+
let doMutation = clientMutation opts client queryName (getVarsTypeNames (Proxy :: _ schema) q <> toGqlQueryString q)
229+
(getVarsJson (Proxy :: _ schema) q)
230+
json <- doMutation `catchError` \err ->
231+
-- Only retry on ECONNREFUSED/ENETUNREACH where the request never reached the server
232+
if isNoConnectionError err then doMutation
233+
else throwError err
223234
decodeJsonData decodeFn json
224235
where
225236
queryName = safeQueryName queryNameUnsafe

0 commit comments

Comments
 (0)