{ "version": 3, "sources": ["libs/rx-utils/src/lib/vax/index.ts"], "sourcesContent": ["import { HttpErrorResponse } from '@angular/common/http';\nimport {\n Observable,\n of as observableOf,\n race,\n range,\n ReplaySubject,\n throwError as observableThrowError,\n timer,\n} from 'rxjs';\nimport { delayWhen, first, merge, retryWhen, skip, switchMap, tap, zip } from 'rxjs/operators';\n\nexport interface RetryConfig {\n // Maximimum amount of time to retry before giving up.\n timeoutMilliseconds?: number;\n // Maximimum number of times to retry before giving up.\n maxAttempts?: number;\n // Initial delay between retries. The delay doubles on each subsequent retry.\n retryDelay?: number;\n // A custom predicate to determine whether to retry on an http error.\n customRetryChecker?: (errResp: HttpErrorResponse) => boolean;\n}\n\nconst DEFAULT_TIMEOUT_MILLIS = 5000;\nconst DEFAULT_MAX_ATTEMPTS = 10;\nconst DEFAULT_RETRY_DELAY_MILLIS = 200;\nconst DEFAULT_RETRY_CHECKER: ((errResp: HttpErrorResponse) => boolean | null) | null = null;\n\n/**\n * This class wraps an http call in retry logic according to a configuration.\n *\n * Only to be used for idempotent calls.\n */\nexport class Retryer {\n public result$: Observable;\n private retriedErrors$$ = new ReplaySubject(1);\n\n private timeoutMilliseconds: number;\n private maxAttempts: number | null;\n private retryDelay: number;\n private customRetryChecker: ((errResp: HttpErrorResponse) => boolean | null) | null;\n\n /**\n *\n * @param call The http call or observable to be retried through subscribing\n * @param config Configure the retry logic\n */\n constructor(call: Observable, config?: RetryConfig) {\n const {\n timeoutMilliseconds = DEFAULT_TIMEOUT_MILLIS,\n maxAttempts = DEFAULT_MAX_ATTEMPTS,\n retryDelay = DEFAULT_RETRY_DELAY_MILLIS,\n customRetryChecker = DEFAULT_RETRY_CHECKER,\n } = config || {};\n this.timeoutMilliseconds = timeoutMilliseconds;\n this.maxAttempts = maxAttempts;\n this.retryDelay = retryDelay;\n this.customRetryChecker = customRetryChecker;\n\n this.result$ = call.pipe(\n retryWhen((errors$: Observable) => this.getRetries(errors$)),\n first(),\n );\n }\n\n private getRetries(errors$: Observable): Observable {\n return errors$.pipe(\n tap((err) => this.retriedErrors$$.next(err)),\n switchMap((errResp) => (this.shouldRetry(errResp) ? observableOf(errResp) : observableThrowError(errResp))),\n zip(range(0, this.maxAttempts - 1), (err, tryNumber) => tryNumber),\n delayWhen((tryNumber) => this.getDelayTimer(tryNumber)),\n merge(this.getCutoffNotifier(errors$)),\n );\n }\n\n private getCutoffNotifier(errors$: Observable): Observable {\n const maxTimeout$ = timer(this.timeoutMilliseconds);\n const throwLastError$ = this.retriedErrors$$.pipe(switchMap((err) => observableThrowError(err)));\n const maxAttempts$ = errors$.pipe(skip(this.maxAttempts - 1), first());\n\n return race(maxTimeout$, maxAttempts$).pipe(switchMap(() => throwLastError$));\n }\n\n private shouldRetry(err: HttpErrorResponse): boolean {\n if (this.customRetryChecker !== null && this.customRetryChecker !== undefined) {\n return this.customRetryChecker(err);\n }\n if (err.error instanceof Error) {\n // non HTTP Error means a client error or network error occurred, we should retry\n return true;\n }\n return err.status >= 500;\n }\n\n private getDelayTimer(tryNumber: number): Observable {\n const exponentialBackoffDelay = Math.pow(2, tryNumber) * this.retryDelay;\n return timer(exponentialBackoffDelay).pipe(first());\n }\n}\n\n/**\n * Usage: httpClient.get('example.com').let(retryWith(retryConfig))\n * OR: httpClient.get('example.com').pipe( retryWith(retryConfig) );\n * @param config Retry configuration\n */\nexport const retryer =\n (config?: RetryConfig) =>\n (call: Observable): Observable => {\n return new Retryer(call, config).result$;\n };\n"], "mappings": "2IAuBA,IAAMA,EAAyB,IACzBC,EAAuB,GACvBC,EAA6B,IAC7BC,EAAiF,KAO1EC,EAAP,KAAc,CAclBC,YAAYC,EAAqBC,EAAoB,CAZ7C,KAAAC,gBAAkB,IAAIC,EAAiC,CAAC,EAa9D,GAAM,CACJC,oBAAAA,EAAsBV,EACtBW,YAAAA,EAAcV,EACdW,WAAAA,EAAaV,EACbW,mBAAAA,EAAqBV,CAAqB,EACxCI,GAAU,CAAA,EACd,KAAKG,oBAAsBA,EAC3B,KAAKC,YAAcA,EACnB,KAAKC,WAAaA,EAClB,KAAKC,mBAAqBA,EAE1B,KAAKC,QAAUR,EAAKS,KAClBC,EAAWC,GAA2C,KAAKC,WAAWD,CAAO,CAAC,EAC9EE,EAAK,CAAE,CAEX,CAEQD,WAAWD,EAAsC,CACvD,OAAOA,EAAQF,KACbK,EAAKC,GAAQ,KAAKb,gBAAgBc,KAAKD,CAAG,CAAC,EAC3CE,EAAWC,GAAa,KAAKC,YAAYD,CAAO,EAAIE,EAAaF,CAAO,EAAIG,EAAqBH,CAAO,CAAE,EAC1GI,EAAIC,EAAM,EAAG,KAAKlB,YAAc,CAAC,EAAG,CAACU,EAAKS,IAAcA,CAAS,EACjEC,EAAWD,GAAc,KAAKE,cAAcF,CAAS,CAAC,EACtDG,EAAM,KAAKC,kBAAkBjB,CAAO,CAAC,CAAC,CAE1C,CAEQiB,kBAAkBjB,EAAsC,CAC9D,IAAMkB,EAAcC,EAAM,KAAK1B,mBAAmB,EAC5C2B,EAAkB,KAAK7B,gBAAgBO,KAAKQ,EAAWF,GAAQM,EAAqBN,CAAG,CAAC,CAAC,EACzFiB,EAAerB,EAAQF,KAAKwB,EAAK,KAAK5B,YAAc,CAAC,EAAGQ,EAAK,CAAE,EAErE,OAAOqB,EAAUL,EAAaG,CAAY,EAAEvB,KAAKQ,EAAU,IAAMc,CAAe,CAAC,CACnF,CAEQZ,YAAYJ,EAAsB,CACxC,OAAI,KAAKR,qBAAuB,MAAQ,KAAKA,qBAAuB4B,OAC3D,KAAK5B,mBAAmBQ,CAAG,EAEhCA,EAAIqB,iBAAiBC,MAEhB,GAEFtB,EAAIuB,QAAU,GACvB,CAEQZ,cAAcF,EAAiB,CACrC,IAAMe,EAA0BC,KAAKC,IAAI,EAAGjB,CAAS,EAAI,KAAKlB,WAC9D,OAAOwB,EAAMS,CAAuB,EAAE9B,KAAKI,EAAK,CAAE,CACpD,GAQW6B,EACPzC,GACHD,GACQ,IAAIF,EAAQE,EAAMC,CAAM,EAAEO", "names": ["DEFAULT_TIMEOUT_MILLIS", "DEFAULT_MAX_ATTEMPTS", "DEFAULT_RETRY_DELAY_MILLIS", "DEFAULT_RETRY_CHECKER", "Retryer", "constructor", "call", "config", "retriedErrors$$", "ReplaySubject", "timeoutMilliseconds", "maxAttempts", "retryDelay", "customRetryChecker", "result$", "pipe", "retryWhen", "errors$", "getRetries", "first", "tap", "err", "next", "switchMap", "errResp", "shouldRetry", "observableOf", "observableThrowError", "zip", "range", "tryNumber", "delayWhen", "getDelayTimer", "merge", "getCutoffNotifier", "maxTimeout$", "timer", "throwLastError$", "maxAttempts$", "skip", "race", "undefined", "error", "Error", "status", "exponentialBackoffDelay", "Math", "pow", "retryer"] }