cloudbroker-sdk 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ # CloudBroker SDK Example — Checkout Service
2
+
3
+ Exemplo funcional de como instrumentar um serviço de checkout com CloudBroker SDK para rastrear custo por feature.
4
+
5
+ ## O Que Faz
6
+
7
+ Este app de exemplo mostra 5 features sendo rastreadas:
8
+
9
+ | Feature | Route | Operation | Label |
10
+ |---------|-------|-----------|-------|
11
+ | **Order Processing** | POST /orders | DynamoDB PutItem | `order-processing` |
12
+ | **Invoice Generation** | POST /orders | S3 PutObject | `invoice-generation` |
13
+ | **Order Retrieval** | GET /orders/:id | DynamoDB GetItem | `order-retrieval` |
14
+ | **Fraud Detection** | POST /verify-fraud | DynamoDB GetItem | `fraud-detection` |
15
+ | **Analytics Tracking** | POST /analytics | DynamoDB PutItem | `analytics-tracking` |
16
+
17
+ Cada feature rastreia seu custo AWS separadamente — depois você pode ver no dashboard CloudBroker quanto cada uma custou.
18
+
19
+ ## Setup (5 minutos)
20
+
21
+ ### 1. Instalar Dependências
22
+
23
+ ```bash
24
+ npm install
25
+ ```
26
+
27
+ ### 2. Configurar Credenciais
28
+
29
+ ```bash
30
+ cp .env.example .env
31
+ # Editar .env com suas credenciais AWS + CloudBroker API Key
32
+ ```
33
+
34
+ **Campos obrigatórios:**
35
+ - `AWS_ACCESS_KEY_ID` — sua chave AWS
36
+ - `AWS_SECRET_ACCESS_KEY` — seu secret AWS
37
+ - `CLOUDBROKER_API_KEY` — sua API key CloudBroker (obter em app.cloudbroker.app.br)
38
+
39
+ ### 3. Rodar
40
+
41
+ ```bash
42
+ npm start
43
+ # Saída: ✅ Checkout Example App running on http://localhost:3000
44
+ ```
45
+
46
+ ### 4. Testar
47
+
48
+ ```bash
49
+ # Criar pedido (rastreia 2 features)
50
+ curl -X POST http://localhost:3000/orders \
51
+ -H "Content-Type: application/json" \
52
+ -d '{
53
+ "customerId": "cust-123",
54
+ "items": [{"name": "Widget", "price": 99.99}],
55
+ "total": 99.99
56
+ }'
57
+
58
+ # Resposta:
59
+ # {
60
+ # "ok": true,
61
+ # "orderId": "order-1719600600000",
62
+ # "message": "Order created successfully!..."
63
+ # }
64
+ ```
65
+
66
+ ### 5. Ver Custos
67
+
68
+ 1. Acesse https://app.cloudbroker.app.br
69
+ 2. Vá a **Custo por Código**
70
+ 3. Você verá os labels: `order-processing`, `invoice-generation`, etc
71
+ 4. **Nota:** Demora ~5 minutos pro CloudBroker processar os dados
72
+
73
+ ## Exemplos de Uso
74
+
75
+ ### Criar Pedido
76
+
77
+ ```bash
78
+ curl -X POST http://localhost:3000/orders \
79
+ -H "Content-Type: application/json" \
80
+ -d '{
81
+ "customerId": "user-456",
82
+ "items": [{"name": "Widget A"}, {"name": "Widget B"}],
83
+ "total": 250.00
84
+ }'
85
+ ```
86
+
87
+ **O que acontece:**
88
+ 1. `order-processing`: DynamoDB registra o pedido
89
+ 2. `invoice-generation`: S3 armazena o JSON da invoice
90
+
91
+ ### Recuperar Pedido
92
+
93
+ ```bash
94
+ curl -X GET http://localhost:3000/orders/order-1719600600000
95
+ ```
96
+
97
+ **O que acontece:**
98
+ 1. `order-retrieval`: DynamoDB busca o pedido
99
+ 2. CloudBroker rastreia o custo dessa operação
100
+
101
+ ### Verificar Fraude
102
+
103
+ ```bash
104
+ curl -X POST http://localhost:3000/verify-fraud \
105
+ -H "Content-Type: application/json" \
106
+ -d '{
107
+ "orderId": "order-123",
108
+ "customerId": "user-456"
109
+ }'
110
+ ```
111
+
112
+ **O que acontece:**
113
+ 1. `fraud-detection`: DynamoDB consulta perfil do cliente
114
+ 2. Retorna score de fraude (0–1)
115
+
116
+ ### Registrar Evento de Analytics
117
+
118
+ ```bash
119
+ curl -X POST http://localhost:3000/analytics \
120
+ -H "Content-Type: application/json" \
121
+ -d '{
122
+ "userId": "user-789",
123
+ "eventType": "page_view",
124
+ "data": {"page": "/checkout"}
125
+ }'
126
+ ```
127
+
128
+ **O que acontece:**
129
+ 1. `analytics-tracking`: DynamoDB armazena o evento
130
+
131
+ ## Entendendo os Custos
132
+
133
+ Depois de ~5 minutos, no dashboard CloudBroker você verá algo assim:
134
+
135
+ ```
136
+ Feature: order-processing
137
+ Requests: 2
138
+ Total Cost: $0.000003
139
+ Cost per Request: $0.0000015
140
+
141
+ Feature: invoice-generation
142
+ Requests: 2
143
+ Total Cost: $0.000002
144
+ Cost per Request: $0.000001
145
+
146
+ Feature: order-retrieval
147
+ Requests: 1
148
+ Total Cost: $0.00000025
149
+ Cost per Request: $0.00000025
150
+
151
+ Feature: fraud-detection
152
+ Requests: 1
153
+ Total Cost: $0.00000025
154
+ Cost per Request: $0.00000025
155
+
156
+ Feature: analytics-tracking
157
+ Requests: 2
158
+ Total Cost: $0.000003
159
+ Cost per Request: $0.0000015
160
+ ```
161
+
162
+ ## Modo Desenvolvimento
163
+
164
+ Rodar com auto-reload:
165
+
166
+ ```bash
167
+ npm run dev
168
+ ```
169
+
170
+ ## Testes E2E
171
+
172
+ ```bash
173
+ npm test
174
+ ```
175
+
176
+ Runs `test/e2e.js` que faz requisições e valida respostas.
177
+
178
+ ## Troubleshooting
179
+
180
+ ### "Nenhum dado aparecendo no dashboard"
181
+
182
+ 1. ✅ API Key configurada?
183
+ ```bash
184
+ echo $CLOUDBROKER_API_KEY
185
+ ```
186
+
187
+ 2. ✅ AWS credenciais válidas? (tente fazer um `aws s3 ls`)
188
+
189
+ 3. ✅ Esperou 5+ minutos? (CloudBroker faz batch)
190
+
191
+ 4. ✅ Fez pelo menos 1 request pro app?
192
+ ```bash
193
+ curl http://localhost:3000/health
194
+ ```
195
+
196
+ ### "Erro 403 na DynamoDB"
197
+
198
+ - Tabelas `CheckoutOrders`, `CustomerProfiles`, `AnalyticsEvents` não existem na sua conta AWS
199
+ - CloudBroker **não cria tabelas automaticamente** — você precisa criar:
200
+ ```bash
201
+ aws dynamodb create-table \
202
+ --table-name CheckoutOrders \
203
+ --attribute-definitions AttributeName=orderId,AttributeType=S \
204
+ --key-schema AttributeName=orderId,KeyType=HASH \
205
+ --billing-mode PAY_PER_REQUEST
206
+ ```
207
+
208
+ ### "Erro ao conectar no S3"
209
+
210
+ - Bucket `checkout-invoices` não existe
211
+ - Crie:
212
+ ```bash
213
+ aws s3 mb s3://checkout-invoices-YOUR-UID --region us-east-2
214
+ ```
215
+
216
+ ## Próximos Passos
217
+
218
+ - Leia [docs/COST_PER_FEATURE.md](../docs/COST_PER_FEATURE.md) pro guia completo
219
+ - Copie os patterns aqui pro seu próprio backend
220
+ - Cada label representa uma feature — customize conforme suas necessidades
221
+ - Monitore no dashboard CloudBroker qual feature custa mais
222
+
223
+ ## Suporte
224
+
225
+ Dúvidas? Abra issue em [github.com/rmoura151179/cloudbroker-sdk](https://github.com/rmoura151179/cloudbroker-sdk).
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "cloudbroker-sdk-example-checkout",
3
+ "version": "1.0.0",
4
+ "description": "Exemplo: Rastrear custo por feature com CloudBroker SDK",
5
+ "type": "module",
6
+ "main": "server.js",
7
+ "scripts": {
8
+ "start": "node server.js",
9
+ "dev": "node --watch server.js",
10
+ "test": "node test/e2e.js"
11
+ },
12
+ "keywords": ["cloudbroker", "finops", "cost-tracking"],
13
+ "author": "CloudBroker",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "express": "^4.18.2",
17
+ "@aws-sdk/client-s3": "^3.400.0",
18
+ "@aws-sdk/client-dynamodb": "^3.400.0",
19
+ "@aws-sdk/lib-dynamodb": "^3.400.0",
20
+ "cloudbroker-sdk": "^0.5.0"
21
+ },
22
+ "devDependencies": {
23
+ "nodemon": "^3.0.1"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ }
28
+ }
@@ -0,0 +1,209 @@
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
4
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
5
+ import { DynamoDBDocumentClient, PutCommand, GetCommand } from '@aws-sdk/lib-dynamodb';
6
+ import { CloudBrokerCloud } from 'cloudbroker-sdk';
7
+
8
+ const app = express();
9
+ app.use(express.json());
10
+
11
+ // ========== AWS Clients Setup (Wrapped) ==========
12
+
13
+ const s3 = CloudBrokerCloud.wrapAWS(new S3Client({ region: 'us-east-2' }), {
14
+ apiKey: process.env.CLOUDBROKER_API_KEY,
15
+ product: process.env.CLOUDBROKER_PRODUCT || 'checkout-example',
16
+ team: process.env.CLOUDBROKER_TEAM || 'platform',
17
+ });
18
+
19
+ const dynamodb = CloudBrokerCloud.wrapAWS(
20
+ DynamoDBDocumentClient.from(new DynamoDBClient({ region: 'us-east-2' })),
21
+ {
22
+ apiKey: process.env.CLOUDBROKER_API_KEY,
23
+ product: process.env.CLOUDBROKER_PRODUCT || 'checkout-example',
24
+ team: process.env.CLOUDBROKER_TEAM || 'platform',
25
+ }
26
+ );
27
+
28
+ // ========== Routes ==========
29
+
30
+ /**
31
+ * Health check (no tracking)
32
+ */
33
+ app.get('/health', (req, res) => {
34
+ res.json({ ok: true, status: 'healthy', timestamp: new Date().toISOString() });
35
+ });
36
+
37
+ /**
38
+ * POST /orders — Create order
39
+ * Tracks: order-processing (DynamoDB), invoice-generation (S3)
40
+ */
41
+ app.post('/orders', async (req, res) => {
42
+ try {
43
+ const order = {
44
+ orderId: `order-${Date.now()}`,
45
+ customerId: req.body.customerId,
46
+ items: req.body.items || [],
47
+ total: req.body.total || 0,
48
+ status: 'pending',
49
+ createdAt: new Date().toISOString(),
50
+ };
51
+
52
+ // Feature 1: Persistir pedido no DynamoDB
53
+ await dynamodb.send(
54
+ new PutCommand({
55
+ TableName: 'CheckoutOrders',
56
+ Item: order,
57
+ }),
58
+ {
59
+ cbLabel: 'order-processing', // CloudBroker rastreia como "order-processing"
60
+ }
61
+ );
62
+
63
+ // Feature 2: Gerar invoice no S3
64
+ const invoiceJson = JSON.stringify({
65
+ orderId: order.orderId,
66
+ customerId: order.customerId,
67
+ items: order.items,
68
+ total: order.total,
69
+ createdAt: order.createdAt,
70
+ });
71
+
72
+ await s3.send(
73
+ new PutObjectCommand({
74
+ Bucket: 'checkout-invoices',
75
+ Key: `invoices/${order.orderId}.json`,
76
+ Body: invoiceJson,
77
+ ContentType: 'application/json',
78
+ }),
79
+ {
80
+ cbLabel: 'invoice-generation', // CloudBroker rastreia como "invoice-generation"
81
+ }
82
+ );
83
+
84
+ res.json({
85
+ ok: true,
86
+ orderId: order.orderId,
87
+ message: 'Order created successfully! Check CloudBroker dashboard for cost breakdown.',
88
+ });
89
+ } catch (error) {
90
+ console.error('[POST /orders] Error:', error.message);
91
+ res.status(500).json({ ok: false, error: error.message });
92
+ }
93
+ });
94
+
95
+ /**
96
+ * GET /orders/:id — Retrieve order
97
+ * Tracks: order-retrieval
98
+ */
99
+ app.get('/orders/:id', async (req, res) => {
100
+ try {
101
+ const result = await dynamodb.send(
102
+ new GetCommand({
103
+ TableName: 'CheckoutOrders',
104
+ Key: { orderId: req.params.id },
105
+ }),
106
+ {
107
+ cbLabel: 'order-retrieval',
108
+ }
109
+ );
110
+
111
+ res.json({
112
+ ok: true,
113
+ order: result.Item || null,
114
+ });
115
+ } catch (error) {
116
+ console.error('[GET /orders/:id] Error:', error.message);
117
+ res.status(500).json({ ok: false, error: error.message });
118
+ }
119
+ });
120
+
121
+ /**
122
+ * POST /verify-fraud — Fraud detection check
123
+ * Tracks: fraud-detection
124
+ */
125
+ app.post('/verify-fraud', async (req, res) => {
126
+ try {
127
+ const { orderId, customerId } = req.body;
128
+
129
+ // Simular: ler perfil de cliente do DynamoDB pra análise
130
+ const result = await dynamodb.send(
131
+ new GetCommand({
132
+ TableName: 'CustomerProfiles',
133
+ Key: { customerId },
134
+ }),
135
+ {
136
+ cbLabel: 'fraud-detection',
137
+ }
138
+ );
139
+
140
+ // Mock fraud score
141
+ const fraudScore = Math.random() * 0.5; // 0–0.5 = low risk
142
+ const isBlocked = fraudScore > 0.4;
143
+
144
+ res.json({
145
+ ok: true,
146
+ orderId,
147
+ customerId,
148
+ fraud_score: parseFloat(fraudScore.toFixed(2)),
149
+ is_blocked: isBlocked,
150
+ message: isBlocked ? 'Order blocked due to fraud risk' : 'Order cleared',
151
+ });
152
+ } catch (error) {
153
+ console.error('[POST /verify-fraud] Error:', error.message);
154
+ res.status(500).json({ ok: false, error: error.message });
155
+ }
156
+ });
157
+
158
+ /**
159
+ * POST /analytics — Track user action
160
+ * Tracks: analytics-tracking
161
+ */
162
+ app.post('/analytics', async (req, res) => {
163
+ try {
164
+ const event = {
165
+ eventId: `event-${Date.now()}`,
166
+ userId: req.body.userId,
167
+ eventType: req.body.eventType,
168
+ data: req.body.data || {},
169
+ timestamp: new Date().toISOString(),
170
+ };
171
+
172
+ // Store event in DynamoDB
173
+ await dynamodb.send(
174
+ new PutCommand({
175
+ TableName: 'AnalyticsEvents',
176
+ Item: event,
177
+ }),
178
+ {
179
+ cbLabel: 'analytics-tracking',
180
+ }
181
+ );
182
+
183
+ res.json({
184
+ ok: true,
185
+ eventId: event.eventId,
186
+ message: 'Analytics event recorded',
187
+ });
188
+ } catch (error) {
189
+ console.error('[POST /analytics] Error:', error.message);
190
+ res.status(500).json({ ok: false, error: error.message });
191
+ }
192
+ });
193
+
194
+ // ========== Server Start ==========
195
+
196
+ const PORT = parseInt(process.env.PORT || '3000', 10);
197
+
198
+ app.listen(PORT, () => {
199
+ console.log(`\n✅ Checkout Example App running on http://localhost:${PORT}`);
200
+ console.log(`📊 Check costs at: https://app.cloudbroker.app.br → Custo por Código\n`);
201
+ console.log('Features being tracked:');
202
+ console.log(' • order-processing (DynamoDB: create order)');
203
+ console.log(' • invoice-generation (S3: store invoice)');
204
+ console.log(' • order-retrieval (DynamoDB: fetch order)');
205
+ console.log(' • fraud-detection (DynamoDB: check profile)');
206
+ console.log(' • analytics-tracking (DynamoDB: log events)\n');
207
+ });
208
+
209
+ export default app;
@@ -0,0 +1,97 @@
1
+ import assert from 'assert';
2
+ import fetch from 'node-fetch';
3
+
4
+ const BASE_URL = 'http://localhost:3000';
5
+
6
+ async function test(name, fn) {
7
+ try {
8
+ await fn();
9
+ console.log(`✅ ${name}`);
10
+ } catch (error) {
11
+ console.error(`❌ ${name}`);
12
+ console.error(` ${error.message}`);
13
+ process.exit(1);
14
+ }
15
+ }
16
+
17
+ async function runTests() {
18
+ console.log('🧪 Running E2E tests...\n');
19
+
20
+ // Test 1: Health check
21
+ await test('Health check', async () => {
22
+ const res = await fetch(`${BASE_URL}/health`);
23
+ assert(res.ok, 'Health check failed');
24
+ const data = await res.json();
25
+ assert(data.ok === true, 'Health check returned ok: false');
26
+ });
27
+
28
+ // Test 2: Create order
29
+ await test('Create order', async () => {
30
+ const res = await fetch(`${BASE_URL}/orders`, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({
34
+ customerId: 'test-cust-123',
35
+ items: [{ name: 'Test Widget', price: 99.99 }],
36
+ total: 99.99,
37
+ }),
38
+ });
39
+ assert(res.ok, `Create order failed: ${res.status}`);
40
+ const data = await res.json();
41
+ assert(data.ok === true, 'Create order returned ok: false');
42
+ assert(data.orderId, 'No orderId in response');
43
+ global.testOrderId = data.orderId; // Save for next test
44
+ });
45
+
46
+ // Test 3: Get order
47
+ await test('Get order', async () => {
48
+ assert(global.testOrderId, 'No orderId from previous test');
49
+ const res = await fetch(`${BASE_URL}/orders/${global.testOrderId}`);
50
+ assert(res.ok, `Get order failed: ${res.status}`);
51
+ const data = await res.json();
52
+ assert(data.ok === true, 'Get order returned ok: false');
53
+ // Order might not exist in DynamoDB (local/test setup), but the endpoint should work
54
+ });
55
+
56
+ // Test 4: Verify fraud
57
+ await test('Verify fraud', async () => {
58
+ const res = await fetch(`${BASE_URL}/verify-fraud`, {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({
62
+ orderId: 'order-test-123',
63
+ customerId: 'cust-test-456',
64
+ }),
65
+ });
66
+ assert(res.ok, `Verify fraud failed: ${res.status}`);
67
+ const data = await res.json();
68
+ assert(data.ok === true, 'Verify fraud returned ok: false');
69
+ assert(typeof data.fraud_score === 'number', 'No fraud_score in response');
70
+ assert(data.fraud_score >= 0 && data.fraud_score <= 1, 'fraud_score out of range');
71
+ });
72
+
73
+ // Test 5: Analytics tracking
74
+ await test('Analytics tracking', async () => {
75
+ const res = await fetch(`${BASE_URL}/analytics`, {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify({
79
+ userId: 'user-test-789',
80
+ eventType: 'page_view',
81
+ data: { page: '/checkout' },
82
+ }),
83
+ });
84
+ assert(res.ok, `Analytics tracking failed: ${res.status}`);
85
+ const data = await res.json();
86
+ assert(data.ok === true, 'Analytics tracking returned ok: false');
87
+ assert(data.eventId, 'No eventId in response');
88
+ });
89
+
90
+ console.log('\n🎉 All tests passed!');
91
+ console.log('📊 Check CloudBroker dashboard in ~5 minutes to see the cost breakdown.');
92
+ }
93
+
94
+ runTests().catch((error) => {
95
+ console.error('Fatal error:', error.message);
96
+ process.exit(1);
97
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudbroker-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "CloudBroker SDK — Token Intelligence & Operational Financial Intelligence",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -8,9 +8,19 @@
8
8
  ".": "./src/index.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "node src/test.js"
11
+ "test": "node src/test.js && node src/test-azure-real.js && node src/test-gemini-real.js"
12
12
  },
13
- "keywords": ["cloudbroker", "finops", "token-intelligence", "openai", "anthropic"],
13
+ "keywords": [
14
+ "cloudbroker",
15
+ "finops",
16
+ "token-intelligence",
17
+ "openai",
18
+ "anthropic"
19
+ ],
14
20
  "license": "MIT",
15
- "dependencies": {}
21
+ "devDependencies": {
22
+ "@azure/cosmos": "^4.10.0",
23
+ "@azure/storage-blob": "^12.33.0",
24
+ "@google/genai": "^2.15.0"
25
+ }
16
26
  }
package/src/client.js CHANGED
@@ -7,6 +7,17 @@ const DEFAULT_AI_ENDPOINT = "https://api.cloudbroker.app.br/api/ai/events";
7
7
  const DEFAULT_CLOUD_ENDPOINT = "https://api.cloudbroker.app.br/api/cloud/events";
8
8
  const DEFAULT_API_BASE = "https://api.cloudbroker.app.br/api";
9
9
  const RETRY_DELAY_MS = 30_000; // 30s entre retries
10
+
11
+ /**
12
+ * Retry que NÃO segura o event loop. Sem `.unref()`, um flush que falha deixa
13
+ * um timer de 30s pendurado que se reagenda — qualquer processo Node curto
14
+ * (Lambda, cron, CLI, suíte de teste) que use o SDK nunca encerra sozinho.
15
+ */
16
+ function scheduleRetry(fn) {
17
+ const t = setTimeout(fn, RETRY_DELAY_MS);
18
+ if (typeof t?.unref === "function") t.unref();
19
+ return t;
20
+ }
10
21
  const MAX_QUEUE_SIZE = 500; // máximo de eventos em memória
11
22
 
12
23
  export class CloudBrokerClient {
@@ -37,7 +48,7 @@ export class CloudBrokerClient {
37
48
  try {
38
49
  const payload = {
39
50
  ...event,
40
- sdkVersion: "0.5.0",
51
+ sdkVersion: "0.7.0",
41
52
  sentAt: new Date().toISOString(),
42
53
  };
43
54
  if (this.#cloudQueue.length < MAX_QUEUE_SIZE) {
@@ -69,7 +80,7 @@ export class CloudBrokerClient {
69
80
  if (!res.ok) {
70
81
  if (this.#debug) console.warn("[CloudBroker] cloud flush failed:", res.status);
71
82
  this.#cloudQueue.unshift(...batch);
72
- setTimeout(() => { this.#cloudFlushing = false; this.#flushCloud(); }, RETRY_DELAY_MS);
83
+ scheduleRetry(() => { this.#cloudFlushing = false; this.#flushCloud(); });
73
84
  return;
74
85
  }
75
86
 
@@ -77,7 +88,7 @@ export class CloudBrokerClient {
77
88
  } catch (err) {
78
89
  this.#cloudQueue.unshift(...batch);
79
90
  if (this.#debug) console.warn("[CloudBroker] cloud flush error:", err.message);
80
- setTimeout(() => { this.#cloudFlushing = false; this.#flushCloud(); }, RETRY_DELAY_MS);
91
+ scheduleRetry(() => { this.#cloudFlushing = false; this.#flushCloud(); });
81
92
  return;
82
93
  }
83
94
 
@@ -93,7 +104,7 @@ export class CloudBrokerClient {
93
104
  try {
94
105
  const payload = {
95
106
  ...event,
96
- sdkVersion: "0.5.0",
107
+ sdkVersion: "0.7.0",
97
108
  sentAt: new Date().toISOString(),
98
109
  };
99
110
 
@@ -184,7 +195,7 @@ export class CloudBrokerClient {
184
195
  // Recoloca na fila e agenda retry com backoff — nunca recursa na hora,
185
196
  // senão um erro persistente (ex: 401) martela o endpoint em loop apertado.
186
197
  this.#queue.unshift(...batch);
187
- setTimeout(() => { this.#flushing = false; this.#flush(); }, RETRY_DELAY_MS);
198
+ scheduleRetry(() => { this.#flushing = false; this.#flush(); });
188
199
  return;
189
200
  }
190
201
 
@@ -195,7 +206,7 @@ export class CloudBrokerClient {
195
206
  if (this.#debug) console.warn("[CloudBroker] flush error:", err.message);
196
207
 
197
208
  // Retry em 30s
198
- setTimeout(() => { this.#flushing = false; this.#flush(); }, RETRY_DELAY_MS);
209
+ scheduleRetry(() => { this.#flushing = false; this.#flush(); });
199
210
  return;
200
211
  }
201
212