Cursor-Paginierung
Forward-Cursor-Paginierung (§1.2) - opake Cursor, Limit-Grenzen, Ende-der-Liste-Marker und stabile Reihenfolge.
Jeder Listen-Endpunkt verwendet Forward-Cursor-Paginierung. Statt Seitenzahlen (die brechen, sobald Elemente eingefügt oder gelöscht werden), liefert der Server einen opaken nextCursor, der den exakten Fortsetzungspunkt kodiert.
Request-Parameter
limit- maximale Anzahl zurückgegebener Elemente. Ganzzahl, 1 bis 100 (Standard25). Werte außerhalb des Bereichs liefern400 VALIDATION(v2-Envelope - siehe Fehler).cursor- opaker String, der in der vorherigen Antwort zurückgegeben wurde. Bei der ersten Anfrage weglassen.
Antwort-Schema
{
"data": [ /* up to `limit` items */ ],
"nextCursor": "eyJpZCI6ImtfN2Y4YSIsIl90cyI6MTcyMzAwMDAwMH0",
"hasMore": true
}nextCursor- unverändert an die nächste Anfrage übergeben. Nicht parsen, dekodieren oder verändern. Es handelt sich um base64url eines serverinternen Zustands, dessen Format sich ändern kann.hasMore-falsesignalisiert das Ende der Liste;nextCursorist dannnull.
Reihenfolge
Alle paginierten Endpunkte sortieren nach Erstellungszeit (neueste zuerst) und brechen Gleichstände anhand der Dokument-ID. Während der Iteration eingefügte Elemente erscheinen am Anfang und werden vom laufenden Scan nicht erfasst. Löschungen werden stillschweigend übersprungen.
Vollständiges Beispiel
curl
# Page 1
curl "https://<your-deployment>.convex.site/api/v1/runs?limit=50" \
-H "Authorization: Bearer cc_live_..."
# Page 2 - use the cursor from page 1
curl "https://<your-deployment>.convex.site/api/v1/runs?limit=50&cursor=eyJpZCI6..." \
-H "Authorization: Bearer cc_live_..."TypeScript
async function* paginate<T>(path: string, key: string) {
let cursor: string | null = null;
do {
const url = new URL(`https://<your-deployment>.convex.site${path}`);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { "Authorization": `Bearer ${key}` },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = await res.json() as {
data: T[];
nextCursor: string | null;
hasMore: boolean;
};
for (const item of body.data) yield item;
cursor = body.hasMore ? body.nextCursor : null;
} while (cursor);
}
// Usage
for await (const run of paginate<{ id: string }>("/api/v1/runs", key)) {
console.log(run.id);
}Python
import os, requests
def paginate(path: str, key: str, limit: int = 100):
cursor = None
base = "https://<your-deployment>.convex.site"
while True:
params = {"limit": limit}
if cursor:
params["cursor"] = cursor
r = requests.get(
f"{base}{path}",
headers={"Authorization": f"Bearer {key}"},
params=params,
timeout=30,
)
r.raise_for_status()
body = r.json()
for item in body["data"]:
yield item
if not body.get("hasMore"):
return
cursor = body["nextCursor"]
for run in paginate("/api/v1/runs", os.environ["CC_KEY"]):
print(run["id"])Ende-der-Liste-Marker
Der Scan endet, sobald hasMore === false ist. Ein leeres data-Array mit hasMore: false bedeutet, dass die Sammlung leer ist (oder alle verbleibenden Elemente herausgefiltert wurden). Schleifen Sie niemals nur bis nextCursor === null, ohne das Flag hasMore zu prüfen.
Anti-Patterns
- Dekodieren Sie nicht die Cursor, um IDs zu extrahieren - verwenden Sie
GET /:id, wenn Sie einen bestimmten Datensatz benötigen. - Cachen Sie nicht Cursor prozess- oder API-Key-widerrufungsübergreifend.
- Verändern Sie nicht den Cursor-String; geben Sie ihn unverändert weiter.