{"version":3,"sources":["../src/http/client.ts"],"sourcesContent":["import {\n  ContentstackAuthError,\n  ContentstackConfigError,\n  ContentstackError,\n  ContentstackForbiddenError,\n  ContentstackInvalidApiKeyError,\n  ContentstackNotFoundError,\n  ContentstackRateLimitError,\n  ContentstackServerError,\n  ContentstackValidationError,\n} from \"./errors.js\"\nimport type { HttpClientConfig, HttpResponse } from \"./types.js\"\n\nconst DEFAULT_TIMEOUT = 30_000\nconst DEFAULT_RETRY_LIMIT = 5\nconst DEFAULT_RETRY_DELAY = 300\nconst MAX_JITTER = 100\n\nexport class ContentstackHttpClient {\n  private readonly config: Required<\n    Pick<HttpClientConfig, \"baseUrl\" | \"timeout\" | \"retryOnError\" | \"retryLimit\" | \"retryDelay\">\n  > & {\n    headers: Record<string, string>\n    fetch: typeof globalThis.fetch\n    resolveHeaders?: HttpClientConfig[\"resolveHeaders\"]\n  }\n\n  constructor(config: HttpClientConfig) {\n    this.config = {\n      baseUrl: config.baseUrl,\n      headers: config.headers ?? {},\n      timeout: config.timeout ?? DEFAULT_TIMEOUT,\n      retryOnError: config.retryOnError ?? true,\n      retryLimit: config.retryLimit ?? DEFAULT_RETRY_LIMIT,\n      retryDelay: config.retryDelay ?? DEFAULT_RETRY_DELAY,\n      fetch: config.fetch ?? globalThis.fetch.bind(globalThis),\n      resolveHeaders: config.resolveHeaders,\n    }\n  }\n\n  async get<T>(path: string, params?: Record<string, string>): Promise<HttpResponse<T>> {\n    let url = `${this.config.baseUrl}${path}`\n    if (params) {\n      const searchParams = new URLSearchParams(params)\n      url += `?${searchParams.toString()}`\n    }\n    return this.request<T>(url, { method: \"GET\" }, path)\n  }\n\n  async post<T>(path: string, body?: unknown): Promise<HttpResponse<T>> {\n    const url = `${this.config.baseUrl}${path}`\n    return this.request<T>(\n      url,\n      {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: body !== undefined ? JSON.stringify(body) : undefined,\n      },\n      path,\n    )\n  }\n\n  async put<T>(path: string, body?: unknown): Promise<HttpResponse<T>> {\n    const url = `${this.config.baseUrl}${path}`\n    return this.request<T>(\n      url,\n      {\n        method: \"PUT\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: body !== undefined ? JSON.stringify(body) : undefined,\n      },\n      path,\n    )\n  }\n\n  async patch<T>(path: string, body?: unknown): Promise<HttpResponse<T>> {\n    const url = `${this.config.baseUrl}${path}`\n    return this.request<T>(\n      url,\n      {\n        method: \"PATCH\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: body !== undefined ? JSON.stringify(body) : undefined,\n      },\n      path,\n    )\n  }\n\n  async delete<T>(path: string): Promise<HttpResponse<T>> {\n    const url = `${this.config.baseUrl}${path}`\n    return this.request<T>(url, { method: \"DELETE\" }, path)\n  }\n\n  async postForm<T>(path: string, params: URLSearchParams): Promise<HttpResponse<T>> {\n    const url = `${this.config.baseUrl}${path}`\n    return this.request<T>(\n      url,\n      {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n        body: params.toString(),\n      },\n      path,\n    )\n  }\n\n  async upload<T>(path: string, form: FormData): Promise<HttpResponse<T>> {\n    const url = `${this.config.baseUrl}${path}`\n    return this.request<T>(url, { method: \"POST\", body: form }, path)\n  }\n\n  /** Return a new client with additional headers merged */\n  withHeaders(headers: Record<string, string>): ContentstackHttpClient {\n    return new ContentstackHttpClient({\n      ...this.config,\n      headers: { ...this.config.headers, ...headers },\n    })\n  }\n\n  /** Return a new client with a different base URL */\n  withBaseUrl(baseUrl: string): ContentstackHttpClient {\n    return new ContentstackHttpClient({\n      ...this.config,\n      baseUrl,\n    })\n  }\n\n  private async request<T>(url: string, init: RequestInit, path: string): Promise<HttpResponse<T>> {\n    const headers = new Headers(this.config.headers)\n    const resolvedHeaders = await this.config.resolveHeaders?.()\n\n    if (resolvedHeaders) {\n      for (const [key, value] of Object.entries(resolvedHeaders)) {\n        headers.set(key, value)\n      }\n    }\n\n    if (init.headers) {\n      const initHeaders =\n        init.headers instanceof Headers\n          ? init.headers\n          : new Headers(init.headers as Record<string, string>)\n      initHeaders.forEach((value, key) => headers.set(key, value))\n    }\n\n    let lastError: Error | undefined\n    const maxAttempts = this.config.retryOnError ? this.config.retryLimit + 1 : 1\n\n    for (let attempt = 0; attempt < maxAttempts; attempt++) {\n      const controller = new AbortController()\n      const timeoutId = setTimeout(() => controller.abort(), this.config.timeout)\n\n      try {\n        const response = await this.config.fetch(url, {\n          ...init,\n          headers,\n          signal: controller.signal,\n        })\n\n        if (response.ok) {\n          const data = (await response.json().catch(() => ({}))) as T\n          return { data, status: response.status, headers: response.headers }\n        }\n\n        // Check if retryable\n        const isRetryable = response.status === 429 || response.status >= 500\n        if (isRetryable && this.config.retryOnError && attempt < maxAttempts - 1) {\n          const delay = this.calculateDelay(response, attempt)\n          await sleep(delay)\n          lastError = await this.createError(response, path)\n          continue\n        }\n\n        throw await this.createError(response, path)\n      } catch (error) {\n        if (error instanceof ContentstackError) {\n          throw error\n        }\n\n        if (error instanceof ContentstackConfigError) {\n          throw error\n        }\n\n        if (error instanceof DOMException && error.name === \"AbortError\") {\n          throw new ContentstackError(`Request timed out after ${this.config.timeout}ms`, {\n            requestPath: path,\n            cause: error,\n          })\n        }\n\n        throw new ContentstackError(\"Network request failed\", {\n          requestPath: path,\n          cause: error instanceof Error ? error : new Error(String(error)),\n        })\n      } finally {\n        clearTimeout(timeoutId)\n      }\n    }\n\n    // Should not reach here, but just in case\n    throw lastError ?? new ContentstackError(\"Request failed after retries\", { requestPath: path })\n  }\n\n  private calculateDelay(response: Response, attempt: number): number {\n    const retryAfter = response.headers.get(\"Retry-After\")\n    if (retryAfter) {\n      const seconds = Number.parseFloat(retryAfter)\n      if (!Number.isNaN(seconds)) {\n        return seconds * 1000\n      }\n    }\n\n    const jitter = Math.random() * MAX_JITTER\n    return this.config.retryDelay * 2 ** attempt + jitter\n  }\n\n  private async createError(response: Response, path: string): Promise<ContentstackError> {\n    let body: Record<string, unknown> = {}\n    try {\n      body = (await response.json()) as Record<string, unknown>\n    } catch {\n      // Response body may not be JSON\n    }\n\n    const message =\n      (body.error_message as string | undefined) ??\n      (body.error_description as string | undefined) ??\n      (body.message as string | undefined) ??\n      `HTTP ${response.status} error`\n    const errorCode = body.error_code as number | undefined\n    const errors = body.errors as Record<string, string[]> | undefined\n    const retryAfter = response.headers.get(\"Retry-After\")\n\n    const opts = { status: response.status, errorCode, errors, requestPath: path }\n\n    switch (response.status) {\n      case 400:\n        return new ContentstackValidationError(message, { ...opts, status: 400 })\n      case 401:\n        return new ContentstackAuthError(message, opts)\n      case 403:\n        return new ContentstackForbiddenError(message, opts)\n      case 404:\n        return new ContentstackNotFoundError(message, opts)\n      case 412:\n        return new ContentstackInvalidApiKeyError(message, opts)\n      case 422:\n        return new ContentstackValidationError(message, { ...opts, status: 422 })\n      case 429:\n        return new ContentstackRateLimitError(message, {\n          ...opts,\n          retryAfter: retryAfter ? Number.parseFloat(retryAfter) : undefined,\n        })\n      default:\n        if (response.status >= 500) {\n          return new ContentstackServerError(message, opts)\n        }\n        return new ContentstackError(message, opts)\n    }\n  }\n}\n\nfunction sleep(ms: number): Promise<void> {\n  return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],"mappings":"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","names":[]}