# Cursor Pagination _(no submodules)_ ## Purpose The cursor module provides the in-memory building blocks for Relay-style cursor-based pagination. It offers utilities for constructing edges, connections, and metadata from any sorted data source without requiring a database adapter. All functions are pure — they accept plain arrays and return plain objects — making them trivially testable and completely portable across runtimes. Cursor-based pagination is preferred over offset pagination for large datasets or real-time feeds because it avoids the "jumping rows" problem: items inserted or deleted between page fetches cannot corrupt a cursor-based window. ## Features - `calculateCursorMeta` — compute `CursorPaginationMeta` (start/end cursor, hasNextPage, hasPreviousPage, optional totalCount) from an edge array. - `createCursorResponse` — build a `CursorPaginatedResponse` in one call. - `createConnection` — build a Relay-style `Connection` with `edges` and `pageInfo`. - `createEdge` / `createEdges` — wrap nodes with their cursor strings. - `normalizeCursorRequest` — clamp limit, default direction, from raw input. - `cursorPaginateArray` — paginate an in-memory array with forward/backward cursor support. ## Usage: Basic Example ```typescript import { cursorPaginateArray } from "@suaveplan/pagination/cursor"; const items = [{ id: "a" }, { id: "b" }, { id: "c" }, { id: "d" }]; // First page — no cursor, limit 2 const page1 = cursorPaginateArray(items, { limit: 2 }, (item) => item.id); // page1.data → [{ id: "a" }, { id: "b" }] // page1.meta.endCursor → "b" // page1.meta.hasNextPage → true // Next page — use endCursor from previous response const page2 = cursorPaginateArray( items, { cursor: page1.meta.endCursor ?? undefined, limit: 2 }, (item) => item.id ); // page2.data → [{ id: "c" }, { id: "d" }] // page2.meta.hasNextPage → false ``` ## Usage: Advanced Example ```typescript import { createEdges, createConnection, normalizeCursorRequest, } from "@suaveplan/pagination/cursor"; // Relay-style connection with totalCount const users = [ { id: "u1", name: "Alice" }, { id: "u2", name: "Bob" }, ]; const req = normalizeCursorRequest({ limit: 10 }, { maxLimit: 50 }); const edges = createEdges(users, (u) => u.id); const connection = createConnection(edges, false, "forward", 2); // connection.edges[0].cursor → "u1" // connection.pageInfo.hasNextPage → false // connection.totalCount → 2 ``` ## API Reference ### `calculateCursorMeta(edges, hasMore, direction?, totalCount?)` | Parameter | Type | Default | |-------------|-----------------------------|-------------| | `edges` | `Edge[]` | — | | `hasMore` | `boolean` | — | | `direction` | `"forward" \| "backward"` | `"forward"` | | `totalCount` | `number \| undefined` | — | Returns `CursorPaginationMeta`. ### `createCursorResponse(edges, hasMore, direction?, totalCount?)` Returns `CursorPaginatedResponse` — the `data` field contains the unwrapped nodes. ### `createConnection(edges, hasMore, direction?, totalCount?)` Returns `Connection` — the Relay-style form with `edges`, `pageInfo`, and optional `totalCount`. ### `createEdge(node, cursor)` Returns `Edge` — `{ node, cursor }`. ### `createEdges(nodes, getCursor)` Maps `nodes` to `Edge[]` using the provided cursor extractor. The extractor receives `(node, index)`. ### `normalizeCursorRequest(input, defaults?)` Clamps `limit` to `[1, maxLimit]` (default `maxLimit = 100`). Defaults `direction` to `"forward"`. Returns a full `CursorPaginationRequest`. ### `cursorPaginateArray(items, request, getCursor?)` Slices `items` according to `cursor`, `limit`, and `direction`. Default cursor extractor is `(_, i) => String(i)`. Returns `CursorPaginatedResponse`. ## Implementation Notes All cursor positions are string values matched by strict equality. The default extractor uses array indices as cursors; callers are encouraged to pass a stable domain-key extractor (e.g., `item.id`) in production. For backward pagination, `startIndex` is clamped to `0` to avoid negative slices. The `hasNextPage`/`hasPreviousPage` logic follows the GraphQL Relay Cursor Connections Specification.