
`import paging` covers cursor pagination for public API handlers. The
pattern is: fetch `limit + 1` rows, hand them to `page_slice`, and write
the result through `api_response.ok_page`. The slice carries the visible
rows, the next cursor, and a `has_more` flag the response can encode.

## Building a page slice

```text
import paging
import api_response

let rows = list_issues_query(limit + 1, cursor)
let slice = paging.page_slice(rows, limit, lambda row: IssueRow: row.id)
return api_response.ok_page(
    Json.empty_object(),
    "issues",
    json_array_map(slice.items, issue_to_json),
    limit,
    slice.next_page_token)
```

`page_slice[T](rows, limit, cursor_for)` keeps `min(limit, len(rows))`
rows. `cursor_for` produces the cursor value for the last visible row;
when `len(rows) > limit`, the slice's `has_more` flag is `true` and
`next_page_token` carries that cursor.

`page_slice_exact[T](rows, limit, cursor_for)` is the variant for queries
that already returned at most `limit` rows; it always reports
`has_more = False` and is the right helper for queries with a `LIMIT`
clause and no overfetch.

## Pairing with route `paginate`

When the route declares `paginate cursor next_page_token`, the boundary
records the cursor field on the response. The handler still produces the
cursor; the metadata is what generated clients use to drive iteration.

```text
get list_issues "/issues" query ListIssuesQuery -> IssueListResponse
    auth none
    cache public(max_age=60, s_maxage=300)
    paginate cursor next_page_token
```

For routes whose cursor is opaque (signed, hashed, or epoch-encoded),
project the cursor through a typed wrapper before returning it; the
boundary does not interpret it.
