@lexnode/legal-data-sdk 1.0.0 → 1.24.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 +385 -11
- package/dist/client.d.ts +100 -0
- package/dist/client.js +476 -0
- package/dist/errors.d.ts +94 -0
- package/dist/errors.js +217 -0
- package/dist/freshness.d.ts +48 -0
- package/dist/freshness.js +105 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +17 -0
- package/dist/paginate.d.ts +21 -0
- package/dist/paginate.js +106 -0
- package/dist/search.d.ts +64 -0
- package/dist/search.js +75 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.js +1 -0
- package/dist/watch.d.ts +93 -0
- package/dist/watch.js +95 -0
- package/dist/webhooks.d.ts +330 -0
- package/dist/webhooks.js +223 -0
- package/package.json +28 -4
- package/schema.d.ts +972 -95
package/dist/client.js
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { LexNodeError } from "./errors.js";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
4
|
+
const DEFAULT_RETRY_BASE_DELAY_MS = 500;
|
|
5
|
+
const DEFAULT_RETRY_MAX_DELAY_MS = 8_000;
|
|
6
|
+
/** Métodos cuja repetição não cria nada de novo, por definição do HTTP. */
|
|
7
|
+
const IDEMPOTENT_METHODS = new Set(["get", "delete"]);
|
|
8
|
+
/**
|
|
9
|
+
* As escritas cuja repetição o CONTRATO declara segura, com o `X-Request-Id`
|
|
10
|
+
* fixado pelo chamador.
|
|
11
|
+
*
|
|
12
|
+
* Só uma rota trata esse cabeçalho como chave de replay; nas demais ele é
|
|
13
|
+
* rastreio, e repetir cria um segundo recurso — dois endpoints de webhook para
|
|
14
|
+
* a mesma URL entregam cada evento em dobro, e o segredo do primeiro fica
|
|
15
|
+
* órfão. Por isso a decisão é pela ROTA, nunca pela presença de um id.
|
|
16
|
+
*/
|
|
17
|
+
const REQUEST_ID_IDEMPOTENT_WRITES = new Set([
|
|
18
|
+
"post /v1/publications/text-searches",
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* As leituras de UM processo em que quem chama escolhe, em `waitSeconds`,
|
|
22
|
+
* quanto o motor espera pela fonte processual.
|
|
23
|
+
*/
|
|
24
|
+
const SOURCE_WAIT_ROUTES = new Set([
|
|
25
|
+
"get /v1/processes/{processNumber}",
|
|
26
|
+
"get /v1/processes/{processNumber}/snapshot",
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* A espera que o motor aplica a essas leituras quando a chamada não traz
|
|
30
|
+
* `waitSeconds`: o padrão do parâmetro no contrato.
|
|
31
|
+
*/
|
|
32
|
+
const SOURCE_WAIT_API_DEFAULT_SECONDS = 20;
|
|
33
|
+
/**
|
|
34
|
+
* O que a chamada espera além da espera do motor: na ficha, até 10 s de
|
|
35
|
+
* enriquecimento depois da fonte, e a rede.
|
|
36
|
+
*/
|
|
37
|
+
const SOURCE_WAIT_MARGIN_MS = 15_000;
|
|
38
|
+
/** O que um valor de cabeçalho não pode conter: `fetch` recusa montá-lo. */
|
|
39
|
+
const FORBIDDEN_HEADER_CHARS = /[\r\n\0]/u;
|
|
40
|
+
/** Hosts em que falar claro é desenvolvimento, não credencial exposta. */
|
|
41
|
+
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
42
|
+
/**
|
|
43
|
+
* A chave é de CONTA e viaja em todo pedido: sem TLS ela vai em texto claro
|
|
44
|
+
* para quem estiver no caminho. É a mesma recusa que o motor aplica à própria
|
|
45
|
+
* URL de produção — só o host local fica de fora, porque ali não há caminho.
|
|
46
|
+
*/
|
|
47
|
+
function requireSecureBaseUrl(baseUrl) {
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = new URL(baseUrl);
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
throw LexNodeError.invalidRequest(`baseUrl inválida: ${baseUrl}`, cause);
|
|
54
|
+
}
|
|
55
|
+
if (parsed.protocol !== "https:" && !LOCAL_HOSTS.has(parsed.hostname)) {
|
|
56
|
+
throw LexNodeError.invalidRequest(`baseUrl precisa ser https: a chave de conta não trafega em claro (${baseUrl}).`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readHeaderNumber(headers, name) {
|
|
60
|
+
const raw = headers.get(name);
|
|
61
|
+
if (raw === null)
|
|
62
|
+
return null;
|
|
63
|
+
const parsed = Number(raw);
|
|
64
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* O teto por minuto sai em TODA resposta autenticada, não só na recusa. Lê-lo
|
|
68
|
+
* no sucesso é o que permite ao consumidor desacelerar ANTES de bater no 429.
|
|
69
|
+
*/
|
|
70
|
+
function readRateLimit(headers) {
|
|
71
|
+
const limit = readHeaderNumber(headers, "RateLimit-Limit");
|
|
72
|
+
const remaining = readHeaderNumber(headers, "RateLimit-Remaining");
|
|
73
|
+
const resetSeconds = readHeaderNumber(headers, "RateLimit-Reset");
|
|
74
|
+
const retryAfterSeconds = readHeaderNumber(headers, "Retry-After");
|
|
75
|
+
if (limit === null &&
|
|
76
|
+
remaining === null &&
|
|
77
|
+
resetSeconds === null &&
|
|
78
|
+
retryAfterSeconds === null) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return { limit, remaining, resetSeconds, retryAfterSeconds };
|
|
82
|
+
}
|
|
83
|
+
function fillPath(template, params) {
|
|
84
|
+
return template.replace(/\{([^}]+)\}/gu, (_match, name) => {
|
|
85
|
+
const value = params?.[name];
|
|
86
|
+
if (value === undefined || value === null || value === "") {
|
|
87
|
+
throw new LexNodeError({
|
|
88
|
+
code: "CLIENT_MISSING_PATH_PARAM",
|
|
89
|
+
message: `A rota ${template} exige o parâmetro de caminho "${name}".`,
|
|
90
|
+
status: 400,
|
|
91
|
+
errorClass: "TERMINAL",
|
|
92
|
+
retryable: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return encodeURIComponent(String(value));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function appendQuery(url, query) {
|
|
99
|
+
if (!query)
|
|
100
|
+
return;
|
|
101
|
+
for (const [key, value] of Object.entries(query)) {
|
|
102
|
+
const entries = (Array.isArray(value) ? value : [value]).filter((entry) => entry !== undefined && entry !== null);
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
url.searchParams.append(key, entry instanceof Date ? entry.toISOString() : String(entry));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* O prazo da chamada que não trouxe `timeoutMs` próprio.
|
|
110
|
+
*
|
|
111
|
+
* Numa leitura que espera pela fonte, o motor pode levar a espera inteira
|
|
112
|
+
* antes de responder — a de `waitSeconds` ou, sem ele, a padrão da API: o
|
|
113
|
+
* prazo cobre essa espera mais a margem e nunca fica abaixo do prazo do
|
|
114
|
+
* cliente. Nas outras rotas, vale o prazo do cliente.
|
|
115
|
+
*/
|
|
116
|
+
function defaultCallTimeoutMs(method, path, query, clientTimeoutMs) {
|
|
117
|
+
if (!SOURCE_WAIT_ROUTES.has(`${method} ${path}`))
|
|
118
|
+
return clientTimeoutMs;
|
|
119
|
+
const requested = Number(query?.waitSeconds);
|
|
120
|
+
const waitSeconds = Number.isFinite(requested) && requested > 0
|
|
121
|
+
? requested
|
|
122
|
+
: SOURCE_WAIT_API_DEFAULT_SECONDS;
|
|
123
|
+
return Math.max(clientTimeoutMs, waitSeconds * 1_000 + SOURCE_WAIT_MARGIN_MS);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Junta o prazo do cliente ao `signal` de quem chamou.
|
|
127
|
+
*
|
|
128
|
+
* Os dois abortam a mesma requisição, e trocar um pelo outro perde metade do
|
|
129
|
+
* controle: sem o prazo, uma chamada sem resposta fica pendurada; sem o sinal
|
|
130
|
+
* de quem chamou, o cancelamento da requisição de cima não alcança a de baixo.
|
|
131
|
+
* O motivo do aborto é preservado para o prazo virar 504 e o cancelamento de
|
|
132
|
+
* fora continuar sendo cancelamento.
|
|
133
|
+
*/
|
|
134
|
+
function linkAbort(timeoutMs, caller) {
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
let timedOut = false;
|
|
137
|
+
const timer = setTimeout(() => {
|
|
138
|
+
timedOut = true;
|
|
139
|
+
controller.abort();
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
const onCallerAbort = () => controller.abort(caller?.reason);
|
|
142
|
+
if (caller) {
|
|
143
|
+
if (caller.aborted)
|
|
144
|
+
controller.abort(caller.reason);
|
|
145
|
+
else
|
|
146
|
+
caller.addEventListener("abort", onCallerAbort, { once: true });
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
signal: controller.signal,
|
|
150
|
+
get timedOut() {
|
|
151
|
+
return timedOut;
|
|
152
|
+
},
|
|
153
|
+
dispose() {
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
caller?.removeEventListener("abort", onCallerAbort);
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function sleep(ms, signal) {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
if (signal?.aborted) {
|
|
162
|
+
reject(signal.reason);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let timer;
|
|
166
|
+
const onAbort = () => {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
reject(signal?.reason);
|
|
169
|
+
};
|
|
170
|
+
timer = setTimeout(() => {
|
|
171
|
+
signal?.removeEventListener("abort", onAbort);
|
|
172
|
+
resolve();
|
|
173
|
+
}, ms);
|
|
174
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Se vale repetir esta chamada.
|
|
179
|
+
*
|
|
180
|
+
* A classe do erro diz se a condição passa sozinha; o método e a ROTA dizem se
|
|
181
|
+
* repetir é seguro. Uma escrita fora da lista só é repetida quando quem chama
|
|
182
|
+
* declara `idempotent: true` — quem conhece o próprio uso pode afirmar o que o
|
|
183
|
+
* contrato não afirma, mas a afirmação é dele, e explícita.
|
|
184
|
+
*
|
|
185
|
+
* O tempo esgotado da fonte (`UPSTREAM_TIMEOUT`) numa leitura que espera por
|
|
186
|
+
* ela não se repete: o motor já esperou tudo o que a chamada pediu, e repetir
|
|
187
|
+
* na hora dobra a espera diante da mesma fonte lenta. As demais recusas
|
|
188
|
+
* retentáveis dessas leituras seguem a política.
|
|
189
|
+
*/
|
|
190
|
+
function mayRetry(error, method, path, options) {
|
|
191
|
+
if (!(error instanceof LexNodeError) || !error.retryable)
|
|
192
|
+
return false;
|
|
193
|
+
if (error.code === "UPSTREAM_TIMEOUT" &&
|
|
194
|
+
SOURCE_WAIT_ROUTES.has(`${method} ${path}`)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (IDEMPOTENT_METHODS.has(method))
|
|
198
|
+
return true;
|
|
199
|
+
if (options.idempotent === true)
|
|
200
|
+
return true;
|
|
201
|
+
return (Boolean(options.requestId) &&
|
|
202
|
+
REQUEST_ID_IDEMPOTENT_WRITES.has(`${method} ${path}`));
|
|
203
|
+
}
|
|
204
|
+
function retryDelayMs(policy, attempt, error) {
|
|
205
|
+
if (error.retryAfterSeconds !== null && error.retryAfterSeconds >= 0) {
|
|
206
|
+
return Math.min(error.retryAfterSeconds * 1_000, policy.maxDelayMs);
|
|
207
|
+
}
|
|
208
|
+
return Math.min(policy.baseDelayMs * 2 ** (attempt - 1), policy.maxDelayMs);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* O cliente de runtime do motor de dados jurídicos.
|
|
212
|
+
*
|
|
213
|
+
* **Servidor apenas, e só por TLS.** A credencial é de CONTA: uma chave no
|
|
214
|
+
* browser é uma chave publicada, e uma chave em `http://` é uma chave lida por
|
|
215
|
+
* quem estiver no caminho — `baseUrl` sem https é recusada aqui, exceto em
|
|
216
|
+
* host local. Um app com usuários finais fala com o próprio backend, e é o
|
|
217
|
+
* backend que fala com o motor — atribuindo cada chamada ao seu sub-tenant por
|
|
218
|
+
* `X-Office-Id`.
|
|
219
|
+
*
|
|
220
|
+
* Sem dependências de runtime: `fetch` global e `node:crypto`.
|
|
221
|
+
*/
|
|
222
|
+
export function createLexNodeClient(options) {
|
|
223
|
+
requireSecureBaseUrl(options.baseUrl);
|
|
224
|
+
const baseUrl = options.baseUrl.replace(/\/+$/u, "");
|
|
225
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
226
|
+
const defaultTimeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
227
|
+
async function attempt(path, method, args, ids) {
|
|
228
|
+
const bag = args;
|
|
229
|
+
const url = new URL(`${baseUrl}${fillPath(String(path), bag.params?.path)}`);
|
|
230
|
+
appendQuery(url, bag.params?.query);
|
|
231
|
+
const officeId = args.officeId === undefined ? options.officeId : args.officeId;
|
|
232
|
+
const headers = {
|
|
233
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
234
|
+
accept: "application/json",
|
|
235
|
+
"x-request-id": ids.requestId,
|
|
236
|
+
"x-trace-id": ids.traceId,
|
|
237
|
+
};
|
|
238
|
+
if (officeId)
|
|
239
|
+
headers["x-office-id"] = officeId;
|
|
240
|
+
if (options.userAgent)
|
|
241
|
+
headers["user-agent"] = options.userAgent;
|
|
242
|
+
if (bag.body !== undefined)
|
|
243
|
+
headers["content-type"] = "application/json";
|
|
244
|
+
// Cabeçalho e corpo são montados ANTES de abrir o socket: o que o
|
|
245
|
+
// transporte recusa montar é defeito do pedido, não falha passageira de
|
|
246
|
+
// rede, e repetir o que nunca saiu só gasta as tentativas.
|
|
247
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
248
|
+
if (FORBIDDEN_HEADER_CHARS.test(value)) {
|
|
249
|
+
throw LexNodeError.invalidRequest(`O cabeçalho ${name} tem quebra de linha ou caractere nulo.`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
let payload;
|
|
253
|
+
try {
|
|
254
|
+
payload = bag.body === undefined ? undefined : JSON.stringify(bag.body);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
throw LexNodeError.invalidRequest("O corpo da chamada não é serializável em JSON.", error);
|
|
258
|
+
}
|
|
259
|
+
const timeoutMs = args.timeoutMs ??
|
|
260
|
+
defaultCallTimeoutMs(String(method), String(path), bag.params?.query, defaultTimeoutMs);
|
|
261
|
+
const link = linkAbort(timeoutMs, args.signal);
|
|
262
|
+
let response;
|
|
263
|
+
let raw;
|
|
264
|
+
let rateLimit;
|
|
265
|
+
try {
|
|
266
|
+
response = await doFetch(url.toString(), {
|
|
267
|
+
method: String(method).toUpperCase(),
|
|
268
|
+
headers,
|
|
269
|
+
body: payload,
|
|
270
|
+
signal: link.signal,
|
|
271
|
+
// O contrato não redireciona; seguir um 30x entregaria ao consumidor o
|
|
272
|
+
// corpo de outro host como se fosse a resposta do motor.
|
|
273
|
+
redirect: "error",
|
|
274
|
+
});
|
|
275
|
+
rateLimit = readRateLimit(response.headers);
|
|
276
|
+
// A leitura do corpo fica DENTRO do prazo: um corpo que trava depois dos
|
|
277
|
+
// cabeçalhos pendura a chamada por tempo indefinido se o vínculo já tiver
|
|
278
|
+
// sido desfeito aqui.
|
|
279
|
+
raw = await response.text();
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (link.timedOut)
|
|
283
|
+
throw LexNodeError.timeout(timeoutMs, error);
|
|
284
|
+
// Cancelamento de quem chamou não é falha da API: sobe como veio.
|
|
285
|
+
if (args.signal?.aborted)
|
|
286
|
+
throw error;
|
|
287
|
+
throw LexNodeError.network(error);
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
link.dispose();
|
|
291
|
+
}
|
|
292
|
+
let parsed;
|
|
293
|
+
try {
|
|
294
|
+
parsed = raw ? JSON.parse(raw) : null;
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
throw LexNodeError.invalidResponse(response.status, raw, rateLimit);
|
|
298
|
+
}
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
const envelope = LexNodeError.fromEnvelope(parsed, response.status, rateLimit);
|
|
301
|
+
throw (envelope ??
|
|
302
|
+
LexNodeError.invalidResponse(response.status, raw, rateLimit));
|
|
303
|
+
}
|
|
304
|
+
const body = parsed;
|
|
305
|
+
if (!body || typeof body !== "object" || !("data" in body)) {
|
|
306
|
+
throw LexNodeError.invalidResponse(response.status, raw, rateLimit);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
data: body.data,
|
|
310
|
+
meta: {
|
|
311
|
+
...body.meta,
|
|
312
|
+
status: response.status,
|
|
313
|
+
rateLimit,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* A chamada genérica, tipada pela rota e pelo método do contrato.
|
|
319
|
+
*
|
|
320
|
+
* Uma função cobre a superfície inteira: rota nova ou campo novo no
|
|
321
|
+
* `openapi.json` chega tipado sem uma linha a mais aqui. Os métodos nomeados
|
|
322
|
+
* abaixo só dão nome ao que um consumidor usa todo dia.
|
|
323
|
+
*/
|
|
324
|
+
async function request(path, method, args) {
|
|
325
|
+
const requestId = args.requestId ?? `req_${randomUUID()}`;
|
|
326
|
+
const traceId = args.traceId ?? `trc_${randomUUID()}`;
|
|
327
|
+
const policy = args.retry ?? options.retry;
|
|
328
|
+
const attempts = Math.max(1, policy?.attempts ?? 1);
|
|
329
|
+
const delays = {
|
|
330
|
+
baseDelayMs: policy?.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS,
|
|
331
|
+
maxDelayMs: policy?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS,
|
|
332
|
+
};
|
|
333
|
+
let round = 1;
|
|
334
|
+
for (;;) {
|
|
335
|
+
try {
|
|
336
|
+
return await attempt(path, method, args, { requestId, traceId });
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const last = round >= attempts;
|
|
340
|
+
if (last ||
|
|
341
|
+
!mayRetry(error, String(method), String(path), {
|
|
342
|
+
requestId: args.requestId,
|
|
343
|
+
idempotent: args.idempotent,
|
|
344
|
+
})) {
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
await sleep(retryDelayMs(delays, round, error), args.signal);
|
|
348
|
+
round += 1;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
request,
|
|
354
|
+
tribunals: {
|
|
355
|
+
list: (query, opts = {}) => request("/v1/tribunals", "get", { params: { query }, ...opts }),
|
|
356
|
+
},
|
|
357
|
+
processes: {
|
|
358
|
+
search: (body, opts = {}) => request("/v1/processes/search", "post", { body, ...opts }),
|
|
359
|
+
get: (processNumber, query, opts = {}) => request("/v1/processes/{processNumber}", "get", {
|
|
360
|
+
params: { path: { processNumber }, query },
|
|
361
|
+
...opts,
|
|
362
|
+
}),
|
|
363
|
+
movements: (processNumber, query, opts = {}) => request("/v1/processes/{processNumber}/movements", "get", {
|
|
364
|
+
params: { path: { processNumber }, query },
|
|
365
|
+
...opts,
|
|
366
|
+
}),
|
|
367
|
+
snapshot: (processNumber, query, opts = {}) => request("/v1/processes/{processNumber}/snapshot", "get", {
|
|
368
|
+
params: { path: { processNumber }, query },
|
|
369
|
+
...opts,
|
|
370
|
+
}),
|
|
371
|
+
related: (processNumber, query, opts = {}) => request("/v1/processes/{processNumber}/related", "get", {
|
|
372
|
+
params: { path: { processNumber }, query },
|
|
373
|
+
...opts,
|
|
374
|
+
}),
|
|
375
|
+
snapshotBatch: (body, opts = {}) => request("/v1/processes/snapshot-batch", "post", { body, ...opts }),
|
|
376
|
+
},
|
|
377
|
+
publications: {
|
|
378
|
+
list: (query, opts = {}) => request("/v1/publications", "get", { params: { query }, ...opts }),
|
|
379
|
+
read: (publicationKey, opts = {}) => request("/v1/publications/{publicationKey}", "get", {
|
|
380
|
+
params: { path: { publicationKey } },
|
|
381
|
+
...opts,
|
|
382
|
+
}),
|
|
383
|
+
textSearch: (body, opts = {}) => request("/v1/publications/text-searches", "post", { body, ...opts }),
|
|
384
|
+
textAlerts: {
|
|
385
|
+
create: (body, opts = {}) => request("/v1/publications/text-alerts", "post", { body, ...opts }),
|
|
386
|
+
list: (query, opts = {}) => request("/v1/publications/text-alerts", "get", {
|
|
387
|
+
params: { query },
|
|
388
|
+
...opts,
|
|
389
|
+
}),
|
|
390
|
+
delete: (alertId, opts = {}) => request("/v1/publications/text-alerts/{alertId}", "delete", {
|
|
391
|
+
params: { path: { alertId } },
|
|
392
|
+
...opts,
|
|
393
|
+
}),
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
monitors: {
|
|
397
|
+
create: (body, opts = {}) => request("/v1/monitors", "post", { body, ...opts }),
|
|
398
|
+
list: (query, opts = {}) => request("/v1/monitors", "get", { params: { query }, ...opts }),
|
|
399
|
+
get: (monitorId, opts = {}) => request("/v1/monitors/{monitorId}", "get", {
|
|
400
|
+
params: { path: { monitorId } },
|
|
401
|
+
...opts,
|
|
402
|
+
}),
|
|
403
|
+
update: (monitorId, body, opts = {}) => request("/v1/monitors/{monitorId}", "patch", {
|
|
404
|
+
params: { path: { monitorId } },
|
|
405
|
+
body,
|
|
406
|
+
...opts,
|
|
407
|
+
}),
|
|
408
|
+
delete: (monitorId, opts = {}) => request("/v1/monitors/{monitorId}", "delete", {
|
|
409
|
+
params: { path: { monitorId } },
|
|
410
|
+
...opts,
|
|
411
|
+
}),
|
|
412
|
+
sync: (monitorId, opts = {}) => request("/v1/monitors/{monitorId}/sync", "post", {
|
|
413
|
+
params: { path: { monitorId } },
|
|
414
|
+
...opts,
|
|
415
|
+
}),
|
|
416
|
+
},
|
|
417
|
+
subscriptions: {
|
|
418
|
+
create: (body, opts = {}) => request("/v1/subscriptions", "post", { body, ...opts }),
|
|
419
|
+
list: (query, opts = {}) => request("/v1/subscriptions", "get", { params: { query }, ...opts }),
|
|
420
|
+
delete: (body, opts = {}) => request("/v1/subscriptions", "delete", { body, ...opts }),
|
|
421
|
+
},
|
|
422
|
+
searchJobs: {
|
|
423
|
+
get: (jobId, opts = {}) => request("/v1/search-jobs/{jobId}", "get", {
|
|
424
|
+
params: { path: { jobId } },
|
|
425
|
+
...opts,
|
|
426
|
+
}),
|
|
427
|
+
list: (query, opts = {}) => request("/v1/search-jobs", "get", { params: { query }, ...opts }),
|
|
428
|
+
cancel: (jobId, opts = {}) => request("/v1/search-jobs/{jobId}/cancel", "post", {
|
|
429
|
+
params: { path: { jobId } },
|
|
430
|
+
...opts,
|
|
431
|
+
}),
|
|
432
|
+
},
|
|
433
|
+
events: {
|
|
434
|
+
list: (query, opts = {}) => request("/v1/events", "get", { params: { query }, ...opts }),
|
|
435
|
+
get: (eventId, opts = {}) => request("/v1/events/{eventId}", "get", {
|
|
436
|
+
params: { path: { eventId } },
|
|
437
|
+
...opts,
|
|
438
|
+
}),
|
|
439
|
+
},
|
|
440
|
+
usage: {
|
|
441
|
+
get: (query, opts = {}) => request("/v1/usage", "get", { params: { query }, ...opts }),
|
|
442
|
+
},
|
|
443
|
+
webhooks: {
|
|
444
|
+
endpoints: {
|
|
445
|
+
list: (query, opts = {}) => request("/v1/webhooks/endpoints", "get", {
|
|
446
|
+
params: { query },
|
|
447
|
+
...opts,
|
|
448
|
+
}),
|
|
449
|
+
create: (body, opts = {}) => request("/v1/webhooks/endpoints", "post", { body, ...opts }),
|
|
450
|
+
update: (endpointId, body, opts = {}) => request("/v1/webhooks/endpoints/{endpointId}", "patch", {
|
|
451
|
+
params: { path: { endpointId } },
|
|
452
|
+
body,
|
|
453
|
+
...opts,
|
|
454
|
+
}),
|
|
455
|
+
delete: (endpointId, opts = {}) => request("/v1/webhooks/endpoints/{endpointId}", "delete", {
|
|
456
|
+
params: { path: { endpointId } },
|
|
457
|
+
...opts,
|
|
458
|
+
}),
|
|
459
|
+
test: (endpointId, opts = {}) => request("/v1/webhooks/endpoints/{endpointId}/test", "post", {
|
|
460
|
+
params: { path: { endpointId } },
|
|
461
|
+
...opts,
|
|
462
|
+
}),
|
|
463
|
+
rotateSecret: (endpointId, opts = {}) => request("/v1/webhooks/endpoints/{endpointId}/rotate-secret", "post", {
|
|
464
|
+
params: { path: { endpointId } },
|
|
465
|
+
...opts,
|
|
466
|
+
}),
|
|
467
|
+
},
|
|
468
|
+
deliveries: (query, opts = {}) => request("/v1/webhooks/deliveries", "get", {
|
|
469
|
+
params: { query },
|
|
470
|
+
...opts,
|
|
471
|
+
}),
|
|
472
|
+
health: (opts = {}) => request("/v1/webhooks/health", "get", { ...opts }),
|
|
473
|
+
unsubscribedEventTypes: (opts = {}) => request("/v1/webhooks/unsubscribed-event-types", "get", { ...opts }),
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { RateLimitSnapshot } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Os códigos de recusa que o contrato declara, mais os que este cliente cunha
|
|
4
|
+
* para falhas locais.
|
|
5
|
+
*
|
|
6
|
+
* Existe para o `switch` do consumidor ter autocompletar e para o erro de
|
|
7
|
+
* digitação virar erro de compilação: o motor responde 404 tanto para
|
|
8
|
+
* `PROCESS_NOT_FOUND` quanto para `MOVEMENTS_NOT_AVAILABLE`, e quem distingue
|
|
9
|
+
* os dois por literal redigitado descobre o engano em produção. A lista é
|
|
10
|
+
* fechada para o que se conhece e aberta para o que vier (ver `LexNodeError`).
|
|
11
|
+
*/
|
|
12
|
+
export declare const LEXNODE_ERROR_CODES: readonly ["INVALID_MONITOR", "TRIBUNAL_NOT_SUPPORTED", "PERSONAL_DOCUMENT_QUERY_NOT_ALLOWED", "VALIDATION_ERROR", "UNAUTHORIZED", "QUOTA_EXCEEDED", "INSUFFICIENT_SCOPE", "LGPD_FORBIDDEN", "PLAN_FEATURE_NOT_INCLUDED", "SUB_TENANT_LIMIT_EXCEEDED", "SANDBOX_UPSTREAM_DISABLED", "MOVEMENTS_NOT_AVAILABLE", "PRECEDENT_NOT_FOUND", "PROCESS_NOT_FOUND", "RESOURCE_NOT_FOUND", "RESOURCE_CONFLICT", "QUERY_TOO_BROAD", "RATE_LIMITED", "UPSTREAM_RATE_LIMITED", "INTERNAL_ERROR", "UPSTREAM_UNAVAILABLE", "DATABASE_TIMEOUT", "DATABASE_UNAVAILABLE", "SYNC_ABANDONED", "UPSTREAM_TIMEOUT", "ACCOUNT_SUSPENDED", "ENTITLEMENT_EXPIRED", "PROJECT_DISABLED", "SUBSCRIPTION_INACTIVE", "MONITOR_ALREADY_EXISTS", "MONITOR_NOT_FOUND", "SEARCH_JOB_NOT_FOUND", "PUBLICATION_NOT_FOUND", "EVENT_NOT_FOUND", "TEXT_ALERT_NOT_FOUND", "TEXT_SEARCH_NOT_FOUND", "CLIENT_TIMEOUT", "CLIENT_NETWORK_ERROR", "CLIENT_INVALID_REQUEST", "CLIENT_MISSING_PATH_PARAM", "INVALID_RESPONSE", "SEARCH_JOB_BUDGET_EXCEEDED", "PAGINATION_LIMIT_REACHED"];
|
|
13
|
+
/**
|
|
14
|
+
* Um código conhecido — ou qualquer outro.
|
|
15
|
+
*
|
|
16
|
+
* O `string & {}` mantém o autocompletar da lista sem fechar a porta: um código
|
|
17
|
+
* novo do motor continua chegando ao `catch` em vez de quebrar a compilação de
|
|
18
|
+
* quem já integrou.
|
|
19
|
+
*/
|
|
20
|
+
export type LexNodeErrorCode = (typeof LEXNODE_ERROR_CODES)[number] | (string & {});
|
|
21
|
+
/**
|
|
22
|
+
* A classificação que o motor carimba em toda recusa: `RETRYABLE` é condição
|
|
23
|
+
* passageira (prazo, 5xx, teto de vazão), `TERMINAL` é o que repetir não cura
|
|
24
|
+
* (validação, autorização, cota, alvo inexistente).
|
|
25
|
+
*/
|
|
26
|
+
export type LexNodeErrorClass = "RETRYABLE" | "TERMINAL";
|
|
27
|
+
export interface LexNodeErrorInit {
|
|
28
|
+
code: LexNodeErrorCode;
|
|
29
|
+
message: string;
|
|
30
|
+
status: number;
|
|
31
|
+
errorClass: LexNodeErrorClass;
|
|
32
|
+
retryable: boolean;
|
|
33
|
+
requestId?: string | null;
|
|
34
|
+
upstreamStatus?: number | null;
|
|
35
|
+
fieldErrors?: Record<string, string[]> | null;
|
|
36
|
+
details?: Record<string, unknown> | null;
|
|
37
|
+
retryAfterSeconds?: number | null;
|
|
38
|
+
rateLimit?: RateLimitSnapshot | null;
|
|
39
|
+
cause?: unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Toda falha que o cliente produz — do motor, da rede ou do prazo — chega como
|
|
43
|
+
* esta classe.
|
|
44
|
+
*
|
|
45
|
+
* Um único tipo para o `catch` do consumidor: ele decide por `retryable` e
|
|
46
|
+
* `code`, não por farejar a forma do que caiu. `requestId` vem junto porque é
|
|
47
|
+
* o que o suporte pede; perdê-lo transforma um incidente rastreável num relato.
|
|
48
|
+
*/
|
|
49
|
+
export declare class LexNodeError extends Error {
|
|
50
|
+
readonly name = "LexNodeError";
|
|
51
|
+
/** O código do contrato (`RATE_LIMITED`, `PROCESS_NOT_FOUND`, …). */
|
|
52
|
+
readonly code: LexNodeErrorCode;
|
|
53
|
+
/** Status HTTP da recusa — ou o que o cliente atribuiu à falha local. */
|
|
54
|
+
readonly status: number;
|
|
55
|
+
readonly class: LexNodeErrorClass;
|
|
56
|
+
readonly retryable: boolean;
|
|
57
|
+
/** Status real da fonte a montante, quando o motor o preservou. */
|
|
58
|
+
readonly upstreamStatus: number | null;
|
|
59
|
+
readonly requestId: string | null;
|
|
60
|
+
/** Erros por campo de uma recusa de validação. */
|
|
61
|
+
readonly fieldErrors: Record<string, string[]> | null;
|
|
62
|
+
readonly details: Record<string, unknown> | null;
|
|
63
|
+
readonly retryAfterSeconds: number | null;
|
|
64
|
+
readonly rateLimit: RateLimitSnapshot | null;
|
|
65
|
+
constructor(init: LexNodeErrorInit);
|
|
66
|
+
/**
|
|
67
|
+
* A recusa como o motor a descreveu. A classe e o `retryable` saem do corpo
|
|
68
|
+
* quando ele os traz; o status é o da resposta, que é o que de fato chegou.
|
|
69
|
+
*/
|
|
70
|
+
static fromEnvelope(envelope: unknown, status: number, rateLimit: RateLimitSnapshot | null): LexNodeError | null;
|
|
71
|
+
/** Prazo esgotado antes de a resposta chegar: nada foi decidido, repetir cabe. */
|
|
72
|
+
static timeout(timeoutMs: number, cause?: unknown): LexNodeError;
|
|
73
|
+
/** A requisição não alcançou o motor: DNS, conexão, TLS, socket derrubado. */
|
|
74
|
+
static network(cause: unknown): LexNodeError;
|
|
75
|
+
/**
|
|
76
|
+
* Respondeu, mas não no envelope do contrato — proxy, página de erro, HTML.
|
|
77
|
+
*
|
|
78
|
+
* 502 porque o que quebrou está no caminho, não no pedido; o status real
|
|
79
|
+
* sobrevive em `upstreamStatus`, e a mensagem carrega o corpo cru truncado,
|
|
80
|
+
* que é a única pista de quem respondeu.
|
|
81
|
+
*/
|
|
82
|
+
static invalidResponse(status: number, raw: string, rateLimit: RateLimitSnapshot | null): LexNodeError;
|
|
83
|
+
/**
|
|
84
|
+
* O pedido não chegou a sair: cabeçalho que o `fetch` recusa montar, corpo
|
|
85
|
+
* que não serializa.
|
|
86
|
+
*
|
|
87
|
+
* TERMINAL de propósito. Repetir o que o transporte barrou antes de abrir o
|
|
88
|
+
* socket não tem como dar certo, e classificá-lo como falha de rede manda o
|
|
89
|
+
* consumidor gastar as tentativas num defeito do próprio código.
|
|
90
|
+
*/
|
|
91
|
+
static invalidRequest(message: string, cause?: unknown): LexNodeError;
|
|
92
|
+
}
|
|
93
|
+
/** Guarda de tipo para o `catch`, sem depender do `instanceof` cruzar realms. */
|
|
94
|
+
export declare function isLexNodeError(value: unknown): value is LexNodeError;
|