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 +27 -0
- package/package.json +1 -1
- package/src/client.js +2 -2
- package/src/cloud-wrapper.js +28 -1
- package/src/context.js +14 -0
- package/src/test.js +14 -0
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
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.
|
|
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.
|
|
96
|
+
sdkVersion: "0.3.0",
|
|
97
97
|
sentAt: new Date().toISOString(),
|
|
98
98
|
};
|
|
99
99
|
|
package/src/cloud-wrapper.js
CHANGED
|
@@ -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
|
-
|
|
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.");
|