@lexnode/legal-data-sdk 1.0.0 → 1.18.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/search.js ADDED
@@ -0,0 +1,75 @@
1
+ import { LexNodeError } from "./errors.js";
2
+ /** Estados em que o trabalho não avança mais. */
3
+ const TERMINAL_STATES = new Set(["SUCCEEDED", "FAILED", "CANCELED"]);
4
+ const DEFAULT_BUDGET_MS = 45_000;
5
+ const DEFAULT_POLL_INTERVAL_MS = 1_500;
6
+ const DEFAULT_MAX_POLL_INTERVAL_MS = 8_000;
7
+ function wait(ms, signal) {
8
+ return new Promise((resolve, reject) => {
9
+ if (signal?.aborted) {
10
+ reject(signal.reason);
11
+ return;
12
+ }
13
+ let timer;
14
+ const onAbort = () => {
15
+ clearTimeout(timer);
16
+ reject(signal?.reason);
17
+ };
18
+ timer = setTimeout(() => {
19
+ signal?.removeEventListener("abort", onAbort);
20
+ resolve();
21
+ }, ms);
22
+ signal?.addEventListener("abort", onAbort, { once: true });
23
+ });
24
+ }
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
+ export async function searchProcesses(client, body, opts = {}) {
32
+ const { data, meta } = await client.processes.search(body, opts);
33
+ if (meta.status === 202) {
34
+ return { kind: "async", job: data, meta };
35
+ }
36
+ return { kind: "sync", result: data, meta };
37
+ }
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
+ export async function waitForSearchJob(client, jobId, options = {}) {
49
+ const budgetMs = options.budgetMs ?? DEFAULT_BUDGET_MS;
50
+ const maxIntervalMs = options.maxPollIntervalMs ?? DEFAULT_MAX_POLL_INTERVAL_MS;
51
+ let intervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
52
+ const deadline = Date.now() + budgetMs;
53
+ for (;;) {
54
+ const { data } = await client.searchJobs.get(jobId, {
55
+ signal: options.signal,
56
+ officeId: options.officeId,
57
+ });
58
+ options.onState?.(data.status, data);
59
+ if (TERMINAL_STATES.has(data.status))
60
+ return data;
61
+ const remainingMs = deadline - Date.now();
62
+ if (remainingMs <= 0) {
63
+ throw new LexNodeError({
64
+ code: "SEARCH_JOB_BUDGET_EXCEEDED",
65
+ message: `O trabalho ${jobId} não concluiu em ${budgetMs} ms; ele segue na fila.`,
66
+ status: 504,
67
+ errorClass: "RETRYABLE",
68
+ retryable: true,
69
+ details: { jobId, status: data.status, budgetMs },
70
+ });
71
+ }
72
+ await wait(Math.min(intervalMs, remainingMs), options.signal);
73
+ intervalMs = Math.min(intervalMs * 2, maxIntervalMs);
74
+ }
75
+ }
@@ -0,0 +1,179 @@
1
+ import type { paths } from "../schema.js";
2
+ /**
3
+ * A ponte entre o documento OpenAPI gerado e as assinaturas do cliente.
4
+ *
5
+ * Tudo aqui deriva de `paths`: nenhuma forma de requisição ou de resposta é
6
+ * redigitada. Quando o contrato ganha rota, campo ou código de resposta, o
7
+ * cliente acompanha sem edição — e uma chamada que deixou de casar o contrato
8
+ * falha no compilador do consumidor, não em produção.
9
+ */
10
+ /** O objeto vazio, escrito sem a sintaxe que a regra de tipo-vazio recusa. */
11
+ type Empty = {
12
+ [K in never]: never;
13
+ };
14
+ /** Os métodos que a superfície `/v1` usa. */
15
+ export type HttpMethod = "get" | "post" | "patch" | "delete";
16
+ /**
17
+ * Os métodos que uma rota de fato aceita.
18
+ *
19
+ * O gerador declara os métodos ausentes como `put?: never`, então filtrar por
20
+ * `keyof` sozinho ofereceria `put` em toda rota; o que separa os dois é o tipo
21
+ * do valor.
22
+ */
23
+ export type MethodOf<P extends keyof paths> = {
24
+ [M in Extract<keyof paths[P], HttpMethod>]: paths[P][M] extends undefined ? never : M;
25
+ }[Extract<keyof paths[P], HttpMethod>];
26
+ /** A operação de uma rota + método, já sem o `undefined` do método ausente. */
27
+ export type Operation<P extends keyof paths, M extends MethodOf<P>> = M extends keyof paths[P] ? NonNullable<paths[P][M]> : never;
28
+ type PathParamsOf<O> = O extends {
29
+ parameters: {
30
+ path: infer T;
31
+ };
32
+ } ? T : never;
33
+ type QueryParamsOf<O> = O extends {
34
+ parameters: {
35
+ query?: infer T;
36
+ };
37
+ } ? NonNullable<T> : never;
38
+ type BodyOf<O> = O extends {
39
+ requestBody: {
40
+ content: {
41
+ "application/json": infer B;
42
+ };
43
+ };
44
+ } ? B : never;
45
+ /** Os códigos de sucesso que a superfície usa. */
46
+ type SuccessStatus = 200 | 201 | 202;
47
+ type JsonBody<R> = R extends {
48
+ content: {
49
+ "application/json": infer C;
50
+ };
51
+ } ? C : never;
52
+ /**
53
+ * O corpo de sucesso — UNIÃO quando a rota responde mais de um código, que é o
54
+ * caso da busca de processos (200 síncrono, 202 enfileirado).
55
+ */
56
+ type SuccessBody<O> = O extends {
57
+ responses: infer R;
58
+ } ? {
59
+ [S in Extract<SuccessStatus, keyof R>]: JsonBody<R[S]>;
60
+ }[Extract<SuccessStatus, keyof R>] : never;
61
+ type DataOf<O> = SuccessBody<O> extends {
62
+ data: infer D;
63
+ } ? D : never;
64
+ type MetaOf<O> = SuccessBody<O> extends {
65
+ meta: infer M;
66
+ } ? M : Record<string, unknown>;
67
+ /** Os parâmetros de caminho de uma rota, ou `never` quando ela não tem. */
68
+ export type PathParams<P extends keyof paths, M extends MethodOf<P>> = PathParamsOf<Operation<P, M>>;
69
+ /** Os parâmetros de consulta de uma rota, ou `never` quando ela não tem. */
70
+ export type Query<P extends keyof paths, M extends MethodOf<P>> = QueryParamsOf<Operation<P, M>>;
71
+ /** O corpo JSON de uma rota, ou `never` quando ela não recebe corpo. */
72
+ export type Body<P extends keyof paths, M extends MethodOf<P>> = BodyOf<Operation<P, M>>;
73
+ /** O `data` do envelope de sucesso de uma rota. */
74
+ export type Data<P extends keyof paths, M extends MethodOf<P>> = DataOf<Operation<P, M>>;
75
+ /** O `meta` do envelope de sucesso de uma rota, como o contrato o declara. */
76
+ export type Meta<P extends keyof paths, M extends MethodOf<P>> = MetaOf<Operation<P, M>>;
77
+ /** O que o motor anuncia sobre o teto por minuto, em TODA resposta. */
78
+ export interface RateLimitSnapshot {
79
+ /** Teto da janela, ou `null` quando o cabeçalho não veio. */
80
+ limit: number | null;
81
+ /** Quanto resta na janela, ou `null` quando o cabeçalho não veio. */
82
+ remaining: number | null;
83
+ /**
84
+ * O que o cabeçalho `RateLimit-Reset` trouxe, ou `null`.
85
+ *
86
+ * Os dois sentidos são do motor, não do cliente: na RECUSA são os segundos
87
+ * até a janela reabrir; no SUCESSO é o COMPRIMENTO da janela (60 s), que é
88
+ * constante. Espaçar por este número depois de um sucesso perto do teto faz
89
+ * dormir a janela inteira — para desacelerar, olhe `remaining`.
90
+ */
91
+ resetSeconds: number | null;
92
+ /** O `Retry-After` da recusa, quando houve. */
93
+ retryAfterSeconds: number | null;
94
+ }
95
+ /**
96
+ * O que o cliente acrescenta ao `meta` do contrato.
97
+ *
98
+ * `status` viaja aqui porque a busca de processos responde 200 ou 202 na MESMA
99
+ * operação: sem o código, distinguir resultado pronto de trabalho enfileirado
100
+ * exigiria farejar campos do corpo.
101
+ */
102
+ export interface MetaExtras {
103
+ status: number;
104
+ rateLimit: RateLimitSnapshot | null;
105
+ }
106
+ /** O par `{ data, meta }` que toda chamada devolve, tipado pela rota. */
107
+ export interface LexNodeResult<P extends keyof paths, M extends MethodOf<P>> {
108
+ data: Data<P, M>;
109
+ meta: Meta<P, M> & MetaExtras;
110
+ }
111
+ type PathSlot<O> = [PathParamsOf<O>] extends [never] ? Empty : {
112
+ path: PathParamsOf<O>;
113
+ };
114
+ type QuerySlot<O> = [QueryParamsOf<O>] extends [never] ? Empty : Empty extends QueryParamsOf<O> ? {
115
+ query?: QueryParamsOf<O>;
116
+ } : {
117
+ query: QueryParamsOf<O>;
118
+ };
119
+ type ParamsBag<O> = PathSlot<O> & QuerySlot<O>;
120
+ /** `params` é obrigatório só quando a rota tem algum parâmetro obrigatório. */
121
+ type ParamsSlot<O> = Empty extends ParamsBag<O> ? {
122
+ params?: ParamsBag<O>;
123
+ } : {
124
+ params: ParamsBag<O>;
125
+ };
126
+ /** `body` é obrigatório só quando a rota exige corpo. */
127
+ type BodySlot<O> = [BodyOf<O>] extends [never] ? {
128
+ body?: never;
129
+ } : Empty extends BodyOf<O> ? {
130
+ body?: BodyOf<O>;
131
+ } : {
132
+ body: BodyOf<O>;
133
+ };
134
+ /** Política de retentativa. Desligada por padrão — ver `createLexNodeClient`. */
135
+ export interface RetryPolicy {
136
+ /** Total de tentativas, contando a primeira. `1` (ou menos) desliga. */
137
+ attempts: number;
138
+ /** Espera da primeira retentativa, em ms; dobra a cada rodada. */
139
+ baseDelayMs?: number;
140
+ /** Teto da espera, em ms. */
141
+ maxDelayMs?: number;
142
+ }
143
+ /** O que toda chamada aceita, além dos parâmetros da própria rota. */
144
+ export interface CallOptions {
145
+ /**
146
+ * Sub-tenant desta chamada. Vence o do construtor; `null` OMITE o cabeçalho
147
+ * mesmo quando o construtor trouxe um — é o que a vigília precisa, porque o
148
+ * motor recorta monitor por escritório na listagem e na remoção.
149
+ */
150
+ officeId?: string | null;
151
+ /** Aborta a chamada. É COMBINADO com o prazo, nunca o substitui. */
152
+ signal?: AbortSignal;
153
+ /** Prazo desta chamada, em ms. Vence o do construtor. */
154
+ timeoutMs?: number;
155
+ /**
156
+ * `X-Request-Id`. Cunhado quando ausente.
157
+ *
158
+ * É rastreio em toda rota. Chave de replay em uma só —
159
+ * `POST /v1/publications/text-searches`, onde reenviar o mesmo valor devolve
160
+ * a busca já congelada sem cobrar de novo; é essa rota, e não a presença do
161
+ * id, que autoriza a retentativa de uma escrita.
162
+ */
163
+ requestId?: string;
164
+ /**
165
+ * Declara que repetir ESTA escrita é seguro, liberando a retentativa numa
166
+ * rota que o contrato não declara idempotente.
167
+ *
168
+ * A afirmação é de quem chama: sem ela, uma escrita repetida cria um segundo
169
+ * recurso — e o consumidor guarda o segundo sem nunca ver o primeiro.
170
+ */
171
+ idempotent?: boolean;
172
+ /** `X-Trace-Id` da operação lógica. Cunhado quando ausente. */
173
+ traceId?: string;
174
+ /** Política de retentativa desta chamada. Vence a do construtor. */
175
+ retry?: RetryPolicy;
176
+ }
177
+ /** O argumento da chamada genérica: parâmetros, corpo e opções de transporte. */
178
+ export type RequestArgs<P extends keyof paths, M extends MethodOf<P>> = ParamsSlot<Operation<P, M>> & BodySlot<Operation<P, M>> & CallOptions;
179
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import type { LexNodeClient } from "./client.js";
2
+ import type { CallOptions, Data } from "./types.js";
3
+ import type { MonitorSyncSucceededPayload, WebhookEvent } from "./webhooks.js";
4
+ /**
5
+ * Vigiar um processo para N interessados.
6
+ *
7
+ * O motor separa duas primitivas que costumam ser confundidas:
8
+ *
9
+ * - o **sensor** (`Monitor`) é quem varre a fonte. Ele é único por alvo dentro
10
+ * da conta e do projeto, e tem teto: pedir um segundo para o mesmo processo
11
+ * responde 409;
12
+ * - a **assinatura** (`ProcessSubscription`) é quem quer ser avisado. Ela é
13
+ * única por `externalRef`, não tem teto, e o aviso
14
+ * `PROCESS_MOVEMENT_CREATED` a ecoa de volta.
15
+ *
16
+ * Milhares de interessados no mesmo processo são, portanto, UM sensor e N
17
+ * assinaturas. Este módulo é essa composição.
18
+ */
19
+ export interface WatchProcessInput {
20
+ processNumber: string;
21
+ tribunalAlias: string;
22
+ /** O sub-tenant do interessado. A ASSINATURA nasce com ele. */
23
+ officeId?: string | null;
24
+ /** O ponteiro do consumidor, ecoado no evento de movimentação. */
25
+ externalRef: string;
26
+ label?: string;
27
+ /** AAAA-MM-DD da distribuição, para a vigília de aparição ter critério. */
28
+ distributedAt?: string;
29
+ }
30
+ export interface WatchProcessResult {
31
+ /**
32
+ * O sensor do alvo, tenha ele nascido agora ou já existido — a recusa 409
33
+ * carrega o id do que está lá, em `details.monitorId`. Nulo só quando a
34
+ * recusa não o traz.
35
+ */
36
+ monitorId: string | null;
37
+ monitorAlreadyExisted: boolean;
38
+ subscription: Data<"/v1/subscriptions", "post">;
39
+ }
40
+ /**
41
+ * Garante o sensor e registra o interessado.
42
+ *
43
+ * O sensor é criado **sem `X-Office-Id`**: o motor recorta monitor por
44
+ * escritório na listagem e na remoção, e um sensor preso ao escritório do
45
+ * primeiro interessado desapareceria para o segundo. A assinatura, sim, nasce
46
+ * com o escritório de quem quer o aviso.
47
+ *
48
+ * A chave de teste (`lapi_test_`) não cria trabalho de fundo: o sensor responde
49
+ * 403 `SANDBOX_UPSTREAM_DISABLED`. A composição só fecha com chave viva.
50
+ */
51
+ export declare function watchProcess(client: LexNodeClient, input: WatchProcessInput, opts?: CallOptions): Promise<WatchProcessResult>;
52
+ export interface UnwatchProcessInput {
53
+ externalRef: string;
54
+ officeId?: string | null;
55
+ }
56
+ /**
57
+ * Remove o interesse de UM assinante.
58
+ *
59
+ * O sensor fica de pé de propósito: quantos interessados restam é contagem do
60
+ * consumidor, e o SDK não tem como adivinhá-la — desligar a varredura aqui
61
+ * calaria o aviso de todo mundo que continua assinando o mesmo processo.
62
+ */
63
+ export declare function unwatchProcess(client: LexNodeClient, input: UnwatchProcessInput, opts?: CallOptions): Promise<Data<"/v1/subscriptions", "delete">>;
64
+ /** O evento de varredura em que um alvo antes ausente apareceu na fonte. */
65
+ export type FirstAppearanceEvent = Extract<WebhookEvent, {
66
+ type: "MONITOR_SYNC_SUCCEEDED";
67
+ }> & {
68
+ payload: MonitorSyncSucceededPayload & {
69
+ firstAppearance: true;
70
+ };
71
+ };
72
+ /**
73
+ * Se este evento anuncia a primeira aparição do alvo na fonte.
74
+ *
75
+ * Importa porque a assinatura criada ANTES de o processo entrar no acervo
76
+ * nasce sem vínculo com a linha canônica, e o leque de
77
+ * `PROCESS_MOVEMENT_CREATED` escolhe assinantes por esse vínculo: até a
78
+ * primeira aparição, o assinante não recebe nada. Refazer o
79
+ * `POST /v1/subscriptions` depois deste evento preenche o vínculo.
80
+ */
81
+ export declare function isFirstAppearance(event: WebhookEvent): event is FirstAppearanceEvent;
82
+ export interface ResubscribeSubscriber {
83
+ externalRef: string;
84
+ officeId?: string | null;
85
+ }
86
+ /**
87
+ * Refaz as assinaturas de um alvo que acabou de aparecer na fonte.
88
+ *
89
+ * Quem são os interessados é do consumidor — o evento não os carrega —, então
90
+ * eles entram por parâmetro. Sem `tribunalAlias` no payload, nada é refeito: a
91
+ * assinatura é por (fonte, tribunal, processo).
92
+ */
93
+ export declare function resubscribeOnFirstAppearance(client: LexNodeClient, event: WebhookEvent, subscribers: ReadonlyArray<ResubscribeSubscriber>, opts?: CallOptions): Promise<Array<Data<"/v1/subscriptions", "post">>>;
package/dist/watch.js ADDED
@@ -0,0 +1,95 @@
1
+ import { LexNodeError } from "./errors.js";
2
+ /** A recusa de alvo já vigiado — que, para esta composição, é sucesso. */
3
+ const MONITOR_ALREADY_EXISTS = "MONITOR_ALREADY_EXISTS";
4
+ /**
5
+ * Garante o sensor e registra o interessado.
6
+ *
7
+ * O sensor é criado **sem `X-Office-Id`**: o motor recorta monitor por
8
+ * escritório na listagem e na remoção, e um sensor preso ao escritório do
9
+ * primeiro interessado desapareceria para o segundo. A assinatura, sim, nasce
10
+ * com o escritório de quem quer o aviso.
11
+ *
12
+ * A chave de teste (`lapi_test_`) não cria trabalho de fundo: o sensor responde
13
+ * 403 `SANDBOX_UPSTREAM_DISABLED`. A composição só fecha com chave viva.
14
+ */
15
+ export async function watchProcess(client, input, opts = {}) {
16
+ let monitorId = null;
17
+ let monitorAlreadyExisted = false;
18
+ try {
19
+ const { data } = await client.monitors.create({
20
+ type: "PROCESS",
21
+ tribunalAlias: input.tribunalAlias,
22
+ processNumber: input.processNumber,
23
+ ...(input.label ? { label: input.label } : {}),
24
+ ...(input.distributedAt ? { distributedAt: input.distributedAt } : {}),
25
+ }, { ...opts, officeId: null });
26
+ monitorId = data.id;
27
+ }
28
+ catch (error) {
29
+ if (!(error instanceof LexNodeError) ||
30
+ error.code !== MONITOR_ALREADY_EXISTS) {
31
+ throw error;
32
+ }
33
+ monitorAlreadyExisted = true;
34
+ const conhecido = error.details?.monitorId;
35
+ monitorId = typeof conhecido === "string" ? conhecido : null;
36
+ }
37
+ const { data: subscription } = await client.subscriptions.create({
38
+ source: "CNJ_DATAJUD",
39
+ tribunalAlias: input.tribunalAlias,
40
+ processNumber: input.processNumber,
41
+ reason: "CASE_LINK",
42
+ externalRef: input.externalRef,
43
+ }, { ...opts, officeId: input.officeId });
44
+ return { monitorId, monitorAlreadyExisted, subscription };
45
+ }
46
+ /**
47
+ * Remove o interesse de UM assinante.
48
+ *
49
+ * O sensor fica de pé de propósito: quantos interessados restam é contagem do
50
+ * consumidor, e o SDK não tem como adivinhá-la — desligar a varredura aqui
51
+ * calaria o aviso de todo mundo que continua assinando o mesmo processo.
52
+ */
53
+ export async function unwatchProcess(client, input, opts = {}) {
54
+ const { data } = await client.subscriptions.delete({ reason: "CASE_LINK", externalRef: input.externalRef }, { ...opts, officeId: input.officeId });
55
+ return data;
56
+ }
57
+ /**
58
+ * Se este evento anuncia a primeira aparição do alvo na fonte.
59
+ *
60
+ * Importa porque a assinatura criada ANTES de o processo entrar no acervo
61
+ * nasce sem vínculo com a linha canônica, e o leque de
62
+ * `PROCESS_MOVEMENT_CREATED` escolhe assinantes por esse vínculo: até a
63
+ * primeira aparição, o assinante não recebe nada. Refazer o
64
+ * `POST /v1/subscriptions` depois deste evento preenche o vínculo.
65
+ */
66
+ export function isFirstAppearance(event) {
67
+ return (event.type === "MONITOR_SYNC_SUCCEEDED" &&
68
+ event.payload.firstAppearance === true);
69
+ }
70
+ /**
71
+ * Refaz as assinaturas de um alvo que acabou de aparecer na fonte.
72
+ *
73
+ * Quem são os interessados é do consumidor — o evento não os carrega —, então
74
+ * eles entram por parâmetro. Sem `tribunalAlias` no payload, nada é refeito: a
75
+ * assinatura é por (fonte, tribunal, processo).
76
+ */
77
+ export async function resubscribeOnFirstAppearance(client, event, subscribers, opts = {}) {
78
+ if (!isFirstAppearance(event))
79
+ return [];
80
+ const { processNumber, tribunalAlias } = event.payload;
81
+ if (!processNumber || !tribunalAlias)
82
+ return [];
83
+ const refeitas = [];
84
+ for (const subscriber of subscribers) {
85
+ const { data } = await client.subscriptions.create({
86
+ source: "CNJ_DATAJUD",
87
+ tribunalAlias,
88
+ processNumber,
89
+ reason: "CASE_LINK",
90
+ externalRef: subscriber.externalRef,
91
+ }, { ...opts, officeId: subscriber.officeId });
92
+ refeitas.push(data);
93
+ }
94
+ return refeitas;
95
+ }