{"version":3,"file":"retryer.cjs","names":["onlineManager","noop","focusManager","isServerEnvironment"],"sources":["../../src/retryer.ts"],"sourcesContent":["import { focusManager } from './focusManager'\nimport { onlineManager } from './onlineManager'\nimport { isServer as isServerEnvironment } from './environmentManager'\nimport { noop, sleep } from './utils'\nimport type { CancelOptions, DefaultError, NetworkMode } from './types'\n\n// TYPES\n\ninterface RetryerConfig<TData = unknown, TError = DefaultError> {\n  fn: () => TData | Promise<TData>\n  initialPromise?: Promise<TData>\n  onCancel?: (error: TError) => void\n  onFail?: (failureCount: number, error: TError) => void\n  onPause?: () => void\n  onContinue?: () => void\n  retry?: RetryValue<TError>\n  retryDelay?: RetryDelayValue<TError>\n  networkMode: NetworkMode | undefined\n  canRun: () => boolean\n}\n\n/**\n * Runs a function with retries, and can pause, continue, or cancel it. Queries and mutations use\n * one for each fetch or execution.\n */\nexport interface Retryer<TData = unknown> {\n  /**\n   * The promise that resolves with the data, or rejects once retries are exhausted or it is\n   * cancelled.\n   */\n  promise: Promise<TData>\n  /**\n   * Cancels the run, rejecting `promise` with a `CancelledError`.\n   */\n  cancel: (cancelOptions?: CancelOptions) => void\n  /**\n   * Resumes a paused run, and returns `promise`.\n   */\n  continue: () => Promise<unknown>\n  /**\n   * Stops further retries. The current attempt still finishes.\n   */\n  cancelRetry: () => void\n  /**\n   * Allows retries again after `cancelRetry`.\n   */\n  continueRetry: () => void\n  /**\n   * Returns whether the run can start now, based on the network mode and `canRun`.\n   */\n  canStart: () => boolean\n  /**\n   * Starts the run, or waits until it can start, and returns `promise`.\n   */\n  start: () => Promise<TData>\n  /**\n   * Returns whether `promise` is still pending, resolved, or rejected.\n   */\n  status: () => 'pending' | 'resolved' | 'rejected'\n}\n\ntype RetryerStatus = 'pending' | 'resolved' | 'rejected'\n\n/** @inline */\nexport type RetryValue<TError> = boolean | number | ShouldRetryFunction<TError>\n\n/** @inline */\ntype ShouldRetryFunction<TError = DefaultError> = (\n  failureCount: number,\n  error: TError,\n) => boolean\n\n/** @inline */\nexport type RetryDelayValue<TError> = number | RetryDelayFunction<TError>\n\n/** @inline */\ntype RetryDelayFunction<TError = DefaultError> = (\n  failureCount: number,\n  error: TError,\n) => number\n\n/**\n * The default `retryDelay`: doubles with every failure, starting at 1 second, up to 30 seconds.\n * @param failureCount - The number of failures so far.\n * @returns The delay before the next retry, in milliseconds.\n */\nfunction defaultRetryDelay(failureCount: number) {\n  return Math.min(1000 * 2 ** failureCount, 30000)\n}\n\n/**\n * Checks whether a fetch can run for the given network mode.\n * @param networkMode - The `networkMode` option. Defaults to `'online'`.\n * @returns `true` if the network mode is not `'online'`, or if it is and the client is online.\n */\nexport function canFetch(networkMode: NetworkMode | undefined): boolean {\n  return (networkMode ?? 'online') === 'online'\n    ? onlineManager.isOnline()\n    : true\n}\n\n/**\n * The error thrown by a `Retryer` (and surfaced to `query.promise`/`mutation`) when a fetch is cancelled, e.g. via\n * `query.cancel()`. `revert`, if `true`, tells the caller to restore the state the query was in before the fetch\n * started instead of surfacing the error. `silent`, if `true`, tells the caller to suppress this error and instead\n * resolve with the promise of the fetch that triggered the cancellation.\n * @example\n * ```ts\n * query.cancel()\n *\n * try {\n *   await query.promise\n * } catch (error) {\n *   if (error instanceof CancelledError) {\n *     // the fetch was cancelled, e.g. via `query.cancel()`\n *   }\n * }\n * ```\n */\nexport class CancelledError extends Error {\n  revert?: boolean\n  silent?: boolean\n  constructor(options?: CancelOptions) {\n    super('CancelledError')\n    this.revert = options?.revert\n    this.silent = options?.silent\n  }\n}\n\n/**\n * Checks whether a value is a `CancelledError`.\n * @param value - The value to check.\n * @returns `true` if `value` is a `CancelledError`.\n * @deprecated Use instanceof `CancelledError` instead.\n */\nexport function isCancelledError(value: any): value is CancelledError {\n  return value instanceof CancelledError\n}\n\n/**\n * Creates a retryer, which runs a function and retries it on failure according to the `retry` and\n * `retryDelay` options. It pauses while the network mode doesn't allow fetching or `canRun` returns\n * `false`, and can be cancelled. Queries and mutations use it to run their `queryFn` and\n * `mutationFn`.\n * @param config - The function to run, the retry options, and the callbacks for failures, pausing,\n * continuing, and cancellation.\n * @returns The retryer, whose `promise` settles with the result of the function.\n */\nexport function createRetryer<TData = unknown, TError = DefaultError>(\n  config: RetryerConfig<TData, TError>,\n): Retryer<TData> {\n  let isRetryCancelled = false\n  let failureCount = 0\n  let continueFn: ((value?: unknown) => void) | undefined\n  let status: RetryerStatus = 'pending'\n  let promiseResolve: (data: TData) => void\n  let promiseReject: (error: TError) => void\n\n  const promise = new Promise<TData>((resolve, reject) => {\n    promiseResolve = resolve\n    promiseReject = reject\n  })\n  promise.catch(noop)\n\n  const isResolved = () => status !== 'pending'\n\n  const cancel = (cancelOptions?: CancelOptions): void => {\n    if (!isResolved()) {\n      const error = new CancelledError(cancelOptions) as TError\n      reject(error)\n\n      config.onCancel?.(error)\n    }\n  }\n  const cancelRetry = () => {\n    isRetryCancelled = true\n  }\n\n  const continueRetry = () => {\n    isRetryCancelled = false\n  }\n\n  const canContinue = () =>\n    focusManager.isFocused() &&\n    (config.networkMode === 'always' || onlineManager.isOnline()) &&\n    config.canRun()\n\n  const canStart = () => canFetch(config.networkMode) && config.canRun()\n\n  const resolve = (value: any) => {\n    if (!isResolved()) {\n      continueFn?.()\n      status = 'resolved'\n      promiseResolve(value)\n    }\n  }\n\n  const reject = (value: any) => {\n    if (!isResolved()) {\n      continueFn?.()\n      status = 'rejected'\n      promiseReject(value)\n    }\n  }\n\n  const pause = (canResume: () => boolean) => {\n    return new Promise((continueResolve) => {\n      continueFn = (value) => {\n        if (isResolved() || canResume()) {\n          continueResolve(value)\n        }\n      }\n      config.onPause?.()\n    }).then(() => {\n      continueFn = undefined\n      if (!isResolved()) {\n        config.onContinue?.()\n      }\n    })\n  }\n\n  // Create loop function\n  const run = () => {\n    // Do nothing if already resolved\n    if (isResolved()) {\n      return\n    }\n\n    let promiseOrValue: any\n\n    // we can re-use config.initialPromise on the first call of run()\n    const initialPromise =\n      failureCount === 0 ? config.initialPromise : undefined\n\n    // Execute query\n    try {\n      promiseOrValue = initialPromise ?? config.fn()\n    } catch (error) {\n      promiseOrValue = Promise.reject(error)\n    }\n\n    Promise.resolve(promiseOrValue)\n      .then(resolve)\n      .catch((error) => {\n        // Stop if the fetch is already resolved\n        if (isResolved()) {\n          return\n        }\n\n        // Do we need to retry the request?\n        const retry = config.retry ?? (isServerEnvironment() ? 0 : 3)\n        const retryDelay = config.retryDelay ?? defaultRetryDelay\n        const delay =\n          typeof retryDelay === 'function'\n            ? retryDelay(failureCount, error)\n            : retryDelay\n        const shouldRetry =\n          retry === true ||\n          (typeof retry === 'number' && failureCount < retry) ||\n          (typeof retry === 'function' && retry(failureCount, error))\n\n        if (isRetryCancelled || !shouldRetry) {\n          // We are done if the query does not need to be retried\n          reject(error)\n          return\n        }\n\n        failureCount++\n\n        // Notify on fail\n        config.onFail?.(failureCount, error)\n\n        // Delay\n        sleep(delay)\n          // Pause if the document is not visible or when the device is offline\n          .then(() => {\n            return canContinue() ? undefined : pause(canContinue)\n          })\n          .then(() => {\n            if (isRetryCancelled) {\n              reject(error)\n            } else {\n              run()\n            }\n          })\n      })\n  }\n\n  return {\n    promise,\n    status: () => status,\n    cancel,\n    continue: () => {\n      continueFn?.()\n      return promise\n    },\n    cancelRetry,\n    continueRetry,\n    canStart,\n    start: () => {\n      // Start loop\n      if (canStart()) {\n        run()\n      } else {\n        // Waiting to start (e.g. queued behind a scope) does not depend on focus,\n        // only on the same conditions that allow starting in the first place\n        pause(canStart).then(run)\n      }\n      return promise\n    },\n  }\n}\n"],"mappings":"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"}