Query — Pagination & streaming
query() caps at limit per call. For anything bigger, you have three
options: offset pagination (fine for admin UIs), cursor pagination (right for
deep scroll), and stream.query() (right for exports).
offset / limit
The obvious shape. Pick a page size, advance the offset.
When to use it: admin tables with a page picker, the user is unlikely to scroll past page 5.
When NOT to: data that changes between requests (rows shift under you; some rows appear twice or get skipped), or pages ≥ ~1000 (offset gets slower with depth — the bridge still has to count-and-discard everything above).
Cursor
Opaque, stable, deep-scroll-safe.
When to use it: any page-2-and-beyond scroll, infinite scroll lists, backend jobs that walk a whole table.
The cursor encodes the current sort position — so keep orderBy and where
identical across calls, or the cursor is meaningless.
stream.query for large reads
For a full export or a multi-page walk, stream.query() is the simplest. One
call, one WebSocket, N rows:
When to use it: full-table walks, CSV exports, batch jobs that fan out to another system.
Protocol: one row frame per matching row, terminated by one done frame
carrying { count, durationMs }. Errors arrive as error frames with a
code — see HTTP & WebSocket for the full table.
Choosing
| Need | Use |
|---|---|
| Admin UI with page links | offset + limit |
| Infinite scroll / deep pages | cursor |
| Export or batch job | stream.query |
| Server-sent updates as rows change | stream.subscribe — different primitive, same lens |
Gotchas
limitinstream.queryis the total cap, not the per-batch size.batchSizeis a hint to the server for how big eachrowframe burst should be — the server may ignore it.- The bridge closes the cursor when the stream ends. If you abort mid-stream, the bridge cleans up on its side — no leaked transactions.
grants.stream.querygates streaming separately fromgrants.query. A lens can expose one without the other.
Next: Recipes.