Paginación y rangos de tiempo
Cómo recorrer las entregas y los registros de una colección con un cursor opaco, y por qué una toma un rango de tiempo y los otros no.
Dos endpoints paginan, y lo hacen de forma distinta a propósito:
| Listado | Rango | Orden | El cursor está ligado a |
|---|---|---|---|
| Entregas | from y to, obligatorios, 366 días como máximo | De más reciente a más antigua | El rango que pidió |
| Registros de una colección | ninguno: recorre la colección entera | Del cambio más antiguo al más reciente | Restaurante, colección y limit |
Ambos responden { "data": [...], "nextCursor": string | null }, ambos topan
limit en 100 con 50 por defecto, y ambos quieren que se les devuelva el cursor
intacto. El resto de esta página es el listado de entregas; la
página de colecciones cubre el otro.
Las entregas son inmutables e interesan por fecha, así que toman un rango. Los registros de una colección cambian sobre el sitio, así que un rango sobre ellos sería una mentira: usted recorre la colección y concilia.
El listado de entregas
Toma un rango de tiempo explícito y obligatorio y devuelve una página más un cursor opaco.
GET /v1/restaurants/{restaurantId}/deliveries
?from=1787846400000
&to=1788451200000
&limit=50
&cursor=eyJpZCI6…
| Parámetro | Obligatorio | Reglas |
|---|---|---|
from | sí | Milisegundos desde la época Unix, como cadena de dígitos. Inclusivo. |
to | sí | Milisegundos desde la época Unix, como cadena de dígitos. Inclusivo. |
limit | no | De 1 a 100. Por defecto, 50. |
cursor | no | El nextCursor de la página anterior, tal cual. |
to debe ser mayor o igual que from, y el intervalo debe ser de 366 días
como máximo. Cualquier otra cosa responde 400. El rango es obligatorio en lugar
de tener un valor por defecto porque un valor por defecto es justo la manera en
que una integración empieza en silencio a releer una década de registros cada
noche.
Orden
De más reciente a más antigua: descendente por occurredAt, y después
descendente por id para deshacer un empate. Dos entregas registradas en el mismo
milisegundo tienen así un orden estable y repetible, que es lo que hace que el
cursor sea sólido.
Recorrer las páginas
nextCursor es null en la última página y una cadena en las demás. Devuélvalo
intacto: es una posición codificada en base64url, no un documento que analizar, y
sus interioridades cambiarán sin previo aviso.
async function* deliveries({ apiKey, restaurantId, from, to }) {
const base = `https://api.backresto.com/v1/restaurants/${restaurantId}/deliveries`;
let cursor = null;
do {
const url = new URL(base);
url.searchParams.set('from', from);
url.searchParams.set('to', to);
url.searchParams.set('limit', '100');
if (cursor !== null) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` }
});
if (!response.ok) {
throw new Error(`BackResto ${response.status}`);
}
const page = await response.json();
yield* page.data;
cursor = page.nextCursor;
} while (cursor !== null);
}
Mantenga from y to idénticos en todas las páginas de un recorrido. El cursor
codifica una posición dentro del rango que pidió; cambiar el rango a mitad del
recorrido no es una petición admitida y no le dará lo que espera.
Consultar de forma incremental
Las entregas son inmutables una vez registradas, así que una consulta incremental
es sencilla: guarde el occurredAt del registro más reciente que tenga
almacenado y úselo como el siguiente from.
Dos detalles que conviene incorporar:
- Solape a propósito. Empiece la siguiente ventana un poco antes del último
registro que vio —una hora sobra— y deduplique por
id. Un dispositivo que estuvo sin conexión durante el servicio sube los datos al reconectarse, así que una entrega puede hacerse visible después de una consulta que ya cubría su marca de tiempo. - Nunca amplíe más allá de 366 días. Si su tarea lleva mucho tiempo sin ejecutarse, recorra el hueco en ventanas de un año en lugar de pedirlo todo de golpe.
Una página vacía no es prueba de que no ocurriera nada; consulte la nota sobre exhaustividad.