@rebound-dlq/node 0.2.4 → 0.2.6
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 +173 -187
- package/dist/adapters/base.adapter.js +5 -2
- package/dist/core/client.d.ts +3 -1
- package/dist/core/client.js +20 -0
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.js +1 -1
- package/dist/core/wrapper.d.ts +4 -1
- package/dist/core/wrapper.js +24 -4
- package/dist/index.d.ts +1 -1
- package/dist/models/payload.d.ts +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,87 +1,84 @@
|
|
|
1
|
-
# Rebound DLQ
|
|
1
|
+
# Rebound DLQ — Node.js SDK
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@rebound-dlq/node)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
SDK oficial em Node.js para integrar sua aplicação com a plataforma **[Rebound DLQ](https://rebound-dlq.com)**.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Capture falhas, registre eventos de erro e garanta rastreabilidade das suas filas mortas — tudo sem impactar a performance da sua aplicação.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Por que usar?
|
|
13
|
+
|
|
14
|
+
- **Zero latência no fluxo principal** — o evento é enfileirado em memória e a aplicação segue sem esperar resposta de rede.
|
|
15
|
+
- **Criptografia local AES-256** — o payload é criptografado antes de sair do seu processo.
|
|
16
|
+
- **Entrega resiliente** — retry automático com backoff exponencial em caso de falha de rede.
|
|
17
|
+
- **Sem config extra** — o comportamento de retry, buffer e retomada é totalmente automático, determinado pela configuração da conta na plataforma.
|
|
11
18
|
|
|
12
|
-
|
|
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.
|
|
19
|
+
> **Pré-requisito:** uma conta e um projeto ativos em [rebound-dlq.com](https://rebound-dlq.com).
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
---
|
|
19
22
|
|
|
20
|
-
##
|
|
23
|
+
## Instalação
|
|
21
24
|
|
|
22
25
|
```bash
|
|
23
26
|
npm install @rebound-dlq/node
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
|
|
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
|
|
29
|
+
---
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
## Configuração
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
A única configuração obrigatória é o `projectSecret`, obtido no painel da plataforma Rebound DLQ.
|
|
40
34
|
|
|
41
|
-
Nunca exponha a chave
|
|
35
|
+
Nunca exponha a chave no código-fonte. Use variáveis de ambiente:
|
|
42
36
|
|
|
43
37
|
```env
|
|
44
38
|
REBOUND_PROJECT_SECRET="seu_project_secret_aqui"
|
|
45
39
|
```
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
---
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
## Agrupamento por fluxo
|
|
50
44
|
|
|
51
|
-
|
|
52
|
-
- `API_ENDPOINT`
|
|
45
|
+
Agora o SDK também pode ajudar a agrupar eventos DLQ por **fluxo de integração** na plataforma.
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
Isso é útil quando o seu cliente quer enxergar, por exemplo, apenas os erros do fluxo `Pagarme`, `ERP`, `Webhook de pedidos` ou qualquer outro processo operacional.
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
npm run example:wrapper
|
|
58
|
-
npm run example:http
|
|
59
|
-
npm run example:kafka
|
|
60
|
-
npm run example:internal
|
|
61
|
-
```
|
|
49
|
+
Os campos usados pela plataforma são:
|
|
62
50
|
|
|
63
|
-
|
|
51
|
+
| Campo | Obrigatório | Descrição |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `flowKey` | ✅ Sim | Identificador técnico e estável do fluxo. Ex.: `pagarme-payments`. |
|
|
54
|
+
| `flowLabel` | Não | Nome amigável exibido no painel. Ex.: `Integração Pagarme`. |
|
|
55
|
+
| `flowStep` | Não | Etapa específica do fluxo. Ex.: `create_charge`, `payment_webhook`, `capture_receivables`. |
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
node scripts/run-example.cjs wrapper
|
|
67
|
-
```
|
|
57
|
+
### Regra de agrupamento
|
|
68
58
|
|
|
69
|
-
|
|
59
|
+
- Se chegar um evento com um `flowKey` já existente naquele projeto, ele é agrupado no mesmo fluxo.
|
|
60
|
+
- Se o `flowKey` ainda não existir, a plataforma cria o fluxo automaticamente.
|
|
61
|
+
- Se o evento não enviar dados de fluxo, ele continua funcionando normalmente, como já funciona hoje.
|
|
70
62
|
|
|
71
|
-
###
|
|
63
|
+
### Exemplo recomendado de nomenclatura
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
```ts
|
|
66
|
+
{
|
|
67
|
+
flowKey: 'pagarme-payments',
|
|
68
|
+
flowLabel: 'Integração Pagarme',
|
|
69
|
+
flowStep: 'payment_webhook'
|
|
70
|
+
}
|
|
71
|
+
```
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
- `endpoint` para definir para qual API Rebound os eventos devem ser enviados
|
|
73
|
+
> Dica: trate `flowKey` como identificador estável. Evite usar textos que mudam com frequência.
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
---
|
|
79
76
|
|
|
80
|
-
## Interfaces
|
|
77
|
+
## Interfaces disponíveis
|
|
81
78
|
|
|
82
|
-
###
|
|
79
|
+
### `DlqWrapper` — recomendado
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
Encapsula qualquer função assíncrona. Se ela lançar uma exceção, o evento de erro é capturado, criptografado e enviado automaticamente em background.
|
|
85
82
|
|
|
86
83
|
```ts
|
|
87
84
|
import { DlqWrapper } from '@rebound-dlq/node';
|
|
@@ -91,38 +88,21 @@ const dlq = new DlqWrapper({
|
|
|
91
88
|
});
|
|
92
89
|
```
|
|
93
90
|
|
|
94
|
-
|
|
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. |
|
|
91
|
+
**Parâmetros:**
|
|
101
92
|
|
|
102
|
-
|
|
93
|
+
| Campo | Obrigatório | Descrição |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `projectSecret` | ✅ Sim | Chave secreta do projeto, usada para autenticação e criptografia local. |
|
|
103
96
|
|
|
104
|
-
|
|
97
|
+
**Uso:**
|
|
105
98
|
|
|
106
99
|
```ts
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const client = new ReboundClient({
|
|
110
|
-
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
111
|
-
endpoint: process.env.REBOUND_API_ENDPOINT,
|
|
112
|
-
});
|
|
100
|
+
const resultado = await dlq.execute(() => minhaFuncaoQuePodefFalhar(dados), dados);
|
|
113
101
|
```
|
|
114
102
|
|
|
115
|
-
|
|
103
|
+
O erro original é sempre relançado para o seu tratamento normal. O SDK apenas captura, criptografa e envia o evento em background.
|
|
116
104
|
|
|
117
|
-
|
|
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`
|
|
105
|
+
**Exemplo completo:**
|
|
126
106
|
|
|
127
107
|
```ts
|
|
128
108
|
import { DlqWrapper } from '@rebound-dlq/node';
|
|
@@ -131,47 +111,56 @@ const dlq = new DlqWrapper({
|
|
|
131
111
|
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
132
112
|
});
|
|
133
113
|
|
|
134
|
-
async function processarPagamento(dados: {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
tentativa: number;
|
|
138
|
-
modulo: string;
|
|
139
|
-
}) {
|
|
140
|
-
if (dados.valor > 1000) {
|
|
141
|
-
throw new Error('Saldo insuficiente na operadora do cartao');
|
|
114
|
+
async function processarPagamento(dados: { userId: string; valor: number }) {
|
|
115
|
+
if (dados.valor > 10000) {
|
|
116
|
+
throw new Error('Limite excedido na operadora');
|
|
142
117
|
}
|
|
143
|
-
|
|
144
|
-
return 'Sucesso';
|
|
118
|
+
return 'aprovado';
|
|
145
119
|
}
|
|
146
120
|
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
userId: '123',
|
|
150
|
-
valor:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const resultado = await dlq.execute(() => processarPagamento(payload), payload);
|
|
157
|
-
console.log('Resultado:', resultado);
|
|
158
|
-
} catch (erro) {
|
|
159
|
-
console.error('Erro de negocio capturado pela aplicacao:', (erro as Error).message);
|
|
160
|
-
}
|
|
121
|
+
try {
|
|
122
|
+
const resultado = await dlq.execute(
|
|
123
|
+
() => processarPagamento({ userId: 'u-123', valor: 15000 }),
|
|
124
|
+
{ userId: 'u-123', valor: 15000 },
|
|
125
|
+
);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// erro relançado normalmente — o SDK já registrou o evento
|
|
128
|
+
console.error((err as Error).message);
|
|
161
129
|
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `DlqWrapper` com fluxo
|
|
133
|
+
|
|
134
|
+
Se você quiser agrupar o evento por fluxo usando o `DlqWrapper`, passe um terceiro parâmetro opcional com o contexto do fluxo.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { DlqWrapper } from '@rebound-dlq/node';
|
|
138
|
+
|
|
139
|
+
const dlq = new DlqWrapper({
|
|
140
|
+
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
141
|
+
});
|
|
162
142
|
|
|
163
|
-
|
|
143
|
+
await dlq.execute(
|
|
144
|
+
() => processarPagamento({ userId: 'u-123', valor: 15000 }),
|
|
145
|
+
{
|
|
146
|
+
userId: 'u-123',
|
|
147
|
+
valor: 15000,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
flowKey: 'pagarme-payments',
|
|
151
|
+
flowLabel: 'Integração Pagarme',
|
|
152
|
+
flowStep: 'create_charge',
|
|
153
|
+
},
|
|
154
|
+
);
|
|
164
155
|
```
|
|
165
156
|
|
|
166
|
-
|
|
157
|
+
> Se você não passar o terceiro parâmetro, o evento continua sendo enviado normalmente, sem vínculo com fluxo.
|
|
158
|
+
|
|
159
|
+
---
|
|
167
160
|
|
|
168
|
-
|
|
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.
|
|
161
|
+
### `ReboundClient` + adapters — uso avançado
|
|
173
162
|
|
|
174
|
-
|
|
163
|
+
Para enviar eventos manualmente ou usar com adapters (HTTP, Kafka etc.).
|
|
175
164
|
|
|
176
165
|
```ts
|
|
177
166
|
import { ReboundClient, HTTPAdapter } from '@rebound-dlq/node';
|
|
@@ -183,123 +172,120 @@ const client = new ReboundClient({
|
|
|
183
172
|
const httpDlq = new HTTPAdapter(client);
|
|
184
173
|
|
|
185
174
|
await httpDlq.sendToDLQ({
|
|
186
|
-
payload: {
|
|
187
|
-
orderId: 'ORD-1001',
|
|
188
|
-
amount: 99.9,
|
|
189
|
-
},
|
|
175
|
+
payload: { orderId: 'ORD-1001', amount: 99.9 },
|
|
190
176
|
error: new Error('Payment gateway timeout'),
|
|
177
|
+
flowKey: 'checkout-payments',
|
|
178
|
+
flowLabel: 'Checkout de pagamentos',
|
|
179
|
+
flowStep: 'gateway_authorization',
|
|
191
180
|
metadata: {
|
|
192
181
|
endpoint: '/api/payments',
|
|
193
182
|
method: 'POST',
|
|
194
183
|
tenantId: 'acme',
|
|
195
|
-
}
|
|
184
|
+
}
|
|
196
185
|
});
|
|
197
186
|
```
|
|
198
187
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
### Falha de rede ou API indisponivel
|
|
202
|
-
|
|
203
|
-
Para erros transientes, o SDK continua aplicando retry em background com backoff exponencial.
|
|
188
|
+
**Parâmetros do `ReboundClient`:**
|
|
204
189
|
|
|
205
|
-
|
|
190
|
+
| Campo | Obrigatório | Descrição |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `projectSecret` | ✅ Sim | Chave secreta do projeto, usada para autenticação e criptografia local. |
|
|
206
193
|
|
|
207
|
-
|
|
194
|
+
### Exemplo com fluxo usando `HTTPAdapter`
|
|
208
195
|
|
|
209
|
-
|
|
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.
|
|
196
|
+
```ts
|
|
197
|
+
import { ReboundClient, HTTPAdapter } from '@rebound-dlq/node';
|
|
230
198
|
|
|
231
|
-
|
|
199
|
+
const client = new ReboundClient({
|
|
200
|
+
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
201
|
+
});
|
|
232
202
|
|
|
233
|
-
|
|
203
|
+
const httpDlq = new HTTPAdapter(client);
|
|
234
204
|
|
|
235
|
-
|
|
236
|
-
|
|
205
|
+
await httpDlq.sendToDLQ({
|
|
206
|
+
payload: {
|
|
207
|
+
orderId: 'ORD-1001',
|
|
208
|
+
amount: 99.9,
|
|
209
|
+
customerId: 'cus_123',
|
|
210
|
+
},
|
|
211
|
+
error: new Error('Payment gateway timeout'),
|
|
212
|
+
flowKey: 'pagarme-payments',
|
|
213
|
+
flowLabel: 'Integração Pagarme',
|
|
214
|
+
flowStep: 'create_charge',
|
|
215
|
+
metadata: {
|
|
216
|
+
endpoint: '/api/payments',
|
|
217
|
+
method: 'POST',
|
|
218
|
+
tenantId: 'acme',
|
|
219
|
+
},
|
|
220
|
+
});
|
|
237
221
|
```
|
|
238
222
|
|
|
239
|
-
|
|
223
|
+
### Exemplo com fluxo usando `KafkaAdapter`
|
|
240
224
|
|
|
241
225
|
```ts
|
|
242
|
-
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
O SDK adiciona internamente:
|
|
246
|
-
|
|
247
|
-
- `/dlq-injestion`
|
|
248
|
-
- `/dlq-injestion/runtime-state`
|
|
249
|
-
|
|
250
|
-
## Matriz de comportamento
|
|
226
|
+
import { ReboundClient, KafkaAdapter } from '@rebound-dlq/node';
|
|
251
227
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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**.
|
|
228
|
+
const client = new ReboundClient({
|
|
229
|
+
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
230
|
+
});
|
|
269
231
|
|
|
270
|
-
|
|
232
|
+
const kafkaDlq = new KafkaAdapter(client);
|
|
271
233
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
-
|
|
234
|
+
await kafkaDlq.sendToDLQ({
|
|
235
|
+
payload: {
|
|
236
|
+
topic: 'payments.events',
|
|
237
|
+
partition: 2,
|
|
238
|
+
key: 'order-1001',
|
|
239
|
+
message: { orderId: 'ORD-1001', status: 'failed' },
|
|
240
|
+
},
|
|
241
|
+
error: new Error('Kafka publish timeout'),
|
|
242
|
+
flowKey: 'pagarme-payments',
|
|
243
|
+
flowLabel: 'Integração Pagarme',
|
|
244
|
+
flowStep: 'payment_webhook',
|
|
245
|
+
metadata: {
|
|
246
|
+
topic: 'payments.events',
|
|
247
|
+
consumerGroup: 'payments-worker',
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
```
|
|
277
251
|
|
|
278
|
-
|
|
252
|
+
### Resumo prático de uso
|
|
279
253
|
|
|
280
|
-
|
|
254
|
+
- Use `DlqWrapper` quando quiser a integração mais simples possível.
|
|
255
|
+
- Use `ReboundClient` + adapters quando quiser separar melhor `payload` e `metadata`.
|
|
256
|
+
- Para agrupamento por fluxo, envie `flowKey`, `flowLabel` e `flowStep` como campos opcionais separados do payload.
|
|
257
|
+
- `metadata` continua existindo para contexto técnico adicional, sem misturar regra de agrupamento com o payload do usuário.
|
|
281
258
|
|
|
282
|
-
|
|
259
|
+
---
|
|
283
260
|
|
|
284
|
-
|
|
261
|
+
## Comportamento automático
|
|
285
262
|
|
|
286
|
-
|
|
263
|
+
O SDK gerencia entrega, retry e limites de plano de forma totalmente automática, sem configuração adicional.
|
|
287
264
|
|
|
288
|
-
|
|
265
|
+
| Situação | O que o SDK faz |
|
|
266
|
+
|---|---|
|
|
267
|
+
| Sucesso na entrega | Remove o evento da fila |
|
|
268
|
+
| Falha de rede / erro transiente | Retry com backoff exponencial |
|
|
269
|
+
| Conta no limite (sem cobrança extra) | Descarta eventos e reconsulta o estado periodicamente |
|
|
270
|
+
| Conta no limite (com cobrança extra ativa) | Mantém eventos em buffer por até 15 min e retenta quando liberado |
|
|
271
|
+
| Conta desbloqueada / upgrade | Retoma o envio automaticamente, sem reiniciar o processo |
|
|
289
272
|
|
|
290
|
-
|
|
273
|
+
> A política de limite e cobrança extra é definida na sua conta na plataforma, não no SDK.
|
|
291
274
|
|
|
292
|
-
|
|
275
|
+
---
|
|
293
276
|
|
|
294
|
-
|
|
277
|
+
## Observações operacionais
|
|
295
278
|
|
|
296
|
-
|
|
279
|
+
- A fila é **in-memory**: eventos não sobrevivem a restart do processo.
|
|
280
|
+
- Para workloads efêmeros, prefira processos com **shutdown gracioso**.
|
|
281
|
+
- O SDK emite logs no console ao entrar em modo de retry, buffer ou descarte.
|
|
297
282
|
|
|
298
|
-
|
|
283
|
+
---
|
|
299
284
|
|
|
300
285
|
## Suporte
|
|
301
286
|
|
|
302
|
-
|
|
287
|
+
Dúvidas de integração ou comportamento? Abra um chamado pela plataforma oficial em [rebound-dlq.com](https://rebound-dlq.com).
|
|
303
288
|
|
|
304
289
|
---
|
|
290
|
+
|
|
305
291
|
Feito por **Codify Labs / Rebound DLQ**.
|
|
@@ -6,13 +6,16 @@ class BaseAdapter {
|
|
|
6
6
|
this.client = client;
|
|
7
7
|
}
|
|
8
8
|
async sendToDLQ(options) {
|
|
9
|
-
const { payload, error, source = 'unknown', metadata = {} } = options;
|
|
9
|
+
const { payload, error, source = 'unknown', metadata = {}, flowKey, flowLabel, flowStep } = options;
|
|
10
10
|
const dlqPayload = {
|
|
11
11
|
payload,
|
|
12
12
|
source,
|
|
13
13
|
error: this.normalizeError(error),
|
|
14
14
|
metadata,
|
|
15
|
-
timestamp: Date.now()
|
|
15
|
+
timestamp: Date.now(),
|
|
16
|
+
flowKey,
|
|
17
|
+
flowLabel,
|
|
18
|
+
flowStep,
|
|
16
19
|
};
|
|
17
20
|
return this.client.send(dlqPayload);
|
|
18
21
|
}
|
package/dist/core/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DLQPayload, ReboundConfig } from
|
|
1
|
+
import { DLQPayload, ReboundConfig } from '../models/payload';
|
|
2
2
|
export declare class ReboundClient {
|
|
3
3
|
private config;
|
|
4
4
|
private readonly baseUrl;
|
|
@@ -7,5 +7,7 @@ export declare class ReboundClient {
|
|
|
7
7
|
private buildIngestionPayload;
|
|
8
8
|
private normalizeError;
|
|
9
9
|
private generateFingerprint;
|
|
10
|
+
private normalizeFlowContext;
|
|
11
|
+
private normalizeOptionalText;
|
|
10
12
|
private encrypt;
|
|
11
13
|
}
|
package/dist/core/client.js
CHANGED
|
@@ -28,10 +28,12 @@ class ReboundClient {
|
|
|
28
28
|
metadata: payload.metadata ?? {},
|
|
29
29
|
timestamp: payload.timestamp || Date.now(),
|
|
30
30
|
};
|
|
31
|
+
const flowContext = this.normalizeFlowContext(payload);
|
|
31
32
|
return {
|
|
32
33
|
fingerprint: this.generateFingerprint(normalizedError, payload.source),
|
|
33
34
|
error: normalizedError,
|
|
34
35
|
payload: this.encrypt(envelope),
|
|
36
|
+
...flowContext,
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
normalizeError(error) {
|
|
@@ -50,6 +52,24 @@ class ReboundClient {
|
|
|
50
52
|
const seed = `${error?.name || 'UnknownError'}${error?.message || ''}${stack}${source}`;
|
|
51
53
|
return crypto_1.default.createHash('sha256').update(seed).digest('hex');
|
|
52
54
|
}
|
|
55
|
+
normalizeFlowContext(payload) {
|
|
56
|
+
const flowKey = this.normalizeOptionalText(payload.flowKey);
|
|
57
|
+
if (!flowKey) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
flowKey,
|
|
62
|
+
flowLabel: this.normalizeOptionalText(payload.flowLabel),
|
|
63
|
+
flowStep: this.normalizeOptionalText(payload.flowStep),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
normalizeOptionalText(value) {
|
|
67
|
+
if (typeof value !== 'string') {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
72
|
+
}
|
|
53
73
|
encrypt(payload) {
|
|
54
74
|
const algorithm = 'aes-256-cbc';
|
|
55
75
|
const key = crypto_1.default
|
package/dist/core/constants.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const DEFAULT_REBOUND_API_ENDPOINT = "
|
|
1
|
+
export declare const DEFAULT_REBOUND_API_ENDPOINT = "https://api.rebound-dlq.com/api/v1";
|
package/dist/core/constants.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_REBOUND_API_ENDPOINT = void 0;
|
|
4
|
-
exports.DEFAULT_REBOUND_API_ENDPOINT = '
|
|
4
|
+
exports.DEFAULT_REBOUND_API_ENDPOINT = 'https://api.rebound-dlq.com/api/v1';
|
package/dist/core/wrapper.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DlqFlowContext } from '../models/payload';
|
|
1
2
|
export interface DlqServiceConfig {
|
|
2
3
|
projectSecret: string;
|
|
3
4
|
endpoint?: string;
|
|
@@ -7,9 +8,11 @@ export declare class DlqWrapper {
|
|
|
7
8
|
private projectSecret;
|
|
8
9
|
private readonly endpoint;
|
|
9
10
|
constructor(config: DlqServiceConfig);
|
|
10
|
-
execute<T>(fn: () => Promise<T>, payload: any): Promise<T>;
|
|
11
|
+
execute<T>(fn: () => Promise<T>, payload: any, flow?: DlqFlowContext): Promise<T>;
|
|
11
12
|
private reportError;
|
|
12
13
|
private generateFingerprint;
|
|
14
|
+
private normalizeFlowContext;
|
|
15
|
+
private normalizeOptionalText;
|
|
13
16
|
private encrypt;
|
|
14
17
|
private sendToApi;
|
|
15
18
|
}
|
package/dist/core/wrapper.js
CHANGED
|
@@ -14,22 +14,23 @@ class DlqWrapper {
|
|
|
14
14
|
const configuredEndpoint = config.endpoint || constants_1.DEFAULT_REBOUND_API_ENDPOINT;
|
|
15
15
|
this.endpoint = (0, endpoint_1.resolveDlqIngestionEndpoint)(configuredEndpoint);
|
|
16
16
|
}
|
|
17
|
-
async execute(fn, payload) {
|
|
17
|
+
async execute(fn, payload, flow) {
|
|
18
18
|
try {
|
|
19
19
|
return await fn();
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
22
|
// MemoryQueue handles retries in the background, making this instant
|
|
23
|
-
this.reportError(error, payload).catch(e => {
|
|
23
|
+
this.reportError(error, payload, flow).catch(e => {
|
|
24
24
|
// Silently fail if reportError fails, so we don't block the client logic
|
|
25
25
|
console.error("[Rebound SDK] Error building payload to DLQ", e);
|
|
26
26
|
});
|
|
27
27
|
throw error;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
async reportError(error, payload) {
|
|
30
|
+
async reportError(error, payload, flow) {
|
|
31
31
|
const fingerprint = this.generateFingerprint(error);
|
|
32
32
|
const encryptedPayload = this.encrypt(payload);
|
|
33
|
+
const normalizedFlow = this.normalizeFlowContext(flow);
|
|
33
34
|
const data = {
|
|
34
35
|
fingerprint,
|
|
35
36
|
error: {
|
|
@@ -37,7 +38,8 @@ class DlqWrapper {
|
|
|
37
38
|
message: error.message,
|
|
38
39
|
stack: error.stack
|
|
39
40
|
},
|
|
40
|
-
payload: encryptedPayload
|
|
41
|
+
payload: encryptedPayload,
|
|
42
|
+
...normalizedFlow,
|
|
41
43
|
};
|
|
42
44
|
return this.sendToApi(data);
|
|
43
45
|
}
|
|
@@ -47,6 +49,24 @@ class DlqWrapper {
|
|
|
47
49
|
.update(`${error.name}${stack}`)
|
|
48
50
|
.digest('hex');
|
|
49
51
|
}
|
|
52
|
+
normalizeFlowContext(flow) {
|
|
53
|
+
const flowKey = this.normalizeOptionalText(flow?.flowKey);
|
|
54
|
+
if (!flowKey) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
flowKey,
|
|
59
|
+
flowLabel: this.normalizeOptionalText(flow?.flowLabel),
|
|
60
|
+
flowStep: this.normalizeOptionalText(flow?.flowStep),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
normalizeOptionalText(value) {
|
|
64
|
+
if (typeof value !== 'string') {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const trimmed = value.trim();
|
|
68
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
69
|
+
}
|
|
50
70
|
encrypt(payload) {
|
|
51
71
|
const algorithm = 'aes-256-cbc';
|
|
52
72
|
// Use projectSecret to derive a 32-byte key.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export type { DlqServiceConfig } from './core/wrapper';
|
|
|
4
4
|
export { HTTPAdapter } from './adapters/http.adapter';
|
|
5
5
|
export { KafkaAdapter } from './adapters/kafka.adapter';
|
|
6
6
|
export { ReboundError, ReboundAPIError } from './core/errors';
|
|
7
|
-
export type { DLQPayload, ReboundConfig, SendToDLQOptions } from './models/payload';
|
|
7
|
+
export type { DLQPayload, DlqFlowContext, ReboundConfig, SendToDLQOptions } from './models/payload';
|
package/dist/models/payload.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface DlqFlowContext {
|
|
2
|
+
flowKey?: string;
|
|
3
|
+
flowLabel?: string;
|
|
4
|
+
flowStep?: string;
|
|
5
|
+
}
|
|
1
6
|
export interface DLQPayload {
|
|
2
7
|
payload: any;
|
|
3
8
|
source: string;
|
|
@@ -9,13 +14,16 @@ export interface DLQPayload {
|
|
|
9
14
|
};
|
|
10
15
|
metadata?: Record<string, any>;
|
|
11
16
|
timestamp?: number;
|
|
17
|
+
flowKey?: string;
|
|
18
|
+
flowLabel?: string;
|
|
19
|
+
flowStep?: string;
|
|
12
20
|
}
|
|
13
21
|
export interface ReboundConfig {
|
|
14
22
|
projectSecret: string;
|
|
15
23
|
endpoint?: string;
|
|
16
24
|
timeout?: number;
|
|
17
25
|
}
|
|
18
|
-
export interface SendToDLQOptions {
|
|
26
|
+
export interface SendToDLQOptions extends DlqFlowContext {
|
|
19
27
|
payload: any;
|
|
20
28
|
error?: Error | string;
|
|
21
29
|
source?: string;
|