{"version":3,"file":"retry.mjs","sources":["../src/retry.ts"],"sourcesContent":["import type {\n\tHttpRequestRetryOptions,\n\tParseRateLimitDelayMs,\n\tRetryEvaluationInput,\n\tRetryHookContext,\n} from './types';\n\nexport const DEFAULT_TIMEOUT_MS = 10_000;\nexport const DEFAULT_RETRIES = 2;\nexport const DEFAULT_RETRY_BASE_DELAY_MS = 200;\nexport const DEFAULT_RETRY_MAX_DELAY_MS = 2_000;\nexport const DEFAULT_RETRY_BACKOFF_FACTOR = 2;\nexport const DEFAULT_RETRY_JITTER_RATIO = 0.2;\nexport const DEFAULT_RETRYABLE_METHODS = [\n\t'GET',\n\t'HEAD',\n\t'OPTIONS',\n\t'PUT',\n\t'DELETE',\n];\nexport const DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];\nexport const DEFAULT_CAP_RATE_LIMIT_DELAY_TO_MAX_DELAY_MS = false;\n\nexport const isRetryableNetworkError = (error: unknown) => {\n\tif (error instanceof Error && error.name === 'AbortError') {\n\t\treturn true;\n\t}\n\n\tconst message =\n\t\terror instanceof Error\n\t\t\t? error.message.toLowerCase()\n\t\t\t: String(error).toLowerCase();\n\treturn (\n\t\tmessage.includes('fetch failed') ||\n\t\tmessage.includes('network') ||\n\t\tmessage.includes('socket') ||\n\t\tmessage.includes('timed out') ||\n\t\tmessage.includes('abort')\n\t);\n};\n\nexport const withBackoffAndJitter = (\n\tattempt: number,\n\tbaseMs: number,\n\tmaxMs: number,\n\tbackoffFactor = DEFAULT_RETRY_BACKOFF_FACTOR,\n\tjitterRatio = DEFAULT_RETRY_JITTER_RATIO\n) => {\n\tconst exp = Math.min(maxMs, baseMs * backoffFactor ** attempt);\n\tif (jitterRatio <= 0) {\n\t\treturn Math.round(exp);\n\t}\n\n\tconst jitter = exp * jitterRatio;\n\tconst min = Math.max(0, exp - jitter);\n\tconst max = exp + jitter;\n\n\treturn Math.round(min + Math.random() * (max - min));\n};\n\nexport const defaultParseRateLimitDelayMs: ParseRateLimitDelayMs = (\n\tresponse: Response\n) => {\n\tconst retryAfterValue = response.headers.get('retry-after');\n\tif (!retryAfterValue) {\n\t\tconst resetHeaders = [\n\t\t\t'x-ratelimit-reset-after',\n\t\t\t'x-rate-limit-reset-after',\n\t\t\t'x-ratelimit-reset',\n\t\t\t'x-rate-limit-reset',\n\t\t];\n\n\t\tfor (const header of resetHeaders) {\n\t\t\tconst value = response.headers.get(header);\n\t\t\tif (!value) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst numeric = Number.parseFloat(value);\n\t\t\tif (!Number.isFinite(numeric)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (header.includes('after')) {\n\t\t\t\treturn Math.max(0, Math.round(numeric * 1000));\n\t\t\t}\n\n\t\t\tconst asEpochMs = numeric > 1_000_000_000_000 ? numeric : numeric * 1000;\n\t\t\treturn Math.max(0, Math.round(asEpochMs - Date.now()));\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tconst numericDelaySeconds = Number.parseFloat(retryAfterValue);\n\tif (Number.isFinite(numericDelaySeconds)) {\n\t\treturn Math.max(0, Math.round(numericDelaySeconds * 1000));\n\t}\n\n\tconst retryAt = Date.parse(retryAfterValue);\n\tif (Number.isNaN(retryAt)) {\n\t\treturn null;\n\t}\n\n\treturn Math.max(0, Math.round(retryAt - Date.now()));\n};\n\nexport const defaultRetryOptions = {\n\tenabled: true,\n\tmaxAttempts: DEFAULT_RETRIES,\n\tbaseDelayMs: DEFAULT_RETRY_BASE_DELAY_MS,\n\tmaxDelayMs: DEFAULT_RETRY_MAX_DELAY_MS,\n\tbackoffFactor: DEFAULT_RETRY_BACKOFF_FACTOR,\n\tjitterRatio: DEFAULT_RETRY_JITTER_RATIO,\n\tretryableMethods: DEFAULT_RETRYABLE_METHODS,\n\tretryableStatusCodes: DEFAULT_RETRY_STATUSES,\n\tcapRateLimitDelayToMaxDelayMs: DEFAULT_CAP_RATE_LIMIT_DELAY_TO_MAX_DELAY_MS,\n\tparseRateLimitDelayMs: defaultParseRateLimitDelayMs,\n\tisRetryableError: isRetryableNetworkError,\n} satisfies Required<Omit<HttpRequestRetryOptions, 'onRetry'>>;\n\nexport const evaluateRetry = async <TRequest = unknown>({\n\tresponse,\n\terror,\n\tattempt,\n\tmaxAttempts,\n\tmethod,\n\tbaseDelayMs,\n\tmaxDelayMs,\n\tbackoffFactor,\n\tjitterRatio,\n\tretryableMethods,\n\tretryableStatusCodes,\n\tcapRateLimitDelayToMaxDelayMs,\n\tparseRateLimitDelayMs,\n\tonRetry,\n\tinput,\n\tserviceName,\n\trequest,\n\tisRetryableError = isRetryableNetworkError,\n}: RetryEvaluationInput<TRequest>): Promise<{\n\tretry: boolean;\n\tdelayMs: number;\n}> => {\n\tconst normalizedMethod = method.toUpperCase();\n\tconst methodAllowsRetry = retryableMethods.includes(normalizedMethod);\n\n\t// Determine if we should retry\n\tlet shouldRetry = false;\n\n\tif (attempt < maxAttempts && methodAllowsRetry) {\n\t\tif (response) {\n\t\t\t// HTTP response: check status codes\n\t\t\tshouldRetry = retryableStatusCodes.includes(response.status);\n\t\t} else {\n\t\t\t// Network error: check error detection\n\t\t\tshouldRetry = isRetryableError?.(error) ?? false;\n\t\t}\n\t}\n\n\tif (!shouldRetry) {\n\t\treturn { retry: false, delayMs: 0 };\n\t}\n\n\t// Calculate delay\n\tlet delayMs = 0;\n\n\tif (response) {\n\t\tconst parsedRateLimitDelayMs = parseRateLimitDelayMs(response);\n\t\tif (parsedRateLimitDelayMs !== null) {\n\t\t\tconst safeRateLimitDelayMs = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.round(parsedRateLimitDelayMs)\n\t\t\t);\n\t\t\tif (!capRateLimitDelayToMaxDelayMs) {\n\t\t\t\tdelayMs = safeRateLimitDelayMs;\n\t\t\t} else {\n\t\t\t\tdelayMs = Math.min(safeRateLimitDelayMs, maxDelayMs);\n\t\t\t}\n\t\t} else {\n\t\t\tdelayMs = withBackoffAndJitter(\n\t\t\t\tattempt,\n\t\t\t\tbaseDelayMs,\n\t\t\t\tmaxDelayMs,\n\t\t\t\tbackoffFactor,\n\t\t\t\tjitterRatio\n\t\t\t);\n\t\t}\n\t} else {\n\t\tdelayMs = withBackoffAndJitter(\n\t\t\tattempt,\n\t\t\tbaseDelayMs,\n\t\t\tmaxDelayMs,\n\t\t\tbackoffFactor,\n\t\t\tjitterRatio\n\t\t);\n\t}\n\n\t// Invoke retry hook\n\tif (onRetry) {\n\t\tconst context: RetryHookContext<TRequest> = {\n\t\t\tresponse: response?.clone(),\n\t\t\terror,\n\t\t\tattempt,\n\t\t\tmaxAttempts,\n\t\t\tmethod: normalizedMethod,\n\t\t\tdelayMs,\n\t\t\tinput,\n\t\t\tserviceName,\n\t\t\trequest,\n\t\t};\n\t\tawait onRetry(context);\n\t}\n\n\treturn { retry: true, delayMs };\n};\n"],"names":["DEFAULT_TIMEOUT_MS","DEFAULT_RETRIES","DEFAULT_RETRY_BASE_DELAY_MS","DEFAULT_RETRY_MAX_DELAY_MS","DEFAULT_RETRY_BACKOFF_FACTOR","DEFAULT_RETRY_JITTER_RATIO","DEFAULT_RETRYABLE_METHODS","DEFAULT_RETRY_STATUSES","DEFAULT_CAP_RATE_LIMIT_DELAY_TO_MAX_DELAY_MS","isRetryableNetworkError","__name","error","message","withBackoffAndJitter","attempt","baseMs","maxMs","backoffFactor","jitterRatio","exp","jitter","min","max","defaultParseRateLimitDelayMs","response","retryAfterValue","resetHeaders","header","value","numeric","asEpochMs","numericDelaySeconds","retryAt","defaultRetryOptions","evaluateRetry","maxAttempts","method","baseDelayMs","maxDelayMs","retryableMethods","retryableStatusCodes","capRateLimitDelayToMaxDelayMs","parseRateLimitDelayMs","onRetry","input","serviceName","request","isRetryableError","normalizedMethod","methodAllowsRetry","shouldRetry","delayMs","parsedRateLimitDelayMs","safeRateLimitDelayMs","context"],"mappings":"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"}