// Shared, dependency-free pagination math used by list pages (e.g. PO History). // Keeps page-size validation and out-of-range page clamping in one testable place. export interface PaginationInput { /** Raw `perPage` value from the query string (may be undefined / invalid). */ perPageParam?: string | number; /** Raw `page` value from the query string (may be undefined / invalid). */ pageParam?: string | number; /** Total number of rows matching the current filter. */ total: number; /** Allowed page sizes; anything else falls back to `defaultPerPage`. */ options: number[]; defaultPerPage: number; } export interface Pagination { perPage: number; page: number; totalPages: number; /** Rows to skip for the current page (Prisma `skip`). */ skip: number; /** Rows to take for the current page (Prisma `take`). */ take: number; } /** * Resolve a safe page size and (1-based) page number from untrusted query * params. `perPage` is clamped to the allowed `options`; `page` is clamped to * `[1, totalPages]` so an out-of-range or non-numeric page never paginates past * the last page. */ export function resolvePagination({ perPageParam, pageParam, total, options, defaultPerPage, }: PaginationInput): Pagination { const perPage = options.includes(Number(perPageParam)) ? Number(perPageParam) : defaultPerPage; const totalPages = Math.max(1, Math.ceil(total / perPage)); const requested = Number(pageParam); const page = Math.min( Math.max(1, Number.isFinite(requested) && requested > 0 ? Math.floor(requested) : 1), totalPages, ); return { perPage, page, totalPages, skip: (page - 1) * perPage, take: perPage }; }