{"version":3,"file":"pagination-helpers.mjs","sources":["../../../src/lib/api/pagination-helpers.ts"],"sourcesContent":["/**\n * @file Pagination Helpers\n * @description Utilities for extracting and managing pagination metadata from API responses.\n * Supports both cursor-based and offset-based pagination patterns.\n */\n\nimport type { ApiResponse, PaginatedResponse, PaginationMeta } from './types';\n\n/**\n * Pagination extraction configuration\n */\nexport interface PaginationConfig {\n  /** Response field containing items array */\n  itemsField?: string;\n  /** Response field containing total count */\n  totalField?: string;\n  /** Response field containing page number */\n  pageField?: string;\n  /** Response field containing page size */\n  pageSizeField?: string;\n  /** Response field containing next cursor */\n  nextCursorField?: string;\n  /** Response field containing previous cursor */\n  prevCursorField?: string;\n  /** Header containing total count */\n  totalHeader?: string;\n  /** Header containing link relations */\n  linkHeader?: string;\n}\n\n/**\n * Default pagination config\n */\nconst DEFAULT_PAGINATION_CONFIG: PaginationConfig = {\n  itemsField: 'items',\n  totalField: 'total',\n  pageField: 'page',\n  pageSizeField: 'pageSize',\n  nextCursorField: 'nextCursor',\n  prevCursorField: 'prevCursor',\n  totalHeader: 'x-total-count',\n  linkHeader: 'link',\n};\n\n/**\n * Extract pagination metadata from response\n *\n * @param response - API response\n * @param config - Extraction configuration\n * @returns Extracted pagination metadata\n *\n * @example\n * ```typescript\n * const pagination = extractPagination(response);\n * console.log(pagination.total); // Total items\n * console.log(pagination.hasMore); // More pages available\n * ```\n */\nexport function extractPagination<T>(\n  response: ApiResponse,\n  config: PaginationConfig = {}\n): PaginatedResponse<T> {\n  const opts = { ...DEFAULT_PAGINATION_CONFIG, ...config };\n  const data = response.data as Record<string, unknown>;\n\n  // Extract items\n  let items: T[] = [];\n  if (Array.isArray(data)) {\n    items = data as T[];\n  } else if (\n    typeof data === 'object' &&\n    data !== null &&\n    opts.itemsField !== null &&\n    opts.itemsField !== undefined &&\n    opts.itemsField !== '' &&\n    opts.itemsField in data\n  ) {\n    items = (data[opts.itemsField] as T[]) ?? [];\n  }\n\n  // Extract metadata from response body\n  const total = getNestedValue<number>(data, opts.totalField) ?? items.length;\n  const page = getNestedValue<number>(data, opts.pageField) ?? 1;\n  const pageSize = getNestedValue<number>(data, opts.pageSizeField) ?? items.length;\n  const nextCursor = getNestedValue<string>(data, opts.nextCursorField);\n  const prevCursor = getNestedValue<string>(data, opts.prevCursorField);\n\n  // Try headers if not found in body\n  const headerTotal =\n    opts.totalHeader !== null && opts.totalHeader !== undefined && opts.totalHeader !== ''\n      ? response.headers[opts.totalHeader]\n      : undefined;\n  const finalTotal =\n    total ??\n    (headerTotal !== null && headerTotal !== undefined && headerTotal !== ''\n      ? parseInt(headerTotal, 10)\n      : items.length);\n\n  // Parse Link header for cursor info\n  const linkHeader =\n    opts.linkHeader !== null && opts.linkHeader !== undefined && opts.linkHeader !== ''\n      ? response.headers[opts.linkHeader]\n      : undefined;\n  const links =\n    linkHeader !== null && linkHeader !== undefined && linkHeader !== ''\n      ? parseLinkHeader(linkHeader)\n      : {};\n\n  const totalPages = Math.ceil(finalTotal / pageSize);\n  const hasMore =\n    nextCursor !== null && nextCursor !== undefined && nextCursor !== '' ? true : page < totalPages;\n\n  return {\n    items,\n    pagination: {\n      total: finalTotal,\n      page,\n      pageSize,\n      totalPages,\n      hasMore,\n      nextCursor: nextCursor ?? links.next,\n      prevCursor: prevCursor ?? links.prev,\n    },\n  };\n}\n\n/**\n * Get nested value from object using dot notation\n */\nfunction getNestedValue<T>(obj: unknown, path?: string): T | undefined {\n  if (\n    path === null ||\n    path === undefined ||\n    path === '' ||\n    obj === null ||\n    obj === undefined ||\n    typeof obj !== 'object'\n  ) {\n    return undefined;\n  }\n\n  const parts = path.split('.');\n  let current: unknown = obj;\n\n  for (const part of parts) {\n    if (current === null || current === undefined) {\n      return undefined;\n    }\n    current = (current as Record<string, unknown>)[part];\n  }\n\n  return current as T;\n}\n\n/**\n * Parse Link header for pagination URLs\n */\nfunction parseLinkHeader(header: string): Record<string, string> {\n  const links: Record<string, string> = {};\n\n  const parts = header.split(',');\n  for (const part of parts) {\n    const match = part.match(/<([^>]+)>;\\s*rel=\"([^\"]+)\"/);\n    if (match) {\n      const [, url, rel] = match;\n      // Extract cursor from URL if present\n      if (url === null || url === undefined || url === '') continue;\n      const urlParams = new URLSearchParams(url.split('?')[1] ?? '');\n      const cursor = urlParams.get('cursor') ?? urlParams.get('after') ?? url;\n      if (rel !== null && rel !== undefined && rel !== '') links[rel] = cursor;\n    }\n  }\n\n  return links;\n}\n\n/**\n * Create a paginated response wrapper\n *\n * @example\n * ```typescript\n * const paginated = createPaginatedResponse(users, {\n *   total: 100,\n *   page: 1,\n *   pageSize: 20,\n * });\n * ```\n */\nexport function createPaginatedResponse<T>(\n  items: T[],\n  meta: Partial<PaginationMeta>\n): PaginatedResponse<T> {\n  const total = meta.total ?? items.length;\n  const pageSize = meta.pageSize ?? items.length;\n  const page = meta.page ?? 1;\n  const totalPages = Math.ceil(total / pageSize);\n\n  return {\n    items,\n    pagination: {\n      total,\n      page,\n      pageSize,\n      totalPages,\n      hasMore: meta.hasMore ?? page < totalPages,\n      nextCursor: meta.nextCursor,\n      prevCursor: meta.prevCursor,\n    },\n  };\n}\n\n/**\n * Merge paginated responses (for infinite scroll)\n *\n * @example\n * ```typescript\n * const combined = mergePaginatedResponses(page1, page2, page3);\n * ```\n */\nexport function mergePaginatedResponses<T>(\n  ...responses: PaginatedResponse<T>[]\n): PaginatedResponse<T> {\n  if (responses.length === 0) {\n    return createPaginatedResponse([], { total: 0 });\n  }\n\n  const items = responses.flatMap((r) => r.items);\n  const lastResponse = responses[responses.length - 1];\n\n  return {\n    items,\n    pagination: {\n      page: responses.length,\n      pageSize: Math.ceil(items.length / responses.length),\n      total: lastResponse?.pagination?.total ?? items.length,\n      totalPages: lastResponse?.pagination?.totalPages ?? 1,\n      hasMore: lastResponse?.pagination?.hasMore ?? false,\n      nextCursor: lastResponse?.pagination?.nextCursor,\n      prevCursor: lastResponse?.pagination?.prevCursor,\n    },\n  };\n}\n"],"names":["DEFAULT_PAGINATION_CONFIG","extractPagination","response","config","opts","data","items","total","getNestedValue","page","pageSize","nextCursor","prevCursor","headerTotal","finalTotal","linkHeader","links","parseLinkHeader","totalPages","hasMore","obj","path","parts","current","part","header","match","url","rel","urlParams","cursor","createPaginatedResponse","meta","mergePaginatedResponses","responses","r","lastResponse"],"mappings":"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"}