{"version":3,"file":"retry.cjs","names":[],"sources":["../../../src/lib/utils/retry.ts"],"sourcesContent":["/**\n * Environment-neutral retry/backoff/timeout primitives shared across HTTP-backed batteries.\n *\n * @module @nhtio/adk/lib/utils/retry\n *\n * @remarks\n * These are pure helpers — no DOM, no Node built-ins, only `setTimeout`, `AbortController`, and\n * `AbortSignal`, all of which exist in every target runtime (Node, browser, edge, workers). They\n * are shared (not duplicated) by the OpenAI Chat Completions battery and the OpenAI Embeddings\n * battery so retry behavior stays identical across the bundled batteries. The bundler inlines this\n * module into each consumer, so sharing introduces no build coupling.\n */\n\n/**\n * Minimal backoff configuration shape. Any retry config carrying `baseDelayMs`/`maxDelayMs`\n * satisfies it structurally, so batteries can pass their own richer config objects directly.\n */\nexport interface BackoffConfig {\n  /** Base delay in milliseconds for the first retry; doubles each attempt (default 500). */\n  baseDelayMs?: number\n  /** Upper bound in milliseconds on any single backoff delay (default 30_000). */\n  maxDelayMs?: number\n}\n\n/**\n * Exponential backoff with a ceiling: `min(baseDelayMs * 2^(attempt-1), maxDelayMs)`.\n *\n * @param attempt - 1-based attempt number.\n * @param cfg - Carries `baseDelayMs` (default 500) and `maxDelayMs` (default 30_000).\n * @returns The (un-jittered) delay in ms.\n */\nexport const computeBackoff = (attempt: number, cfg: BackoffConfig): number => {\n  const base = cfg.baseDelayMs ?? 500\n  const max = cfg.maxDelayMs ?? 30_000\n  return Math.min(base * Math.pow(2, attempt - 1), max)\n}\n\n/**\n * Abort-aware jittered sleep used for retry backoff.\n *\n * @remarks\n * Resolves (never rejects) the instant `signal` aborts, so an aborted caller does not stay parked\n * in a backoff delay — the caller's retry loop re-checks abort state immediately after and bails.\n * The timer and the abort listener are both torn down on whichever fires first, so nothing leaks.\n *\n * @param ms - Base delay in ms; jittered by ±10%.\n * @param signal - Optional abort signal that short-circuits the sleep.\n */\nexport const sleepWithJitter = (ms: number, signal?: AbortSignal): Promise<void> => {\n  const jittered = ms * (0.9 + Math.random() * 0.2)\n  return new Promise((resolve) => {\n    if (signal?.aborted) {\n      resolve()\n      return\n    }\n    let onAbort: (() => void) | undefined\n    const timer = setTimeout(() => {\n      if (onAbort && signal) signal.removeEventListener('abort', onAbort)\n      resolve()\n    }, jittered)\n    if (signal) {\n      onAbort = () => {\n        clearTimeout(timer)\n        resolve()\n      }\n      signal.addEventListener('abort', onAbort, { once: true })\n    }\n  })\n}\n\n/**\n * Parses an HTTP `Retry-After` header value (delta-seconds or HTTP-date) into milliseconds.\n *\n * @param raw - The header value.\n * @returns Milliseconds to wait, or `0` when the value cannot be parsed or is in the past.\n */\nexport const parseRetryAfter = (raw: string): number => {\n  const asNum = Number(raw)\n  if (Number.isFinite(asNum)) return asNum * 1000\n  const asDate = Date.parse(raw)\n  if (Number.isFinite(asDate)) return Math.max(0, asDate - Date.now())\n  return 0\n}\n\n/**\n * Combines several abort signals into one.\n *\n * @remarks\n * Returns the linked signal plus a `dispose` that detaches any listeners the fallback path\n * attached, so repeated links on a long-lived signal (one per retry attempt) do not accumulate\n * listeners. The native `AbortSignal.any` path self-manages its listeners, so `dispose` is a no-op\n * there.\n *\n * @param signals - The signals to combine.\n * @returns The linked signal and a `dispose` to tear down fallback listeners.\n */\nexport const linkAbortSignals = (\n  signals: ReadonlyArray<AbortSignal>\n): { signal: AbortSignal; dispose: () => void } => {\n  const anyFn = (AbortSignal as unknown as { any?: (sigs: AbortSignal[]) => AbortSignal }).any\n  if (typeof anyFn === 'function') {\n    return { signal: anyFn(signals as AbortSignal[]), dispose: () => {} }\n  }\n  // Fallback for older runtimes: hand-link via a fresh controller.\n  const ctrl = new AbortController()\n  const links: Array<{ sig: AbortSignal; handler: () => void }> = []\n  for (const sig of signals) {\n    if (sig.aborted) {\n      ctrl.abort()\n      break\n    }\n    const handler = () => ctrl.abort()\n    sig.addEventListener('abort', handler, { once: true })\n    links.push({ sig, handler })\n  }\n  return {\n    signal: ctrl.signal,\n    dispose: () => {\n      for (const { sig, handler } of links) sig.removeEventListener('abort', handler)\n      links.length = 0\n    },\n  }\n}\n"],"mappings":";;;;;;;;;;AA+BA,IAAa,kBAAkB,SAAiB,QAA+B;CAC7E,MAAM,OAAO,IAAI,eAAe;CAChC,MAAM,MAAM,IAAI,cAAc;CAC9B,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,GAAG,UAAU,CAAC,GAAG,GAAG;AACtD;;;;;;;;;;;;AAaA,IAAa,mBAAmB,IAAY,WAAwC;CAClF,MAAM,WAAW,MAAM,KAAM,KAAK,OAAO,IAAI;CAC7C,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,QAAQ,SAAS;GACnB,QAAQ;GACR;EACF;EACA,IAAI;EACJ,MAAM,QAAQ,iBAAiB;GAC7B,IAAI,WAAW,QAAQ,OAAO,oBAAoB,SAAS,OAAO;GAClE,QAAQ;EACV,GAAG,QAAQ;EACX,IAAI,QAAQ;GACV,gBAAgB;IACd,aAAa,KAAK;IAClB,QAAQ;GACV;GACA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EAC1D;CACF,CAAC;AACH;;;;;;;AAQA,IAAa,mBAAmB,QAAwB;CACtD,MAAM,QAAQ,OAAO,GAAG;CACxB,IAAI,OAAO,SAAS,KAAK,GAAG,OAAO,QAAQ;CAC3C,MAAM,SAAS,KAAK,MAAM,GAAG;CAC7B,IAAI,OAAO,SAAS,MAAM,GAAG,OAAO,KAAK,IAAI,GAAG,SAAS,KAAK,IAAI,CAAC;CACnE,OAAO;AACT;;;;;;;;;;;;;AAcA,IAAa,oBACX,YACiD;CACjD,MAAM,QAAS,YAA0E;CACzF,IAAI,OAAO,UAAU,YACnB,OAAO;EAAE,QAAQ,MAAM,OAAwB;EAAG,eAAe,CAAC;CAAE;CAGtE,MAAM,OAAO,IAAI,gBAAgB;CACjC,MAAM,QAA0D,CAAC;CACjE,KAAK,MAAM,OAAO,SAAS;EACzB,IAAI,IAAI,SAAS;GACf,KAAK,MAAM;GACX;EACF;EACA,MAAM,gBAAgB,KAAK,MAAM;EACjC,IAAI,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EACrD,MAAM,KAAK;GAAE;GAAK;EAAQ,CAAC;CAC7B;CACA,OAAO;EACL,QAAQ,KAAK;EACb,eAAe;GACb,KAAK,MAAM,EAAE,KAAK,aAAa,OAAO,IAAI,oBAAoB,SAAS,OAAO;GAC9E,MAAM,SAAS;EACjB;CACF;AACF"}