Alpha. Vary is under active development and not ready for production use. Syntax, APIs, performance, and behaviour may change between releases.

paging

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

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.

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.