38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
import {
|
|
ApiErrorCode,
|
|
apiErrorCode,
|
|
isApiErrorCode,
|
|
isBrokerageAccessLoss,
|
|
} from "./problem-details";
|
|
|
|
type ReadState = { error: unknown; isError: boolean; isFetching: boolean };
|
|
|
|
type RetryableRead = ReadState & { refetch: () => Promise<unknown> };
|
|
|
|
// Automatic retries run inside one fetchAppRead call and Query's own retry is
|
|
// off, so a query in the error state has exhausted them. The state stays
|
|
// "error" while a manual retry runs, which keeps the warning in place. Access
|
|
// loss and a lost session keep their own actions and never offer a plain retry.
|
|
function isReadFailureExhausted(state: ReadState): boolean {
|
|
if (!state.isError) return false;
|
|
const code = apiErrorCode(state.error);
|
|
|
|
return !isBrokerageAccessLoss(code)
|
|
&& !isApiErrorCode(code, ApiErrorCode.Unauthorized);
|
|
}
|
|
|
|
// Retry repeats only the GET of the failed queries, never a source sync.
|
|
function readRetry(reads: RetryableRead[]) {
|
|
const failed = reads.filter(read => isReadFailureExhausted(read));
|
|
|
|
return {
|
|
exhausted: failed.length > 0,
|
|
onRetry: () => {
|
|
void Promise.allSettled(failed.map(read => read.refetch()));
|
|
},
|
|
retrying: failed.some(read => read.isFetching),
|
|
};
|
|
}
|
|
|
|
export { isReadFailureExhausted, readRetry };
|