/** Pure pagination math shared by server components, query helpers and tests (§295 URL state). */ export interface PageInfo { /** 1-based current page, clamped into [1, pageCount]. */ page: number; pageSize: number; total: number; pageCount: number; /** SQL OFFSET for the clamped page. */ offset: number; /** 1-based index of the first row shown (0 when total = 0). */ from: number; /** 1-based index of the last row shown (0 when total = 0). */ to: number; hasPrev: boolean; hasNext: boolean; } export function pageCount(total: number, pageSize: number): number { if (!Number.isFinite(total) || total <= 0) return 1; const size = Math.max(1, Math.floor(pageSize)); return Math.max(1, Math.ceil(total / size)); } /** Clamp a requested page into the valid range for `total` rows (always ≥ 1). */ export function clampPage(page: number, total: number, pageSize: number): number { const n = Number.isFinite(page) ? Math.floor(page) : 1; return Math.min(pageCount(total, pageSize), Math.max(1, n)); } export function offsetFor(page: number, pageSize: number): number { return (Math.max(1, Math.floor(page)) - 1) * Math.max(1, Math.floor(pageSize)); } export function pageInfo(page: number, pageSize: number, total: number): PageInfo { const size = Math.max(1, Math.floor(pageSize)); const count = pageCount(total, size); const p = clampPage(page, total, size); const from = total === 0 ? 0 : (p - 1) * size + 1; const to = total === 0 ? 0 : Math.min(total, p * size); return { page: p, pageSize: size, total, pageCount: count, offset: (p - 1) * size, from, to, hasPrev: p > 1, hasNext: p < count }; } /** * Compact list of page numbers to show as links: first, last, current ±1, with `null` gaps. * e.g. page 7 of 20 → [1, null, 6, 7, 8, null, 20]. */ export function pageWindow(page: number, count: number, radius = 1): Array { if (count <= 1) return [1]; const want = new Set([1, count]); for (let i = page - radius; i <= page + radius; i++) if (i >= 1 && i <= count) want.add(i); const sorted = [...want].sort((a, b) => a - b); const out: Array = []; for (let i = 0; i < sorted.length; i++) { const cur = sorted[i]!; const prev = sorted[i - 1]; if (prev != null && cur - prev === 2) out.push(prev + 1); else if (prev != null && cur - prev > 2) out.push(null); out.push(cur); } return out; }