cloudbroker-sdk 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -146,6 +146,33 @@ Cada chamada gera um evento `POST /api/cloud/events` com `service`, `operation`,
146
146
  `product`, `team`, `label`, `requests`, `bytesIn`/`bytesOut`, `costUSD` estimado e
147
147
  `latencyMs`. O `cbLabel` nunca é repassado ao AWS SDK.
148
148
 
149
+ ### Atribuição automática por requisição (Express)
150
+
151
+ Em vez de passar `cbLabel` em cada `.send()`, use o middleware: todas as chamadas
152
+ de nuvem feitas durante a requisição herdam um label de negócio automaticamente
153
+ (via `AsyncLocalStorage`). Ótimo pra agrupar custo por **módulo** ou **rota**.
154
+
155
+ ```js
156
+ import express from "express";
157
+ import { CloudBrokerCloud } from "cloudbroker-sdk";
158
+
159
+ const app = express();
160
+
161
+ // label fixo, "MÉTODO /caminho" (default), ou uma função req → label:
162
+ app.use(CloudBrokerCloud.expressMiddleware({
163
+ label: (req) => {
164
+ if (req.path.startsWith("/api/auth")) return "Autenticação";
165
+ if (req.path.startsWith("/api/billing")) return "Billing";
166
+ return "Outros";
167
+ },
168
+ }));
169
+
170
+ // daqui pra frente, qualquer s3.send(...)/ddb.send(...) dentro da request
171
+ // é atribuído ao módulo retornado — sem cbLabel manual.
172
+ ```
173
+
174
+ Prioridade do label: `cbLabel` explícito no `.send()` > contexto do middleware > `null`.
175
+
149
176
  ## Garantias
150
177
 
151
178
  - **Fire-and-forget**: se o CloudBroker estiver fora, sua aplicação não é afetada
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudbroker-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CloudBroker SDK — Token Intelligence & Operational Financial Intelligence",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/client.js CHANGED
@@ -37,7 +37,7 @@ export class CloudBrokerClient {
37
37
  try {
38
38
  const payload = {
39
39
  ...event,
40
- sdkVersion: "0.2.0",
40
+ sdkVersion: "0.3.0",
41
41
  sentAt: new Date().toISOString(),
42
42
  };
43
43
  if (this.#cloudQueue.length < MAX_QUEUE_SIZE) {
@@ -93,7 +93,7 @@ export class CloudBrokerClient {
93
93
  try {
94
94
  const payload = {
95
95
  ...event,
96
- sdkVersion: "0.2.0",
96
+ sdkVersion: "0.3.0",
97
97
  sentAt: new Date().toISOString(),
98
98
  };
99
99
 
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { CloudBrokerClient } from "./client.js";
26
26
  import { estimateCloudCost } from "./pricing-cloud.js";
27
+ import { cbStore, currentLabel } from "./context.js";
27
28
 
28
29
  export class CloudBrokerCloud {
29
30
  /**
@@ -53,7 +54,9 @@ export class CloudBrokerCloud {
53
54
  client.send = async (command, sendOptions) => {
54
55
  const start = Date.now();
55
56
  const operation = (command?.constructor?.name || "").replace(/Command$/, "");
56
- const cbLabel = sendOptions?.cbLabel || null;
57
+ // Label: explícito no .send() tem prioridade; senão usa o do contexto da
58
+ // requisição (expressMiddleware), senão null.
59
+ const cbLabel = sendOptions?.cbLabel || currentLabel() || null;
57
60
  const bytesIn = CloudBrokerCloud.#inputBytes(command?.input);
58
61
 
59
62
  // Passa adiante apenas as options nativas — nunca vaza cbLabel pro AWS SDK.
@@ -91,6 +94,30 @@ export class CloudBrokerCloud {
91
94
  return client;
92
95
  }
93
96
 
97
+ /**
98
+ * Middleware Express que atribui um label de negócio a todas as chamadas de
99
+ * nuvem feitas durante a requisição (via AsyncLocalStorage). Assim o custo por
100
+ * código agrupa por módulo/rota sem precisar de cbLabel manual em cada send.
101
+ *
102
+ * @param {object} [options]
103
+ * @param {string|((req)=>string)} [options.label] — label fixo, ou função que
104
+ * recebe o req e devolve o label (ex: mapear rota → módulo de negócio).
105
+ * Default: "MÉTODO /caminho" da requisição.
106
+ * @returns {(req,res,next)=>void}
107
+ */
108
+ static expressMiddleware(options = {}) {
109
+ const resolve =
110
+ typeof options.label === "function" ? options.label :
111
+ typeof options.label === "string" ? () => options.label :
112
+ (req) => `${req.method || ""} ${req.baseUrl || ""}${req.route?.path || req.path || ""}`.trim();
113
+
114
+ return (req, res, next) => {
115
+ let label = null;
116
+ try { label = resolve(req); } catch { label = null; }
117
+ cbStore.run({ label }, () => next());
118
+ };
119
+ }
120
+
94
121
  /** Detecta o serviço AWS pelo nome do construtor do client. */
95
122
  static #detectService(client) {
96
123
  const name = client?.constructor?.name || "";
package/src/context.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Contexto de requisição via AsyncLocalStorage.
3
+ * Permite atribuir um label de negócio (módulo / rota) a todas as chamadas de
4
+ * nuvem disparadas dentro de uma requisição, sem precisar passar cbLabel manual
5
+ * em cada `.send()`. Server-side apenas (Node).
6
+ */
7
+ import { AsyncLocalStorage } from "node:async_hooks";
8
+
9
+ export const cbStore = new AsyncLocalStorage();
10
+
11
+ /** Label de negócio do contexto atual, se houver. */
12
+ export function currentLabel() {
13
+ return cbStore.getStore()?.label || null;
14
+ }
package/src/test.js CHANGED
@@ -153,5 +153,19 @@ assert.strictEqual(ev.billingUnit, "s3-put");
153
153
  assert.ok(ev.costUSD > 0, "deve ter custo estimado");
154
154
  console.log("✅ wrapAWS: evento capturado, cbLabel não vazou, resultado preservado");
155
155
 
156
+ // 3) expressMiddleware — label do contexto (ALS) entra sem cbLabel explícito
157
+ const mw = CloudBrokerCloud.expressMiddleware({ label: (req) => `modulo:${req.path}` });
158
+ const fakeReq = { method: "GET", path: "/api/billing" };
159
+ await new Promise((resolve) => {
160
+ mw(fakeReq, {}, async () => {
161
+ await s3.send(new PutObjectCommand({ Bucket: "b", Key: "k", Body: "x" })); // sem cbLabel — herda do contexto
162
+ resolve();
163
+ });
164
+ });
165
+ await new Promise((r) => setTimeout(r, 50));
166
+ const ctxEvent = captured[captured.length - 1].events[0];
167
+ assert.strictEqual(ctxEvent.label, "modulo:/api/billing", `label do contexto esperado, veio ${ctxEvent.label}`);
168
+ console.log("✅ expressMiddleware: label do contexto aplicado sem cbLabel explícito");
169
+
156
170
  globalThis.fetch = realFetch;
157
171
  console.log("\n🎉 Todos os testes passaram.");