4.3 KiB
Offset Pagination
(no submodules)
Purpose
The offset pagination module provides the computational building blocks for traditional page-number-based pagination. It handles request normalisation, metadata calculation, page-to-offset conversion, and in-memory array slicing. All functions are pure and synchronous except estimateTotalCount, which calls an adapter to sample the dataset.
Offset pagination is the correct choice when total count is cheap to compute, pages are shallow (< a few thousand rows), or the UI must render absolute page numbers. For deep pagination on large datasets, prefer keyset or cursor pagination to avoid scanning skipped rows.
Features
calculateOffsetMeta— compute full metadata (page, limit, total, totalPages, hasNext, hasPrevious).createOffsetResponse— assemble a typedOffsetPaginatedResponse<T>envelope.normalizeOffsetRequest— clamp page/limit to configured defaults, default sortOrder.pageToOffset— convert 1-based page number to a zero-based array/SQL offset.offsetToPage— convert a zero-based offset back to a 1-based page number.getPageRange— produce a windowed list of page numbers for UI navigation controls.paginateArray— slice an in-memory array and return a fullOffsetPaginatedResponse<T>.estimateTotalCount— sample-based approximate total count via adapter.
Usage: Basic Example
import { paginateArray } from "@suaveplan/pagination/offset";
const items = Array.from({ length: 50 }, (_, i) => ({ id: i + 1 }));
const page2 = paginateArray(items, 2, 10);
// page2.data → items 11–20
// page2.meta.page → 2
// page2.meta.totalPages → 5
// page2.meta.hasNext → true
// page2.meta.hasPrevious → true
Usage: Advanced Example
import {
normalizeOffsetRequest,
getPageRange,
pageToOffset,
offsetToPage,
} from "@suaveplan/pagination/offset";
// Normalise untrusted input from a query string
const req = normalizeOffsetRequest(
{ page: -5, limit: 9999 },
{ maxLimit: 100, defaultLimit: 20 }
);
// req.page → 1 (clamped), req.limit → 100 (clamped)
// Render page buttons for a 300-item result set
const pages = getPageRange(15, 30, 7);
// → [12, 13, 14, 15, 16, 17, 18]
// Convert offset 40 at limit 10 → page 5
const page = offsetToPage(40, 10); // → 5
const offset = pageToOffset(5, 10); // → 40
API Reference
calculateOffsetMeta(total, page, limit): OffsetPaginationMeta
| Parameter | Type | Description |
|---|---|---|
total |
number |
Total item count across all pages |
page |
number |
Current 1-based page number |
limit |
number |
Items per page |
Returns OffsetPaginationMeta with totalPages, hasNext, and hasPrevious.
createOffsetResponse<T>(data, total, page, limit): OffsetPaginatedResponse<T>
Wraps data and computed metadata into the standard response envelope.
normalizeOffsetRequest(input, defaults?): OffsetPaginationRequest
Clamps page to ≥ 1 and limit to [minLimit, maxLimit]. Defaults: minLimit=1, maxLimit=100, defaultLimit=20, defaultPage=1.
pageToOffset(page, limit): number
Returns Math.max(0, (page - 1) * limit).
offsetToPage(offset, limit): number
Returns Math.floor(offset / limit) + 1. Returns 1 if limit ≤ 0.
getPageRange(currentPage, totalPages, windowSize?): number[]
Default windowSize is 5. The window is centred on currentPage and never exceeds [1, totalPages].
paginateArray<T>(items, page, limit): OffsetPaginatedResponse<T>
Slices items[offset..offset+limit] and builds the full response with computed metadata.
estimateTotalCount(adapter, opts): Promise<number>
Samples up to opts.sampleSize (default 1000) items from the adapter and extrapolates. Returns 0 if the adapter reports zero total.
Implementation Notes
normalizeOffsetRequest uses Math.floor to discard fractional page/limit values from untrusted API input. The sortOrder field defaults to "desc" because most read-heavy UIs show newest items first. getPageRange is intentionally view-agnostic — it returns page numbers only; rendering (ellipsis, prev/next buttons) belongs in the UI layer.