{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Pinia Colada Retry plugin.\n *\n * Adds the ability to retry failed queries.\n *\n * @module @pinia/colada-plugin-retry\n */\nimport type { PiniaColadaPluginContext } from '@pinia/colada'\n\n/**\n * Options for the Pinia Colada Retry plugin.\n */\nexport interface RetryOptions {\n  /**\n   * The delay between retries. Can be a duration in ms or a function that\n   * receives the attempt number (starts at 0) and returns a duration in ms. By\n   * default, it will wait 2^attempt * 1000 ms, but never more than 30 seconds.\n   *\n   * @param attempt -\n   * @returns\n   */\n  delay?: number | ((attempt: number) => number)\n\n  /**\n   * The maximum number of times to retry the operation. Set to 0 to disable or\n   * to Infinity to retry forever. It can also be a function that receives the\n   * failure count and the error and returns if it should retry. Defaults to 3.\n   * **Must be a positive number**.\n   */\n  retry?: number | ((failureCount: number, error: unknown) => boolean)\n}\n\nexport interface RetryEntry {\n  retryCount: number\n  timeoutId?: ReturnType<typeof setTimeout>\n}\n\nconst RETRY_OPTIONS_DEFAULTS = {\n  delay: (attempt: number) => {\n    const time = Math.min(\n      2 ** attempt * 1000,\n      // never more than 30 seconds\n      30_000,\n    )\n    console.log(`⏲️ delaying attempt #${attempt + 1} by ${time}ms`)\n    return time\n  },\n  retry: (count) => {\n    console.log(`🔄 Retrying ${'🟨'.repeat(count + 1)}${'⬜️'.repeat(2 - count)}`)\n    return count < 2\n  },\n} satisfies Required<RetryOptions>\n\n/**\n * Plugin that adds the ability to retry failed queries.\n *\n * @param globalOptions - global options for the retries\n */\nexport function PiniaColadaRetry(\n  globalOptions?: RetryOptions,\n): (context: PiniaColadaPluginContext) => void {\n  const defaults = { ...RETRY_OPTIONS_DEFAULTS, ...globalOptions }\n\n  return ({ queryCache }) => {\n    const retryMap = new Map<string, RetryEntry>()\n\n    let isInternalCall = false\n    queryCache.$onAction(({ name, args, after, onError }) => {\n      // cleanup all pending retries when data is deleted (means the data is not needed anymore)\n      if (name === 'remove') {\n        const [cacheEntry] = args\n        const key = cacheEntry.key.join('/')\n        const entry = retryMap.get(key)\n        if (entry) {\n          clearTimeout(entry.timeoutId)\n          retryMap.delete(key)\n        }\n      }\n\n      if (name !== 'fetch') return\n      const [queryEntry] = args\n      const localOptions = queryEntry.options?.retry\n\n      const options = {\n        ...(typeof localOptions === 'object'\n          ? localOptions\n          : {\n              retry: localOptions,\n            }),\n      } satisfies RetryOptions\n\n      const retry = options.retry ?? defaults.retry\n      const delay = options.delay ?? defaults.delay\n      // avoid setting up anything at all\n      if (retry === 0) return\n\n      const key = queryEntry.key.join('/')\n\n      // clear any pending retry\n      clearTimeout(retryMap.get(key)?.timeoutId)\n      // if the user manually calls the action, reset the retry count\n      if (!isInternalCall) {\n        retryMap.delete(key)\n      }\n\n      const retryFetch = () => {\n        if (queryEntry.state.value.status === 'error') {\n          const error = queryEntry.state.value.error\n          // ensure the entry exists\n          let entry = retryMap.get(key)\n          if (!entry) {\n            entry = { retryCount: 0 }\n            retryMap.set(key, entry)\n          }\n\n          const shouldRetry\n            = typeof retry === 'number' ? retry > entry.retryCount : retry(entry.retryCount, error)\n\n          if (shouldRetry) {\n            const delayTime = typeof delay === 'function' ? delay(entry.retryCount) : delay\n            entry.timeoutId = setTimeout(() => {\n              if (!queryEntry.active) {\n                retryMap.delete(key)\n                return\n              }\n              // NOTE: we could add some default error handler\n              isInternalCall = true\n              Promise.resolve(queryCache.fetch(queryEntry)).catch(\n                process.env.NODE_ENV !== 'test' ? console.error : () => {},\n              )\n              isInternalCall = false\n              if (entry) {\n                entry.retryCount++\n              }\n            }, delayTime)\n          } else {\n            // remove the entry if we are not going to retry\n            retryMap.delete(key)\n          }\n        } else {\n          // remove the entry if it worked out to reset it\n          retryMap.delete(key)\n        }\n      }\n      onError(retryFetch)\n      after(retryFetch)\n    })\n  }\n}\n\ndeclare module '@pinia/colada' {\n  // eslint-disable-next-line unused-imports/no-unused-vars\n  export interface UseQueryOptions<TData, TError, TDataInitial> {\n    /**\n     * Options for the retries of this query added by `@pinia/colada-plugin-retry`.\n     */\n    retry?: RetryOptions | Exclude<RetryOptions['retry'], undefined>\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAqCA,IAAM,yBAAyB;AAAA,EAC7B,OAAO,CAAC,YAAoB;AAC1B,UAAM,OAAO,KAAK;AAAA,MAChB,KAAK,UAAU;AAAA;AAAA,MAEf;AAAA,IACF;AACA,YAAQ,IAAI,kCAAwB,UAAU,CAAC,OAAO,IAAI,IAAI;AAC9D,WAAO;AAAA,EACT;AAAA,EACA,OAAO,CAAC,UAAU;AAChB,YAAQ,IAAI,sBAAe,YAAK,OAAO,QAAQ,CAAC,CAAC,GAAG,eAAK,OAAO,IAAI,KAAK,CAAC,EAAE;AAC5E,WAAO,QAAQ;AAAA,EACjB;AACF;AAOO,SAAS,iBACd,eAC6C;AAC7C,QAAM,WAAW,EAAE,GAAG,wBAAwB,GAAG,cAAc;AAE/D,SAAO,CAAC,EAAE,WAAW,MAAM;AACzB,UAAM,WAAW,oBAAI,IAAwB;AAE7C,QAAI,iBAAiB;AACrB,eAAW,UAAU,CAAC,EAAE,MAAM,MAAM,OAAO,QAAQ,MAAM;AAEvD,UAAI,SAAS,UAAU;AACrB,cAAM,CAAC,UAAU,IAAI;AACrB,cAAMA,OAAM,WAAW,IAAI,KAAK,GAAG;AACnC,cAAM,QAAQ,SAAS,IAAIA,IAAG;AAC9B,YAAI,OAAO;AACT,uBAAa,MAAM,SAAS;AAC5B,mBAAS,OAAOA,IAAG;AAAA,QACrB;AAAA,MACF;AAEA,UAAI,SAAS,QAAS;AACtB,YAAM,CAAC,UAAU,IAAI;AACrB,YAAM,eAAe,WAAW,SAAS;AAEzC,YAAM,UAAU;AAAA,QACd,GAAI,OAAO,iBAAiB,WACxB,eACA;AAAA,UACE,OAAO;AAAA,QACT;AAAA,MACN;AAEA,YAAM,QAAQ,QAAQ,SAAS,SAAS;AACxC,YAAM,QAAQ,QAAQ,SAAS,SAAS;AAExC,UAAI,UAAU,EAAG;AAEjB,YAAM,MAAM,WAAW,IAAI,KAAK,GAAG;AAGnC,mBAAa,SAAS,IAAI,GAAG,GAAG,SAAS;AAEzC,UAAI,CAAC,gBAAgB;AACnB,iBAAS,OAAO,GAAG;AAAA,MACrB;AAEA,YAAM,aAAa,MAAM;AACvB,YAAI,WAAW,MAAM,MAAM,WAAW,SAAS;AAC7C,gBAAM,QAAQ,WAAW,MAAM,MAAM;AAErC,cAAI,QAAQ,SAAS,IAAI,GAAG;AAC5B,cAAI,CAAC,OAAO;AACV,oBAAQ,EAAE,YAAY,EAAE;AACxB,qBAAS,IAAI,KAAK,KAAK;AAAA,UACzB;AAEA,gBAAM,cACF,OAAO,UAAU,WAAW,QAAQ,MAAM,aAAa,MAAM,MAAM,YAAY,KAAK;AAExF,cAAI,aAAa;AACf,kBAAM,YAAY,OAAO,UAAU,aAAa,MAAM,MAAM,UAAU,IAAI;AAC1E,kBAAM,YAAY,WAAW,MAAM;AACjC,kBAAI,CAAC,WAAW,QAAQ;AACtB,yBAAS,OAAO,GAAG;AACnB;AAAA,cACF;AAEA,+BAAiB;AACjB,sBAAQ,QAAQ,WAAW,MAAM,UAAU,CAAC,EAAE;AAAA,gBAC5C,QAAQ,IAAI,aAAa,SAAS,QAAQ,QAAQ,MAAM;AAAA,gBAAC;AAAA,cAC3D;AACA,+BAAiB;AACjB,kBAAI,OAAO;AACT,sBAAM;AAAA,cACR;AAAA,YACF,GAAG,SAAS;AAAA,UACd,OAAO;AAEL,qBAAS,OAAO,GAAG;AAAA,UACrB;AAAA,QACF,OAAO;AAEL,mBAAS,OAAO,GAAG;AAAA,QACrB;AAAA,MACF;AACA,cAAQ,UAAU;AAClB,YAAM,UAAU;AAAA,IAClB,CAAC;AAAA,EACH;AACF;","names":["key"]}