Standard Library

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.

← Collections
Scanner →