import { PaginatedResponse, PaginationMeta } from '@edr/types'; import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; /** Raw page/pageSize as they arrive from a query DTO (both optional). */ export interface PageRequest { page?: number; pageSize?: number; } export interface PaginationOptions { defaultPageSize?: number; maxPageSize?: number; } export interface NormalizedPage { page: number; pageSize: number; skip: number; take: number; } const DEFAULT_PAGE_SIZE = 20; const MAX_PAGE_SIZE = 100; /** Clamp raw query values into a safe page window (page ≥ 1, pageSize capped). */ export function normalizePagination( request: PageRequest, options: PaginationOptions = {}, ): NormalizedPage { const defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE; const maxPageSize = options.maxPageSize ?? MAX_PAGE_SIZE; const page = Math.max(1, Math.floor(request.page ?? 1) || 1); const requested = Math.floor(request.pageSize ?? defaultPageSize) || defaultPageSize; const pageSize = Math.min(Math.max(1, requested), maxPageSize); return { page, pageSize, skip: (page - 1) * pageSize, take: pageSize }; } export function buildPaginationMeta( total: number, page: number, pageSize: number, ): PaginationMeta { const totalPages = pageSize > 0 ? Math.ceil(total / pageSize) : 0; return { page, pageSize, total, totalPages, hasNextPage: page < totalPages, hasPreviousPage: page > 1, }; } /** * Apply skip/take to a query builder, run it, and wrap the result in the * shared `PaginatedResponse` envelope. Ordering and filtering must already be * applied by the caller. */ export async function paginateQuery( qb: SelectQueryBuilder, request: PageRequest, options?: PaginationOptions, ): Promise> { const { page, pageSize, skip, take } = normalizePagination(request, options); const [items, total] = await qb.skip(skip).take(take).getManyAndCount(); return { items, meta: buildPaginationMeta(total, page, pageSize) }; } /** * Paginate an already-materialized array. Prefer `paginateQuery` (DB-level * LIMIT/OFFSET); use this only for lists that are inherently in-memory. */ export function paginateArray( rows: readonly T[], request: PageRequest, options?: PaginationOptions, ): PaginatedResponse { const { page, pageSize, skip } = normalizePagination(request, options); return { items: rows.slice(skip, skip + pageSize), meta: buildPaginationMeta(rows.length, page, pageSize), }; }