@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/dist/lookup.d.ts CHANGED
@@ -1,24 +1,32 @@
1
1
  import type { LexNodeClient } from "./client.js";
2
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
3
  type CreateData = Data<"/v1/processes/lookups", "post">;
11
4
  type CreateMeta = Meta<"/v1/processes/lookups", "post"> & MetaExtras;
12
- /** A consulta como a leitura dela a devolve. */
5
+ /**
6
+ * A consulta em segundo plano, como `GET /v1/processes/lookups/{lookupId}` a
7
+ * devolve.
8
+ */
13
9
  export type ProcessLookup = Data<"/v1/processes/lookups/{lookupId}", "get">;
10
+ /** O estado da consulta em segundo plano. */
14
11
  export type ProcessLookupState = ProcessLookup["status"];
12
+ /** A visão pedida: `PROCESS` (a ficha) ou `SNAPSHOT` (o retrato). */
15
13
  export type ProcessLookupView = ProcessLookup["view"];
16
- /** O que se pede: tribunal, número, visão, espera e a cópia na falha. */
14
+ /**
15
+ * O pedido de `POST /v1/processes/lookups`: o tribunal, o número, a visão, a
16
+ * espera pela fonte e se a cópia guardada serve na falha dela.
17
+ */
17
18
  export type ProcessLookupInput = Body<"/v1/processes/lookups", "post">;
18
- /** A cópia guardada que o 202 entrega enquanto a consulta segue. */
19
+ /**
20
+ * A cópia guardada, com a data, que a resposta 202 traz quando o pedido tem
21
+ * `staleOnFailure: true`.
22
+ */
19
23
  export type StaleProcessCopy = NonNullable<CreateData["stale"]>;
20
- /** O processo entregue na visão pedida: a ficha ou o retrato. */
24
+ /** O processo na visão pedida: a ficha ou o retrato. */
21
25
  export type ProcessLookupRead<V extends ProcessLookupView> = V extends "SNAPSHOT" ? NonNullable<CreateData["snapshot"]> : NonNullable<CreateData["process"]>;
26
+ /**
27
+ * O desfecho de `lookupProcess`: `ready` com o processo em `data`, ou
28
+ * `pending` com a consulta aberta em `lookup`.
29
+ */
22
30
  export type ProcessLookupOutcome<V extends ProcessLookupView = ProcessLookupView> = {
23
31
  kind: "ready";
24
32
  /** A ficha ou o retrato, como a leitura direta os devolveria. */
@@ -27,53 +35,138 @@ export type ProcessLookupOutcome<V extends ProcessLookupView = ProcessLookupView
27
35
  meta: CreateMeta;
28
36
  } | {
29
37
  kind: "pending";
30
- /** A consulta aberta, a ser acompanhada por `waitForProcessLookup`. */
38
+ /** A consulta aberta; acompanhe-a com `waitForProcessLookup`. */
31
39
  lookup: CreateData;
32
40
  /** A cópia guardada, com a data, quando pedida em `staleOnFailure`. */
33
41
  stale: StaleProcessCopy | null;
34
42
  meta: CreateMeta;
35
43
  };
36
44
  /**
37
- * Lê um processo e diz, no tipo, qual dos dois desfechos aconteceu.
45
+ * Lê um processo e diz no tipo se ele veio na hora ou se a leitura seguiu em
46
+ * segundo plano.
47
+ *
48
+ * Chama `POST /v1/processes/lookups`. Com 200, devolve `kind: "ready"` e o
49
+ * processo em `data`, como a leitura direta o devolveria; com 202, devolve
50
+ * `kind: "pending"` e a consulta aberta em `lookup`, para acompanhar com
51
+ * `waitForProcessLookup` ou pelo evento `PROCESS_LOOKUP_COMPLETED`. Uma
52
+ * consulta aberta não é um processo sem dados. Pedir a mesma leitura de novo
53
+ * devolve a mesma consulta, então repetir é seguro, e a política de
54
+ * retentativa, quando ligada, repete esta chamada.
55
+ *
56
+ * Lança `LexNodeError` nas recusas da API e `INVALID_RESPONSE` quando o 200
57
+ * vem sem o processo da visão pedida.
38
58
  *
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.
59
+ * @example
60
+ * ```ts
61
+ * const desfecho = await lookupProcess(lexnode, {
62
+ * tribunalAlias: "TJSP",
63
+ * processNumber: numero,
64
+ * view: "SNAPSHOT",
65
+ * });
66
+ * if (desfecho.kind === "ready") desfecho.data.movements;
67
+ * else guardarConsulta(desfecho.lookup.lookupId);
68
+ * ```
43
69
  */
44
70
  export declare function lookupProcess<V extends ProcessLookupView>(client: LexNodeClient, input: ProcessLookupInput & {
45
71
  view: V;
46
72
  }, opts?: CallOptions): Promise<ProcessLookupOutcome<V>>;
73
+ /** As opções de `waitForProcessLookup`. */
47
74
  export interface WaitForProcessLookupOptions {
48
75
  /**
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.
76
+ * O tempo máximo de espera, em ms, contando as leituras e as retentativas.
77
+ * Padrão: 60 000. A consulta pode seguir tentando por horas; num ambiente
78
+ * serverless, escolha um valor que caiba no prazo da sua função.
53
79
  */
54
80
  budgetMs?: number;
81
+ /** Cancela a espera; a consulta continua. */
55
82
  signal?: AbortSignal;
56
- /** Chamado a cada leitura da consulta, para a UI acompanhar. */
57
- onState?: (state: ProcessLookupState, lookup: ProcessLookup) => void;
83
+ /**
84
+ * Chamada a cada leitura, com o estado, a consulta e o `meta`, por exemplo
85
+ * para mostrar o progresso.
86
+ */
87
+ onState?: (state: ProcessLookupState, lookup: ProcessLookup, meta: Meta<"/v1/processes/lookups/{lookupId}", "get"> & MetaExtras) => void;
88
+ /** O escritório (sub-tenant) das leituras; veja `CallOptions.officeId`. */
58
89
  officeId?: string | null;
59
90
  }
60
91
  /**
61
- * Espera a consulta terminar, dentro do orçamento.
92
+ * Espera a consulta em segundo plano terminar e devolve a consulta terminada:
93
+ * `SUCCEEDED`, com o processo enquanto o resultado vale; `FAILED`, com
94
+ * `error`; ou `CANCELED`.
62
95
  *
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.
96
+ * Lê a consulta na hora da próxima tentativa (`nextAttemptAt`), a cada 10 s
97
+ * enquanto uma tentativa está em curso ou na fila e, quando a leitura é
98
+ * recusada por vazão, depois do `Retry-After`. Ler a consulta não custa
99
+ * créditos, mas conta no limite de vazão da chave.
68
100
  *
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`.
101
+ * Lança `LexNodeError` `PROCESS_LOOKUP_BUDGET_EXCEEDED` (retentável) quando
102
+ * `budgetMs` acaba antes do fim, ou assim que a próxima leitura útil cairia
103
+ * depois dele. A consulta continua: `details.nextReadAt` diz quando vale ler
104
+ * de novo, e o evento `PROCESS_LOOKUP_COMPLETED` avisa o fim. Repassa as
105
+ * demais falhas das leituras.
77
106
  */
78
107
  export declare function waitForProcessLookup(client: LexNodeClient, lookupId: string, options?: WaitForProcessLookupOptions): Promise<ProcessLookup>;
108
+ type ProcessRecoveryMeta = CreateMeta | (Meta<"/v1/processes/lookups/{lookupId}", "get"> & MetaExtras);
109
+ /**
110
+ * O desfecho de `readProcessWithRecovery`: `ready` com o processo; `pending`
111
+ * com a consulta aberta, a cópia guardada quando houver e a próxima leitura
112
+ * útil em `nextReadAt`; ou `failed`, `canceled` e `expired`, com a consulta
113
+ * terminada sem o processo. Nenhum desses três afirma que o processo não
114
+ * existe.
115
+ */
116
+ export type ProcessRecoveryOutcome<V extends ProcessLookupView> = {
117
+ kind: "ready";
118
+ data: ProcessLookupRead<V>;
119
+ lookup: CreateData | ProcessLookup;
120
+ meta: ProcessRecoveryMeta;
121
+ } | {
122
+ kind: "pending";
123
+ lookup: CreateData | ProcessLookup;
124
+ stale: StaleProcessCopy | null;
125
+ nextReadAt: string | null;
126
+ } | {
127
+ kind: "failed" | "canceled" | "expired";
128
+ lookup: ProcessLookup;
129
+ };
130
+ /**
131
+ * Lê um processo com recuperação: abre a consulta e, quando ela segue em
132
+ * segundo plano, acompanha-a por até `completionBudgetMs`.
133
+ *
134
+ * `staleOnFailure` é obrigatório: com `true`, você aceita receber, na falha da
135
+ * fonte, uma cópia guardada anterior a ela — confira a data em
136
+ * `sourceFetchedAt`. Sem `completionBudgetMs`, ou com 0, devolve na hora o
137
+ * desfecho da abertura. Ao esgotar o orçamento, devolve `pending` com a mesma
138
+ * consulta, sem abrir outra nem cancelá-la; continue com
139
+ * `processLookups.get(lookupId)` ou pelo evento `PROCESS_LOOKUP_COMPLETED`.
140
+ * Abortar o `signal` interrompe a espera com o motivo do aborto e também não
141
+ * cancela a consulta.
142
+ *
143
+ * Lança `RangeError` quando `completionBudgetMs` é negativo ou não finito, e
144
+ * `LexNodeError` nas recusas da API.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const leitura = await readProcessWithRecovery(
149
+ * lexnode,
150
+ * {
151
+ * tribunalAlias: "TJSP",
152
+ * processNumber,
153
+ * view: "PROCESS",
154
+ * staleOnFailure: true,
155
+ * },
156
+ * { completionBudgetMs: 30_000 },
157
+ * );
158
+ * if (leitura.kind === "ready") {
159
+ * usarProcesso(leitura.data);
160
+ * } else if (leitura.kind === "pending") {
161
+ * guardarConsulta(leitura.lookup.lookupId, leitura.nextReadAt);
162
+ * }
163
+ * ```
164
+ */
165
+ export declare function readProcessWithRecovery<V extends ProcessLookupView>(client: LexNodeClient, input: ProcessLookupInput & {
166
+ view: V;
167
+ staleOnFailure: boolean;
168
+ }, options?: CallOptions & {
169
+ completionBudgetMs?: number;
170
+ onState?: WaitForProcessLookupOptions["onState"];
171
+ }): Promise<ProcessRecoveryOutcome<V>>;
79
172
  export {};
package/dist/lookup.js CHANGED
@@ -1,17 +1,8 @@
1
1
  import { LexNodeError, isLexNodeError } from "./errors.js";
2
- /** O processo que a consulta entregou, na visão dela, ou nulo. */
3
2
  function readOf(lookup, view) {
4
3
  const read = view === "SNAPSHOT" ? lookup.snapshot : lookup.process;
5
4
  return (read ?? null);
6
5
  }
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
6
  export async function lookupProcess(client, input, opts = {}) {
16
7
  const { data, meta } = await client.processLookups.create(input, opts);
17
8
  if (meta.status === 202) {
@@ -30,17 +21,9 @@ export async function lookupProcess(client, input, opts = {}) {
30
21
  }
31
22
  return { kind: "ready", data: read, lookup: data, meta };
32
23
  }
33
- /** Estados em que a consulta não avança mais. */
34
24
  const TERMINAL_STATES = new Set(["SUCCEEDED", "FAILED", "CANCELED"]);
35
25
  const DEFAULT_BUDGET_MS = 60_000;
36
- /** A menor espera entre duas leituras, para a próxima tentativa que está perto. */
37
26
  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
27
  const IN_FLIGHT_POLL_INTERVAL_MS = 10_000;
45
28
  function wait(ms, signal) {
46
29
  return new Promise((resolve, reject) => {
@@ -75,24 +58,6 @@ function budgetExceeded(lookupId, budgetMs, state, nextReadAt) {
75
58
  },
76
59
  });
77
60
  }
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
61
  export async function waitForProcessLookup(client, lookupId, options = {}) {
97
62
  const budgetMs = options.budgetMs ?? DEFAULT_BUDGET_MS;
98
63
  const deadline = Date.now() + budgetMs;
@@ -115,7 +80,7 @@ export async function waitForProcessLookup(client, lookupId, options = {}) {
115
80
  }
116
81
  let nextReadAt;
117
82
  try {
118
- const { data } = await client.processLookups.get(lookupId, {
83
+ const { data, meta } = await client.processLookups.get(lookupId, {
119
84
  signal,
120
85
  officeId: options.officeId,
121
86
  });
@@ -124,7 +89,7 @@ export async function waitForProcessLookup(client, lookupId, options = {}) {
124
89
  throw budgetExceeded(lookupId, budgetMs, lastState, Date.now());
125
90
  }
126
91
  lastState = data.status;
127
- options.onState?.(data.status, data);
92
+ options.onState?.(data.status, data, meta);
128
93
  if (TERMINAL_STATES.has(data.status))
129
94
  return data;
130
95
  const nextAttemptAt = data.nextAttemptAt
@@ -156,3 +121,64 @@ export async function waitForProcessLookup(client, lookupId, options = {}) {
156
121
  options.signal?.removeEventListener("abort", onCallerAbort);
157
122
  }
158
123
  }
124
+ export async function readProcessWithRecovery(client, input, options = {}) {
125
+ const budgetMs = options.completionBudgetMs ?? 0;
126
+ options.signal?.throwIfAborted();
127
+ if (!Number.isFinite(budgetMs) || budgetMs < 0)
128
+ throw new RangeError("completionBudgetMs deve ser finito e não negativo.");
129
+ const initial = await lookupProcess(client, input, options);
130
+ options.signal?.throwIfAborted();
131
+ if (initial.kind === "ready")
132
+ return initial;
133
+ const pending = {
134
+ kind: "pending",
135
+ lookup: initial.lookup,
136
+ stale: initial.stale,
137
+ nextReadAt: initial.lookup.nextAttemptAt ?? null,
138
+ };
139
+ if (budgetMs === 0)
140
+ return pending;
141
+ let latest = initial.lookup;
142
+ let latestMeta = initial.meta;
143
+ try {
144
+ const lookup = await waitForProcessLookup(client, initial.lookup.lookupId, {
145
+ budgetMs,
146
+ signal: options.signal,
147
+ officeId: options.officeId,
148
+ onState: (state, currentLookup, meta) => {
149
+ latest = currentLookup;
150
+ latestMeta = meta;
151
+ options.onState?.(state, currentLookup, meta);
152
+ },
153
+ });
154
+ if (lookup.status === "FAILED")
155
+ return { kind: "failed", lookup };
156
+ if (lookup.status === "CANCELED")
157
+ return { kind: "canceled", lookup };
158
+ if (lookup.resultExpired)
159
+ return { kind: "expired", lookup };
160
+ const data = readOf(lookup, input.view);
161
+ if (!data)
162
+ throw new LexNodeError({
163
+ code: "INVALID_RESPONSE",
164
+ message: "Consulta concluída sem o resultado da visão pedida.",
165
+ status: 502,
166
+ retryable: true,
167
+ errorClass: "RETRYABLE",
168
+ });
169
+ return { kind: "ready", data, lookup, meta: latestMeta };
170
+ }
171
+ catch (error) {
172
+ if (options.signal?.aborted)
173
+ throw options.signal.reason;
174
+ if (!isLexNodeError(error) ||
175
+ error.code !== "PROCESS_LOOKUP_BUDGET_EXCEEDED")
176
+ throw error;
177
+ const details = error.details;
178
+ return {
179
+ ...pending,
180
+ lookup: latest,
181
+ nextReadAt: details?.nextReadAt ?? latest.nextAttemptAt ?? null,
182
+ };
183
+ }
184
+ }
@@ -1,19 +1,29 @@
1
1
  import type { paths } from "../schema.js";
2
2
  import type { LexNodeClient } from "./client.js";
3
3
  import type { Data, MethodOf, RequestArgs } from "./types.js";
4
+ /** As opções de `paginate`. */
4
5
  export interface PaginateOptions {
5
6
  /**
6
- * Teto de páginas. É EXPLÍCITO de propósito: cada página é uma chamada
7
- * medida, e um laço sem teto sobre uma conta grande gasta a franquia inteira
8
- * antes de alguém perceber.
7
+ * O máximo de páginas lidas. Padrão: 100. Cada página é uma chamada, que
8
+ * conta no limite de vazão e, nas rotas que cobram, nos créditos.
9
9
  */
10
10
  maxPages?: number;
11
11
  }
12
12
  /**
13
- * Percorre uma rota paginada página a página, na forma que ela usa.
13
+ * Percorre uma rota paginada e entrega o `data` de uma página por vez.
14
14
  *
15
+ * Funciona com as três formas de paginação da API: o cursor em `meta`, o
16
+ * cursor em `data` e as consultas por página (`page` e `pageSize`) ou por
17
+ * deslocamento (`offset` e `limit`). Termina na última página ou quando o
18
+ * cursor se repete.
19
+ *
20
+ * Lança `LexNodeError` `PAGINATION_LIMIT_REACHED` (não retentável) ao passar
21
+ * de `maxPages` — aumente o limite ou restrinja a consulta — e repassa o erro
22
+ * de qualquer página.
23
+ *
24
+ * @example
15
25
  * ```ts
16
- * for await (const page of paginate(client, "/v1/monitors", "get", {})) {
26
+ * for await (const page of paginate(lexnode, "/v1/monitors", "get", {})) {
17
27
  * for (const monitor of page) console.log(monitor.id);
18
28
  * }
19
29
  * ```
package/dist/paginate.js CHANGED
@@ -1,17 +1,4 @@
1
1
  import { LexNodeError } from "./errors.js";
2
- /**
3
- * O laço de páginas, escrito uma vez.
4
- *
5
- * O motor pagina de três jeitos, herdados do que cada superfície precisava: o
6
- * cursor no `meta` do envelope (rotas cujo `data` é um array), o cursor dentro
7
- * do `data` (rotas com `items`), e o par página/tamanho ou deslocamento/teto da
8
- * consulta de acervo. Sem isto, cada consumidor reescreve o mesmo laço três
9
- * vezes — e o que ele erra é sempre o mesmo: para na primeira página, ou anda
10
- * para sempre porque o cursor deixou de avançar.
11
- *
12
- * O cursor do `meta` não é declarado no contrato gerado (o `meta` daquelas
13
- * rotas é aberto), então é lido aqui com conferência de tipo em runtime.
14
- */
15
2
  const DEFAULT_MAX_PAGES = 100;
16
3
  function asRecord(value) {
17
4
  return value && typeof value === "object" && !Array.isArray(value)
@@ -26,19 +13,12 @@ function readNumber(bag, key) {
26
13
  const value = bag?.[key];
27
14
  return typeof value === "number" && Number.isFinite(value) ? value : null;
28
15
  }
29
- /** Quantos itens a página trouxe, para o deslocamento andar pelo que veio. */
30
16
  function pageLength(data, payload) {
31
17
  if (Array.isArray(data))
32
18
  return data.length;
33
19
  const items = payload?.items;
34
20
  return Array.isArray(items) ? items.length : 0;
35
21
  }
36
- /**
37
- * A consulta da próxima página, ou `null` quando a última chegou.
38
- *
39
- * As três formas são decididas aqui, uma vez, na ordem em que o motor as usa —
40
- * cursor primeiro, porque é a única que não depende de um `total`.
41
- */
42
22
  function nextQuery(data, meta, query) {
43
23
  const payload = asRecord(data);
44
24
  const cursor = readCursor(asRecord(meta)) ?? readCursor(payload);
@@ -64,15 +44,6 @@ function nextQuery(data, meta, query) {
64
44
  }
65
45
  return null;
66
46
  }
67
- /**
68
- * Percorre uma rota paginada página a página, na forma que ela usa.
69
- *
70
- * ```ts
71
- * for await (const page of paginate(client, "/v1/monitors", "get", {})) {
72
- * for (const monitor of page) console.log(monitor.id);
73
- * }
74
- * ```
75
- */
76
47
  export async function* paginate(client, path, method, args, options = {}) {
77
48
  const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;
78
49
  let query = {
@@ -87,7 +58,6 @@ export async function* paginate(client, path, method, args, options = {}) {
87
58
  });
88
59
  yield data;
89
60
  const proxima = nextQuery(data, meta, query);
90
- // Cursor que não anda é laço infinito disfarçado de paginação.
91
61
  if (!proxima ||
92
62
  (proxima.cursor !== undefined && proxima.cursor === visto)) {
93
63
  return;
package/dist/search.d.ts CHANGED
@@ -1,21 +1,22 @@
1
1
  import type { LexNodeClient } from "./client.js";
2
2
  import type { Body, CallOptions, Data, Meta, MetaExtras } from "./types.js";
3
- /**
4
- * A busca de processos responde de dois jeitos na MESMA operação: 200 com o
5
- * resultado pronto, 202 com um trabalho enfileirado. O discriminador é o
6
- * código HTTP — o corpo do 202 traz `jobId` e nenhum `items`, mas farejar
7
- * campos é conclusão por ausência, e o status é a afirmação.
8
- */
9
3
  type SearchData = Data<"/v1/processes/search", "post">;
10
4
  type SearchMeta = Meta<"/v1/processes/search", "post"> & MetaExtras;
11
- /** O resultado já pronto, servido dentro da própria chamada. */
5
+ /** O resultado pronto, que a busca devolve com 200. */
12
6
  export type ProcessSearchResult = Extract<SearchData, {
13
7
  items: unknown;
14
8
  }>;
15
- /** O trabalho enfileirado, a ser acompanhado por `waitForSearchJob`. */
9
+ /**
10
+ * A busca enfileirada, que a busca devolve com 202; acompanhe-a com
11
+ * `waitForSearchJob`.
12
+ */
16
13
  export type QueuedSearchJob = Extract<SearchData, {
17
14
  jobId: unknown;
18
15
  }>;
16
+ /**
17
+ * O desfecho de `searchProcesses`: `sync` com o resultado em `result`, ou
18
+ * `async` com a busca enfileirada em `job`.
19
+ */
19
20
  export type ProcessSearchOutcome = {
20
21
  kind: "sync";
21
22
  result: ProcessSearchResult;
@@ -25,40 +26,73 @@ export type ProcessSearchOutcome = {
25
26
  job: QueuedSearchJob;
26
27
  meta: SearchMeta;
27
28
  };
28
- /** O trabalho como a consulta de estado o devolve. */
29
+ /** A busca enfileirada, como `GET /v1/search-jobs/{jobId}` a devolve. */
29
30
  export type SearchJob = Data<"/v1/search-jobs/{jobId}", "get">;
31
+ /** O estado da busca enfileirada. */
30
32
  export type SearchJobState = SearchJob["status"];
33
+ /** As opções de `waitForSearchJob`. */
31
34
  export interface WaitForSearchJobOptions {
32
35
  /**
33
- * Teto de espera, em ms. É EXPLÍCITO de propósito: num ambiente serverless a
34
- * espera consome a franquia da função, e um laço sem orçamento gasta a
35
- * requisição inteira antes de o trabalho concluir.
36
+ * O tempo máximo de espera, em ms. Padrão: 45 000. Num ambiente serverless,
37
+ * escolha um valor que caiba no prazo da sua função.
36
38
  */
37
39
  budgetMs?: number;
38
- /** Primeira espera entre consultas, em ms; dobra até `maxPollIntervalMs`. */
40
+ /**
41
+ * A primeira espera entre leituras, em ms. Padrão: 1 500. Dobra a cada
42
+ * leitura, até `maxPollIntervalMs`.
43
+ */
39
44
  pollIntervalMs?: number;
45
+ /** A maior espera entre leituras, em ms. Padrão: 8 000. */
40
46
  maxPollIntervalMs?: number;
47
+ /** Cancela a espera; a busca continua na fila. */
41
48
  signal?: AbortSignal;
42
- /** Chamado a cada leitura de estado, para a UI acompanhar. */
49
+ /**
50
+ * Chamada a cada leitura, com o estado e a busca, por exemplo para mostrar
51
+ * o progresso.
52
+ */
43
53
  onState?: (state: SearchJobState, job: SearchJob) => void;
54
+ /** O escritório (sub-tenant) das leituras; veja `CallOptions.officeId`. */
44
55
  officeId?: string | null;
45
56
  }
46
57
  /**
47
- * Busca processos e diz, no tipo, qual dos dois desfechos aconteceu.
58
+ * Busca processos e diz no tipo se o resultado veio pronto ou se a busca foi
59
+ * enfileirada.
60
+ *
61
+ * `POST /v1/processes/search` responde 200 com o resultado ou 202 com a busca
62
+ * enfileirada. O desfecho `sync` traz o resultado em `result`; o `async` traz
63
+ * a busca em `job`, para acompanhar com `waitForSearchJob` ou pelo evento
64
+ * `SEARCH_JOB_COMPLETED`. Uma busca enfileirada não é um resultado vazio.
65
+ * Lança `LexNodeError` nas recusas da API.
48
66
  *
49
- * Sem isto, o consumidor lê `data.items` num corpo que só tem `jobId` e chama
50
- * de "nenhum resultado" o que é "ainda estou buscando".
67
+ * @example
68
+ * ```ts
69
+ * const desfecho = await searchProcesses(lexnode, {
70
+ * tribunalAlias: "TJSP",
71
+ * number,
72
+ * });
73
+ * if (desfecho.kind === "sync") {
74
+ * desfecho.result.items;
75
+ * } else {
76
+ * const job = await waitForSearchJob(lexnode, desfecho.job.jobId, {
77
+ * budgetMs: 20_000,
78
+ * });
79
+ * job.status; // "SUCCEEDED" | "FAILED" | "CANCELED"
80
+ * }
81
+ * ```
51
82
  */
52
83
  export declare function searchProcesses(client: LexNodeClient, body: Body<"/v1/processes/search", "post">, opts?: CallOptions): Promise<ProcessSearchOutcome>;
53
84
  /**
54
- * Espera um trabalho de busca chegar ao fim, com recuo e orçamento.
85
+ * Espera uma busca enfileirada terminar e devolve a busca em `SUCCEEDED`,
86
+ * `FAILED` ou `CANCELED`.
55
87
  *
56
- * O estouro do orçamento é um erro PRÓPRIO e retentável: o trabalho continua
57
- * na fila do motor, e quem espera pode voltar a acompanhá-lo depois — ou
58
- * receber o `SEARCH_JOB_COMPLETED` por webhook e não esperar nada.
88
+ * Lê a busca de tempos em tempos, com espera crescente entre as leituras.
89
+ * Lança `LexNodeError` `SEARCH_JOB_BUDGET_EXCEEDED` (retentável) quando
90
+ * `budgetMs` acaba antes do fim: a busca continua na fila, e você pode voltar
91
+ * a lê-la depois ou receber o evento `SEARCH_JOB_COMPLETED`. Repassa os erros
92
+ * das leituras.
59
93
  *
60
- * Não há o que esperar com chave de teste (`lapi_test_`): ela não enfileira
61
- * trabalho, e a submissão assíncrona responde 403 `SANDBOX_UPSTREAM_DISABLED`.
94
+ * Com a chave de teste (`lapi_test_`) não há busca a esperar: a busca
95
+ * assíncrona responde 403 `SANDBOX_UPSTREAM_DISABLED`.
62
96
  */
63
97
  export declare function waitForSearchJob(client: LexNodeClient, jobId: string, options?: WaitForSearchJobOptions): Promise<SearchJob>;
64
98
  export {};
package/dist/search.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { LexNodeError } from "./errors.js";
2
- /** Estados em que o trabalho não avança mais. */
3
2
  const TERMINAL_STATES = new Set(["SUCCEEDED", "FAILED", "CANCELED"]);
4
3
  const DEFAULT_BUDGET_MS = 45_000;
5
4
  const DEFAULT_POLL_INTERVAL_MS = 1_500;
@@ -22,12 +21,6 @@ function wait(ms, signal) {
22
21
  signal?.addEventListener("abort", onAbort, { once: true });
23
22
  });
24
23
  }
25
- /**
26
- * Busca processos e diz, no tipo, qual dos dois desfechos aconteceu.
27
- *
28
- * Sem isto, o consumidor lê `data.items` num corpo que só tem `jobId` e chama
29
- * de "nenhum resultado" o que é "ainda estou buscando".
30
- */
31
24
  export async function searchProcesses(client, body, opts = {}) {
32
25
  const { data, meta } = await client.processes.search(body, opts);
33
26
  if (meta.status === 202) {
@@ -35,16 +28,6 @@ export async function searchProcesses(client, body, opts = {}) {
35
28
  }
36
29
  return { kind: "sync", result: data, meta };
37
30
  }
38
- /**
39
- * Espera um trabalho de busca chegar ao fim, com recuo e orçamento.
40
- *
41
- * O estouro do orçamento é um erro PRÓPRIO e retentável: o trabalho continua
42
- * na fila do motor, e quem espera pode voltar a acompanhá-lo depois — ou
43
- * receber o `SEARCH_JOB_COMPLETED` por webhook e não esperar nada.
44
- *
45
- * Não há o que esperar com chave de teste (`lapi_test_`): ela não enfileira
46
- * trabalho, e a submissão assíncrona responde 403 `SANDBOX_UPSTREAM_DISABLED`.
47
- */
48
31
  export async function waitForSearchJob(client, jobId, options = {}) {
49
32
  const budgetMs = options.budgetMs ?? DEFAULT_BUDGET_MS;
50
33
  const maxIntervalMs = options.maxPollIntervalMs ?? DEFAULT_MAX_POLL_INTERVAL_MS;