Traducción de
docs/en/runtime/list-contract.md, que es el original. Si discrepan, manda el inglés.
Todos los listados del plano de control hablan un mismo contrato. Un cliente lo aprende una vez, y un endpoint nuevo no puede inventarse el suyo.
GET /applications?page=2&pageSize=25&sort=-createdAt,name&search=ana&filter[state]=captured
| Parámetro | Por defecto | Qué significa |
|---|---|---|
page |
1 |
Empieza en 1 |
pageSize |
25, tope 200 |
Un tamaño sin tope es una denegación de servicio accidental sobre cualquier tabla con historia detrás |
sort |
según el endpoint | Separado por comas; un - delante es descendente |
search |
— | Texto libre, sin distinguir mayúsculas, sobre los campos que el endpoint abre |
filter[campo] |
— | Coincidencia exacta o por subcadena, según declare el endpoint |
Todos los listados responden con el mismo sobre:
{ "data": [ … ], "meta": { "page": 2, "pageSize": 25, "total": 314, "lastPage": 13 } }
#Listas blancas, no comodidades
Cada endpoint declara un ListSpec: qué campos se pueden ordenar, cuáles filtrar
y cómo, cuáles se buscan, y el orden que se usa cuando quien llama no pide
ninguno.
No es ceremonia. Un sort abierto deja a cualquiera ordenar por una columna sin
índice en una tabla con historia detrás, y un filter abierto le deja sondear
campos que el endpoint nunca quiso exponer. Un campo no declarado es un error, no
un parámetro ignorado en silencio:
| Error | Causa |
|---|---|
unsortable_field |
sort nombró un campo que el endpoint no abrió, y la respuesta lista los que sí |
unfilterable_field |
filter[…] nombró un campo que el endpoint no abrió |
#Dos propiedades que es fácil perder
Un listado nunca va sin orden. Cuando quien llama no pide ninguno, aplica el
del endpoint — y la clave primaria se añade siempre al final. Ordenar por una
columna con empates deja las filas empatadas en el orden en que la base las
encontró, y ese orden no es estable entre dos consultas, así que la página 2 de un
listado ordenado por name puede repetir una fila de la página 1 y saltarse otra
entera. Un desempate único es lo que hace que paginar signifique algo.
Un filtro estrecha y nunca ensancha. El alcance propio del endpoint —la aplicación a la que pertenece un listado, las filas que quien llama puede ver— se interseca con los filtros. Un filtro no puede ser una salida del inquilino. Ver identidad y alcance.
#Listados que no son tablas
Los escenarios propios y el catálogo de proveedores viven en código, no en la base. Se paginan con el mismo ayudante sobre un array, con las mismas listas blancas aplicadas igual, porque un cliente no debería tener que distinguir esos listados del resto.
#El lado de la consola
useEntityCollection implementa la mitad de quien llama, una vez: parámetros,
carga, paginación, ordenación, filtrado, búsqueda y sincronía opcional con la URL,
para que una vista filtrada sea un enlace.
core/data/serialization/list-params.serializer.ts mapea su forma a la consulta
de arriba — los filtros van anidados y no esparcidos en el nivel superior, para
que un filtro llamado search, page o sort no pueda chocar con el contrato.
Una columna que dice sortable tiene que nombrar un campo que el endpoint
declaró ordenable, o la API responde unsortable_field y el operador se entera.
Que las dos concuerden no se repite en el fichero de columnas a propósito:
una segunda copia de la regla podría discrepar de la primera.
#Dónde vive
apps/api/src/core/http/list-query.dto.ts— la forma de la consulta y el sobre.apps/api/src/core/http/paginate.ts— las listas blancas, el desempate y la variante sobre arrays.apps/console/app/core/composables/collection/useEntityCollection.ts— la mitad de quien llama.apps/api/test/integration/list-contract.spec.ts— lo que lo fija.