/** * Pagination utilities for API endpoints */ export interface PaginationParams { page?: number; limit?: number; sortBy?: string; sortOrder?: "asc" | "desc"; } export interface PaginationMetadata { page: number; limit: number; total: number; totalPages: number; hasNextPage: boolean; hasPrevPage: boolean; } export interface PaginatedResponse { data: T[]; pagination: PaginationMetadata; } /** * Parse pagination parameters from URL search params */ export function parsePaginationParams(searchParams: URLSearchParams): { page: number; limit: number; sortBy?: string; sortOrder: "asc" | "desc"; } { const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10)); const limit = Math.min( 100, Math.max(1, parseInt(searchParams.get("limit") || "20", 10)), ); const sortBy = searchParams.get("sortBy") || undefined; const sortOrder = (searchParams.get("sortOrder") || "desc") as "asc" | "desc"; return { page, limit, sortBy, sortOrder }; } /** * Create pagination metadata */ export function createPaginationMetadata( page: number, limit: number, total: number, ): PaginationMetadata { const totalPages = Math.ceil(total / limit); return { page, limit, total, totalPages, hasNextPage: page < totalPages, hasPrevPage: page > 1, }; } /** * Apply pagination to an array (for in-memory pagination) * Use this sparingly - prefer database-level pagination */ export function paginateArray( items: T[], page: number, limit: number, ): PaginatedResponse { const offset = (page - 1) * limit; const paginatedItems = items.slice(offset, offset + limit); return { data: paginatedItems, pagination: createPaginationMetadata(page, limit, items.length), }; } /** * Calculate SQL LIMIT and OFFSET from page and limit */ export function getPaginationOffsets(page: number, limit: number) { const offset = (page - 1) * limit; return { limit, offset }; }