@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 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
  }
@@ -1,4 +1,4 @@
1
- import { DLQPayload, ReboundConfig } from "../models/payload";
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
  }
@@ -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
@@ -1 +1 @@
1
- export declare const DEFAULT_REBOUND_API_ENDPOINT = "http://localhost:3001/api/v1";
1
+ export declare const DEFAULT_REBOUND_API_ENDPOINT = "https://api.rebound-dlq.com/api/v1";
@@ -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 = 'http://localhost:3001/api/v1';
4
+ exports.DEFAULT_REBOUND_API_ENDPOINT = 'https://api.rebound-dlq.com/api/v1';
@@ -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
  }
@@ -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';
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebound-dlq/node",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": ["dist"],