Pagination par curseur
Pagination par curseur en avant (§1.2) - curseurs opaques, bornes de limit, marqueur de fin de liste et ordre stable.
Chaque endpoint de liste utilise la pagination par curseur en avant. Au lieu de numéros de page (qui cassent lorsque des éléments sont insérés ou supprimés), le serveur renvoie un nextCursor opaque qui encode le point de continuation exact.
Paramètres de requête
limit- nombre maximal d’éléments à renvoyer. Entier, 1 à 100 (défaut25). Les valeurs hors de la plage renvoient400 VALIDATION(enveloppe v2 - voir errors).cursor- chaîne opaque renvoyée par la réponse précédente. Omettez-la à la première requête.
Forme de la réponse
{
"data": [ /* up to `limit` items */ ],
"nextCursor": "eyJpZCI6ImtfN2Y4YSIsIl90cyI6MTcyMzAwMDAwMH0",
"hasMore": true
}nextCursor- passez-le tel quel à la requête suivante. Ne le parsez pas, ne le décodez pas, ne le modifiez pas. C’est du base64url d’un état interne au serveur et le format peut changer.hasMore-falsesignale la fin de la liste ;nextCursorseranull.
Ordre
Tous les endpoints paginés ordonnent par heure de création (le plus récent d’abord) et départagent les égalités par ID de document. Les éléments insérés pendant l’itération apparaissent en tête et ne sont pas observés par un scan en cours. Les suppressions sont silencieusement ignorées.
Exemple complet
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"])Marqueur de fin de liste
Le scan se termine lorsque hasMore === false. Un tableau data vide avec hasMore: false signifie que la collection est vide (ou que tous les éléments restants ont été filtrés). Ne bouclez jamais jusqu’à nextCursor === null sans vérifier le flag hasMore.
Anti-patterns
- Ne décodez pas les curseurs pour en extraire des IDs - utilisez
GET /:idsi vous avez besoin d’un enregistrement spécifique. - Ne mettez pas en cache les curseurs entre processus ou à travers les révocations de clé API.
- Ne modifiez pas la chaîne du curseur ; passez-la verbatim.