@lexnode/legal-data-sdk 1.29.0 → 1.31.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/README.md +222 -183
- package/dist/client.d.ts +217 -24
- package/dist/client.js +6 -103
- package/dist/errors.d.ts +86 -41
- package/dist/errors.js +1 -49
- package/dist/freshness.d.ts +88 -41
- package/dist/freshness.js +0 -47
- package/dist/index.d.ts +12 -8
- package/dist/index.js +1 -11
- package/dist/lookup.d.ts +130 -37
- package/dist/lookup.js +63 -37
- package/dist/paginate.d.ts +15 -5
- package/dist/paginate.js +0 -30
- package/dist/search.d.ts +57 -23
- package/dist/search.js +0 -17
- package/dist/types.d.ts +77 -68
- package/dist/watch.d.ts +78 -43
- package/dist/watch.js +0 -35
- package/dist/webhooks.d.ts +184 -93
- package/dist/webhooks.js +0 -65
- package/package.json +6 -3
- package/schema.d.ts +558 -518
package/README.md
CHANGED
|
@@ -5,20 +5,20 @@ processos e movimentações, publicações dos diários oficiais, jurisprudênci
|
|
|
5
5
|
monitores e webhooks.
|
|
6
6
|
|
|
7
7
|
O pacote traz **os tipos gerados do contrato OpenAPI** e um **cliente de
|
|
8
|
-
runtime** construído sobre eles:
|
|
8
|
+
runtime** construído sobre eles: chamadas com os cabeçalhos certos, erros
|
|
9
9
|
classificados, verificação de assinatura de webhook, espera de busca
|
|
10
|
-
assíncrona, leitura do
|
|
10
|
+
assíncrona, leitura do frescor da fonte e a vigília de um processo para vários
|
|
11
11
|
interessados.
|
|
12
12
|
|
|
13
13
|
- **Sem dependências de runtime.** `fetch` global e `node:crypto`.
|
|
14
14
|
- **Node >= 20**, ESM.
|
|
15
15
|
|
|
16
|
-
> ⚠️ **
|
|
17
|
-
> no browser ela
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
> backend
|
|
21
|
-
>
|
|
16
|
+
> ⚠️ **Só no servidor, e só por TLS.** A credencial é uma chave de **conta**:
|
|
17
|
+
> no browser ela fica exposta, e em `http://` qualquer um no caminho a lê —
|
|
18
|
+
> uma `baseUrl` sem https é recusada no construtor (exceto no host local, para
|
|
19
|
+
> desenvolvimento). Um app com usuários finais chama o próprio backend, e o
|
|
20
|
+
> backend chama a API, atribuindo cada chamada ao sub-tenant dela (um
|
|
21
|
+
> escritório, um usuário final) pelo cabeçalho `X-Office-Id`.
|
|
22
22
|
|
|
23
23
|
## Instalação
|
|
24
24
|
|
|
@@ -34,33 +34,33 @@ import { createLexNodeClient } from "@lexnode/legal-data-sdk";
|
|
|
34
34
|
const lexnode = createLexNodeClient({
|
|
35
35
|
baseUrl: "https://api.lexnode.com.br",
|
|
36
36
|
apiKey: process.env.LEXNODE_API_KEY!,
|
|
37
|
-
//
|
|
37
|
+
// Escritório padrão; cada chamada pode trocá-lo.
|
|
38
38
|
officeId: "escritorio-01",
|
|
39
39
|
timeoutMs: 30_000,
|
|
40
40
|
userAgent: "meu-app/1.0",
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
const { data, meta } = await lexnode.processes.get("00012345620268260100", {
|
|
44
|
-
tribunalAlias: "
|
|
44
|
+
tribunalAlias: "TJSP",
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
console.log(data.formattedNumber, meta.credits, meta.rateLimit?.remaining);
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Toda chamada
|
|
51
|
-
quando
|
|
52
|
-
|
|
53
|
-
é o do contrato mais `status` (o código HTTP) e `rateLimit` (os
|
|
54
|
-
`RateLimit-*`, lidos **
|
|
50
|
+
Toda chamada envia `Authorization: Bearer`, `X-Request-Id`, `X-Trace-Id` e,
|
|
51
|
+
quando houver, `X-Office-Id`; respeita o prazo **e** o `signal` que você
|
|
52
|
+
passar, o que vier primeiro; e devolve `{ data, meta }` tipados pela rota. O
|
|
53
|
+
`meta` é o do contrato mais `status` (o código HTTP) e `rateLimit` (os
|
|
54
|
+
cabeçalhos `RateLimit-*`, lidos **também no sucesso**, e o `Retry-After` da
|
|
55
|
+
recusa).
|
|
55
56
|
|
|
56
|
-
Em `rateLimit`, use `remaining` para desacelerar
|
|
57
|
-
cabeçalho `RateLimit-Reset
|
|
58
|
-
|
|
57
|
+
Em `rateLimit`, use `remaining` para desacelerar. `resetSeconds` repete o
|
|
58
|
+
cabeçalho `RateLimit-Reset`, que tem dois sentidos: na recusa, os segundos até
|
|
59
|
+
a janela reabrir; no sucesso, a duração da janela (60 s).
|
|
59
60
|
|
|
60
61
|
O pacote **não valida a resposta em tempo de execução**: `data` e `meta` chegam
|
|
61
|
-
tipados pelo contrato
|
|
62
|
-
|
|
63
|
-
dado alimenta um banco, por exemplo — valida o que consome.
|
|
62
|
+
tipados pelo contrato da versão instalada. Se o dado alimenta um banco, por
|
|
63
|
+
exemplo, valide o que consumir.
|
|
64
64
|
|
|
65
65
|
### Métodos nomeados e a chamada genérica
|
|
66
66
|
|
|
@@ -80,8 +80,8 @@ await lexnode.webhooks.unsubscribedEventTypes();
|
|
|
80
80
|
await lexnode.usage.get();
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Qualquer rota do contrato
|
|
84
|
-
|
|
83
|
+
Qualquer rota do contrato, inclusive as que não têm método nomeado, sai pela
|
|
84
|
+
chamada genérica, tipada por `paths`:
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
const { data } = await lexnode.request("/v1/precedents", "get", {
|
|
@@ -91,9 +91,9 @@ const { data } = await lexnode.request("/v1/precedents", "get", {
|
|
|
91
91
|
|
|
92
92
|
### Páginas
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
A API pagina de três jeitos: cursor no `meta`, cursor no `data`, e página e
|
|
95
|
+
tamanho ou deslocamento e limite. `paginate` percorre qualquer um deles, até
|
|
96
|
+
`maxPages` páginas (padrão 100) — cada página é uma chamada:
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
99
|
import { paginate } from "@lexnode/legal-data-sdk";
|
|
@@ -116,7 +116,7 @@ try {
|
|
|
116
116
|
error.class; // "RETRYABLE" | "TERMINAL"
|
|
117
117
|
error.retryable;
|
|
118
118
|
error.status;
|
|
119
|
-
error.upstreamStatus; // o status
|
|
119
|
+
error.upstreamStatus; // o status que a fonte respondeu, quando informado
|
|
120
120
|
error.requestId; // o que o suporte pede
|
|
121
121
|
error.fieldErrors; // recusa de validação, por campo
|
|
122
122
|
error.retryAfterSeconds;
|
|
@@ -126,17 +126,17 @@ try {
|
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Prazo esgotado vira `CLIENT_TIMEOUT` (504, retentável); falha de rede vira
|
|
129
|
-
`CLIENT_NETWORK_ERROR` (502, retentável); resposta fora do
|
|
130
|
-
`INVALID_RESPONSE` (502) com o
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
`CLIENT_NETWORK_ERROR` (502, retentável); resposta fora do formato da API vira
|
|
130
|
+
`INVALID_RESPONSE` (502), com o início do corpo na mensagem; e o pedido que não
|
|
131
|
+
se monta — cabeçalho com quebra de linha, corpo que não vira JSON — vira
|
|
132
|
+
`CLIENT_INVALID_REQUEST` (400, **não** retentável), porque não chegou a ser
|
|
133
|
+
enviado.
|
|
134
134
|
|
|
135
135
|
`LEXNODE_ERROR_CODES` traz o catálogo para o `switch`, e `LexNodeErrorCode`
|
|
136
|
-
mantém o autocompletar
|
|
137
|
-
|
|
138
|
-
existe na fonte) quanto para `MOVEMENTS_NOT_AVAILABLE` (existe, mas as
|
|
139
|
-
movimentações não
|
|
136
|
+
mantém o autocompletar e aceita um código novo da API. Na leitura de processo
|
|
137
|
+
isso importa: a API responde 404 tanto para `PROCESS_NOT_FOUND` (o processo não
|
|
138
|
+
existe na fonte) quanto para `MOVEMENTS_NOT_AVAILABLE` (ele existe, mas as
|
|
139
|
+
movimentações não vieram) — trate os dois pelo `code`.
|
|
140
140
|
|
|
141
141
|
### Retentativa (opcional, desligada por padrão)
|
|
142
142
|
|
|
@@ -148,19 +148,22 @@ const lexnode = createLexNodeClient({
|
|
|
148
148
|
});
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
Repete só o que é `RETRYABLE`, respeitando `Retry-After
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
declara
|
|
151
|
+
Repete só o que é `RETRYABLE`, respeitando `Retry-After`: quando a recusa pede
|
|
152
|
+
uma espera maior que `maxDelayMs` (8 s por padrão), como a da manutenção
|
|
153
|
+
declarada pela fonte, ela é lançada sem repetição, e `retryAfterSeconds` diz
|
|
154
|
+
quando vale tentar de novo. `GET` e `DELETE` são repetidos por definição do
|
|
155
|
+
HTTP. Uma **escrita** só é repetida quando o contrato declara a repetição
|
|
156
|
+
segura — hoje `POST /v1/publications/text-searches`, que usa o `requestId` que
|
|
157
|
+
você fixar como chave de reenvio — ou quando você mesmo o declara na chamada:
|
|
156
158
|
|
|
157
159
|
```ts
|
|
158
160
|
await lexnode.webhooks.endpoints.create(corpo, { idempotent: true });
|
|
159
161
|
```
|
|
160
162
|
|
|
161
|
-
A
|
|
162
|
-
endpoints para a mesma URL entregam cada evento em dobro, e o segredo do
|
|
163
|
-
primeiro
|
|
163
|
+
A declaração é sua: sem ela, um `POST` repetido cria um **segundo** recurso —
|
|
164
|
+
dois endpoints para a mesma URL entregam cada evento em dobro, e o segredo do
|
|
165
|
+
primeiro se perde. Nas outras rotas, o `requestId` só serve para rastrear a
|
|
166
|
+
chamada.
|
|
164
167
|
|
|
165
168
|
A exceção é o `UPSTREAM_TIMEOUT` da leitura de um processo, que a política não
|
|
166
169
|
repete — ver [a seção da fonte lenta](#ler-um-processo-quando-a-fonte-está-lenta).
|
|
@@ -173,15 +176,16 @@ import {
|
|
|
173
176
|
webhookRejectionStatus,
|
|
174
177
|
} from "@lexnode/legal-data-sdk";
|
|
175
178
|
|
|
176
|
-
// O corpo
|
|
179
|
+
// O corpo como chegou, nunca um JSON serializado de novo: a assinatura vale
|
|
180
|
+
// para os bytes.
|
|
177
181
|
const resultado = parseWebhookEvent(
|
|
178
182
|
rawBody,
|
|
179
183
|
request.headers,
|
|
180
184
|
process.env.LEXNODE_WEBHOOK_SIGNING_SECRET!,
|
|
181
185
|
);
|
|
182
186
|
|
|
183
|
-
//
|
|
184
|
-
//
|
|
187
|
+
// Cada recusa tem o seu status: a assinatura que não confere é 401; o segredo
|
|
188
|
+
// ausente no seu receptor é 503.
|
|
185
189
|
if (!resultado.ok) {
|
|
186
190
|
return reply.status(webhookRejectionStatus(resultado.reason)).send();
|
|
187
191
|
}
|
|
@@ -210,22 +214,22 @@ switch (resultado.event.type) {
|
|
|
210
214
|
}
|
|
211
215
|
break;
|
|
212
216
|
case "PROCESS_LOOKUP_COMPLETED":
|
|
213
|
-
//
|
|
217
|
+
// Aponta a consulta: o processo se lê em payload.pollUrl, sem nova cobrança.
|
|
214
218
|
resultado.event.payload.lookupId;
|
|
215
219
|
break;
|
|
216
220
|
case "WEBHOOK_TEST":
|
|
217
|
-
// A entrega de teste
|
|
218
|
-
//
|
|
221
|
+
// A entrega de teste (`webhooks.endpoints.test` ou o portal). Chega a
|
|
222
|
+
// qualquer endpoint sem constar de `eventTypes`; use-a para validar o
|
|
223
|
+
// receptor antes do primeiro evento.
|
|
219
224
|
break;
|
|
220
225
|
default:
|
|
221
226
|
break;
|
|
222
227
|
}
|
|
223
228
|
```
|
|
224
229
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
deduplicação e todas as seguintes parecerem repetição dela.
|
|
230
|
+
Entrega sem `id` ou sem `occurredAt` é recusada como `INVALID_ENVELOPE`. A
|
|
231
|
+
mesma entrega pode chegar mais de uma vez: use o `id` para descartar a
|
|
232
|
+
repetida.
|
|
229
233
|
|
|
230
234
|
`WEBHOOK_EVENT_TYPES` é o catálogo **assinável** (o que entra em `eventTypes`
|
|
231
235
|
ao cadastrar um endpoint) e não inclui `WEBHOOK_TEST`, que chega sem ser
|
|
@@ -234,22 +238,20 @@ pedido — use `isWebhookTest(event)` para separá-lo.
|
|
|
234
238
|
`verifyWebhookSignature(rawBody, headers, secret)` faz só a conferência e
|
|
235
239
|
devolve o motivo da recusa (`MISSING_SIGNATURE`, `MALFORMED_SIGNATURE`,
|
|
236
240
|
`TIMESTAMP_MISMATCH`, `TIMESTAMP_OUT_OF_TOLERANCE`, `SIGNATURE_MISMATCH`,
|
|
237
|
-
`RAW_BODY_REQUIRED`),
|
|
238
|
-
diferentes virarem a mesma resposta.
|
|
241
|
+
`RAW_BODY_REQUIRED`), para você tratar cada caso.
|
|
239
242
|
|
|
240
|
-
O corpo entra como `string` ou `Buffer
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
O corpo entra como `string` ou `Buffer`, como chegou. Um objeto já convertido
|
|
244
|
+
de JSON é recusado como `RAW_BODY_REQUIRED`: serializar de novo muda a ordem
|
|
245
|
+
das chaves, e a assinatura deixa de conferir.
|
|
243
246
|
|
|
244
|
-
A
|
|
245
|
-
cabeçalho `x-lexnode-timestamp`
|
|
246
|
-
|
|
247
|
-
ausência dele um motivo de recusa, porque ela não prova nada.
|
|
247
|
+
A tolerância do instante e o digest usam o `t` que vem **dentro** da
|
|
248
|
+
assinatura. O cabeçalho `x-lexnode-timestamp` repete esse valor: quando vem,
|
|
249
|
+
precisa ser igual; quando falta, não é motivo de recusa.
|
|
248
250
|
|
|
249
251
|
## Busca síncrona ou assíncrona
|
|
250
252
|
|
|
251
253
|
`POST /v1/processes/search` responde **200** com o resultado pronto ou **202**
|
|
252
|
-
com
|
|
254
|
+
com uma busca enfileirada. O SDK diz qual foi, no tipo:
|
|
253
255
|
|
|
254
256
|
```ts
|
|
255
257
|
import { searchProcesses, waitForSearchJob } from "@lexnode/legal-data-sdk";
|
|
@@ -259,9 +261,8 @@ const desfecho = await searchProcesses(lexnode, { tribunalAlias, number });
|
|
|
259
261
|
if (desfecho.kind === "sync") {
|
|
260
262
|
desfecho.result.items;
|
|
261
263
|
} else {
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// SEARCH_JOB_COMPLETED.
|
|
264
|
+
// Espere com orçamento — num ambiente serverless, dentro do prazo da
|
|
265
|
+
// função — ou receba o SEARCH_JOB_COMPLETED por webhook.
|
|
265
266
|
const job = await waitForSearchJob(lexnode, desfecho.job.jobId, {
|
|
266
267
|
budgetMs: 20_000,
|
|
267
268
|
});
|
|
@@ -272,30 +273,30 @@ if (desfecho.kind === "sync") {
|
|
|
272
273
|
## Um número, várias instâncias
|
|
273
274
|
|
|
274
275
|
A fonte processual guarda um documento por instância do processo — 1º grau, 2º
|
|
275
|
-
grau, turma recursal… —, às vezes dois do mesmo grau, e o
|
|
276
|
-
na sua instância.
|
|
276
|
+
grau, turma recursal… —, às vezes dois do mesmo grau, e o LexNode guarda cada
|
|
277
|
+
um na sua instância.
|
|
277
278
|
|
|
278
279
|
**O topo não troca.** O topo da ficha (`data`) e do retrato (`data.process` e
|
|
279
280
|
`data.movements`) é sempre a instância principal do número, com o grau em
|
|
280
281
|
`degree`: a mesma de uma leitura para a outra, qualquer que seja a ordem em que
|
|
281
282
|
a fonte devolve os documentos. Ela só muda quando uma resposta completa da
|
|
282
|
-
fonte pelo número deixa de trazê-la.
|
|
283
|
-
|
|
283
|
+
fonte pelo número deixa de trazê-la. Se você compara movimentos pela chave, o
|
|
284
|
+
histórico de outro grau não aparece como novo no topo — e o topo pode não ser o
|
|
284
285
|
grau que anda: o 2º grau guardado primeiro continua no topo enquanto o 1º se
|
|
285
286
|
move.
|
|
286
287
|
|
|
287
288
|
**Todos os graus em `instances`.** A ficha e o retrato listam em `instances`
|
|
288
|
-
todos os documentos
|
|
289
|
+
todos os documentos guardados do número, a principal primeiro
|
|
289
290
|
(`primary: true`). Cada um diz de onde veio nesta resposta (`origin`: `SOURCE`
|
|
290
|
-
quando a fonte o trouxe, `LAKE` quando
|
|
291
|
-
(`fetchedAt`) e desde quando a resposta completa da fonte vem sem ele
|
|
292
|
-
(`absentSince`). No retrato, cada instância traz os movimentos da última
|
|
293
|
-
dela, do mais recente ao mais antigo e sem o payload bruto da fonte; a
|
|
294
|
-
|
|
295
|
-
com lista vazia, que diria
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
lista.
|
|
291
|
+
quando a fonte o trouxe, `LAKE` quando veio da cópia guardada), quando foi
|
|
292
|
+
buscado (`fetchedAt`) e desde quando a resposta completa da fonte vem sem ele
|
|
293
|
+
(`absentSince`). No retrato, cada instância traz os movimentos da última
|
|
294
|
+
captura dela, do mais recente ao mais antigo e sem o payload bruto da fonte; a
|
|
295
|
+
instância cujos movimentos nunca foram capturados vem com
|
|
296
|
+
`movementsCaptured: false` e `movements` nulo, nunca com lista vazia, que diria
|
|
297
|
+
que o grau não andou. Todos os documentos levam a classificação do número
|
|
298
|
+
inteiro — basta um em segredo de justiça para todos saírem marcados —, e a
|
|
299
|
+
ficha retida pelo controle de confidencialidade sai sem a lista.
|
|
299
300
|
|
|
300
301
|
```ts
|
|
301
302
|
const { data } = await lexnode.processes.snapshot(numero, { tribunalAlias });
|
|
@@ -309,9 +310,9 @@ for (const instancia of data.instances) {
|
|
|
309
310
|
|
|
310
311
|
No lote (`processes.snapshotBatch`), `instances` traz só o cabeçalho de cada
|
|
311
312
|
documento; os movimentos de cada instância vêm no retrato de um processo. A
|
|
312
|
-
busca por número completo
|
|
313
|
-
fonte ainda traz do número, a principal primeiro; quando eles
|
|
314
|
-
`limit`, a busca vai à fonte, que pagina.
|
|
313
|
+
busca por número completo respondida com os dados guardados devolve um item por
|
|
314
|
+
documento que a fonte ainda traz do número, a principal primeiro; quando eles
|
|
315
|
+
não cabem em `limit`, a busca vai à fonte, que pagina.
|
|
315
316
|
|
|
316
317
|
**Recorte por grau.** A lista de movimentações aceita `degree` e devolve só os
|
|
317
318
|
movimentos que o documento daquele grau trouxe na última captura; cada item diz
|
|
@@ -327,21 +328,21 @@ data.items; // só o que o 1º grau trouxe
|
|
|
327
328
|
```
|
|
328
329
|
|
|
329
330
|
**O grau novo chega num evento só.** Quando outro grau de um processo assinado
|
|
330
|
-
aparece na fonte, o histórico dele
|
|
331
|
-
`PROCESS_MOVEMENT_CREATED
|
|
332
|
-
|
|
333
|
-
`
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
331
|
+
aparece na fonte, o histórico dele não sai movimento a movimento em
|
|
332
|
+
`PROCESS_MOVEMENT_CREATED`: chega um `PROCESS_INSTANCE_DISCOVERED`, com o grau,
|
|
333
|
+
o órgão julgador e as contagens — `historyMovementCount` não saiu em aviso
|
|
334
|
+
próprio, `announcedMovementCount` saiu também como movimento novo. O
|
|
335
|
+
`PROCESS_MOVEMENT_CREATED` diz o grau do movimento mais recente (`degree`,
|
|
336
|
+
`instanceSourceId`) e quantos movimentos novos vieram de cada grau
|
|
337
|
+
(`instances`). Assine os dois: só com o movimento, você não fica sabendo do
|
|
338
|
+
grau que apareceu.
|
|
338
339
|
|
|
339
340
|
**`meta.otherInstances`: o resumo do que veio.** Quando a resposta da fonte traz
|
|
340
341
|
outros documentos do número além da principal, `meta.otherInstances` resume cada
|
|
341
342
|
um com o grau (`grau`), a data em que a fonte o atualizou (`updatedAt`) e o
|
|
342
343
|
órgão julgador (`courtBody`, quando vem). A lista não sai para processo restrito
|
|
343
|
-
e não inclui documento
|
|
344
|
-
|
|
344
|
+
e não inclui documento em segredo de justiça; os documentos inteiros estão em
|
|
345
|
+
`instances`.
|
|
345
346
|
|
|
346
347
|
```ts
|
|
347
348
|
for (const outra of meta.otherInstances ?? []) {
|
|
@@ -350,24 +351,24 @@ for (const outra of meta.otherInstances ?? []) {
|
|
|
350
351
|
```
|
|
351
352
|
|
|
352
353
|
**Continuação.** Na API nada é cortado: o retrato traz os movimentos guardados
|
|
353
|
-
de cada instância, e a lista de movimentações lê
|
|
354
|
-
à fonte. No canal para agentes (MCP),
|
|
355
|
-
resposta: de cada grau vêm os movimentos
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
ou o acompanha.
|
|
354
|
+
de cada instância, e a lista de movimentações lê os movimentos guardados, grau a
|
|
355
|
+
grau, sem ir à fonte. No canal para agentes (MCP), que não passa por este
|
|
356
|
+
pacote, `consultar_processo` cabe numa resposta: de cada grau vêm os movimentos
|
|
357
|
+
mais recentes, o grau que se moveu por último primeiro, e a mesma tool com
|
|
358
|
+
`grau` e `pagina` traz os seguintes, por 1 crédito e sem ir à fonte — só para
|
|
359
|
+
quem já leu o processo ou o acompanha.
|
|
359
360
|
|
|
360
361
|
## Ler um processo quando a fonte está lenta
|
|
361
362
|
|
|
362
|
-
A ficha (`processes.get`) vai à fonte processual quando
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
perto de um minuto por consulta. `waitSeconds` diz quanto
|
|
366
|
-
|
|
363
|
+
A ficha (`processes.get`) vai à fonte processual quando não há uma cópia recente
|
|
364
|
+
guardada; o retrato (`processes.snapshot`) vai sempre. Os dois só respondem com
|
|
365
|
+
a cópia guardada quando a fonte falha (seção seguinte). A fonte às vezes leva
|
|
366
|
+
perto de um minuto por consulta. `waitSeconds` diz quanto a API espera por ela:
|
|
367
|
+
de 10 a 180 segundos, padrão 20.
|
|
367
368
|
|
|
368
369
|
```ts
|
|
369
370
|
const { data, meta } = await lexnode.processes.get(numero, {
|
|
370
|
-
tribunalAlias: "
|
|
371
|
+
tribunalAlias: "TJSP",
|
|
371
372
|
waitSeconds: 120,
|
|
372
373
|
});
|
|
373
374
|
```
|
|
@@ -384,27 +385,27 @@ chamada genérica (`request`) nessas duas rotas.
|
|
|
384
385
|
duas leituras é lentidão da fonte, não ausência do processo, e a política de
|
|
385
386
|
retentativa não o repete: a espera já correu inteira, e repetir na hora a
|
|
386
387
|
dobraria diante da mesma fonte lenta. As outras recusas retentáveis seguem a
|
|
387
|
-
política. Para insistir, a decisão é sua — uma nova chamada com espera maior,
|
|
388
|
-
|
|
388
|
+
política. Para insistir, a decisão é sua — uma nova chamada com espera maior, a
|
|
389
|
+
leitura que segue em segundo plano (`readProcessWithRecovery`, abaixo) ou a
|
|
390
|
+
vigília do processo (`watchProcess`).
|
|
389
391
|
|
|
390
392
|
**`meta.partialIndex`: o processo veio de parte do índice.** Quando a fonte
|
|
391
393
|
responde com só parte do índice e o processo estava na parte que respondeu, ele
|
|
392
394
|
é servido, e `meta.partialIndex` diz quantas partes falharam (`shardsFailed`),
|
|
393
395
|
de quantas (`shardsTotal`, nulo quando a fonte não as contou) e se o prazo
|
|
394
|
-
interno dela estourou (`timedOut`). O documento
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
retentável, nunca 404.
|
|
396
|
+
interno dela estourou (`timedOut`). O documento já guardado que não veio sai em
|
|
397
|
+
`instances` com `origin: "LAKE"` e a data dele, e outro grau ainda não guardado,
|
|
398
|
+
se existir, pode estar na parte que não respondeu. A resposta parcial sem o
|
|
399
|
+
processo é 502 `UPSTREAM_UNAVAILABLE` retentável, nunca 404.
|
|
399
400
|
|
|
400
401
|
```ts
|
|
401
402
|
if (meta.partialIndex) {
|
|
402
|
-
//
|
|
403
|
+
// Um grau deste número ainda não guardado pode não ter vindo.
|
|
403
404
|
}
|
|
404
405
|
```
|
|
405
406
|
|
|
406
407
|
Antes de recusar a resposta parcial que veio sem o processo porque partes do
|
|
407
|
-
índice ficaram fora,
|
|
408
|
+
índice ficaram fora, a API consulta a fonte de novo — até duas vezes, com uma
|
|
408
409
|
pausa curta, enquanto a espera couber.
|
|
409
410
|
|
|
410
411
|
**Agentes têm a espera deles.** No canal para agentes (MCP), as tools
|
|
@@ -415,7 +416,7 @@ mesma faixa e com padrão de 120 s; esse canal não passa pelo SDK.
|
|
|
415
416
|
|
|
416
417
|
Quando a fonte processual falha — não responde dentro de `waitSeconds`, fica
|
|
417
418
|
indisponível, responde com parte do índice sem poder servir o processo ou pede
|
|
418
|
-
para esperar por volume — e
|
|
419
|
+
para esperar por volume — e há uma cópia guardada do processo, a ficha
|
|
419
420
|
(`processes.get`) e o retrato (`processes.snapshot`) respondem com essa cópia em
|
|
420
421
|
vez do erro. A resposta diz que é cópia, e de quando:
|
|
421
422
|
|
|
@@ -423,38 +424,39 @@ vez do erro. A resposta diz que é cópia, e de quando:
|
|
|
423
424
|
`UPSTREAM_UNAVAILABLE` ou `UPSTREAM_RATE_LIMITED`;
|
|
424
425
|
- `meta.lakeRescue.retryAfterSeconds`, repetido no header `Retry-After`, diz na
|
|
425
426
|
recusa por volume quanto esperar antes de consultar a fonte de novo. É a
|
|
426
|
-
espera
|
|
427
|
-
fica `null` no sucesso;
|
|
427
|
+
espera pedida pela fonte, não pelo limite de vazão da API:
|
|
428
|
+
`meta.rateLimit.retryAfterSeconds` fica `null` no sucesso;
|
|
428
429
|
- `meta.sourceFetchedAt` diz quando a cópia foi buscada na fonte. Esse campo
|
|
429
|
-
acompanha toda resposta das duas leituras,
|
|
430
|
-
que o controle de confidencialidade entrega retida, sem o teor.
|
|
430
|
+
acompanha toda resposta das duas leituras, venha ela da fonte ou da cópia,
|
|
431
|
+
menos a ficha que o controle de confidencialidade entrega retida, sem o teor.
|
|
431
432
|
|
|
432
433
|
A cópia custa 1 crédito, na ficha e no retrato. O retrato confere o saldo pelo
|
|
433
434
|
preço cheio, 8, antes de ir à fonte; a ficha confere pelo preço do que
|
|
434
435
|
entregou, depois da leitura. No retrato guardado, cada instância traz os
|
|
435
|
-
movimentos da última vez que
|
|
436
|
+
movimentos da última vez que ela foi buscada, com a data em
|
|
436
437
|
`instances[].fetchedAt`, e `sourcePayload` é nulo: o que andou depois dessas
|
|
437
438
|
datas não está na resposta. O número que a resposta completa mais recente da
|
|
438
|
-
fonte já não trazia em documento nenhum não
|
|
439
|
-
data vale para os dados do processo, e o resumo das publicações
|
|
440
|
-
relido na hora.
|
|
439
|
+
fonte já não trazia em documento nenhum não tem cópia servida. Na ficha
|
|
440
|
+
guardada, a data vale para os dados do processo, e o resumo das publicações
|
|
441
|
+
(`djen`) é relido na hora.
|
|
441
442
|
|
|
442
|
-
**Até que idade.** Sem pedir nada, só a cópia de até 24 h
|
|
443
|
-
nessas duas rotas, uma resposta de sucesso já
|
|
444
|
-
desse prazo. A cópia de qualquer idade vem com
|
|
443
|
+
**Até que idade.** Sem pedir nada, só a cópia de até 24 h responde na falha da
|
|
444
|
+
fonte — nessas duas rotas, uma resposta de sucesso já pode ser uma cópia
|
|
445
|
+
guardada dentro desse prazo. A cópia de qualquer idade vem com
|
|
446
|
+
`staleOnFailure: true`:
|
|
445
447
|
|
|
446
448
|
```ts
|
|
447
449
|
import { describeFreshness, lakeRescueOf } from "@lexnode/legal-data-sdk";
|
|
448
450
|
|
|
449
451
|
const retrato = await lexnode.processes.snapshot(numero, {
|
|
450
|
-
tribunalAlias: "
|
|
452
|
+
tribunalAlias: "TJSP",
|
|
451
453
|
staleOnFailure: true,
|
|
452
454
|
});
|
|
453
455
|
|
|
454
|
-
const
|
|
455
|
-
if (
|
|
456
|
+
const copia = lakeRescueOf(retrato);
|
|
457
|
+
if (copia) {
|
|
456
458
|
// A fonte falhou e isto é a cópia guardada: o que andou depois de
|
|
457
|
-
//
|
|
459
|
+
// copia.sourceFetchedAt não está aqui.
|
|
458
460
|
mostrar(describeFreshness(retrato));
|
|
459
461
|
// "cópia guardada em 26/09/2026, servida porque a fonte não respondeu a tempo"
|
|
460
462
|
}
|
|
@@ -465,16 +467,17 @@ if (socorro) {
|
|
|
465
467
|
devolve a data de qualquer resposta dessas leituras. As duas aceitam o envelope
|
|
466
468
|
inteiro e o item do lote.
|
|
467
469
|
|
|
468
|
-
**
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
confidencialidade da leitura; além de 24 h, só a recebe
|
|
472
|
-
registrado no processo — de uma leitura anterior dele ou de uma
|
|
473
|
-
ativa —, e
|
|
470
|
+
**Quando a cópia não é servida.** A resposta da fonte que não traz o processo segue 404
|
|
471
|
+
`PROCESS_NOT_FOUND`, com cópia guardada ou sem ela: é a fonte dizendo que não
|
|
472
|
+
tem o número, e não uma falha dela. A cópia passa pelo mesmo controle de
|
|
473
|
+
confidencialidade da leitura; além de 24 h, só a recebe a conta que já tem
|
|
474
|
+
interesse registrado no processo — de uma leitura anterior dele ou de uma
|
|
475
|
+
assinatura ativa —, e as demais recebem a falha da fonte, como se não houvesse
|
|
476
|
+
cópia.
|
|
474
477
|
|
|
475
|
-
**No lote.** `processes.snapshotBatch`
|
|
476
|
-
|
|
477
|
-
de 8.
|
|
478
|
+
**No lote.** `processes.snapshotBatch` só usa a cópia de até 24 h. O item
|
|
479
|
+
respondido com ela traz `lakeRescue`, com a causa e a data, e custa 1 crédito
|
|
480
|
+
em vez de 8.
|
|
478
481
|
|
|
479
482
|
## Leitura que não coube na espera
|
|
480
483
|
|
|
@@ -488,13 +491,46 @@ em segundo plano em vez de virar erro:
|
|
|
488
491
|
`PROCESS`) ou `snapshot` (visão `SNAPSHOT`), com as instâncias do número em
|
|
489
492
|
`instances`, o mesmo `meta` e o mesmo preço da leitura direta;
|
|
490
493
|
- **202**: a fonte falhou — não respondeu a tempo, ficou indisponível ou pediu
|
|
491
|
-
para esperar —, e
|
|
492
|
-
tentativa, por até 6 h (`giveUpAt`). Nada é cobrado na hora: o preço
|
|
493
|
-
leitura fica retido em `creditsReserved`, o máximo que a entrega cobra, e
|
|
494
|
+
para esperar —, e a API abriu uma consulta que segue tentando sozinha, até
|
|
495
|
+
60 s por tentativa, por até 6 h (`giveUpAt`). Nada é cobrado na hora: o preço
|
|
496
|
+
da leitura fica retido em `creditsReserved`, o máximo que a entrega cobra, e
|
|
494
497
|
só é cobrado se o processo vier. Se não vier, a retenção volta;
|
|
495
498
|
- qualquer outra recusa é a da leitura direta: 404 quando a fonte diz não ter o
|
|
496
499
|
processo, 403 no segredo de justiça, 402 sem saldo.
|
|
497
500
|
|
|
501
|
+
Para uma integração que precisa continuar mesmo quando a fonte demora, use
|
|
502
|
+
`readProcessWithRecovery`. A aceitação da cópia antiga é explícita, e a espera
|
|
503
|
+
total é limitada. O fim da espera devolve `pending`, com a mesma consulta e a
|
|
504
|
+
próxima leitura útil, sem cancelar a consulta nem abrir outra:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
import { readProcessWithRecovery } from "@lexnode/legal-data-sdk";
|
|
508
|
+
|
|
509
|
+
const leitura = await readProcessWithRecovery(lexnode, {
|
|
510
|
+
tribunalAlias: "TJSP",
|
|
511
|
+
processNumber: numeroDoProcesso,
|
|
512
|
+
view: "PROCESS",
|
|
513
|
+
waitSeconds: 5,
|
|
514
|
+
staleOnFailure: true, // aceita cópia anterior à falha: confira sourceFetchedAt
|
|
515
|
+
}, { completionBudgetMs: 30_000 });
|
|
516
|
+
|
|
517
|
+
if (leitura.kind === "ready") {
|
|
518
|
+
usarProcesso(leitura.data, leitura.meta.sourceFetchedAt);
|
|
519
|
+
} else if (leitura.kind === "pending") {
|
|
520
|
+
guardarConsulta(leitura.lookup.lookupId, leitura.nextReadAt);
|
|
521
|
+
// leitura.stale, quando presente, conserva a data da cópia.
|
|
522
|
+
// Continue pelo pollUrl ou pelo webhook PROCESS_LOOKUP_COMPLETED.
|
|
523
|
+
} else {
|
|
524
|
+
registrarDesfecho(leitura.kind, leitura.lookup.error);
|
|
525
|
+
// failed, canceled ou expired; nenhum deles afirma que o processo não existe.
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Sem `completionBudgetMs`, a função devolve na hora o desfecho da abertura (200
|
|
530
|
+
ou 202). O orçamento cobre só o acompanhamento, e cada chamada mantém o próprio
|
|
531
|
+
prazo. Abortar a espera não cancela a consulta. O custo, o controle de
|
|
532
|
+
confidencialidade e a cobrança única são os de `processLookups.create`.
|
|
533
|
+
|
|
498
534
|
`lookupProcess` diz, no tipo, qual dos dois desfechos aconteceu, e
|
|
499
535
|
`waitForProcessLookup` espera a consulta terminar:
|
|
500
536
|
|
|
@@ -502,7 +538,7 @@ em segundo plano em vez de virar erro:
|
|
|
502
538
|
import { lookupProcess, waitForProcessLookup } from "@lexnode/legal-data-sdk";
|
|
503
539
|
|
|
504
540
|
const desfecho = await lookupProcess(lexnode, {
|
|
505
|
-
tribunalAlias: "
|
|
541
|
+
tribunalAlias: "TJSP",
|
|
506
542
|
processNumber: numero,
|
|
507
543
|
view: "SNAPSHOT",
|
|
508
544
|
});
|
|
@@ -510,9 +546,9 @@ const desfecho = await lookupProcess(lexnode, {
|
|
|
510
546
|
if (desfecho.kind === "ready") {
|
|
511
547
|
desfecho.data.movements; // o retrato, como processes.snapshot o devolveria
|
|
512
548
|
} else {
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
// uma tentativa em curso
|
|
549
|
+
// Espere com orçamento. A consulta é lida de novo na hora da próxima
|
|
550
|
+
// tentativa (nextAttemptAt), quando o estado pode mudar, e a cada 10 s
|
|
551
|
+
// enquanto uma tentativa está em curso.
|
|
516
552
|
const consulta = await waitForProcessLookup(
|
|
517
553
|
lexnode,
|
|
518
554
|
desfecho.lookup.lookupId!,
|
|
@@ -532,22 +568,23 @@ chamada repetida tenta a fonte na hora, dentro da espera dela. O tempo
|
|
|
532
568
|
esgotado da fonte continua sem repetição automática.
|
|
533
569
|
|
|
534
570
|
**A consulta se lê de graça, por 24 h.** `processLookups.get(lookupId)` custa 0
|
|
535
|
-
e, depois da entrega, traz o processo por 24 h (`resultExpiresAt`), relido
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
completam a superfície; o cancelamento devolve a
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
571
|
+
e, depois da entrega, traz o processo por 24 h (`resultExpiresAt`), relido e
|
|
572
|
+
passado de novo pelo controle de confidencialidade: o processo que passou a
|
|
573
|
+
segredo de justiça depois responde 403. O retrato lido da consulta traz a
|
|
574
|
+
instância principal no topo e cada instância com os movimentos da última
|
|
575
|
+
captura dela, e `sourcePayload` é nulo. `processLookups.list` e
|
|
576
|
+
`processLookups.cancel` completam a superfície; o cancelamento devolve a
|
|
577
|
+
retenção e não gera evento.
|
|
578
|
+
|
|
579
|
+
**Por webhook, sem esperar nada.** O fim sai em `PROCESS_LOOKUP_COMPLETED`, que
|
|
580
|
+
aponta a consulta e nunca traz o processo; o movimento novo que a entrega
|
|
581
|
+
trouxer chega no `PROCESS_MOVEMENT_CREATED` de sempre.
|
|
545
582
|
|
|
546
583
|
**A cópia guardada e a atualização, uma compra só.** Com `staleOnFailure: true`,
|
|
547
|
-
o 202 traz em `stale` a cópia
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
584
|
+
o 202 traz em `stale` a cópia guardada, com a data e a causa, por 1 crédito, e
|
|
585
|
+
a consulta segue buscando a versão atual. A entrega cobra só o resto do preço
|
|
586
|
+
(`alreadyCharged` diz quanto já foi): as duas juntas custam o mesmo que a
|
|
587
|
+
leitura direta, e a atualização que não vier não cobra mais nada.
|
|
551
588
|
|
|
552
589
|
**A leitura direta fecha a consulta.** A ficha ou o retrato do mesmo processo
|
|
553
590
|
lidos com sucesso por `processes.get` ou `processes.snapshot` fecham a consulta
|
|
@@ -560,8 +597,8 @@ consulta.
|
|
|
560
597
|
## Frescor: o `total: 0` qualificado
|
|
561
598
|
|
|
562
599
|
Um resultado vazio não distingue sozinho "não existe" de "a fonte ainda não
|
|
563
|
-
indexou".
|
|
564
|
-
(processo e diário):
|
|
600
|
+
indexou". A API publica a data-limite do índice da fonte; o SDK a lê nos dois
|
|
601
|
+
eixos (processo e diário):
|
|
565
602
|
|
|
566
603
|
```ts
|
|
567
604
|
import {
|
|
@@ -577,15 +614,15 @@ if (isNotYetIndexed(busca, { since: "2026-09-15" })) {
|
|
|
577
614
|
mostrar(describeFreshness(busca)); // "fonte lida até 10/09/2026"
|
|
578
615
|
}
|
|
579
616
|
|
|
580
|
-
readThrough(busca); // Date | null — null
|
|
617
|
+
readThrough(busca); // Date | null — null quando a resposta não declara a data-limite
|
|
581
618
|
```
|
|
582
619
|
|
|
583
620
|
## Vigília com vários interessados
|
|
584
621
|
|
|
585
|
-
|
|
586
|
-
conta e
|
|
587
|
-
`externalRef`, sem
|
|
588
|
-
um
|
|
622
|
+
A vigília de um processo tem duas partes: o **monitor**, que consulta a fonte —
|
|
623
|
+
um por processo na conta, e o plano limita quantos —, e a **assinatura**, que
|
|
624
|
+
recebe o aviso — uma por `externalRef`, sem limite, e o `externalRef` volta no
|
|
625
|
+
evento. N interessados no mesmo processo são um monitor e N assinaturas.
|
|
589
626
|
|
|
590
627
|
```ts
|
|
591
628
|
import { unwatchProcess, watchProcess } from "@lexnode/legal-data-sdk";
|
|
@@ -594,23 +631,26 @@ const { monitorId, monitorAlreadyExisted, subscription } = await watchProcess(
|
|
|
594
631
|
lexnode,
|
|
595
632
|
{
|
|
596
633
|
processNumber: "00012345620268260100",
|
|
597
|
-
tribunalAlias: "
|
|
634
|
+
tribunalAlias: "TJSP",
|
|
598
635
|
officeId: "usuario-4711",
|
|
599
636
|
externalRef: "caso-42",
|
|
600
637
|
},
|
|
601
638
|
);
|
|
602
639
|
|
|
603
|
-
// Um interessado
|
|
640
|
+
// Um interessado sai; o monitor continua ativo para os demais.
|
|
604
641
|
await unwatchProcess(lexnode, {
|
|
605
642
|
externalRef: "caso-42",
|
|
606
643
|
officeId: "usuario-4711",
|
|
607
644
|
});
|
|
608
645
|
```
|
|
609
646
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
647
|
+
Quando o último interessado sair, remova o monitor com
|
|
648
|
+
`lexnode.monitors.delete(monitorId)`.
|
|
649
|
+
|
|
650
|
+
**Limitação conhecida:** uma assinatura criada **antes** de o processo aparecer
|
|
651
|
+
na fonte não recebe `PROCESS_MOVEMENT_CREATED` até a primeira aparição dele. O
|
|
652
|
+
contorno é refazer a assinatura quando chegar o `MONITOR_SYNC_SUCCEEDED` com
|
|
653
|
+
`firstAppearance: true`:
|
|
614
654
|
|
|
615
655
|
```ts
|
|
616
656
|
import { resubscribeOnFirstAppearance } from "@lexnode/legal-data-sdk";
|
|
@@ -620,10 +660,9 @@ await resubscribeOnFirstAppearance(lexnode, evento, [
|
|
|
620
660
|
]);
|
|
621
661
|
```
|
|
622
662
|
|
|
623
|
-
##
|
|
663
|
+
## Os tipos do contrato
|
|
624
664
|
|
|
625
|
-
O
|
|
626
|
-
sempre:
|
|
665
|
+
O pacote exporta `paths` e `operations`, gerados do contrato OpenAPI:
|
|
627
666
|
|
|
628
667
|
```ts
|
|
629
668
|
import type { operations, paths } from "@lexnode/legal-data-sdk";
|
|
@@ -636,7 +675,7 @@ type Tribunals =
|
|
|
636
675
|
operations["listTribunals"]["responses"][200]["content"]["application/json"]["data"];
|
|
637
676
|
```
|
|
638
677
|
|
|
639
|
-
E
|
|
678
|
+
E atalhos derivados deles:
|
|
640
679
|
|
|
641
680
|
```ts
|
|
642
681
|
import type { Body, Data, Query } from "@lexnode/legal-data-sdk";
|
|
@@ -648,11 +687,11 @@ type Tribunais = Data<"/v1/tribunals", "get">;
|
|
|
648
687
|
|
|
649
688
|
## A chave de teste
|
|
650
689
|
|
|
651
|
-
A chave `lapi_test_`
|
|
652
|
-
cria trabalho
|
|
653
|
-
respondem 403 `SANDBOX_UPSTREAM_DISABLED`.
|
|
654
|
-
`async` de `searchProcesses` só
|
|
655
|
-
|
|
690
|
+
A chave `lapi_test_` relê o que a conta já buscou com a chave de produção, mas
|
|
691
|
+
**não consulta a fonte nem cria trabalho em segundo plano**: monitor, vigília,
|
|
692
|
+
colheita e busca assíncrona respondem 403 `SANDBOX_UPSTREAM_DISABLED`. Por isso
|
|
693
|
+
`watchProcess` e o ramo `async` de `searchProcesses` só funcionam com a chave
|
|
694
|
+
de produção.
|
|
656
695
|
|
|
657
696
|
## Referências
|
|
658
697
|
|