@riligar/contract 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +1 -1
- package/src/respostas.js +57 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riligar/contract",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "O contrato de API da stack RiLiGar: envelope \u00fanico, cat\u00e1logo de c\u00f3digos de erro e os helpers que tornam imposs\u00edvel responder fora dele.",
|
package/src/index.js
CHANGED
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
// requisito legítimo daquele caminho — declarado no export map, não escondido
|
|
23
23
|
// numa cadeia de reexports.
|
|
24
24
|
export { CODES, STATUS_BY_CODE, RETRIABLE, isCode, toCode, codeFromStatus, ALIASES } from './codes.js'
|
|
25
|
-
export { ok, criado, colecao, falha, falhaComStatus, corpoDeFalha, paginacao } from './respostas.js'
|
|
25
|
+
export { ok, criado, colecao, falha, falhaComStatus, corpoDeFalha, paginacao, pagina } from './respostas.js'
|
package/src/respostas.js
CHANGED
|
@@ -48,17 +48,42 @@ export const criado = (recurso, { headers } = {}) => responder(recurso, 201, hea
|
|
|
48
48
|
*
|
|
49
49
|
* `hasMore` é explícito de propósito: o consumidor não deveria precisar
|
|
50
50
|
* comparar `offset + limit` com `total` para saber se continua paginando.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
*
|
|
52
|
+
* ────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
* `total: null` QUANDO NINGUÉM CONTOU — e isso é o ponto deste helper.
|
|
54
|
+
*
|
|
55
|
+
* Até 10/09 os defaults preenchiam `page` a partir do próprio array: uma rota
|
|
56
|
+
* que NUNCA paginou devolvia `{limit: N, offset: 0, total: N, hasMore: false}`,
|
|
57
|
+
* byte a byte idêntico ao de uma rota que paginou e chegou ao fim. Das 39
|
|
58
|
+
* rotas de coleção da stack, 29 não paginavam e todas as 29 afirmavam
|
|
59
|
+
* `hasMore: false` — o campo que existe para o agente não ter de deduzir
|
|
60
|
+
* deduzia errado em 87% dos casos.
|
|
61
|
+
*
|
|
62
|
+
* Agora: quem não passa `total` recebe `total: null` e `hasMore: null`. Nulo é
|
|
63
|
+
* "não sei", e é diferente de zero e de falso. O agente que lê `total: null`
|
|
64
|
+
* sabe que precisa perguntar de outro jeito; o que lê `hasMore: false` para de
|
|
65
|
+
* paginar — e essas duas conclusões não podem sair da mesma resposta.
|
|
66
|
+
*
|
|
67
|
+
* Quem pagina de verdade passa `total` e recebe `hasMore` calculado. Quem
|
|
68
|
+
* devolve a lista inteira de propósito passa `total: itens.length`, e aí o
|
|
69
|
+
* `hasMore: false` é uma afirmação, não um default.
|
|
53
70
|
*/
|
|
54
71
|
export const colecao = (itens, pagina = {}, { status = 200, headers } = {}) => {
|
|
55
72
|
const items = Array.isArray(itens) ? itens : []
|
|
56
73
|
const limit = pagina.limit ?? items.length
|
|
57
74
|
const offset = pagina.offset ?? 0
|
|
58
|
-
const total = pagina.total ?? offset + items.length
|
|
59
|
-
const hasMore = pagina.hasMore ?? offset + items.length < total
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
// `null` só quando NINGUÉM contou. `total: 0` é uma contagem legítima.
|
|
77
|
+
const total = pagina.total ?? null
|
|
78
|
+
const hasMore = pagina.hasMore ?? (total === null ? null : offset + items.length < total)
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* `extra` vai na RAIZ, não dentro de `page`. O `activeDeployId` do Hoster e
|
|
82
|
+
* as `contagens` do Monitors são contexto do CONJUNTO, não paginação —
|
|
83
|
+
* dentro de `page` eles diriam que são, e um cliente genérico que lê
|
|
84
|
+
* `page` para paginar tropeçaria neles.
|
|
85
|
+
*/
|
|
86
|
+
return responder({ items, page: { limit, offset, total, hasMore }, ...(pagina.extra ?? {}) }, status, headers)
|
|
62
87
|
}
|
|
63
88
|
|
|
64
89
|
/**
|
|
@@ -118,6 +143,33 @@ export const paginacao = (url, { limitPadrao = 50, limitMaximo = 200 } = {}) =>
|
|
|
118
143
|
return { limit, offset }
|
|
119
144
|
}
|
|
120
145
|
|
|
146
|
+
/**
|
|
147
|
+
* A página de uma consulta paginada, pronta para o `colecao`.
|
|
148
|
+
*
|
|
149
|
+
* Existe para tornar MECÂNICO o que a stack errava à mão em duas frentes:
|
|
150
|
+
*
|
|
151
|
+
* 1. **O `limit` reportado tem de ser o APLICADO.** Duas rotas clampavam na
|
|
152
|
+
* camada de dados e reportavam o valor cru da query: com `?limit=5000`
|
|
153
|
+
* devolviam 200 linhas e diziam `page:{limit:5000, hasMore:false}`. O
|
|
154
|
+
* cliente conclui "pedi 5000, vieram 200, logo acabou" — e para de paginar
|
|
155
|
+
* no meio.
|
|
156
|
+
*
|
|
157
|
+
* 2. **`total` vem de quem contou.** Passar `total` é obrigatório aqui, porque
|
|
158
|
+
* o `colecao` sozinho não tem como saber se ninguém contou ou se a conta
|
|
159
|
+
* deu o tamanho do array.
|
|
160
|
+
*
|
|
161
|
+
* const { limit, offset } = paginacao(url)
|
|
162
|
+
* const linhas = await buscar(limit, offset)
|
|
163
|
+
* const [{ total }] = await contar()
|
|
164
|
+
* return colecao(linhas, pagina({ limit, offset, total }))
|
|
165
|
+
*/
|
|
166
|
+
export const pagina = ({ limit, offset = 0, total, extra }) => {
|
|
167
|
+
if (!Number.isFinite(total)) {
|
|
168
|
+
throw new TypeError('pagina() exige `total` numérico — use colecao(itens) direto quando ninguém contou.')
|
|
169
|
+
}
|
|
170
|
+
return { limit, offset, total, extra }
|
|
171
|
+
}
|
|
172
|
+
|
|
121
173
|
/**
|
|
122
174
|
* Uma falha de que só se sabe o status HTTP.
|
|
123
175
|
*
|