@rebound-dlq/node 0.2.2 → 0.2.4

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
@@ -3,66 +3,149 @@
3
3
  [![NPM Version](https://img.shields.io/npm/v/@rebound-dlq/node)](https://www.npmjs.com/package/@rebound-dlq/node)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- O **@rebound-dlq/node** é o SDK oficial em Node.js para integração com a plataforma **[Rebound DLQ](https://rebound-dlq.com)**.
6
+ O **@rebound-dlq/node** e o SDK oficial em Node.js para integrar sua aplicacao com a plataforma **[Rebound DLQ](https://rebound-dlq.com)**.
7
7
 
8
- Este pacote permite capturar falhas em integrações (APIs de terceiros, pagamentos, envio de e-mails, etc.) de forma transparente para a sua aplicação Node.js. Sem travar sua regra de negócio e sem adicionar latência. Quando uma operação falha, o SDK captura o payload, criptografa a informação e envia o evento em plano de fundo (background) para a sua conta no Rebound DLQ, aplicando estratégias de retry exponencial automaticamente se houver instabilidade na rede.
8
+ Ele captura falhas da sua aplicacao, criptografa o payload localmente e envia o evento para a sua conta em background, sem travar a regra de negocio. A fila e mantida em memoria e o SDK ja entende os cenarios de rede instavel, limite de plano, overage e retomada automatica apos upgrade ou mudanca de configuracao da conta.
9
9
 
10
10
  ## Por que usar o Rebound DLQ?
11
11
 
12
- - **Zero Latência (In-Memory Queue)**: A aplicação não espera a resposta do envio do log para a nossa infraestrutura. A execução ocorre inteiramente em memória e em background, liberando o processo Node.js principal na hora (`O(1)`).
13
- - 🛡️ **Segurança e Criptografia Local**: Todo o payload de erro é criptografado localmente na sua máquina (usando AES-256) antes mesmo de trafegar pela rede. Seus dados chegam anônimos na nossa infraestrutura, e somente você consegue descriptografá-los com a sua chave secreta.
14
- - 🔁 **Resiliência e Retentativa Automática**: Falhas de rede ou indisponibilidade? O SDK mantém o evento a salvo em memória e aplica _backoff exponencial_ para garantir a entrega quando a conexão for reestabelecida.
12
+ - **Zero latencia no fluxo principal**: o evento e enfileirado em memoria e a sua aplicacao segue executando sem esperar round-trip de rede.
13
+ - **Criptografia local AES-256**: o payload e criptografado antes de sair do seu processo.
14
+ - **Entrega resiliente**: falhas de rede e indisponibilidade temporaria continuam usando retry com backoff exponencial.
15
+ - **Comportamento orientado ao plano da conta**: o SDK sabe quando deve continuar, quando deve aguardar e quando deve descartar eventos acima do limite.
16
+ - **Retomada automatica**: quando a conta faz upgrade ou libera overage, o SDK volta a enviar sozinho.
15
17
 
16
- > ⚠️ **Atenção**: Este SDK requer uma conta ativa na plataforma **[Rebound DLQ](https://rebound-dlq.com)**.
18
+ > Importante: este SDK requer uma conta e um projeto ativos no Rebound DLQ.
17
19
 
18
- ## Instalação
19
-
20
- Abra o terminal e instale o pacote via npm, yarn ou pnpm:
20
+ ## Instalacao
21
21
 
22
22
  ```bash
23
23
  npm install @rebound-dlq/node
24
24
  ```
25
25
 
26
- ## Configuração Segura
26
+ ## Requisitos
27
+
28
+ Para usar o SDK com o comportamento completo desta versao:
29
+
30
+ - uma chave valida do projeto
31
+ - uma API Rebound atualizada, com suporte a:
32
+ - `POST /dlq-injestion`
33
+ - `POST /dlq-injestion/runtime-state`
34
+ - autenticacao HMAC com `x-api-key`, `x-sdk-timestamp` e `x-sdk-signature`
35
+ - resposta estruturada de bloqueio por limite de plano
27
36
 
28
- O SDK precisa da chave de integração do seu projeto (`projectSecret`).
37
+ Se voce usa a plataforma oficial atualizada da Rebound, nao precisa configurar nada extra no cliente para esse fluxo. O comportamento de buffer, descarte e retomada e automatico.
29
38
 
30
- **Prática Recomendada:** Nunca exponha (hard-code) a sua chave diretamente no código-fonte. Armazene a sua chave `projectSecret` de forma segura utilizando variáveis de ambiente (ex: arquivo `.env`).
39
+ ## Configuracao segura
40
+
41
+ Nunca exponha a chave diretamente no codigo-fonte. Use variaveis de ambiente:
31
42
 
32
43
  ```env
33
- # Arquivo .env
34
- REBOUND_PROJECT_SECRET="sk_dev_sua_chave_secreta_aqui"
44
+ REBOUND_PROJECT_SECRET="seu_project_secret_aqui"
35
45
  ```
36
46
 
37
- > **Dica de Ambiente**:
38
- > - Chaves iniciadas com `sk_dev_` enviarão eventos automaticamente para o ambiente de testes/sandbox.
39
- > - Chaves iniciadas com `sk_live_` enviarão eventos para o ambiente de produção.
47
+ ## Executando os examples do repositorio
48
+
49
+ Os arquivos em `examples/` foram deixados simples para teste local. Cada example tem duas variaveis no topo do arquivo:
50
+
51
+ - `PROJECT_SECRET`
52
+ - `API_ENDPOINT`
40
53
 
41
- ## Uso Básico
54
+ Edite esses valores diretamente no arquivo que voce quer testar e depois rode o script.
42
55
 
43
- A forma ideal de utilizar o SDK é encapsular a sua função com o método `.execute()` oferecido pela classe `DlqWrapper`.
56
+ ```bash
57
+ npm run example:wrapper
58
+ npm run example:http
59
+ npm run example:kafka
60
+ npm run example:internal
61
+ ```
62
+
63
+ Se preferir, voce tambem pode chamar o runner diretamente:
64
+
65
+ ```bash
66
+ node scripts/run-example.cjs wrapper
67
+ ```
44
68
 
45
- Se atente à inicialização e uso:
69
+ Os exemplos encerram sozinhos apos uma pequena janela de observacao para nao ficarem presos em retries de background quando a API estiver offline. Se quiser alterar isso, use `REBOUND_EXAMPLE_GRACE_MS`.
46
70
 
47
- ```typescript
71
+ ### Fluxo unico de configuracao
72
+
73
+ O SDK agora trabalha com um fluxo unico:
74
+
75
+ - `projectSecret` para autenticar e derivar a criptografia local
76
+ - `endpoint` para definir para qual API Rebound os eventos devem ser enviados
77
+
78
+ Nao existe mais diferenca publica entre ambiente de teste e producao dentro do SDK. Se voce quiser apontar para outra API, basta informar `endpoint` explicitamente.
79
+
80
+ ## Interfaces disponiveis
81
+
82
+ ### 1. `DlqWrapper`
83
+
84
+ Melhor opcao para encapsular uma funcao que pode falhar.
85
+
86
+ ```ts
48
87
  import { DlqWrapper } from '@rebound-dlq/node';
49
88
 
50
- // Inicializa o SDK consumindo de variáveis de ambiente
51
89
  const dlq = new DlqWrapper({
52
- projectSecret: process.env.REBOUND_PROJECT_SECRET,
90
+ projectSecret: process.env.REBOUND_PROJECT_SECRET!,
53
91
  });
92
+ ```
93
+
94
+ #### Parametros
95
+
96
+ | Campo | Obrigatorio | Descricao |
97
+ | --- | --- | --- |
98
+ | `projectSecret` | Sim | Chave secreta do projeto usada para criptografia local e autenticacao do SDK. |
99
+ | `endpoint` | Nao | URL base da sua API Rebound, por exemplo `https://api.suaempresa.com/api/v1`. O SDK monta `/dlq-injestion` automaticamente, mas tambem aceita a URL completa por compatibilidade. |
100
+ | `maxFailures` | Nao | Mantido apenas por compatibilidade retroativa. Atualmente nao altera o comportamento da fila. |
101
+
102
+ ### 2. `ReboundClient`
54
103
 
55
- // A função que se conecta ao seu provedor ou ao seu banco de dados (que pode falhar)
56
- async function processarPagamento(dados) {
104
+ Melhor opcao para enviar payloads DLQ manualmente ou usar os adapters.
105
+
106
+ ```ts
107
+ import { ReboundClient } from '@rebound-dlq/node';
108
+
109
+ const client = new ReboundClient({
110
+ projectSecret: process.env.REBOUND_PROJECT_SECRET!,
111
+ endpoint: process.env.REBOUND_API_ENDPOINT,
112
+ });
113
+ ```
114
+
115
+ #### Parametros
116
+
117
+ | Campo | Obrigatorio | Descricao |
118
+ | --- | --- | --- |
119
+ | `projectSecret` | Sim | Secret do projeto criado no painel, usado para autenticar as chamadas do SDK. |
120
+ | `endpoint` | Nao | URL base da sua API Rebound, por exemplo `https://api.suaempresa.com/api/v1`. O SDK monta `/dlq-injestion` automaticamente, mas tambem aceita a URL completa por compatibilidade. |
121
+ | `timeout` | Nao | Campo reservado no tipo de configuracao. Atualmente nao altera o transporte. |
122
+
123
+ Quando voce usa `ReboundClient` ou um adapter, o SDK converte o payload manual para o contrato de ingestao da API, gera `fingerprint`, criptografa o envelope localmente e envia no mesmo formato aceito pelo backend.
124
+
125
+ ## Uso recomendado com `DlqWrapper`
126
+
127
+ ```ts
128
+ import { DlqWrapper } from '@rebound-dlq/node';
129
+
130
+ const dlq = new DlqWrapper({
131
+ projectSecret: process.env.REBOUND_PROJECT_SECRET!,
132
+ });
133
+
134
+ async function processarPagamento(dados: {
135
+ userId: string;
136
+ valor: number;
137
+ tentativa: number;
138
+ modulo: string;
139
+ }) {
57
140
  if (dados.valor > 1000) {
58
- throw new Error('Saldo insuficiente na operadora do cartão');
141
+ throw new Error('Saldo insuficiente na operadora do cartao');
59
142
  }
60
- return 'Sucesso!';
143
+
144
+ return 'Sucesso';
61
145
  }
62
146
 
63
147
  async function main() {
64
- // Os dados de contexto que você gostaria de analisar depois se algo der errado
65
- const payloadQueSeraSalvoNaDLQ = {
148
+ const payload = {
66
149
  userId: '123',
67
150
  valor: 5000,
68
151
  tentativa: 1,
@@ -70,37 +153,153 @@ async function main() {
70
153
  };
71
154
 
72
155
  try {
73
- console.log("Iniciando transação...");
74
-
75
- // Protegemos a função com o wrapper. Se der erro dentro de processarPagamento,
76
- // o dlq envia os detalhes do erro E o payloadQueSeraSalvoNaDLQ para o Rebound automatically!
77
- const resultado = await dlq.execute(
78
- () => processarPagamento(payloadQueSeraSalvoNaDLQ),
79
- payloadQueSeraSalvoNaDLQ
80
- );
81
-
82
- console.log('Finalizado com:', resultado);
83
-
156
+ const resultado = await dlq.execute(() => processarPagamento(payload), payload);
157
+ console.log('Resultado:', resultado);
84
158
  } catch (erro) {
85
- // A aplicação não morre; o erro é lançado apenas para seu tratamento habitual
86
- console.log('Aconteceu um erro previsto na aplicação:', erro.message);
159
+ console.error('Erro de negocio capturado pela aplicacao:', (erro as Error).message);
87
160
  }
88
161
  }
89
162
 
90
163
  main();
91
164
  ```
92
165
 
93
- ### O que acontece por baixo dos panos neste exemplo?
166
+ ### O que acontece nesse fluxo
167
+
168
+ 1. Sua funcao falha e a excecao e capturada.
169
+ 2. O payload e criptografado localmente com AES-256.
170
+ 3. O evento e colocado em uma fila em memoria.
171
+ 4. O SDK envia o evento em background com autenticacao HMAC.
172
+ 5. O erro original continua sendo relancado para o seu tratamento normal.
173
+
174
+ ## Uso manual com `ReboundClient`
175
+
176
+ ```ts
177
+ import { ReboundClient, HTTPAdapter } from '@rebound-dlq/node';
178
+
179
+ const client = new ReboundClient({
180
+ projectSecret: process.env.REBOUND_PROJECT_SECRET!,
181
+ });
182
+
183
+ const httpDlq = new HTTPAdapter(client);
184
+
185
+ await httpDlq.sendToDLQ({
186
+ payload: {
187
+ orderId: 'ORD-1001',
188
+ amount: 99.9,
189
+ },
190
+ error: new Error('Payment gateway timeout'),
191
+ metadata: {
192
+ endpoint: '/api/payments',
193
+ method: 'POST',
194
+ tenantId: 'acme',
195
+ },
196
+ });
197
+ ```
198
+
199
+ ## Comportamento de entrega
200
+
201
+ ### Falha de rede ou API indisponivel
202
+
203
+ Para erros transientes, o SDK continua aplicando retry em background com backoff exponencial.
204
+
205
+ ### Conta liberada para overage
206
+
207
+ Quando a conta pode cobrar uso extra, a ingestao continua normalmente e o excedente e faturado pela plataforma na fatura Stripe da conta.
208
+
209
+ ### Conta bloqueada por limite com `bill_extra` desativado
210
+
211
+ Quando a conta opta por **nao exceder o limite**:
212
+
213
+ - o SDK nao insiste em retry infinito para esse caso
214
+ - o evento atual e descartado
215
+ - novos eventos tambem sao descartados enquanto a conta continuar bloqueada
216
+ - o SDK consulta o estado da conta periodicamente e volta a enviar sozinho quando ela se tornar elegivel de novo
217
+
218
+ Esse comportamento existe para respeitar a decisao explicita da conta de nao gerar custo extra.
219
+
220
+ ### Conta bloqueada por limite, mas com escolha de `bill_extra`
221
+
222
+ Quando a conta escolheu **aceitar cobranca extra** e a plataforma ainda responde com bloqueio temporario, o SDK assume que o cliente prefere **nao perder eventos** e usa um buffer temporario:
223
+
224
+ - os eventos ficam em um `blocked buffer` em memoria por ate **15 minutos**
225
+ - o buffer tem limite de **250 eventos por processo e por chave**
226
+ - o SDK faz polling autenticado a cada **60 segundos**
227
+ - quando a conta volta a ficar apta, os eventos buffered sao reenfileirados e enviados em ordem FIFO
228
+
229
+ Se a conta continuar bloqueada alem da janela de buffer, os eventos buffered expiram e sao descartados com log explicito.
230
+
231
+ ## Observacao sobre `endpoint`
232
+
233
+ Prefira informar o `endpoint` como **URL base da API**. Exemplo recomendado:
234
+
235
+ ```ts
236
+ endpoint: 'https://sua-api.com/api/v1'
237
+ ```
238
+
239
+ Tambem funciona por compatibilidade:
240
+
241
+ ```ts
242
+ endpoint: 'https://sua-api.com/api/v1/dlq-injestion'
243
+ ```
244
+
245
+ O SDK adiciona internamente:
246
+
247
+ - `/dlq-injestion`
248
+ - `/dlq-injestion/runtime-state`
249
+
250
+ ## Matriz de comportamento
251
+
252
+ | Situacao | Comportamento do SDK |
253
+ | --- | --- |
254
+ | `2xx` da API | remove o item da fila |
255
+ | falha de rede / `5xx` / erro transiente | retry com backoff exponencial |
256
+ | limite de plano com politica `block` | descarta enquanto bloqueado e reconsulta estado |
257
+ | limite de plano com escolha de `bill_extra`, mas bloqueio temporario | usa blocked buffer e reconsulta estado |
258
+ | conta liberada novamente | retoma automaticamente sem reiniciar o processo |
259
+
260
+ ## O que o cliente precisa saber sobre limites e cobranca extra
261
+
262
+ Nao existe parametro novo no SDK para habilitar esse comportamento. Ele depende da configuracao da conta no Rebound DLQ:
263
+
264
+ - se a conta habilitar cobranca extra para eventos, a plataforma tenta continuar a ingestao e cobrar o excedente
265
+ - se a conta desabilitar cobranca extra, o SDK entende que pode haver perda de dados acima do limite e passa a descartar enquanto durar o bloqueio
266
+ - se a conta fizer upgrade ou alterar essa politica, o SDK detecta a mudanca automaticamente na proxima sincronizacao
267
+
268
+ Em outras palavras: **a decisao de produto e da conta; o SDK apenas executa essa politica automaticamente**.
269
+
270
+ ## Observacoes operacionais importantes
271
+
272
+ - a fila do SDK e **in-memory**
273
+ - eventos pendentes ou buffered **nao sobrevivem** a restart do processo
274
+ - workloads de curta duracao ou ambientes muito efemeros podem perder eventos se o processo encerrar logo apos o enqueue
275
+ - para maior seguranca operacional, prefira rodar o SDK em processos com shutdown gracioso
276
+ - o SDK registra logs no console quando entra em modo de retry, descarte, buffer e retomada
277
+
278
+ ## Compatibilidade com APIs antigas
279
+
280
+ Se a API de destino ainda nao suportar o contrato estruturado de bloqueio por limite, o SDK continua tratando respostas nao `2xx` como erros retryaveis. Ou seja: o fluxo novo de `buffer` e `discard` depende da API estar atualizada.
281
+
282
+ ## FAQ rapido
283
+
284
+ ### Preciso alterar meu codigo para usar o buffer ou o descarte?
285
+
286
+ Nao. Esse comportamento e automatico nesta versao do SDK.
287
+
288
+ ### Preciso informar um parametro novo no construtor?
289
+
290
+ Nao. Basta manter `projectSecret` e, se necessario, `endpoint`.
291
+
292
+ ### O SDK vai me avisar quando a conta for desbloqueada?
293
+
294
+ Sim. Ele faz polling em background e retoma sozinho quando a API indicar que a conta voltou a poder ingerir eventos.
295
+
296
+ ### O buffer guarda tudo indefinidamente?
94
297
 
95
- 1. Ao chamar `.execute()`, se o `processarPagamento` lançar qualquer **exceção**.
96
- 2. Essa exceção é **capturada sem dor**.
97
- 3. A variável `payloadQueSeraSalvoNaDLQ` entra na **memória RAM do SDK**.
98
- 4. É processada **criptografia local AES-256** no dado, utilizando a sua chave.
99
- 5. Em Background, um processo envia de forma segura este evento para a plataforma Rebound, garantindo entrega do Dead Letter Queue para reprocessamento futuro ou simples visualização pela Dashboard.
298
+ Nao. Ele e temporario, fica apenas em memoria, dura ate 15 minutos e respeita o limite de 250 eventos por processo/chave.
100
299
 
101
300
  ## Suporte
102
301
 
103
- Em caso de dúvidas, problemas de implementação ou bugs, abra um chamado pela Dashboard oficial do [Rebound Platform](https://rebound-dlq.com).
302
+ Se voce tiver duvidas de integracao, comportamento de limites ou operacao do SDK, abra um chamado pela plataforma oficial da [Rebound DLQ](https://rebound-dlq.com).
104
303
 
105
- ***
106
- _Feito com ❤️ por **[Codify Labs] / [Rebound DLQ]**._
304
+ ---
305
+ Feito por **Codify Labs / Rebound DLQ**.
@@ -29,7 +29,7 @@ class BaseAdapter {
29
29
  message: error.message,
30
30
  stack: error.stack,
31
31
  name: error.name,
32
- code: error.code
32
+ code: error.code !== undefined ? String(error.code) : undefined
33
33
  };
34
34
  }
35
35
  }
@@ -4,4 +4,8 @@ export declare class ReboundClient {
4
4
  private readonly baseUrl;
5
5
  constructor(config: ReboundConfig);
6
6
  send(payload: DLQPayload): Promise<void>;
7
+ private buildIngestionPayload;
8
+ private normalizeError;
9
+ private generateFingerprint;
10
+ private encrypt;
7
11
  }
@@ -1,25 +1,68 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.ReboundClient = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
4
8
  const constants_1 = require("./constants");
9
+ const endpoint_1 = require("./endpoint");
5
10
  const queue_1 = require("./queue");
6
11
  class ReboundClient {
7
12
  constructor(config) {
8
13
  this.config = config;
9
- const isTest = config.apiKey.startsWith('sk_test_');
10
- this.baseUrl = config.endpoint ||
11
- (isTest
12
- ? constants_1.REBOUND_API_ENDPOINTS.TEST
13
- : constants_1.REBOUND_API_ENDPOINTS.PRODUCTION);
14
+ const configuredEndpoint = config.endpoint || constants_1.DEFAULT_REBOUND_API_ENDPOINT;
15
+ this.baseUrl = (0, endpoint_1.resolveDlqBaseEndpoint)(configuredEndpoint);
14
16
  }
15
17
  async send(payload) {
16
- const endpoint = `${this.baseUrl}/dlq-injestion`;
17
- const enrichedPayload = {
18
- ...payload,
18
+ const endpoint = (0, endpoint_1.resolveDlqIngestionEndpoint)(this.baseUrl);
19
+ const ingestionPayload = this.buildIngestionPayload(payload);
20
+ // Enqueue directly in memory so the application doesn't block on network responses
21
+ queue_1.MemoryQueue.getInstance().enqueue(endpoint, this.config.projectSecret, ingestionPayload);
22
+ }
23
+ buildIngestionPayload(payload) {
24
+ const normalizedError = this.normalizeError(payload.error);
25
+ const envelope = {
26
+ payload: payload.payload,
27
+ source: payload.source,
28
+ metadata: payload.metadata ?? {},
19
29
  timestamp: payload.timestamp || Date.now(),
20
30
  };
21
- // Enqueue directly in memory so the application doesn't block on network responses
22
- queue_1.MemoryQueue.getInstance().enqueue(endpoint, this.config.apiKey, enrichedPayload);
31
+ return {
32
+ fingerprint: this.generateFingerprint(normalizedError, payload.source),
33
+ error: normalizedError,
34
+ payload: this.encrypt(envelope),
35
+ };
36
+ }
37
+ normalizeError(error) {
38
+ if (!error) {
39
+ return undefined;
40
+ }
41
+ return {
42
+ message: error.message,
43
+ stack: error.stack,
44
+ name: error.name,
45
+ code: error.code !== undefined ? String(error.code) : undefined,
46
+ };
47
+ }
48
+ generateFingerprint(error, source) {
49
+ const stack = (error?.stack || '').split('\n').slice(0, 3).join('');
50
+ const seed = `${error?.name || 'UnknownError'}${error?.message || ''}${stack}${source}`;
51
+ return crypto_1.default.createHash('sha256').update(seed).digest('hex');
52
+ }
53
+ encrypt(payload) {
54
+ const algorithm = 'aes-256-cbc';
55
+ const key = crypto_1.default
56
+ .createHash('sha256')
57
+ .update(String(this.config.projectSecret))
58
+ .digest('base64')
59
+ .substring(0, 32);
60
+ const iv = crypto_1.default.randomBytes(16);
61
+ const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
62
+ const cipher = crypto_1.default.createCipheriv(algorithm, Buffer.from(key), iv);
63
+ let encrypted = cipher.update(payloadString, 'utf8', 'hex');
64
+ encrypted += cipher.final('hex');
65
+ return `${iv.toString('hex')}:${encrypted}`;
23
66
  }
24
67
  }
25
68
  exports.ReboundClient = ReboundClient;
@@ -1,4 +1 @@
1
- export declare const REBOUND_API_ENDPOINTS: {
2
- readonly TEST: "https://rebound-dlq-api-2.vercel.app/api/v1";
3
- readonly PRODUCTION: "https://rebound-dlq-api-2.vercel.app/api/v1";
4
- };
1
+ export declare const DEFAULT_REBOUND_API_ENDPOINT = "http://localhost:3001/api/v1";
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.REBOUND_API_ENDPOINTS = void 0;
4
- exports.REBOUND_API_ENDPOINTS = {
5
- TEST: 'https://rebound-dlq-api-2.vercel.app/api/v1',
6
- PRODUCTION: 'https://rebound-dlq-api-2.vercel.app/api/v1'
7
- };
3
+ exports.DEFAULT_REBOUND_API_ENDPOINT = void 0;
4
+ exports.DEFAULT_REBOUND_API_ENDPOINT = 'http://localhost:3001/api/v1';
@@ -0,0 +1,2 @@
1
+ export declare function resolveDlqIngestionEndpoint(endpoint: string): string;
2
+ export declare function resolveDlqBaseEndpoint(endpoint: string): string;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDlqIngestionEndpoint = resolveDlqIngestionEndpoint;
4
+ exports.resolveDlqBaseEndpoint = resolveDlqBaseEndpoint;
5
+ const DLQ_INGESTION_SEGMENT = '/dlq-injestion';
6
+ function resolveDlqIngestionEndpoint(endpoint) {
7
+ const normalizedEndpoint = endpoint.replace(/\/+$/, '');
8
+ if (normalizedEndpoint.endsWith(DLQ_INGESTION_SEGMENT)) {
9
+ return normalizedEndpoint;
10
+ }
11
+ return `${normalizedEndpoint}${DLQ_INGESTION_SEGMENT}`;
12
+ }
13
+ function resolveDlqBaseEndpoint(endpoint) {
14
+ const normalizedEndpoint = endpoint.replace(/\/+$/, '');
15
+ if (normalizedEndpoint.endsWith(DLQ_INGESTION_SEGMENT)) {
16
+ return normalizedEndpoint.slice(0, -DLQ_INGESTION_SEGMENT.length);
17
+ }
18
+ return normalizedEndpoint;
19
+ }
@@ -2,11 +2,34 @@ export declare class MemoryQueue {
2
2
  private static instance;
3
3
  private queue;
4
4
  private isProcessing;
5
+ private isProcessingScheduled;
5
6
  private baseDelayMs;
6
7
  private maxDelayMs;
8
+ private blockedStates;
7
9
  private constructor();
8
10
  static getInstance(): MemoryQueue;
9
- enqueue(endpoint: string, apiKey: string, payload: any): void;
11
+ enqueue(endpoint: string, projectSecret: string, payload: any): void;
10
12
  private processQueue;
11
13
  private applyBackoff;
14
+ private ensureProcessing;
15
+ private sendSignedJsonRequest;
16
+ private parseBlockedResponse;
17
+ private normalizeBlockedPayload;
18
+ private activateBlockedState;
19
+ private routeBlockedItem;
20
+ private bufferItem;
21
+ private pruneExpiredBufferedItems;
22
+ private scheduleProbe;
23
+ private probeBlockedState;
24
+ private fetchRuntimeState;
25
+ private normalizeRuntimeStateResponse;
26
+ private resumeBlockedState;
27
+ private logBlockedMode;
28
+ private buildQueueKey;
29
+ private buildRuntimeStateEndpoint;
30
+ private normalizeRecheckAfterMs;
31
+ private normalizeBufferWindowMs;
32
+ private isJsonResponse;
33
+ private isRecord;
34
+ private buildRetryWarning;
12
35
  }
@@ -1,12 +1,55 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.MemoryQueue = void 0;
37
+ const crypto = __importStar(require("crypto"));
38
+ const DLQ_INGESTION_BLOCK_CODE = 'DLQ_INGESTION_BLOCKED_LIMIT';
39
+ const DEFAULT_RECHECK_AFTER_MS = 60 * 1000;
40
+ const DEFAULT_BUFFER_WINDOW_MS = 15 * 60 * 1000;
41
+ const MIN_RECHECK_AFTER_MS = 15 * 1000;
42
+ const MIN_BUFFER_WINDOW_MS = 60 * 1000;
43
+ const MAX_BUFFER_WINDOW_MS = 60 * 60 * 1000;
44
+ const MAX_BUFFERED_ITEMS_PER_KEY = 250;
4
45
  class MemoryQueue {
5
46
  constructor() {
6
47
  this.queue = [];
7
48
  this.isProcessing = false;
49
+ this.isProcessingScheduled = false;
8
50
  this.baseDelayMs = 1000;
9
51
  this.maxDelayMs = 60000; // max 60 seconds between retries
52
+ this.blockedStates = new Map();
10
53
  }
11
54
  static getInstance() {
12
55
  if (!MemoryQueue.instance) {
@@ -14,61 +57,330 @@ class MemoryQueue {
14
57
  }
15
58
  return MemoryQueue.instance;
16
59
  }
17
- enqueue(endpoint, apiKey, payload) {
18
- this.queue.push({
60
+ enqueue(endpoint, projectSecret, payload) {
61
+ const item = {
19
62
  endpoint,
20
- apiKey,
63
+ projectSecret,
21
64
  payload,
22
65
  retryCount: 0
23
- });
24
- if (!this.isProcessing) {
25
- // Offload to Event Loop so it returns instantly for the main application thread
26
- setTimeout(() => {
27
- this.processQueue().catch((err) => {
28
- console.error('[Rebound SDK] Unexpected queue error:', err);
29
- });
30
- }, 0);
66
+ };
67
+ const queueKey = this.buildQueueKey(endpoint, projectSecret);
68
+ const blockedState = this.blockedStates.get(queueKey);
69
+ if (blockedState) {
70
+ this.routeBlockedItem(item, blockedState);
71
+ this.scheduleProbe(queueKey, blockedState);
72
+ return;
31
73
  }
74
+ this.queue.push(item);
75
+ this.ensureProcessing();
32
76
  }
33
77
  async processQueue() {
34
78
  this.isProcessing = true;
35
- while (this.queue.length > 0) {
36
- const item = this.queue[0]; // FIFO
37
- try {
38
- const response = await fetch(item.endpoint, {
39
- method: 'POST',
40
- headers: {
41
- 'Content-Type': 'application/json',
42
- 'x-api-key': `Bearer ${item.apiKey}`
43
- },
44
- body: JSON.stringify(item.payload),
45
- });
46
- if (response.ok) {
47
- if (item.retryCount > 0) {
48
- console.log(`[Rebound SDK] Event successfully delivered after ${item.retryCount} retries.`);
49
- }
50
- // Success: Remove item from memory queue
79
+ try {
80
+ while (this.queue.length > 0) {
81
+ const item = this.queue[0]; // FIFO
82
+ const queueKey = this.buildQueueKey(item.endpoint, item.projectSecret);
83
+ const blockedState = this.blockedStates.get(queueKey);
84
+ if (blockedState) {
51
85
  this.queue.shift();
86
+ this.routeBlockedItem(item, blockedState);
87
+ this.scheduleProbe(queueKey, blockedState);
88
+ continue;
52
89
  }
53
- else {
54
- // API error (ex 429, 500)
55
- await this.applyBackoff(item);
90
+ try {
91
+ const response = await this.sendSignedJsonRequest(item.endpoint, item.projectSecret, item.payload);
92
+ if (response.ok) {
93
+ if (item.retryCount > 0) {
94
+ console.log(`[Rebound SDK] Event successfully delivered after ${item.retryCount} retries.`);
95
+ }
96
+ this.queue.shift();
97
+ continue;
98
+ }
99
+ const blockedResponse = await this.parseBlockedResponse(response);
100
+ if (blockedResponse) {
101
+ this.queue.shift();
102
+ this.activateBlockedState(item, blockedResponse);
103
+ continue;
104
+ }
105
+ await this.applyBackoff(item, {
106
+ reason: 'http',
107
+ statusCode: response.status,
108
+ });
109
+ }
110
+ catch (error) {
111
+ // Network unstable / Connection refused
112
+ await this.applyBackoff(item, { reason: 'network' });
56
113
  }
57
- }
58
- catch (error) {
59
- // Network unstable / Connection refused
60
- await this.applyBackoff(item);
61
114
  }
62
115
  }
63
- this.isProcessing = false;
116
+ finally {
117
+ this.isProcessing = false;
118
+ }
64
119
  }
65
- async applyBackoff(item) {
120
+ async applyBackoff(item, context) {
66
121
  item.retryCount++;
67
122
  const delay = Math.min(this.baseDelayMs * Math.pow(2, item.retryCount), this.maxDelayMs);
68
123
  if (item.retryCount === 1) {
69
- console.warn(`[Rebound SDK] Connectivity issue sending DLQ event. Retrying in background to guarantee delivery...`);
124
+ console.warn(this.buildRetryWarning(context));
70
125
  }
71
126
  return new Promise((resolve) => setTimeout(resolve, delay));
72
127
  }
128
+ ensureProcessing() {
129
+ if (this.isProcessing || this.isProcessingScheduled) {
130
+ return;
131
+ }
132
+ this.isProcessingScheduled = true;
133
+ // Offload to Event Loop so it returns instantly for the main application thread
134
+ setTimeout(() => {
135
+ this.isProcessingScheduled = false;
136
+ this.processQueue().catch((err) => {
137
+ console.error('[Rebound SDK] Unexpected queue error:', err);
138
+ });
139
+ }, 0);
140
+ }
141
+ async sendSignedJsonRequest(endpoint, projectSecret, payload) {
142
+ const timestamp = Date.now().toString();
143
+ const payloadString = JSON.stringify(payload);
144
+ const dataToSign = `${timestamp}.${payloadString}`;
145
+ const signature = crypto
146
+ .createHmac('sha256', projectSecret)
147
+ .update(dataToSign)
148
+ .digest('hex');
149
+ return fetch(endpoint, {
150
+ method: 'POST',
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ 'x-api-key': `Bearer ${projectSecret}`,
154
+ 'x-sdk-timestamp': timestamp,
155
+ 'x-sdk-signature': signature
156
+ },
157
+ body: payloadString,
158
+ });
159
+ }
160
+ async parseBlockedResponse(response) {
161
+ if (response.status !== 403 || !this.isJsonResponse(response)) {
162
+ return null;
163
+ }
164
+ try {
165
+ const payload = await response.json();
166
+ return this.normalizeBlockedPayload(payload);
167
+ }
168
+ catch {
169
+ return null;
170
+ }
171
+ }
172
+ normalizeBlockedPayload(payload) {
173
+ if (!this.isRecord(payload)) {
174
+ return null;
175
+ }
176
+ if (payload.code !== DLQ_INGESTION_BLOCK_CODE || payload.retryable !== false) {
177
+ return null;
178
+ }
179
+ const queueAction = payload.queueAction === 'buffer' ? 'buffer' : 'discard';
180
+ const bufferWindowMs = queueAction === 'buffer'
181
+ ? this.normalizeBufferWindowMs(payload.bufferWindowSeconds)
182
+ : 0;
183
+ return {
184
+ queueAction,
185
+ bufferWindowMs,
186
+ recheckAfterMs: this.normalizeRecheckAfterMs(payload.recheckAfterSeconds),
187
+ currentPeriodEnd: typeof payload.currentPeriodEnd === 'string' ? payload.currentPeriodEnd : null,
188
+ };
189
+ }
190
+ activateBlockedState(item, payload) {
191
+ const queueKey = this.buildQueueKey(item.endpoint, item.projectSecret);
192
+ const previousState = this.blockedStates.get(queueKey) ?? null;
193
+ const previousAction = previousState?.queueAction ?? null;
194
+ const blockedState = previousState ?? {
195
+ endpoint: item.endpoint,
196
+ projectSecret: item.projectSecret,
197
+ queueAction: payload.queueAction,
198
+ bufferWindowMs: payload.bufferWindowMs,
199
+ recheckAfterMs: payload.recheckAfterMs,
200
+ currentPeriodEnd: payload.currentPeriodEnd,
201
+ buffer: [],
202
+ timer: null,
203
+ probeInFlight: false,
204
+ };
205
+ blockedState.queueAction = payload.queueAction;
206
+ blockedState.bufferWindowMs = payload.bufferWindowMs;
207
+ blockedState.recheckAfterMs = payload.recheckAfterMs;
208
+ blockedState.currentPeriodEnd = payload.currentPeriodEnd;
209
+ if (blockedState.queueAction === 'discard' && blockedState.buffer.length > 0) {
210
+ blockedState.buffer = [];
211
+ }
212
+ this.blockedStates.set(queueKey, blockedState);
213
+ this.routeBlockedItem(item, blockedState);
214
+ this.scheduleProbe(queueKey, blockedState);
215
+ if (!previousState || previousAction !== blockedState.queueAction) {
216
+ this.logBlockedMode(blockedState);
217
+ }
218
+ }
219
+ routeBlockedItem(item, blockedState) {
220
+ if (blockedState.queueAction === 'buffer') {
221
+ this.bufferItem(blockedState, item);
222
+ return;
223
+ }
224
+ }
225
+ bufferItem(blockedState, item) {
226
+ this.pruneExpiredBufferedItems(blockedState);
227
+ blockedState.buffer.push({
228
+ ...item,
229
+ expiresAt: Date.now() + blockedState.bufferWindowMs,
230
+ });
231
+ if (blockedState.buffer.length <= MAX_BUFFERED_ITEMS_PER_KEY) {
232
+ return;
233
+ }
234
+ const overflowCount = blockedState.buffer.length - MAX_BUFFERED_ITEMS_PER_KEY;
235
+ blockedState.buffer.splice(0, overflowCount);
236
+ console.warn(`[Rebound SDK] Buffer cap reached while DLQ ingestion is blocked. Dropped ${overflowCount} older buffered event(s).`);
237
+ }
238
+ pruneExpiredBufferedItems(blockedState) {
239
+ const now = Date.now();
240
+ const originalLength = blockedState.buffer.length;
241
+ blockedState.buffer = blockedState.buffer.filter((item) => item.expiresAt > now);
242
+ const droppedCount = originalLength - blockedState.buffer.length;
243
+ if (droppedCount > 0) {
244
+ console.warn(`[Rebound SDK] Dropped ${droppedCount} buffered event(s) after the temporary blocked window expired.`);
245
+ }
246
+ }
247
+ scheduleProbe(queueKey, blockedState) {
248
+ if (blockedState.timer) {
249
+ return;
250
+ }
251
+ blockedState.timer = setTimeout(() => {
252
+ blockedState.timer = null;
253
+ this.probeBlockedState(queueKey).catch((error) => {
254
+ console.error('[Rebound SDK] Failed to refresh blocked ingestion state:', error);
255
+ });
256
+ }, blockedState.recheckAfterMs);
257
+ }
258
+ async probeBlockedState(queueKey) {
259
+ const blockedState = this.blockedStates.get(queueKey);
260
+ if (!blockedState || blockedState.probeInFlight) {
261
+ return;
262
+ }
263
+ blockedState.probeInFlight = true;
264
+ try {
265
+ this.pruneExpiredBufferedItems(blockedState);
266
+ const runtimeState = await this.fetchRuntimeState(blockedState);
267
+ if (!runtimeState) {
268
+ return;
269
+ }
270
+ if (runtimeState.allowed) {
271
+ this.resumeBlockedState(queueKey, blockedState);
272
+ return;
273
+ }
274
+ const previousAction = blockedState.queueAction;
275
+ blockedState.queueAction = runtimeState.queueAction;
276
+ blockedState.bufferWindowMs = runtimeState.bufferWindowMs;
277
+ blockedState.recheckAfterMs = runtimeState.recheckAfterMs;
278
+ blockedState.currentPeriodEnd = runtimeState.currentPeriodEnd;
279
+ if (blockedState.queueAction === 'discard' && blockedState.buffer.length > 0) {
280
+ blockedState.buffer = [];
281
+ }
282
+ if (previousAction !== blockedState.queueAction) {
283
+ this.logBlockedMode(blockedState);
284
+ }
285
+ }
286
+ finally {
287
+ blockedState.probeInFlight = false;
288
+ if (this.blockedStates.has(queueKey)) {
289
+ this.scheduleProbe(queueKey, blockedState);
290
+ }
291
+ }
292
+ }
293
+ async fetchRuntimeState(blockedState) {
294
+ const response = await this.sendSignedJsonRequest(this.buildRuntimeStateEndpoint(blockedState.endpoint), blockedState.projectSecret, {});
295
+ if (!response.ok || !this.isJsonResponse(response)) {
296
+ return null;
297
+ }
298
+ try {
299
+ const payload = await response.json();
300
+ return this.normalizeRuntimeStateResponse(payload);
301
+ }
302
+ catch {
303
+ return null;
304
+ }
305
+ }
306
+ normalizeRuntimeStateResponse(payload) {
307
+ if (!this.isRecord(payload) || typeof payload.allowed !== 'boolean') {
308
+ return null;
309
+ }
310
+ if (payload.allowed) {
311
+ return {
312
+ allowed: true,
313
+ queueAction: 'discard',
314
+ bufferWindowMs: 0,
315
+ recheckAfterMs: this.normalizeRecheckAfterMs(payload.recheckAfterSeconds),
316
+ currentPeriodEnd: typeof payload.currentPeriodEnd === 'string' ? payload.currentPeriodEnd : null,
317
+ };
318
+ }
319
+ const blockedPayload = this.normalizeBlockedPayload(payload);
320
+ if (!blockedPayload) {
321
+ return null;
322
+ }
323
+ return {
324
+ allowed: false,
325
+ ...blockedPayload,
326
+ };
327
+ }
328
+ resumeBlockedState(queueKey, blockedState) {
329
+ this.blockedStates.delete(queueKey);
330
+ if (blockedState.timer) {
331
+ clearTimeout(blockedState.timer);
332
+ blockedState.timer = null;
333
+ }
334
+ this.pruneExpiredBufferedItems(blockedState);
335
+ if (blockedState.buffer.length > 0) {
336
+ const bufferedItems = blockedState.buffer.map(({ expiresAt, ...item }) => item);
337
+ this.queue = [...bufferedItems, ...this.queue];
338
+ console.log(`[Rebound SDK] DLQ ingestion resumed. Replaying ${bufferedItems.length} buffered event(s).`);
339
+ }
340
+ if (this.queue.length > 0) {
341
+ this.ensureProcessing();
342
+ }
343
+ }
344
+ logBlockedMode(blockedState) {
345
+ const cycleHint = blockedState.currentPeriodEnd
346
+ ? ` Current cycle ends at ${blockedState.currentPeriodEnd}.`
347
+ : '';
348
+ if (blockedState.queueAction === 'buffer') {
349
+ const bufferMinutes = Math.round(blockedState.bufferWindowMs / 60000);
350
+ console.warn(`[Rebound SDK] DLQ ingestion is temporarily blocked by the account plan. Buffering events for up to ${bufferMinutes} minute(s) while the account plan or billing settings are adjusted.${cycleHint}`);
351
+ return;
352
+ }
353
+ console.warn(`[Rebound SDK] DLQ ingestion is blocked by the account plan and extra usage is disabled. New events will be discarded until the account becomes eligible again.${cycleHint}`);
354
+ }
355
+ buildQueueKey(endpoint, projectSecret) {
356
+ return `${endpoint}::${projectSecret}`;
357
+ }
358
+ buildRuntimeStateEndpoint(endpoint) {
359
+ return `${endpoint.replace(/\/+$/, '')}/runtime-state`;
360
+ }
361
+ normalizeRecheckAfterMs(value) {
362
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
363
+ return DEFAULT_RECHECK_AFTER_MS;
364
+ }
365
+ return Math.max(Math.trunc(value * 1000), MIN_RECHECK_AFTER_MS);
366
+ }
367
+ normalizeBufferWindowMs(value) {
368
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
369
+ return DEFAULT_BUFFER_WINDOW_MS;
370
+ }
371
+ return Math.min(Math.max(Math.trunc(value * 1000), MIN_BUFFER_WINDOW_MS), MAX_BUFFER_WINDOW_MS);
372
+ }
373
+ isJsonResponse(response) {
374
+ return (response.headers.get('content-type') ?? '').includes('application/json');
375
+ }
376
+ isRecord(value) {
377
+ return typeof value === 'object' && value !== null;
378
+ }
379
+ buildRetryWarning(context) {
380
+ if (context.reason === 'http' && context.statusCode) {
381
+ return `[Rebound SDK] DLQ API rejected the event with HTTP ${context.statusCode}. Retrying in background while the issue persists...`;
382
+ }
383
+ return '[Rebound SDK] Connectivity issue sending DLQ event. Retrying in background to guarantee delivery...';
384
+ }
73
385
  }
74
386
  exports.MemoryQueue = MemoryQueue;
@@ -6,16 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DlqWrapper = void 0;
7
7
  const crypto_1 = __importDefault(require("crypto"));
8
8
  const constants_1 = require("./constants");
9
+ const endpoint_1 = require("./endpoint");
9
10
  const queue_1 = require("./queue");
10
11
  class DlqWrapper {
11
12
  constructor(config) {
12
13
  this.projectSecret = config.projectSecret;
13
- // Identifica se é ambiente de teste através do projectSecret (mesma lógica da apiKey no ReboundClient)
14
- const isTest = config.projectSecret.startsWith('sk_dev_');
15
- this.endpoint = config.endpoint ||
16
- (isTest
17
- ? constants_1.REBOUND_API_ENDPOINTS.TEST
18
- : constants_1.REBOUND_API_ENDPOINTS.PRODUCTION) + '/dlq-injestion'; // Usar o sufixo apropriado
14
+ const configuredEndpoint = config.endpoint || constants_1.DEFAULT_REBOUND_API_ENDPOINT;
15
+ this.endpoint = (0, endpoint_1.resolveDlqIngestionEndpoint)(configuredEndpoint);
19
16
  }
20
17
  async execute(fn, payload) {
21
18
  try {
@@ -11,7 +11,7 @@ export interface DLQPayload {
11
11
  timestamp?: number;
12
12
  }
13
13
  export interface ReboundConfig {
14
- apiKey: string;
14
+ projectSecret: string;
15
15
  endpoint?: string;
16
16
  timeout?: number;
17
17
  }
package/package.json CHANGED
@@ -1,18 +1,23 @@
1
- {
2
- "name": "@rebound-dlq/node",
3
- "version": "0.2.2",
4
- "main": "dist/index.js",
5
- "types": "dist/index.d.ts",
6
- "files": ["dist"],
7
- "scripts": {
8
- "build": "tsc",
9
- "dev": "tsc --watch",
10
- "json-server": "npx json-server --watch mock-server.json --port 3005",
11
- "test:wrapper": "npx ts-node examples/wrapper-example.ts"
12
- },
13
- "devDependencies": {
14
- "@types/node": "^20.0.0",
15
- "json-server": "^1.0.0-beta.3",
16
- "typescript": "^5.0.0"
17
- }
18
- }
1
+ {
2
+ "name": "@rebound-dlq/node",
3
+ "version": "0.2.4",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "files": ["dist"],
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "build:examples": "node scripts/run-example.cjs --build-only",
10
+ "dev": "tsc --watch",
11
+ "json-server": "npx json-server --watch mock-server.json --port 3005",
12
+ "example:wrapper": "node scripts/run-example.cjs wrapper-example",
13
+ "example:http": "node scripts/run-example.cjs http-example",
14
+ "example:kafka": "node scripts/run-example.cjs kafka-example",
15
+ "example:internal": "node scripts/run-example.cjs internal-errors-example",
16
+ "test:wrapper": "node scripts/run-example.cjs wrapper-example"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^20.0.0",
20
+ "json-server": "^1.0.0-beta.3",
21
+ "typescript": "^5.0.0"
22
+ }
23
+ }