cloudbroker-sdk 0.3.0 → 0.4.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
@@ -152,26 +152,28 @@ Em vez de passar `cbLabel` em cada `.send()`, use o middleware: todas as chamada
152
152
  de nuvem feitas durante a requisição herdam um label de negócio automaticamente
153
153
  (via `AsyncLocalStorage`). Ótimo pra agrupar custo por **módulo** ou **rota**.
154
154
 
155
+ Cada campo (`label`, `product`, `team`) pode ser fixo (string) ou uma função
156
+ `(req) => string`. Isso permite a hierarquia **Grupo (team) → App (product) →
157
+ Trecho (label) → Serviço** na visão de custo.
158
+
155
159
  ```js
156
160
  import express from "express";
157
161
  import { CloudBrokerCloud } from "cloudbroker-sdk";
158
162
 
159
163
  const app = express();
160
164
 
161
- // label fixo, "MÉTODO /caminho" (default), ou uma função req → label:
162
165
  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
- },
166
+ team: (req) => (req.path.startsWith("/api/admin") ? "Plataforma" : "Produto"), // Grupo
167
+ product: (req) => req.path.split("/")[2] || "core", // App
168
+ label: (req) => `${req.method} ${req.path}`, // Trecho
168
169
  }));
169
170
 
170
171
  // daqui pra frente, qualquer s3.send(...)/ddb.send(...) dentro da request
171
- // é atribuído ao módulo retornado — sem cbLabel manual.
172
+ // é atribuído ao grupo/app/trecho — sem cbLabel manual.
172
173
  ```
173
174
 
174
- Prioridade do label: `cbLabel` explícito no `.send()` > contexto do middleware > `null`.
175
+ Prioridade de cada campo: explícito no `.send()` (`cbLabel`/`cbProduct`/`cbTeam`) >
176
+ contexto do middleware > valor estático do `wrapAWS` > `null`.
175
177
 
176
178
  ## Garantias
177
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudbroker-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.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.3.0",
40
+ sdkVersion: "0.4.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.3.0",
96
+ sdkVersion: "0.4.0",
97
97
  sentAt: new Date().toISOString(),
98
98
  };
99
99
 
@@ -24,7 +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
+ import { cbStore } from "./context.js";
28
28
 
29
29
  export class CloudBrokerCloud {
30
30
  /**
@@ -54,13 +54,16 @@ export class CloudBrokerCloud {
54
54
  client.send = async (command, sendOptions) => {
55
55
  const start = Date.now();
56
56
  const operation = (command?.constructor?.name || "").replace(/Command$/, "");
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
+ // Prioridade: explícito no .send() > contexto da requisição (expressMiddleware)
58
+ // > valor estático do wrapAWS. Permite Grupo/App/Trecho por requisição.
59
+ const ctx = cbStore.getStore() || {};
60
+ const cbLabel = sendOptions?.cbLabel || ctx.label || null;
61
+ const evProduct = sendOptions?.cbProduct || ctx.product || product || null;
62
+ const evTeam = sendOptions?.cbTeam || ctx.team || team || null;
60
63
  const bytesIn = CloudBrokerCloud.#inputBytes(command?.input);
61
64
 
62
- // Passa adiante apenas as options nativas — nunca vaza cbLabel pro AWS SDK.
63
- const { cbLabel: _omit, ...awsOptions } = sendOptions || {};
65
+ // Passa adiante apenas as options nativas — nunca vaza campos cb* pro AWS SDK.
66
+ const { cbLabel: _l, cbProduct: _p, cbTeam: _t, ...awsOptions } = sendOptions || {};
64
67
  const result = await originalSend(command, Object.keys(awsOptions).length ? awsOptions : undefined);
65
68
 
66
69
  try {
@@ -73,8 +76,8 @@ export class CloudBrokerCloud {
73
76
  provider: "aws",
74
77
  service,
75
78
  operation,
76
- product: product || null,
77
- team: team || null,
79
+ product: evProduct,
80
+ team: evTeam,
78
81
  label: cbLabel,
79
82
  requests: 1,
80
83
  bytesIn,
@@ -95,26 +98,29 @@ export class CloudBrokerCloud {
95
98
  }
96
99
 
97
100
  /**
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
+ * Middleware Express que atribui contexto de negócio a todas as chamadas de
102
+ * nuvem feitas durante a requisição (via AsyncLocalStorage). Cada campo pode ser
103
+ * fixo (string) ou uma função (req) => string — ex: mapear rota → módulo/app/grupo.
104
+ * Sem precisar passar cbLabel/cbProduct/cbTeam manual em cada send.
101
105
  *
102
106
  * @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.
107
+ * @param {string|((req)=>string)} [options.label] — trecho (default: "MÉTODO /caminho")
108
+ * @param {string|((req)=>string)} [options.product] — App (sobrescreve o product do wrapAWS)
109
+ * @param {string|((req)=>string)} [options.team] — Grupo (sobrescreve o team do wrapAWS)
106
110
  * @returns {(req,res,next)=>void}
107
111
  */
108
112
  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
+ const mk = (opt, fallback) =>
114
+ typeof opt === "function" ? opt :
115
+ typeof opt === "string" ? () => opt :
116
+ fallback;
117
+ const resolveLabel = mk(options.label, (req) => `${req.method || ""} ${req.baseUrl || ""}${req.route?.path || req.path || ""}`.trim());
118
+ const resolveProduct = mk(options.product, () => undefined);
119
+ const resolveTeam = mk(options.team, () => undefined);
113
120
 
114
121
  return (req, res, next) => {
115
- let label = null;
116
- try { label = resolve(req); } catch { label = null; }
117
- cbStore.run({ label }, () => next());
122
+ const safe = (fn) => { try { return fn(req); } catch { return undefined; } };
123
+ cbStore.run({ label: safe(resolveLabel), product: safe(resolveProduct), team: safe(resolveTeam) }, () => next());
118
124
  };
119
125
  }
120
126
 
package/src/test.js CHANGED
@@ -154,18 +154,24 @@ 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
156
  // 3) expressMiddleware — label do contexto (ALS) entra sem cbLabel explícito
157
- const mw = CloudBrokerCloud.expressMiddleware({ label: (req) => `modulo:${req.path}` });
157
+ const mw = CloudBrokerCloud.expressMiddleware({
158
+ label: (req) => `modulo:${req.path}`,
159
+ product: (req) => (req.path.startsWith("/api/billing") ? "Billing" : "Outros"),
160
+ team: "Plataforma",
161
+ });
158
162
  const fakeReq = { method: "GET", path: "/api/billing" };
159
163
  await new Promise((resolve) => {
160
164
  mw(fakeReq, {}, async () => {
161
- await s3.send(new PutObjectCommand({ Bucket: "b", Key: "k", Body: "x" })); // sem cbLabel — herda do contexto
165
+ await s3.send(new PutObjectCommand({ Bucket: "b", Key: "k", Body: "x" })); // sem cb* — herda do contexto
162
166
  resolve();
163
167
  });
164
168
  });
165
169
  await new Promise((r) => setTimeout(r, 50));
166
170
  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");
171
+ assert.strictEqual(ctxEvent.label, "modulo:/api/billing", `label do contexto esperado, veio ${ctxEvent.label}`);
172
+ assert.strictEqual(ctxEvent.product, "Billing", `product do contexto esperado, veio ${ctxEvent.product}`);
173
+ assert.strictEqual(ctxEvent.team, "Plataforma", `team do contexto esperado, veio ${ctxEvent.team}`);
174
+ console.log("✅ expressMiddleware: label/product/team do contexto aplicados sem cb* explícito");
169
175
 
170
176
  globalThis.fetch = realFetch;
171
177
  console.log("\n🎉 Todos os testes passaram.");