/** * Whether the network is worth trying, and when to try again after it wasn't. * * `navigator.onLine` is necessary but not sufficient: it reports the state of * the network interface, so it stays `true` behind a captive portal, on a * connection that resolves DNS but reaches nothing, and while the API itself * is down. This tracks what actually happened to requests as well, so the * first failure is the only one an app pays for — everything after it inside * the backoff window skips the doomed round trip and answers from the local * store immediately, which is the difference between an app that freezes when * the wifi drops and one that does not. */ /** The request never reached the server, so nothing was decided by it. */ export declare function isNetworkError(error: unknown): boolean; /** * Is the server still answering an earlier attempt of this same write? * * The only correct response is to ask again — which is exactly what the * server's own message says, and exactly what this SDK used not to do. */ export declare function isIdempotencyInProgressError(error: unknown): boolean; /** Is this failure worth another attempt later? */ export declare function isRetryableError(error: unknown): boolean; /** * Did this write fail because the row is already there? * * Matched on the SQLSTATE the server passes through (`23505`, unique_violation) * and on 409, never on the message — a duplicate-key message names the * constraint and the values, so it is neither stable nor safe to parse. * * The queue uses this to recognise its own earlier attempt. A create whose * response was lost is replayed, and for a row carrying an id the SDK generated * the server can only be rejecting it because the first attempt actually landed. * * Which is why the status alone cannot decide it: `IDEMPOTENCY_KEY_IN_PROGRESS` * is a 409 that means the opposite — the row may not exist at all. Read as a * duplicate, the queue looked for a row that was never written, found nothing, * concluded there was nothing left to do and deleted the write from the queue. */ export declare function isDuplicateKeyError(error: unknown): boolean; export interface ConnectivityOptions { /** First retry delay after a failure. Defaults to 1 000 ms. */ initialBackoffMs?: number; /** Ceiling for the doubling retry delay. Defaults to 60 000 ms. */ maxBackoffMs?: number; /** * Let a known-failed connection suppress further attempts until the * backoff window opens. On by default — it is what makes a read or write * during an outage instant instead of a timeout. Turn it off when nothing * will ever wake the client up again (no retry timer, no `online` event), * where suppressing attempts would mean never recovering. */ respectBackoff?: boolean; /** Injected for tests. */ now?: () => number; /** Injected for tests; must return a handle `clearTimeout` accepts. */ setTimer?: (fn: () => void, ms: number) => ReturnType; clearTimer?: (handle: ReturnType) => void; } export declare class ConnectivityMonitor { private state; private backoffMs; private readonly initialBackoffMs; private readonly maxBackoffMs; private retryAt; private timer?; private listeners; private readonly respectBackoff; private readonly now; private readonly setTimer; private readonly clearTimer; /** Called when the backoff window expires, to drive an automatic retry. */ onRetryDue?: () => void; private readonly handleOnline; private readonly handleOffline; constructor(options?: ConnectivityOptions); /** What the app should be told: are we connected? */ isOnline(): boolean; /** * Should this request even be sent? False means "answer from the local * store instead" — the request would only burn a timeout to reach the same * conclusion the last one already did. */ shouldAttempt(): boolean; /** A request reached the server. */ markSuccess(): void; /** A request did not reach the server: we are offline until proven otherwise. */ markFailure(): void; /** * Back off and try again later without claiming the connection is gone. * This is what a 429 or a 503 deserves — the server answered, so the app * is demonstrably online; it just should not hammer. */ deferRetry(): void; /** Milliseconds until the next attempt is allowed; 0 when one is allowed now. */ msUntilRetry(): number; onChange(listener: (online: boolean) => void): () => void; dispose(): void; private scheduleRetry; private clearPendingTimer; private setState; }