Paginazione a cursore
Paginazione a cursore in avanti (§1.2) - cursori opachi, limiti di limit, marcatore di fine lista e ordinamento stabile.
Ogni endpoint di lista usa la paginazione a cursore in avanti. Invece dei numeri di pagina (che si rompono quando gli elementi vengono inseriti o eliminati), il server restituisce un nextCursor opaco che codifica il punto esatto di continuazione.
Parametri della richiesta
limit- numero massimo di elementi da restituire. Intero, da 1 a 100 (default25). I valori fuori dall’intervallo restituiscono400 VALIDATION(envelope v2 - vedi errors).cursor- stringa opaca restituita dalla risposta precedente. Omettila alla prima richiesta.
Forma della risposta
{
"data": [ /* up to `limit` items */ ],
"nextCursor": "eyJpZCI6ImtfN2Y4YSIsIl90cyI6MTcyMzAwMDAwMH0",
"hasMore": true
}nextCursor- passalo invariato alla richiesta successiva. Non fare il parsing, non decodificarlo, non modificarlo. È base64url di uno stato interno al server e il formato può cambiare.hasMore-falsesegnala la fine della lista;nextCursorsarànull.
Ordinamento
Tutti gli endpoint paginati ordinano per orario di creazione (dal più recente) e risolvono i pareggi per ID di documento. Gli elementi inseriti durante l’iterazione appaiono in testa e non vengono osservati da uno scan in corso. Le eliminazioni vengono saltate silenziosamente.
Esempio completo
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"])Marcatore di fine lista
Lo scan termina quando hasMore === false. Un array data vuoto con hasMore: false significa che la collezione è vuota (o che tutti gli elementi rimanenti sono stati filtrati). Non ciclare mai fino a nextCursor === null senza controllare il flag hasMore.
Anti-pattern
- Non decodificare i cursori per estrarre gli ID - usa
GET /:idse ti serve un record specifico. - Non mettere in cache i cursori tra processi o attraverso le revoche delle chiavi API.
- Non modificare la stringa del cursore; passala verbatim.