@lexnode/legal-data-sdk 1.27.0 → 1.29.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 CHANGED
@@ -73,6 +73,7 @@ await lexnode.publications.list({ oabNumber: "123456", oabState: "SP" });
73
73
  await lexnode.monitors.create({ type: "OAB", oabNumber: "123456", oabState: "SP" });
74
74
  await lexnode.subscriptions.create({ tribunalAlias, processNumber, externalRef });
75
75
  await lexnode.searchJobs.get(jobId);
76
+ await lexnode.processLookups.get(lookupId);
76
77
  await lexnode.events.list({ limit: 50 });
77
78
  await lexnode.webhooks.endpoints.list();
78
79
  await lexnode.webhooks.unsubscribedEventTypes();
@@ -208,6 +209,10 @@ switch (resultado.event.type) {
208
209
  resultado.event.payload.error.code;
209
210
  }
210
211
  break;
212
+ case "PROCESS_LOOKUP_COMPLETED":
213
+ // Um ponteiro: o processo se lê em payload.pollUrl, sem nova cobrança.
214
+ resultado.event.payload.lookupId;
215
+ break;
211
216
  case "WEBHOOK_TEST":
212
217
  // A entrega de teste do console. Chega a qualquer endpoint, não se assina
213
218
  // e não se recusa — é ela que valida o receptor antes do primeiro evento.
@@ -471,6 +476,87 @@ ativa —, e os demais recebem a falha da fonte, como se não houvesse cópia.
471
476
  item socorrido traz `lakeRescue` com a causa e a data, e custa 1 crédito em vez
472
477
  de 8.
473
478
 
479
+ ## Leitura que não coube na espera
480
+
481
+ Quando a fonte processual não entrega dentro de `waitSeconds`, a ficha e o
482
+ retrato respondem com o erro dela (ou com a cópia guardada, seção anterior).
483
+ `processLookups.create` faz a MESMA leitura, e o que não coube na espera segue
484
+ em segundo plano em vez de virar erro:
485
+
486
+ - **200**: a leitura coube. `data` é uma consulta `SUCCEEDED` sem consulta
487
+ aberta (`lookupId` nulo) que traz o processo em `process` (visão
488
+ `PROCESS`) ou `snapshot` (visão `SNAPSHOT`), com as instâncias do número em
489
+ `instances`, o mesmo `meta` e o mesmo preço da leitura direta;
490
+ - **202**: a fonte falhou — não respondeu a tempo, ficou indisponível ou pediu
491
+ para esperar —, e o motor abriu uma consulta que tenta sozinha, até 60 s por
492
+ tentativa, por até 6 h (`giveUpAt`). Nada é cobrado na hora: o preço da
493
+ leitura fica retido em `creditsReserved`, o máximo que a entrega cobra, e
494
+ só é cobrado se o processo vier. Se não vier, a retenção volta;
495
+ - qualquer outra recusa é a da leitura direta: 404 quando a fonte diz não ter o
496
+ processo, 403 no segredo de justiça, 402 sem saldo.
497
+
498
+ `lookupProcess` diz, no tipo, qual dos dois desfechos aconteceu, e
499
+ `waitForProcessLookup` espera a consulta terminar:
500
+
501
+ ```ts
502
+ import { lookupProcess, waitForProcessLookup } from "@lexnode/legal-data-sdk";
503
+
504
+ const desfecho = await lookupProcess(lexnode, {
505
+ tribunalAlias: "api_publica_tjsp",
506
+ processNumber: numero,
507
+ view: "SNAPSHOT",
508
+ });
509
+
510
+ if (desfecho.kind === "ready") {
511
+ desfecho.data.movements; // o retrato, como processes.snapshot o devolveria
512
+ } else {
513
+ // O orçamento é explícito. A leitura seguinte só acontece na hora da
514
+ // próxima tentativa (nextAttemptAt): antes dela, o estado não muda. Com
515
+ // uma tentativa em curso, a consulta é lida de novo a cada 10 s.
516
+ const consulta = await waitForProcessLookup(
517
+ lexnode,
518
+ desfecho.lookup.lookupId!,
519
+ { budgetMs: 10 * 60_000 },
520
+ );
521
+ if (consulta.status === "SUCCEEDED" && consulta.snapshot) {
522
+ consulta.snapshot.movements;
523
+ }
524
+ }
525
+ ```
526
+
527
+ **Repetir é seguro.** A mesma leitura pedida de novo — mesma conta,
528
+ escritório, processo e visão — devolve a mesma consulta (`deduped: true`), sem
529
+ nova retenção; por isso `processLookups.create` entra na política de
530
+ retentativa como uma leitura. Quando a próxima tentativa agendada está longe, a
531
+ chamada repetida tenta a fonte na hora, dentro da espera dela. O tempo
532
+ esgotado da fonte continua sem repetição automática.
533
+
534
+ **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 do
536
+ acervo e passado de novo pelo controle de confidencialidade: o processo que
537
+ passou a segredo de justiça depois responde 403. O retrato lido da consulta
538
+ traz a instância principal no topo e cada instância com os movimentos da
539
+ última captura dela, e `sourcePayload` é nulo. `processLookups.list` e `processLookups.cancel`
540
+ completam a superfície; o cancelamento devolve a retenção e não gera evento.
541
+
542
+ **Por webhook, sem esperar nada.** O fim sai em `PROCESS_LOOKUP_COMPLETED`, um
543
+ ponteiro para a consulta que nunca traz o processo; o movimento novo que a
544
+ entrega trouxer chega no `PROCESS_MOVEMENT_CREATED` de sempre.
545
+
546
+ **A cópia guardada e a atualização, uma compra só.** Com `staleOnFailure: true`,
547
+ o 202 traz em `stale` a cópia que o acervo guarda, com a data e a causa, por 1
548
+ crédito, e a consulta segue buscando a versão atual. A entrega cobra só o resto
549
+ do preço (`alreadyCharged` diz quanto já foi): as duas juntas custam o mesmo que
550
+ a leitura direta, e a atualização que não vier não cobra mais nada.
551
+
552
+ **A leitura direta fecha a consulta.** A ficha ou o retrato do mesmo processo
553
+ lidos com sucesso por `processes.get` ou `processes.snapshot` fecham a consulta
554
+ aberta sem cobrar — a leitura já cobrou.
555
+
556
+ **Tetos.** Até 5 consultas abertas por chave e 20 por conta; além disso, a
557
+ falha da fonte volta como a leitura direta a daria. A chave de teste nunca abre
558
+ consulta.
559
+
474
560
  ## Frescor: o `total: 0` qualificado
475
561
 
476
562
  Um resultado vazio não distingue sozinho "não existe" de "a fonte ainda não
package/dist/client.d.ts CHANGED
@@ -12,8 +12,9 @@ export interface LexNodeClientOptions {
12
12
  officeId?: string;
13
13
  /**
14
14
  * Prazo de cada chamada, em ms. Padrão: 30 s. A ficha e o retrato de um
15
- * processo levam o maior entre este prazo e a espera pela fonte mais 15 s —
16
- * `waitSeconds`, ou o padrão de 20 s da API quando ele falta.
15
+ * processo, e a leitura que segue em segundo plano, levam o maior entre este
16
+ * prazo e a espera pela fonte mais 15 s — `waitSeconds`, ou o padrão de 20 s
17
+ * da API quando ele falta.
17
18
  */
18
19
  timeoutMs?: number;
19
20
  /** Transporte alternativo — o `fetch` global é o padrão. */
@@ -71,6 +72,19 @@ export declare function createLexNodeClient(options: LexNodeClientOptions): {
71
72
  list: (query?: Query<"/v1/subscriptions", "get">, opts?: CallOptions) => Promise<LexNodeResult<"/v1/subscriptions", "get">>;
72
73
  delete: (body: Body<"/v1/subscriptions", "delete">, opts?: CallOptions) => Promise<LexNodeResult<"/v1/subscriptions", "delete">>;
73
74
  };
75
+ processLookups: {
76
+ /**
77
+ * A leitura de UM processo que segue em segundo plano quando a fonte não
78
+ * entrega dentro de `waitSeconds`: 200 com o processo, ou 202 com a
79
+ * consulta aberta. Repetir é seguro — o motor devolve a consulta já
80
+ * aberta da mesma leitura em vez de abrir outra —, então a retentativa
81
+ * vale para ela como para uma leitura.
82
+ */
83
+ create: (body: Body<"/v1/processes/lookups", "post">, opts?: CallOptions) => Promise<LexNodeResult<"/v1/processes/lookups", "post">>;
84
+ get: (lookupId: string, opts?: CallOptions) => Promise<LexNodeResult<"/v1/processes/lookups/{lookupId}", "get">>;
85
+ list: (query?: Query<"/v1/processes/lookups", "get">, opts?: CallOptions) => Promise<LexNodeResult<"/v1/processes/lookups", "get">>;
86
+ cancel: (lookupId: string, opts?: CallOptions) => Promise<LexNodeResult<"/v1/processes/lookups/{lookupId}/cancel", "post">>;
87
+ };
74
88
  searchJobs: {
75
89
  get: (jobId: string, opts?: CallOptions) => Promise<LexNodeResult<"/v1/search-jobs/{jobId}", "get">>;
76
90
  list: (query?: Query<"/v1/search-jobs", "get">, opts?: CallOptions) => Promise<LexNodeResult<"/v1/search-jobs", "get">>;
package/dist/client.js CHANGED
@@ -18,12 +18,14 @@ const REQUEST_ID_IDEMPOTENT_WRITES = new Set([
18
18
  "post /v1/publications/text-searches",
19
19
  ]);
20
20
  /**
21
- * As leituras de UM processo em que quem chama escolhe, em `waitSeconds`,
22
- * quanto o motor espera pela fonte processual.
21
+ * As chamadas em que quem chama escolhe, em `waitSeconds`, quanto o motor
22
+ * espera pela fonte processual, e onde a espera viaja: na consulta das duas
23
+ * leituras de UM processo, no corpo da leitura que segue em segundo plano.
23
24
  */
24
- const SOURCE_WAIT_ROUTES = new Set([
25
- "get /v1/processes/{processNumber}",
26
- "get /v1/processes/{processNumber}/snapshot",
25
+ const SOURCE_WAIT_ROUTES = new Map([
26
+ ["get /v1/processes/{processNumber}", "query"],
27
+ ["get /v1/processes/{processNumber}/snapshot", "query"],
28
+ ["post /v1/processes/lookups", "body"],
27
29
  ]);
28
30
  /**
29
31
  * A espera que o motor aplica a essas leituras quando a chamada não traz
@@ -117,10 +119,14 @@ function appendQuery(url, query) {
117
119
  * prazo cobre essa espera mais a margem e nunca fica abaixo do prazo do
118
120
  * cliente. Nas outras rotas, vale o prazo do cliente.
119
121
  */
120
- function defaultCallTimeoutMs(method, path, query, clientTimeoutMs) {
121
- if (!SOURCE_WAIT_ROUTES.has(`${method} ${path}`))
122
+ function defaultCallTimeoutMs(method, path, carriers, clientTimeoutMs) {
123
+ const carrier = SOURCE_WAIT_ROUTES.get(`${method} ${path}`);
124
+ if (!carrier)
122
125
  return clientTimeoutMs;
123
- const requested = Number(query?.waitSeconds);
126
+ const source = carriers[carrier];
127
+ const requested = Number(source && typeof source === "object"
128
+ ? source.waitSeconds
129
+ : undefined);
124
130
  const waitSeconds = Number.isFinite(requested) && requested > 0
125
131
  ? requested
126
132
  : SOURCE_WAIT_API_DEFAULT_SECONDS;
@@ -261,7 +267,7 @@ export function createLexNodeClient(options) {
261
267
  throw LexNodeError.invalidRequest("O corpo da chamada não é serializável em JSON.", error);
262
268
  }
263
269
  const timeoutMs = args.timeoutMs ??
264
- defaultCallTimeoutMs(String(method), String(path), bag.params?.query, defaultTimeoutMs);
270
+ defaultCallTimeoutMs(String(method), String(path), { query: bag.params?.query, body: bag.body }, defaultTimeoutMs);
265
271
  const link = linkAbort(timeoutMs, args.signal);
266
272
  let response;
267
273
  let raw;
@@ -423,6 +429,29 @@ export function createLexNodeClient(options) {
423
429
  list: (query, opts = {}) => request("/v1/subscriptions", "get", { params: { query }, ...opts }),
424
430
  delete: (body, opts = {}) => request("/v1/subscriptions", "delete", { body, ...opts }),
425
431
  },
432
+ processLookups: {
433
+ /**
434
+ * A leitura de UM processo que segue em segundo plano quando a fonte não
435
+ * entrega dentro de `waitSeconds`: 200 com o processo, ou 202 com a
436
+ * consulta aberta. Repetir é seguro — o motor devolve a consulta já
437
+ * aberta da mesma leitura em vez de abrir outra —, então a retentativa
438
+ * vale para ela como para uma leitura.
439
+ */
440
+ create: (body, opts = {}) => request("/v1/processes/lookups", "post", {
441
+ body,
442
+ idempotent: true,
443
+ ...opts,
444
+ }),
445
+ get: (lookupId, opts = {}) => request("/v1/processes/lookups/{lookupId}", "get", {
446
+ params: { path: { lookupId } },
447
+ ...opts,
448
+ }),
449
+ list: (query, opts = {}) => request("/v1/processes/lookups", "get", { params: { query }, ...opts }),
450
+ cancel: (lookupId, opts = {}) => request("/v1/processes/lookups/{lookupId}/cancel", "post", {
451
+ params: { path: { lookupId } },
452
+ ...opts,
453
+ }),
454
+ },
426
455
  searchJobs: {
427
456
  get: (jobId, opts = {}) => request("/v1/search-jobs/{jobId}", "get", {
428
457
  params: { path: { jobId } },
package/dist/errors.d.ts CHANGED
@@ -9,7 +9,7 @@ import type { RateLimitSnapshot } from "./types.js";
9
9
  * os dois por literal redigitado descobre o engano em produção. A lista é
10
10
  * fechada para o que se conhece e aberta para o que vier (ver `LexNodeError`).
11
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"];
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", "PROCESS_LOOKUP_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", "PROCESS_LOOKUP_BUDGET_EXCEEDED", "PAGINATION_LIMIT_REACHED"];
13
13
  /**
14
14
  * Um código conhecido — ou qualquer outro.
15
15
  *
package/dist/errors.js CHANGED
@@ -43,6 +43,7 @@ export const LEXNODE_ERROR_CODES = [
43
43
  "MONITOR_ALREADY_EXISTS",
44
44
  "MONITOR_NOT_FOUND",
45
45
  "SEARCH_JOB_NOT_FOUND",
46
+ "PROCESS_LOOKUP_NOT_FOUND",
46
47
  "PUBLICATION_NOT_FOUND",
47
48
  "EVENT_NOT_FOUND",
48
49
  "TEXT_ALERT_NOT_FOUND",
@@ -54,6 +55,7 @@ export const LEXNODE_ERROR_CODES = [
54
55
  "CLIENT_MISSING_PATH_PARAM",
55
56
  "INVALID_RESPONSE",
56
57
  "SEARCH_JOB_BUDGET_EXCEEDED",
58
+ "PROCESS_LOOKUP_BUDGET_EXCEEDED",
57
59
  "PAGINATION_LIMIT_REACHED",
58
60
  ];
59
61
  const RAW_MESSAGE_MAX_CHARS = 500;
package/dist/index.d.ts CHANGED
@@ -15,9 +15,11 @@ export { LEXNODE_ERROR_CODES, LexNodeError, isLexNodeError } from "./errors.js";
15
15
  export type { LexNodeErrorClass, LexNodeErrorCode, LexNodeErrorInit, } from "./errors.js";
16
16
  export type { Body, CallOptions, Data, HttpMethod, LexNodeResult, Meta, MetaExtras, MethodOf, Operation, PathParams, Query, RateLimitSnapshot, RequestArgs, RetryPolicy, } from "./types.js";
17
17
  export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, LEXNODE_WEBHOOK_HEADERS, WEBHOOK_EVENT_TYPES, WEBHOOK_TEST_EVENT_TYPE, isKnownWebhookEventType, isReceiverFault, isWebhookTest, parseWebhookEvent, verifyWebhookSignature, webhookRejectionStatus, } from "./webhooks.js";
18
- export type { MonitorSyncFailedPayload, MonitorSyncInstance, MonitorSyncSucceededPayload, PeriodEndingPayload, ProcessInstanceDiscoveredPayload, ProcessInstanceDiscoveredWithheldPayload, ProcessMovementCreatedPayload, ProcessMovementInstanceCount, ProcessSourceIndex, PublicationCreatedPayload, PublicationHarvestCompletedPayload, PublicationTextAlertMatch, PublicationTextAlertMatchedPayload, QuotaLowPayload, SubscribableWebhookEventType, SearchJobFailedPayload, SearchJobSucceededPayload, SubscriptionPastDuePayload, SuspensionSignal, UnknownWebhookEvent, VerifyWebhookOptions, WebhookEvent, WebhookEventEnvelope, WebhookEventType, WebhookHeaders, WebhookParseRejection, WebhookParseResult, WebhookRawBody, WebhookRejection, WebhookTestPayload, WebhookVerification, } from "./webhooks.js";
18
+ export type { MonitorSyncFailedPayload, MonitorSyncInstance, MonitorSyncSucceededPayload, PeriodEndingPayload, ProcessInstanceDiscoveredPayload, ProcessInstanceDiscoveredWithheldPayload, ProcessLookupFailedPayload, ProcessLookupSucceededPayload, ProcessMovementCreatedPayload, ProcessMovementInstanceCount, ProcessSourceIndex, PublicationCreatedPayload, PublicationHarvestCompletedPayload, PublicationTextAlertMatch, PublicationTextAlertMatchedPayload, QuotaLowPayload, SubscribableWebhookEventType, SearchJobFailedPayload, SearchJobSucceededPayload, SubscriptionPastDuePayload, SuspensionSignal, UnknownWebhookEvent, VerifyWebhookOptions, WebhookEvent, WebhookEventEnvelope, WebhookEventType, WebhookHeaders, WebhookParseRejection, WebhookParseResult, WebhookRawBody, WebhookRejection, WebhookTestPayload, WebhookVerification, } from "./webhooks.js";
19
19
  export { paginate } from "./paginate.js";
20
20
  export type { PaginateOptions } from "./paginate.js";
21
+ export { lookupProcess, waitForProcessLookup } from "./lookup.js";
22
+ export type { ProcessLookup, ProcessLookupInput, ProcessLookupOutcome, ProcessLookupRead, ProcessLookupState, ProcessLookupView, StaleProcessCopy, WaitForProcessLookupOptions, } from "./lookup.js";
21
23
  export { searchProcesses, waitForSearchJob } from "./search.js";
22
24
  export type { ProcessSearchOutcome, ProcessSearchResult, QueuedSearchJob, SearchJob, SearchJobState, WaitForSearchJobOptions, } from "./search.js";
23
25
  export { describeFreshness, freshnessOf, isNotYetIndexed, lakeRescueOf, readThrough, sourceFetchedAtOf, } from "./freshness.js";
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export { createLexNodeClient } from "./client.js";
12
12
  export { LEXNODE_ERROR_CODES, LexNodeError, isLexNodeError } from "./errors.js";
13
13
  export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, LEXNODE_WEBHOOK_HEADERS, WEBHOOK_EVENT_TYPES, WEBHOOK_TEST_EVENT_TYPE, isKnownWebhookEventType, isReceiverFault, isWebhookTest, parseWebhookEvent, verifyWebhookSignature, webhookRejectionStatus, } from "./webhooks.js";
14
14
  export { paginate } from "./paginate.js";
15
+ export { lookupProcess, waitForProcessLookup } from "./lookup.js";
15
16
  export { searchProcesses, waitForSearchJob } from "./search.js";
16
17
  export { describeFreshness, freshnessOf, isNotYetIndexed, lakeRescueOf, readThrough, sourceFetchedAtOf, } from "./freshness.js";
17
18
  export { isFirstAppearance, resubscribeOnFirstAppearance, unwatchProcess, watchProcess, } from "./watch.js";
@@ -0,0 +1,79 @@
1
+ import type { LexNodeClient } from "./client.js";
2
+ import type { Body, CallOptions, Data, Meta, MetaExtras } from "./types.js";
3
+ /**
4
+ * A leitura de UM processo que segue em segundo plano responde de dois jeitos
5
+ * na MESMA operação: 200 com o processo, quando ele coube na espera, ou 202
6
+ * com a consulta que segue tentando a fonte sozinha. O discriminador é o
7
+ * código HTTP, como na busca: a consulta aberta também tem `status`, mas quem
8
+ * afirma o desfecho da chamada é o 200 ou o 202.
9
+ */
10
+ type CreateData = Data<"/v1/processes/lookups", "post">;
11
+ type CreateMeta = Meta<"/v1/processes/lookups", "post"> & MetaExtras;
12
+ /** A consulta como a leitura dela a devolve. */
13
+ export type ProcessLookup = Data<"/v1/processes/lookups/{lookupId}", "get">;
14
+ export type ProcessLookupState = ProcessLookup["status"];
15
+ export type ProcessLookupView = ProcessLookup["view"];
16
+ /** O que se pede: tribunal, número, visão, espera e a cópia na falha. */
17
+ export type ProcessLookupInput = Body<"/v1/processes/lookups", "post">;
18
+ /** A cópia guardada que o 202 entrega enquanto a consulta segue. */
19
+ export type StaleProcessCopy = NonNullable<CreateData["stale"]>;
20
+ /** O processo entregue na visão pedida: a ficha ou o retrato. */
21
+ export type ProcessLookupRead<V extends ProcessLookupView> = V extends "SNAPSHOT" ? NonNullable<CreateData["snapshot"]> : NonNullable<CreateData["process"]>;
22
+ export type ProcessLookupOutcome<V extends ProcessLookupView = ProcessLookupView> = {
23
+ kind: "ready";
24
+ /** A ficha ou o retrato, como a leitura direta os devolveria. */
25
+ data: ProcessLookupRead<V>;
26
+ lookup: CreateData;
27
+ meta: CreateMeta;
28
+ } | {
29
+ kind: "pending";
30
+ /** A consulta aberta, a ser acompanhada por `waitForProcessLookup`. */
31
+ lookup: CreateData;
32
+ /** A cópia guardada, com a data, quando pedida em `staleOnFailure`. */
33
+ stale: StaleProcessCopy | null;
34
+ meta: CreateMeta;
35
+ };
36
+ /**
37
+ * Lê um processo e diz, no tipo, qual dos dois desfechos aconteceu.
38
+ *
39
+ * Sem isto, o consumidor lê `data.process` num corpo que é uma consulta
40
+ * aberta e chama de "sem dados" o que é "ainda estou buscando". A mesma
41
+ * leitura pedida de novo devolve a mesma consulta — repetir é seguro, e o
42
+ * cliente repete sozinho quando a política de retentativa estiver ligada.
43
+ */
44
+ export declare function lookupProcess<V extends ProcessLookupView>(client: LexNodeClient, input: ProcessLookupInput & {
45
+ view: V;
46
+ }, opts?: CallOptions): Promise<ProcessLookupOutcome<V>>;
47
+ export interface WaitForProcessLookupOptions {
48
+ /**
49
+ * Teto de espera total, em ms, incluindo as leituras e suas retentativas.
50
+ * EXPLÍCITO de propósito, como na busca: a consulta
51
+ * tenta por horas, e um laço sem orçamento gasta a requisição inteira de um
52
+ * ambiente serverless esperando uma fonte fora do ar.
53
+ */
54
+ budgetMs?: number;
55
+ signal?: AbortSignal;
56
+ /** Chamado a cada leitura da consulta, para a UI acompanhar. */
57
+ onState?: (state: ProcessLookupState, lookup: ProcessLookup) => void;
58
+ officeId?: string | null;
59
+ }
60
+ /**
61
+ * Espera a consulta terminar, dentro do orçamento.
62
+ *
63
+ * Lê a consulta de novo só quando há o que mudar: na hora da próxima tentativa
64
+ * (`nextAttemptAt`) — antes dela, a leitura devolve o mesmo estado —, a cada
65
+ * 10 s enquanto uma tentativa está em curso ou espera a vez na fila, e depois
66
+ * do `Retry-After` quando a própria leitura é recusada por vazão. Leitura não
67
+ * cobra, mas conta no teto por minuto da chave.
68
+ *
69
+ * O estouro do orçamento é um erro PRÓPRIO e retentável
70
+ * (`PROCESS_LOOKUP_BUDGET_EXCEEDED`): a consulta continua no motor, e quem
71
+ * espera pode voltar a lê-la depois — `details.nextReadAt` diz quando vale a
72
+ * pena — ou receber o `PROCESS_LOOKUP_COMPLETED` por webhook e não esperar
73
+ * nada. Quando a próxima leitura útil cai depois do orçamento, o erro sai na
74
+ * hora: esperar até o fim dele não mudaria nada. Devolve a consulta
75
+ * terminada: `SUCCEEDED`, com o processo enquanto o resultado vale; `FAILED`,
76
+ * com `error`; ou `CANCELED`.
77
+ */
78
+ export declare function waitForProcessLookup(client: LexNodeClient, lookupId: string, options?: WaitForProcessLookupOptions): Promise<ProcessLookup>;
79
+ export {};
package/dist/lookup.js ADDED
@@ -0,0 +1,158 @@
1
+ import { LexNodeError, isLexNodeError } from "./errors.js";
2
+ /** O processo que a consulta entregou, na visão dela, ou nulo. */
3
+ function readOf(lookup, view) {
4
+ const read = view === "SNAPSHOT" ? lookup.snapshot : lookup.process;
5
+ return (read ?? null);
6
+ }
7
+ /**
8
+ * Lê um processo e diz, no tipo, qual dos dois desfechos aconteceu.
9
+ *
10
+ * Sem isto, o consumidor lê `data.process` num corpo que é uma consulta
11
+ * aberta e chama de "sem dados" o que é "ainda estou buscando". A mesma
12
+ * leitura pedida de novo devolve a mesma consulta — repetir é seguro, e o
13
+ * cliente repete sozinho quando a política de retentativa estiver ligada.
14
+ */
15
+ export async function lookupProcess(client, input, opts = {}) {
16
+ const { data, meta } = await client.processLookups.create(input, opts);
17
+ if (meta.status === 202) {
18
+ return { kind: "pending", lookup: data, stale: data.stale ?? null, meta };
19
+ }
20
+ const read = readOf(data, input.view);
21
+ if (!read) {
22
+ throw new LexNodeError({
23
+ code: "INVALID_RESPONSE",
24
+ message: `A consulta respondeu ${meta.status} sem o processo da visão ${input.view}.`,
25
+ status: 502,
26
+ errorClass: "RETRYABLE",
27
+ retryable: true,
28
+ upstreamStatus: meta.status,
29
+ });
30
+ }
31
+ return { kind: "ready", data: read, lookup: data, meta };
32
+ }
33
+ /** Estados em que a consulta não avança mais. */
34
+ const TERMINAL_STATES = new Set(["SUCCEEDED", "FAILED", "CANCELED"]);
35
+ const DEFAULT_BUDGET_MS = 60_000;
36
+ /** A menor espera entre duas leituras, para a próxima tentativa que está perto. */
37
+ const MIN_POLL_INTERVAL_MS = 2_000;
38
+ /**
39
+ * A espera entre leituras da consulta sem hora futura: a tentativa em curso
40
+ * (`nextAttemptAt` nulo) ou a vencida que espera a vez na fila. O desfecho
41
+ * dela sai em até um minuto, e ler a cada poucos segundos só gastaria o teto
42
+ * por minuto da chave.
43
+ */
44
+ const IN_FLIGHT_POLL_INTERVAL_MS = 10_000;
45
+ function wait(ms, signal) {
46
+ return new Promise((resolve, reject) => {
47
+ if (signal?.aborted) {
48
+ reject(signal.reason);
49
+ return;
50
+ }
51
+ let timer;
52
+ const onAbort = () => {
53
+ clearTimeout(timer);
54
+ reject(signal?.reason);
55
+ };
56
+ timer = setTimeout(() => {
57
+ signal?.removeEventListener("abort", onAbort);
58
+ resolve();
59
+ }, ms);
60
+ signal?.addEventListener("abort", onAbort, { once: true });
61
+ });
62
+ }
63
+ function budgetExceeded(lookupId, budgetMs, state, nextReadAt) {
64
+ return new LexNodeError({
65
+ code: "PROCESS_LOOKUP_BUDGET_EXCEEDED",
66
+ message: `A consulta ${lookupId} não termina dentro do orçamento de ${budgetMs} ms; ela segue tentando no motor.`,
67
+ status: 504,
68
+ errorClass: "RETRYABLE",
69
+ retryable: true,
70
+ details: {
71
+ lookupId,
72
+ status: state,
73
+ budgetMs,
74
+ nextReadAt: new Date(nextReadAt).toISOString(),
75
+ },
76
+ });
77
+ }
78
+ /**
79
+ * Espera a consulta terminar, dentro do orçamento.
80
+ *
81
+ * Lê a consulta de novo só quando há o que mudar: na hora da próxima tentativa
82
+ * (`nextAttemptAt`) — antes dela, a leitura devolve o mesmo estado —, a cada
83
+ * 10 s enquanto uma tentativa está em curso ou espera a vez na fila, e depois
84
+ * do `Retry-After` quando a própria leitura é recusada por vazão. Leitura não
85
+ * cobra, mas conta no teto por minuto da chave.
86
+ *
87
+ * O estouro do orçamento é um erro PRÓPRIO e retentável
88
+ * (`PROCESS_LOOKUP_BUDGET_EXCEEDED`): a consulta continua no motor, e quem
89
+ * espera pode voltar a lê-la depois — `details.nextReadAt` diz quando vale a
90
+ * pena — ou receber o `PROCESS_LOOKUP_COMPLETED` por webhook e não esperar
91
+ * nada. Quando a próxima leitura útil cai depois do orçamento, o erro sai na
92
+ * hora: esperar até o fim dele não mudaria nada. Devolve a consulta
93
+ * terminada: `SUCCEEDED`, com o processo enquanto o resultado vale; `FAILED`,
94
+ * com `error`; ou `CANCELED`.
95
+ */
96
+ export async function waitForProcessLookup(client, lookupId, options = {}) {
97
+ const budgetMs = options.budgetMs ?? DEFAULT_BUDGET_MS;
98
+ const deadline = Date.now() + budgetMs;
99
+ let lastState = null;
100
+ const controller = new AbortController();
101
+ const { signal } = controller;
102
+ const onCallerAbort = () => controller.abort(options.signal?.reason);
103
+ if (options.signal?.aborted)
104
+ onCallerAbort();
105
+ else
106
+ options.signal?.addEventListener("abort", onCallerAbort, { once: true });
107
+ const timer = setTimeout(() => {
108
+ controller.abort(budgetExceeded(lookupId, budgetMs, lastState, Date.now()));
109
+ }, Math.max(0, budgetMs));
110
+ try {
111
+ for (;;) {
112
+ signal.throwIfAborted();
113
+ if (Date.now() >= deadline) {
114
+ throw budgetExceeded(lookupId, budgetMs, lastState, Date.now());
115
+ }
116
+ let nextReadAt;
117
+ try {
118
+ const { data } = await client.processLookups.get(lookupId, {
119
+ signal,
120
+ officeId: options.officeId,
121
+ });
122
+ signal.throwIfAborted();
123
+ if (Date.now() >= deadline) {
124
+ throw budgetExceeded(lookupId, budgetMs, lastState, Date.now());
125
+ }
126
+ lastState = data.status;
127
+ options.onState?.(data.status, data);
128
+ if (TERMINAL_STATES.has(data.status))
129
+ return data;
130
+ const nextAttemptAt = data.nextAttemptAt
131
+ ? Date.parse(data.nextAttemptAt)
132
+ : Number.NaN;
133
+ const now = Date.now();
134
+ nextReadAt =
135
+ Number.isFinite(nextAttemptAt) && nextAttemptAt > now
136
+ ? Math.max(now + MIN_POLL_INTERVAL_MS, nextAttemptAt)
137
+ : now + IN_FLIGHT_POLL_INTERVAL_MS;
138
+ }
139
+ catch (error) {
140
+ signal.throwIfAborted();
141
+ if (!isLexNodeError(error) ||
142
+ !error.retryable ||
143
+ error.retryAfterSeconds === null) {
144
+ throw error;
145
+ }
146
+ nextReadAt = Date.now() + error.retryAfterSeconds * 1_000;
147
+ }
148
+ if (nextReadAt >= deadline) {
149
+ throw budgetExceeded(lookupId, budgetMs, lastState, nextReadAt);
150
+ }
151
+ await wait(nextReadAt - Date.now(), signal);
152
+ }
153
+ }
154
+ finally {
155
+ clearTimeout(timer);
156
+ options.signal?.removeEventListener("abort", onCallerAbort);
157
+ }
158
+ }
@@ -323,6 +323,48 @@ export interface SearchJobFailedPayload {
323
323
  };
324
324
  pollUrl: string;
325
325
  }
326
+ /**
327
+ * O fim de uma consulta de processo em segundo plano
328
+ * (`POST /v1/processes/lookups`). É um ponteiro: o processo nunca viaja no
329
+ * evento — lê-se em `pollUrl`, sem nova cobrança, pelo controle de
330
+ * confidencialidade de quem lê. O movimento novo que a entrega trouxer chega no
331
+ * `PROCESS_MOVEMENT_CREATED` de sempre.
332
+ */
333
+ interface ProcessLookupCompletedBase {
334
+ lookupId: string;
335
+ view: "PROCESS" | "SNAPSHOT";
336
+ tribunalAlias: string;
337
+ /** Só os 20 dígitos. */
338
+ processNumber: string;
339
+ officeId: string | null;
340
+ traceId: string | null;
341
+ /** Idas à fonte, a leitura que abriu a consulta incluída. */
342
+ attempts: number;
343
+ pollUrl: string;
344
+ }
345
+ export interface ProcessLookupSucceededPayload extends ProcessLookupCompletedBase {
346
+ status: "SUCCEEDED";
347
+ /**
348
+ * Quem entregou: a fonte, o acervo que outra leitura atualizou, ou uma
349
+ * leitura direta da mesma conta, que já tinha cobrado — então esta não cobra.
350
+ */
351
+ satisfiedBy: "SOURCE" | "LAKE" | "DIRECT_READ";
352
+ creditsCharged: number;
353
+ }
354
+ export interface ProcessLookupFailedPayload extends ProcessLookupCompletedBase {
355
+ status: "FAILED";
356
+ satisfiedBy: null;
357
+ /** A retenção inteira, devolvida ao saldo. */
358
+ creditsRefunded: number;
359
+ error: {
360
+ class: "RETRYABLE" | "TERMINAL";
361
+ code: string;
362
+ /** O texto público fixo do código. */
363
+ message: string;
364
+ retryable: boolean;
365
+ upstreamStatus?: number;
366
+ };
367
+ }
326
368
  export interface PublicationTextAlertMatch {
327
369
  source: string;
328
370
  publicationKey: string;
@@ -379,7 +421,7 @@ type Typed<T extends string, P> = WebhookEventEnvelope & {
379
421
  payload: P;
380
422
  };
381
423
  /** Os tipos do catálogo e a entrega de teste, discriminados por `type`. */
382
- export type WebhookEvent = Typed<"PROCESS_MOVEMENT_CREATED", ProcessMovementCreatedPayload> | Typed<"PROCESS_INSTANCE_DISCOVERED", ProcessInstanceDiscoveredPayload | ProcessInstanceDiscoveredWithheldPayload> | Typed<"PUBLICATION_CREATED", PublicationCreatedPayload> | Typed<"MONITOR_SYNC_SUCCEEDED", MonitorSyncSucceededPayload> | Typed<"MONITOR_SYNC_FAILED", MonitorSyncFailedPayload> | Typed<"SEARCH_JOB_COMPLETED", SearchJobSucceededPayload | SearchJobFailedPayload> | Typed<"PUBLICATION_TEXT_ALERT_MATCHED", PublicationTextAlertMatchedPayload> | Typed<"PUBLICATION_HARVEST_COMPLETED", PublicationHarvestCompletedPayload> | Typed<"QUOTA_LOW", QuotaLowPayload> | Typed<"PERIOD_ENDING", PeriodEndingPayload> | Typed<"SUBSCRIPTION_PAST_DUE", SubscriptionPastDuePayload> | Typed<"WEBHOOK_TEST", WebhookTestPayload>;
424
+ export type WebhookEvent = Typed<"PROCESS_MOVEMENT_CREATED", ProcessMovementCreatedPayload> | Typed<"PROCESS_INSTANCE_DISCOVERED", ProcessInstanceDiscoveredPayload | ProcessInstanceDiscoveredWithheldPayload> | Typed<"PUBLICATION_CREATED", PublicationCreatedPayload> | Typed<"MONITOR_SYNC_SUCCEEDED", MonitorSyncSucceededPayload> | Typed<"MONITOR_SYNC_FAILED", MonitorSyncFailedPayload> | Typed<"SEARCH_JOB_COMPLETED", SearchJobSucceededPayload | SearchJobFailedPayload> | Typed<"PUBLICATION_TEXT_ALERT_MATCHED", PublicationTextAlertMatchedPayload> | Typed<"PUBLICATION_HARVEST_COMPLETED", PublicationHarvestCompletedPayload> | Typed<"PROCESS_LOOKUP_COMPLETED", ProcessLookupSucceededPayload | ProcessLookupFailedPayload> | Typed<"QUOTA_LOW", QuotaLowPayload> | Typed<"PERIOD_ENDING", PeriodEndingPayload> | Typed<"SUBSCRIPTION_PAST_DUE", SubscriptionPastDuePayload> | Typed<"WEBHOOK_TEST", WebhookTestPayload>;
383
425
  /**
384
426
  * O evento que o catálogo ainda não descreve.
385
427
  *
@@ -402,7 +444,7 @@ export type WebhookEventType = WebhookEvent["type"];
402
444
  */
403
445
  export declare const WEBHOOK_TEST_EVENT_TYPE: "WEBHOOK_TEST";
404
446
  /** Os tipos que se pode ASSINAR ao cadastrar um endpoint. */
405
- export declare const WEBHOOK_EVENT_TYPES: readonly ["PROCESS_MOVEMENT_CREATED", "PROCESS_INSTANCE_DISCOVERED", "PUBLICATION_CREATED", "MONITOR_SYNC_SUCCEEDED", "MONITOR_SYNC_FAILED", "SEARCH_JOB_COMPLETED", "PUBLICATION_TEXT_ALERT_MATCHED", "PUBLICATION_HARVEST_COMPLETED", "QUOTA_LOW", "PERIOD_ENDING", "SUBSCRIPTION_PAST_DUE"];
447
+ export declare const WEBHOOK_EVENT_TYPES: readonly ["PROCESS_MOVEMENT_CREATED", "PROCESS_INSTANCE_DISCOVERED", "PUBLICATION_CREATED", "MONITOR_SYNC_SUCCEEDED", "MONITOR_SYNC_FAILED", "SEARCH_JOB_COMPLETED", "PUBLICATION_TEXT_ALERT_MATCHED", "PUBLICATION_HARVEST_COMPLETED", "PROCESS_LOOKUP_COMPLETED", "QUOTA_LOW", "PERIOD_ENDING", "SUBSCRIPTION_PAST_DUE"];
406
448
  /**
407
449
  * O que entra em `eventTypes` ao cadastrar um endpoint. É subconjunto próprio
408
450
  * de `WebhookEventType`: mandar a entrega de teste nessa lista é recusa.
package/dist/webhooks.js CHANGED
@@ -124,6 +124,7 @@ export const WEBHOOK_EVENT_TYPES = [
124
124
  "SEARCH_JOB_COMPLETED",
125
125
  "PUBLICATION_TEXT_ALERT_MATCHED",
126
126
  "PUBLICATION_HARVEST_COMPLETED",
127
+ "PROCESS_LOOKUP_COMPLETED",
127
128
  "QUOTA_LOW",
128
129
  "PERIOD_ENDING",
129
130
  "SUBSCRIPTION_PAST_DUE",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexnode/legal-data-sdk",
3
- "version": "1.27.0",
3
+ "version": "1.29.0",
4
4
  "description": "Cliente TypeScript do LexNode, o motor de dados jur\u00eddicos do Brasil: transporte tipado, erros classificados, verifica\u00e7\u00e3o de webhook e os tipos gerados do contrato OpenAPI.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.lexnode.com.br/docs",