first commit
This commit is contained in:
105
docs/foundation/data/pagination/src/cursor/cursor.md
Normal file
105
docs/foundation/data/pagination/src/cursor/cursor.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Cursor Pagination
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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<T>` in one call.
|
||||
- `createConnection` — build a Relay-style `Connection<T>` 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<T>(edges, hasMore, direction?, totalCount?)`
|
||||
|
||||
| Parameter | Type | Default |
|
||||
|-------------|-----------------------------|-------------|
|
||||
| `edges` | `Edge<T>[]` | — |
|
||||
| `hasMore` | `boolean` | — |
|
||||
| `direction` | `"forward" \| "backward"` | `"forward"` |
|
||||
| `totalCount` | `number \| undefined` | — |
|
||||
|
||||
Returns `CursorPaginationMeta`.
|
||||
|
||||
### `createCursorResponse<T>(edges, hasMore, direction?, totalCount?)`
|
||||
|
||||
Returns `CursorPaginatedResponse<T>` — the `data` field contains the unwrapped nodes.
|
||||
|
||||
### `createConnection<T>(edges, hasMore, direction?, totalCount?)`
|
||||
|
||||
Returns `Connection<T>` — the Relay-style form with `edges`, `pageInfo`, and optional `totalCount`.
|
||||
|
||||
### `createEdge<T>(node, cursor)`
|
||||
|
||||
Returns `Edge<T>` — `{ node, cursor }`.
|
||||
|
||||
### `createEdges<T>(nodes, getCursor)`
|
||||
|
||||
Maps `nodes` to `Edge<T>[]` 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<T>(items, request, getCursor?)`
|
||||
|
||||
Slices `items` according to `cursor`, `limit`, and `direction`. Default cursor extractor is `(_, i) => String(i)`. Returns `CursorPaginatedResponse<T>`.
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user