{"version":3,"file":"webhook-delivery-DZeUlZhi.cjs","names":[],"sources":["../src/http-client.ts","../src/retry-classification.ts","../src/webhook-delivery.ts"],"sourcesContent":["/**\n * HTTP client for sending webhook events\n *\n * Provides proper error handling, timeout support, and response parsing\n */\n\nexport type HttpRetryOptions = {\n  retries?: number;\n  delayMs?: number;\n};\n\nexport type HttpClientOptions = {\n  timeoutMs?: number;\n  retry?: HttpRetryOptions;\n};\n\nexport type HttpSuccess<T = unknown> = {\n  ok: true;\n  status: number;\n  data: T | null;\n};\n\nexport type HttpNetworkError = {\n  ok: false;\n  kind: 'network';\n  timedOut: boolean;\n  cause: Error;\n};\n\nexport type HttpStatusError<E = unknown> = {\n  ok: false;\n  kind: 'http';\n  status: number;\n  statusText: string;\n  body: E;\n};\n\nexport type HttpResult<T = unknown, E = unknown> =\n  | HttpSuccess<T>\n  | HttpNetworkError\n  | HttpStatusError<E>;\n\nexport type HttpRequestOptions = {\n  method?: string;\n  headers?: Record<string, string>;\n  body?: string;\n  timeoutMs?: number;\n};\n\nasync function parseBody(response: Response): Promise<unknown> {\n  const text = await response.text();\n  if (text.trim().length === 0) return null;\n\n  try {\n    return JSON.parse(text) as unknown;\n  } catch {\n    return text;\n  }\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n  if (!(error instanceof Error)) return false;\n  return error.name === 'AbortError' || error.name === 'TimeoutError';\n}\n\n/**\n * Create an HTTP client with timeout and error handling\n *\n * @param options Configuration for timeout and retry behavior\n * @returns HTTP client with request method\n *\n * @example\n * ```typescript\n * const client = createHttpClient({ timeoutMs: 5000 })\n * const result = await client.request('https://example.com', {\n *   method: 'POST',\n *   body: JSON.stringify({ event: 'test' })\n * })\n * ```\n */\nexport function createHttpClient(options: HttpClientOptions = {}) {\n  const defaultTimeoutMs = options.timeoutMs ?? 30_000;\n\n  return {\n    async request<T = unknown, E = unknown>(\n      url: string,\n      requestOptions: HttpRequestOptions = {},\n    ): Promise<HttpResult<T, E>> {\n      const timeoutMs = requestOptions.timeoutMs ?? defaultTimeoutMs;\n      const controller = new AbortController();\n      const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);\n\n      try {\n        const response = await fetch(url, {\n          method: requestOptions.method ?? 'GET',\n          headers: requestOptions.headers,\n          body: requestOptions.body,\n          signal: controller.signal,\n        });\n\n        if (!response.ok) {\n          const body = (await parseBody(response)) as E;\n          return {\n            ok: false,\n            kind: 'http',\n            status: response.status,\n            statusText: response.statusText,\n            body,\n          };\n        }\n\n        const data = (await parseBody(response)) as T;\n        return { ok: true, status: response.status, data };\n      } catch (error) {\n        const cause = error instanceof Error ? error : new Error(String(error));\n        return {\n          ok: false,\n          kind: 'network',\n          timedOut: isTimeoutError(error),\n          cause,\n        };\n      } finally {\n        clearTimeout(timeoutHandle);\n      }\n    },\n  };\n}\n","/**\n * Error classification and retry logic for webhook providers\n *\n * Intelligently maps HTTP status codes to retriable vs non-retriable errors\n */\n\nexport type ProviderErrorCode =\n  | 'VALIDATION'\n  | 'CONFIG'\n  | 'RATE_LIMITED'\n  | 'PROVIDER'\n  | 'NETWORK';\n\n/**\n * Structured error for event subscriber failures\n *\n * Includes error code, retriability flag, and optional details from the provider\n */\nexport class SubscriberProviderError extends Error {\n  readonly code: ProviderErrorCode;\n  readonly retriable: boolean;\n  readonly details?: unknown;\n\n  constructor(options: {\n    message: string;\n    code: ProviderErrorCode;\n    retriable: boolean;\n    details?: unknown;\n    cause?: unknown;\n  }) {\n    super(options.message, options.cause ? { cause: options.cause } : undefined);\n    this.name = 'SubscriberProviderError';\n    this.code = options.code;\n    this.retriable = options.retriable;\n    this.details = options.details;\n  }\n}\n\nexport type MappedHttpError = { code: ProviderErrorCode; retriable: boolean };\n\n/**\n * Map HTTP status codes to error classification\n *\n * - 400, 422: VALIDATION (not retriable)\n * - 401, 403, 404: CONFIG (not retriable)\n * - 429: RATE_LIMITED (retriable)\n * - 5xx: PROVIDER (retriable)\n * - 2xx-3xx: Success (not error)\n */\nexport function mapHttpStatus(status: number): MappedHttpError {\n  switch (status) {\n    case 400:\n    case 422: {\n      return { code: 'VALIDATION', retriable: false };\n    }\n    case 401:\n    case 403:\n    case 404: {\n      return { code: 'CONFIG', retriable: false };\n    }\n    case 429: {\n      return { code: 'RATE_LIMITED', retriable: true };\n    }\n    default: {\n      return { code: 'PROVIDER', retriable: status >= 500 };\n    }\n  }\n}\n\n/**\n * Check if an error is retriable\n *\n * Returns the retriable flag from SubscriberProviderError, or true for unknown errors\n */\nexport function isProviderRetriable(error: unknown): boolean {\n  if (error instanceof SubscriberProviderError) return error.retriable;\n  return true;\n}\n","/**\n * Shared JSON webhook delivery: timeout-bounded POST with classified\n * errors and exponential-backoff retries. Used by `WebhookSubscriber`\n * and `SecuritySubscriber` so delivery semantics stay identical.\n */\n\nimport type { createHttpClient } from './http-client';\nimport {\n  mapHttpStatus,\n  SubscriberProviderError,\n  isProviderRetriable,\n} from './retry-classification';\n\nexport type HttpClient = ReturnType<typeof createHttpClient>;\n\nexport type JsonDeliveryOptions = {\n  method?: 'POST' | 'PUT';\n  headers?: Record<string, string>;\n  /** Attempts including the first. Default 3. */\n  maxRetries?: number;\n  /** Base backoff delay; doubles per attempt. Default 1000. */\n  retryDelayMs?: number;\n  /** Prefix for error messages, e.g. `Webhook` or `Security webhook`. */\n  label?: string;\n};\n\nfunction delay(ms: number): Promise<void> {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * POST `payload` as JSON, retrying retriable failures with exponential\n * backoff. Throws a classified `SubscriberProviderError` once retries are\n * exhausted — callers rely on the `EventSubscriber` base class to route\n * that through `handleError`.\n */\nexport async function postJsonWithRetry(\n  client: HttpClient,\n  url: string,\n  payload: unknown,\n  options: JsonDeliveryOptions = {},\n): Promise<void> {\n  const maxRetries = options.maxRetries ?? 3;\n  const retryDelayMs = options.retryDelayMs ?? 1000;\n  const method = options.method ?? 'POST';\n  const label = options.label ?? 'Webhook';\n  let lastError: Error | undefined;\n\n  for (let attempt = 1; attempt <= maxRetries; attempt++) {\n    const response = await client.request<unknown, unknown>(url, {\n      method,\n      headers: {\n        'Content-Type': 'application/json',\n        ...options.headers,\n      },\n      body: JSON.stringify(payload),\n    });\n\n    if (response.ok) return;\n\n    if (response.kind === 'network') {\n      lastError = new SubscriberProviderError({\n        message: response.timedOut\n          ? `${label} request timed out`\n          : `${label} network request failed`,\n        code: 'NETWORK',\n        retriable: true,\n        details: response.cause,\n        cause: response.cause,\n      });\n    } else {\n      const mapped = mapHttpStatus(response.status);\n      lastError = new SubscriberProviderError({\n        message: `${label} returned ${response.status}: ${response.statusText}`,\n        code: mapped.code,\n        retriable: mapped.retriable,\n        details: response.body,\n      });\n    }\n\n    const canRetry = isProviderRetriable(lastError) && attempt < maxRetries;\n    if (!canRetry) break;\n\n    await delay(retryDelayMs * 2 ** (attempt - 1));\n  }\n\n  throw lastError ?? new Error(`${label} send failed`);\n}\n"],"mappings":";;AAiDA,eAAe,UAAU,UAAsC;CAC7D,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;CAErC,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAAyB;CAC/C,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,OAAO,MAAM,SAAS,gBAAgB,MAAM,SAAS;AACvD;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,UAA6B,CAAC,GAAG;CAChE,MAAM,mBAAmB,QAAQ,aAAa;CAE9C,OAAO,EACL,MAAM,QACJ,KACA,iBAAqC,CAAC,GACX;EAC3B,MAAM,YAAY,eAAe,aAAa;EAC9C,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,gBAAgB,iBAAiB,WAAW,MAAM,GAAG,SAAS;EAEpE,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ,eAAe,UAAU;IACjC,SAAS,eAAe;IACxB,MAAM,eAAe;IACrB,QAAQ,WAAW;GACrB,CAAC;GAED,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAQ,MAAM,UAAU,QAAQ;IACtC,OAAO;KACL,IAAI;KACJ,MAAM;KACN,QAAQ,SAAS;KACjB,YAAY,SAAS;KACrB;IACF;GACF;GAEA,MAAM,OAAQ,MAAM,UAAU,QAAQ;GACtC,OAAO;IAAE,IAAI;IAAM,QAAQ,SAAS;IAAQ;GAAK;EACnD,SAAS,OAAO;GACd,MAAM,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GACtE,OAAO;IACL,IAAI;IACJ,MAAM;IACN,UAAU,eAAe,KAAK;IAC9B;GACF;EACF,UAAU;GACR,aAAa,aAAa;EAC5B;CACF,EACF;AACF;;;;;;;;;AC5GA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,SAMT;EACD,MAAM,QAAQ,SAAS,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,MAAS;EAC3E,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;EACpB,KAAK,YAAY,QAAQ;EACzB,KAAK,UAAU,QAAQ;CACzB;AACF;;;;;;;;;;AAaA,SAAgB,cAAc,QAAiC;CAC7D,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,KACH,OAAO;GAAE,MAAM;GAAc,WAAW;EAAM;EAEhD,KAAK;EACL,KAAK;EACL,KAAK,KACH,OAAO;GAAE,MAAM;GAAU,WAAW;EAAM;EAE5C,KAAK,KACH,OAAO;GAAE,MAAM;GAAgB,WAAW;EAAK;EAEjD,SACE,OAAO;GAAE,MAAM;GAAY,WAAW,UAAU;EAAI;CAExD;AACF;;;;;;AAOA,SAAgB,oBAAoB,OAAyB;CAC3D,IAAI,iBAAiB,yBAAyB,OAAO,MAAM;CAC3D,OAAO;AACT;;;;ACnDA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;;;;;AAQA,eAAsB,kBACpB,QACA,KACA,SACA,UAA+B,CAAC,GACjB;CACf,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI;CAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,YAAY,WAAW;EACtD,MAAM,WAAW,MAAM,OAAO,QAA0B,KAAK;GAC3D;GACA,SAAS;IACP,gBAAgB;IAChB,GAAG,QAAQ;GACb;GACA,MAAM,KAAK,UAAU,OAAO;EAC9B,CAAC;EAED,IAAI,SAAS,IAAI;EAEjB,IAAI,SAAS,SAAS,WACpB,YAAY,IAAI,wBAAwB;GACtC,SAAS,SAAS,WACd,GAAG,MAAM,sBACT,GAAG,MAAM;GACb,MAAM;GACN,WAAW;GACX,SAAS,SAAS;GAClB,OAAO,SAAS;EAClB,CAAC;OACI;GACL,MAAM,SAAS,cAAc,SAAS,MAAM;GAC5C,YAAY,IAAI,wBAAwB;IACtC,SAAS,GAAG,MAAM,YAAY,SAAS,OAAO,IAAI,SAAS;IAC3D,MAAM,OAAO;IACb,WAAW,OAAO;IAClB,SAAS,SAAS;GACpB,CAAC;EACH;EAGA,IAAI,EADa,oBAAoB,SAAS,KAAK,UAAU,aAC9C;EAEf,MAAM,MAAM,eAAe,MAAM,UAAU,EAAE;CAC/C;CAEA,MAAM,6BAAa,IAAI,MAAM,GAAG,MAAM,aAAa;AACrD"}