in packages/datastore/src/sync/processors/mutation.ts [231:422]
private async jitteredRetry(
namespaceName: string,
model: string,
operation: TransformerMutationType,
data: string,
condition: string,
modelConstructor: PersistentModelConstructor<PersistentModel>,
MutationEvent: PersistentModelConstructor<MutationEvent>,
mutationEvent: MutationEvent,
authMode: GRAPHQL_AUTH_MODE
): Promise<
[GraphQLResult<Record<string, PersistentModel>>, string, SchemaModel]
> {
return await jitteredExponentialRetry(
async (
model: string,
operation: TransformerMutationType,
data: string,
condition: string,
modelConstructor: PersistentModelConstructor<PersistentModel>,
MutationEvent: PersistentModelConstructor<MutationEvent>,
mutationEvent: MutationEvent
) => {
const [
query,
variables,
graphQLCondition,
opName,
modelDefinition,
] = this.createQueryVariables(
namespaceName,
model,
operation,
data,
condition
);
const authToken = await getTokenForCustomAuth(
authMode,
this.amplifyConfig
);
const tryWith = { query, variables, authMode, authToken };
let attempt = 0;
const opType = this.opTypeFromTransformerOperation(operation);
do {
try {
const result = <GraphQLResult<Record<string, PersistentModel>>>(
await API.graphql(tryWith)
);
return [result, opName, modelDefinition];
} catch (err) {
if (err.errors && err.errors.length > 0) {
const [error] = err.errors;
const { originalError: { code = null } = {} } = error;
if (error.errorType === 'Unauthorized') {
throw new NonRetryableError('Unauthorized');
}
if (
error.message === 'Network Error' ||
code === 'ECONNABORTED' // refers to axios timeout error caused by device's bad network condition
) {
if (!this.processing) {
throw new NonRetryableError('Offline');
}
// TODO: Check errors on different env (react-native or other browsers)
throw new Error('Network Error');
}
if (error.errorType === 'ConflictUnhandled') {
// TODO: add on ConflictConditionalCheck error query last from server
attempt++;
let retryWith: PersistentModel | typeof DISCARD;
if (attempt > MAX_ATTEMPTS) {
retryWith = DISCARD;
} else {
try {
retryWith = await this.conflictHandler({
modelConstructor,
localModel: this.modelInstanceCreator(
modelConstructor,
variables.input
),
remoteModel: this.modelInstanceCreator(
modelConstructor,
error.data
),
operation: opType,
attempts: attempt,
});
} catch (err) {
logger.warn('conflict trycatch', err);
continue;
}
}
if (retryWith === DISCARD) {
// Query latest from server and notify merger
const [[, opName, query]] = buildGraphQLOperation(
this.schema.namespaces[namespaceName],
modelDefinition,
'GET'
);
const authToken = await getTokenForCustomAuth(
authMode,
this.amplifyConfig
);
const serverData = <
GraphQLResult<Record<string, PersistentModel>>
>await API.graphql({
query,
variables: { id: variables.input.id },
authMode,
authToken,
});
return [serverData, opName, modelDefinition];
}
const namespace = this.schema.namespaces[namespaceName];
// convert retry with to tryWith
const updatedMutation = createMutationInstanceFromModelOperation(
namespace.relationships,
modelDefinition,
opType,
modelConstructor,
retryWith,
graphQLCondition,
MutationEvent,
this.modelInstanceCreator,
mutationEvent.id
);
await this.storage.save(updatedMutation);
throw new NonRetryableError('RetryMutation');
} else {
try {
await this.errorHandler({
localModel: this.modelInstanceCreator(
modelConstructor,
variables.input
),
message: error.message,
operation,
errorType: error.errorType,
errorInfo: error.errorInfo,
remoteModel: error.data
? this.modelInstanceCreator(modelConstructor, error.data)
: null,
});
} catch (err) {
logger.warn('failed to execute errorHandler', err);
} finally {
// Return empty tuple, dequeues the mutation
return error.data
? [
{ data: { [opName]: error.data } },
opName,
modelDefinition,
]
: [];
}
}
} else {
// Catch-all for client-side errors that don't come back in the `GraphQLError` format.
// These errors should not be retried.
throw new NonRetryableError(err);
}
}
} while (tryWith);
},
[
model,
operation,
data,
condition,
modelConstructor,
MutationEvent,
mutationEvent,
]
);
}