@ferrow/pagination-helper
v2.0.0
Published
Cursor and offset pagination over in-memory arrays, with opaque base64url cursors, tamper detection, and a keyset (seek) helper for pagination that stays stable under concurrent inserts. Zero runtime dependencies.
Maintainers
Readme
pagination-helper
Cursor and offset pagination over in-memory arrays for TypeScript/Node,
plus a keyset ("seek") helper for pagination that stays stable when items
are inserted mid-walk. Cursors are opaque base64url tokens with tamper
detection — a corrupted or hand-edited cursor throws a clear error
instead of silently misbehaving. Zero runtime dependencies (the base64url
codec is hand-rolled, not Buffer-based, so it also works outside Node).
Install
Copy src/index.ts into your project, or build this repo (npm run build)
and depend on the compiled dist/.
Quickstart
import { paginate } from 'pagination-helper';
let page = paginate(items, { pageSize: 20 });
// page.items, page.nextCursor, page.prevCursor, page.totalPages, page.totalItems
const page2 = paginate(items, { cursor: page.nextCursor, pageSize: 20 });Keyset (seek) pagination
Offset pagination shifts every subsequent page by one whenever an item is inserted or removed earlier in the collection. Keyset pagination walks by comparing a sort key instead of counting offsets, so it stays stable:
import { keysetPaginate } from 'pagination-helper';
// sortedItems must already be sorted ascending by keyFn, with unique keys
let page = keysetPaginate(sortedItems, (item) => item.id, null, 20);
const nextPage = keysetPaginate(sortedItems, (item) => item.id, page.nextCursor, 20);Tamper detection
import { InvalidCursorError, paginate } from 'pagination-helper';
try {
paginate(items, { cursor: 'garbage', pageSize: 20 });
} catch (e) {
if (e instanceof InvalidCursorError) { /* bad base64, bad JSON, or wrong shape */ }
}API
paginate(items: T[], { cursor?, pageSize }): PageResult<T>—{ items, nextCursor, prevCursor, totalPages, totalItems, pageSize }.cursorencodes an offset; omit/nullfor page 1.keysetPaginate(sortedItems: T[], keyFn: (item: T) => string | number, cursor, pageSize): KeysetPageResult<T>—{ items, nextCursor, prevCursor }.sortedItemsmust be sorted ascending bykeyFnwith unique key values.class InvalidCursorError extends Error— thrown by both functions when a cursor fails to base64url-decode, fails to JSON-parse, or decodes to the wrong shape.
Scope and limits
- Operates on arrays already fully loaded in memory — it does not issue
database queries. For a real keyset-paginated DB query you still write
the
WHERE key > ? ORDER BY key LIMIT ?yourself; this library gives you the cursor encode/decode and index-walking logic around it. keysetPaginaterequires the input already sorted ascending bykeyFnwith unique keys — it does not sort or dedupe for you, and behavior is undefined (not validated) if that precondition is violated.- Cursors encode an offset or a sort-key value, not a snapshot of the
page contents —
paginate's offset cursor is still susceptible to items shifting between pages if the underlying array changes (that's exactly the problemkeysetPaginatesolves).
Sponsored by Ferrow
Part of the ferrow-toolkit collection · Sponsored by Ferrow
