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.
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.
paginateWhen 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.