@rebound-dlq/node 0.2.5 → 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 +130 -1
- 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
|
@@ -40,6 +40,40 @@ REBOUND_PROJECT_SECRET="seu_project_secret_aqui"
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
+
## Agrupamento por fluxo
|
|
44
|
+
|
|
45
|
+
Agora o SDK também pode ajudar a agrupar eventos DLQ por **fluxo de integração** na plataforma.
|
|
46
|
+
|
|
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.
|
|
48
|
+
|
|
49
|
+
Os campos usados pela plataforma são:
|
|
50
|
+
|
|
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`. |
|
|
56
|
+
|
|
57
|
+
### Regra de agrupamento
|
|
58
|
+
|
|
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.
|
|
62
|
+
|
|
63
|
+
### Exemplo recomendado de nomenclatura
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
{
|
|
67
|
+
flowKey: 'pagarme-payments',
|
|
68
|
+
flowLabel: 'Integração Pagarme',
|
|
69
|
+
flowStep: 'payment_webhook'
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> Dica: trate `flowKey` como identificador estável. Evite usar textos que mudam com frequência.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
43
77
|
## Interfaces disponíveis
|
|
44
78
|
|
|
45
79
|
### `DlqWrapper` — recomendado
|
|
@@ -95,6 +129,33 @@ try {
|
|
|
95
129
|
}
|
|
96
130
|
```
|
|
97
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
|
+
});
|
|
142
|
+
|
|
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
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> Se você não passar o terceiro parâmetro, o evento continua sendo enviado normalmente, sem vínculo com fluxo.
|
|
158
|
+
|
|
98
159
|
---
|
|
99
160
|
|
|
100
161
|
### `ReboundClient` + adapters — uso avançado
|
|
@@ -113,11 +174,14 @@ const httpDlq = new HTTPAdapter(client);
|
|
|
113
174
|
await httpDlq.sendToDLQ({
|
|
114
175
|
payload: { orderId: 'ORD-1001', amount: 99.9 },
|
|
115
176
|
error: new Error('Payment gateway timeout'),
|
|
177
|
+
flowKey: 'checkout-payments',
|
|
178
|
+
flowLabel: 'Checkout de pagamentos',
|
|
179
|
+
flowStep: 'gateway_authorization',
|
|
116
180
|
metadata: {
|
|
117
181
|
endpoint: '/api/payments',
|
|
118
182
|
method: 'POST',
|
|
119
183
|
tenantId: 'acme',
|
|
120
|
-
}
|
|
184
|
+
}
|
|
121
185
|
});
|
|
122
186
|
```
|
|
123
187
|
|
|
@@ -127,6 +191,71 @@ await httpDlq.sendToDLQ({
|
|
|
127
191
|
|---|---|---|
|
|
128
192
|
| `projectSecret` | ✅ Sim | Chave secreta do projeto, usada para autenticação e criptografia local. |
|
|
129
193
|
|
|
194
|
+
### Exemplo com fluxo usando `HTTPAdapter`
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { ReboundClient, HTTPAdapter } from '@rebound-dlq/node';
|
|
198
|
+
|
|
199
|
+
const client = new ReboundClient({
|
|
200
|
+
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const httpDlq = new HTTPAdapter(client);
|
|
204
|
+
|
|
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
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Exemplo com fluxo usando `KafkaAdapter`
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { ReboundClient, KafkaAdapter } from '@rebound-dlq/node';
|
|
227
|
+
|
|
228
|
+
const client = new ReboundClient({
|
|
229
|
+
projectSecret: process.env.REBOUND_PROJECT_SECRET!,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const kafkaDlq = new KafkaAdapter(client);
|
|
233
|
+
|
|
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
|
+
```
|
|
251
|
+
|
|
252
|
+
### Resumo prático de uso
|
|
253
|
+
|
|
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.
|
|
258
|
+
|
|
130
259
|
---
|
|
131
260
|
|
|
132
261
|
## Comportamento automático
|
|
@@ -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;
|