@burn0/burn0 0.2.5 → 0.2.7
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 +9 -1
- package/dist/{chunk-H3A5NM5C.mjs → chunk-UI6QVWA4.mjs} +111 -38
- package/dist/cli/index.js +141 -39
- package/dist/index.js +110 -37
- package/dist/index.mjs +1 -1
- package/dist/register.js +110 -37
- package/dist/register.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,14 @@ LLMs, SaaS, infrastructure. See per-request costs in real time.<br><br>
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
+
|
|
33
|
+
### 🎬 See it in action
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
https://github.com/user-attachments/assets/56962fc8-b9cf-49b2-9481-bc10aca6fb56
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
32
40
|
## The Problem
|
|
33
41
|
|
|
34
42
|
You're running OpenAI, Anthropic, Stripe, Supabase, SendGrid, and a dozen other APIs. Your monthly bill is $2,847 and climbing 340% month-over-month.
|
|
@@ -86,7 +94,7 @@ echo 'BURN0_API_KEY=b0_sk_your_key_here' >> .env
|
|
|
86
94
|
# 3. Restart — costs now sync to burn0.dev
|
|
87
95
|
```
|
|
88
96
|
|
|
89
|
-
Now you get a **live event feed**, **cost breakdown by service**, **monthly projections**, and **full request history** — all at [burn0.dev/dashboard](https://burn0.dev/dashboard).
|
|
97
|
+
Now you get a **live event feed**, **cost breakdown by service**, **monthly projections**, and **full request history** — all at [burn0.dev/dashboard](https://burn0## The Problem.dev/dashboard).
|
|
90
98
|
|
|
91
99
|
> burn0 only syncs metadata (service, model, tokens, cost, latency) — never request/response bodies or your API keys.
|
|
92
100
|
|
|
@@ -397,32 +397,10 @@ function createRestorer(deps) {
|
|
|
397
397
|
// src/transport/dispatcher.ts
|
|
398
398
|
function createDispatcher(mode2, deps) {
|
|
399
399
|
return (event) => {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
break;
|
|
405
|
-
case "dev-cloud":
|
|
406
|
-
deps.logEvent?.(event);
|
|
407
|
-
deps.writeLedger?.(event);
|
|
408
|
-
deps.addToBatch?.(event);
|
|
409
|
-
break;
|
|
410
|
-
case "prod-cloud":
|
|
411
|
-
deps.logEvent?.(event);
|
|
412
|
-
deps.writeLedger?.(event);
|
|
413
|
-
deps.addToBatch?.(event);
|
|
414
|
-
break;
|
|
415
|
-
case "prod-local":
|
|
416
|
-
deps.logEvent?.(event);
|
|
417
|
-
break;
|
|
418
|
-
case "test-enabled":
|
|
419
|
-
deps.logEvent?.(event);
|
|
420
|
-
deps.writeLedger?.(event);
|
|
421
|
-
deps.addToBatch?.(event);
|
|
422
|
-
break;
|
|
423
|
-
case "test-disabled":
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
400
|
+
if (mode2 === "test-disabled") return;
|
|
401
|
+
deps.logEvent?.(event);
|
|
402
|
+
deps.writeLedger?.(event);
|
|
403
|
+
deps.addToBatch?.(event);
|
|
426
404
|
};
|
|
427
405
|
}
|
|
428
406
|
|
|
@@ -468,6 +446,7 @@ import fs from "fs";
|
|
|
468
446
|
import path from "path";
|
|
469
447
|
var BURN0_DIR = ".burn0";
|
|
470
448
|
var LEDGER_FILE = "costs.jsonl";
|
|
449
|
+
var SYNC_MARKER_FILE = "last-sync.txt";
|
|
471
450
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
472
451
|
var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
473
452
|
var LocalLedger = class {
|
|
@@ -491,6 +470,24 @@ var LocalLedger = class {
|
|
|
491
470
|
return [];
|
|
492
471
|
}
|
|
493
472
|
}
|
|
473
|
+
readUnsynced() {
|
|
474
|
+
const all = this.read();
|
|
475
|
+
const lastSync = this.getLastSyncTime();
|
|
476
|
+
if (!lastSync) return all;
|
|
477
|
+
return all.filter((e) => new Date(e.timestamp).getTime() > lastSync);
|
|
478
|
+
}
|
|
479
|
+
markSynced() {
|
|
480
|
+
this.ensureDir();
|
|
481
|
+
fs.writeFileSync(path.join(this.dirPath, SYNC_MARKER_FILE), (/* @__PURE__ */ new Date()).toISOString());
|
|
482
|
+
}
|
|
483
|
+
getLastSyncTime() {
|
|
484
|
+
try {
|
|
485
|
+
const ts = fs.readFileSync(path.join(this.dirPath, SYNC_MARKER_FILE), "utf-8").trim();
|
|
486
|
+
return new Date(ts).getTime();
|
|
487
|
+
} catch {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
494
491
|
ensureDir() {
|
|
495
492
|
if (!fs.existsSync(this.dirPath)) fs.mkdirSync(this.dirPath, { recursive: true });
|
|
496
493
|
}
|
|
@@ -535,10 +532,17 @@ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch)
|
|
|
535
532
|
},
|
|
536
533
|
body: JSON.stringify({ events, sdk_version: SDK_VERSION })
|
|
537
534
|
});
|
|
538
|
-
if (response.ok)
|
|
535
|
+
if (response.ok) {
|
|
536
|
+
if (isDebug()) console.log(`[burn0] Shipped ${events.length} events`);
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
if (isDebug()) {
|
|
540
|
+
const body = await response.text().catch(() => "");
|
|
541
|
+
console.warn(`[burn0] Shipping rejected: ${response.status} ${body}`);
|
|
542
|
+
}
|
|
539
543
|
} catch (err) {
|
|
540
544
|
if (isDebug()) {
|
|
541
|
-
console.warn("[burn0]
|
|
545
|
+
console.warn("[burn0] Shipping failed:", err.message);
|
|
542
546
|
}
|
|
543
547
|
}
|
|
544
548
|
if (attempt < maxAttempts - 1) {
|
|
@@ -796,31 +800,100 @@ try {
|
|
|
796
800
|
}
|
|
797
801
|
var ticker = createTicker({ todayCost, todayCalls, perServiceCosts });
|
|
798
802
|
var batch = null;
|
|
799
|
-
|
|
800
|
-
|
|
803
|
+
var lateInitDone = false;
|
|
804
|
+
var failedEvents = [];
|
|
805
|
+
function createBatch(key) {
|
|
806
|
+
return new BatchBuffer({
|
|
801
807
|
sizeThreshold: 50,
|
|
802
808
|
timeThresholdMs: 1e4,
|
|
803
809
|
maxSize: 500,
|
|
804
810
|
onFlush: (events) => {
|
|
805
|
-
|
|
811
|
+
const toShip = failedEvents.length > 0 ? [...failedEvents, ...events] : events;
|
|
812
|
+
failedEvents = [];
|
|
813
|
+
shipEvents(toShip, key, BURN0_API_URL, originalFetch2).then((ok) => {
|
|
814
|
+
if (!ok) {
|
|
815
|
+
failedEvents = toShip.slice(-500);
|
|
816
|
+
}
|
|
817
|
+
}).catch(() => {
|
|
818
|
+
failedEvents = toShip.slice(-500);
|
|
806
819
|
});
|
|
807
820
|
}
|
|
808
821
|
});
|
|
809
822
|
}
|
|
823
|
+
if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
|
|
824
|
+
batch = createBatch(apiKey);
|
|
825
|
+
}
|
|
826
|
+
var pendingEvents = [];
|
|
827
|
+
function lateInit(event) {
|
|
828
|
+
if (batch) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const lateKey = getApiKey();
|
|
832
|
+
if (!lateKey) {
|
|
833
|
+
if (event) pendingEvents.push(event);
|
|
834
|
+
if (!lateInitDone) {
|
|
835
|
+
lateInitDone = true;
|
|
836
|
+
setTimeout(() => {
|
|
837
|
+
lateInitDone = false;
|
|
838
|
+
if (pendingEvents.length > 0) {
|
|
839
|
+
const e = pendingEvents.shift();
|
|
840
|
+
lateInit(e);
|
|
841
|
+
} else {
|
|
842
|
+
lateInit();
|
|
843
|
+
}
|
|
844
|
+
}, 0);
|
|
845
|
+
}
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
lateInitDone = true;
|
|
849
|
+
apiKey = lateKey;
|
|
850
|
+
mode = detectMode({ isTTY: isTTY(), apiKey });
|
|
851
|
+
batch = createBatch(lateKey);
|
|
852
|
+
fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
|
|
853
|
+
});
|
|
854
|
+
for (const e of pendingEvents) {
|
|
855
|
+
batch.add(e);
|
|
856
|
+
}
|
|
857
|
+
pendingEvents.length = 0;
|
|
858
|
+
syncLedger(lateKey);
|
|
859
|
+
}
|
|
860
|
+
function syncLedger(key) {
|
|
861
|
+
try {
|
|
862
|
+
const unsynced = ledger.readUnsynced();
|
|
863
|
+
if (unsynced.length === 0) {
|
|
864
|
+
ledger.markSynced();
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const promises = [];
|
|
868
|
+
for (let i = 0; i < unsynced.length; i += 500) {
|
|
869
|
+
const chunk = unsynced.slice(i, i + 500);
|
|
870
|
+
promises.push(shipEvents(chunk, key, BURN0_API_URL, originalFetch2));
|
|
871
|
+
}
|
|
872
|
+
Promise.all(promises).then((results) => {
|
|
873
|
+
if (results.every(Boolean)) {
|
|
874
|
+
ledger.markSynced();
|
|
875
|
+
}
|
|
876
|
+
}).catch(() => {
|
|
877
|
+
});
|
|
878
|
+
} catch {
|
|
879
|
+
}
|
|
880
|
+
}
|
|
810
881
|
var shouldWriteLedger = mode !== "test-disabled" && mode !== "prod-local";
|
|
811
882
|
var dispatch = createDispatcher(mode, {
|
|
812
883
|
logEvent: (e) => ticker.tick(e),
|
|
813
884
|
writeLedger: shouldWriteLedger ? (e) => ledger.write(e) : void 0,
|
|
814
|
-
addToBatch:
|
|
885
|
+
addToBatch: (e) => {
|
|
886
|
+
lateInit();
|
|
887
|
+
batch?.add(e);
|
|
888
|
+
}
|
|
815
889
|
});
|
|
816
890
|
var preloaded = checkImportOrder();
|
|
817
891
|
if (preloaded.length > 0) {
|
|
818
|
-
console.warn(
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
console.warn("[burn0] No API key \u2014 costs not tracked. Get one free at burn0.dev/api");
|
|
892
|
+
console.warn(
|
|
893
|
+
`[burn0] Warning: These SDKs were imported before burn0 and may not be tracked: ${preloaded.join(", ")}. Move \`import '@burn0/burn0'\` to the top of your entry file.`
|
|
894
|
+
);
|
|
822
895
|
}
|
|
823
|
-
if (canPatch() && mode !== "test-disabled"
|
|
896
|
+
if (canPatch() && mode !== "test-disabled") {
|
|
824
897
|
const onEvent = (event) => {
|
|
825
898
|
const enriched = enrichEvent(event);
|
|
826
899
|
dispatch(enriched);
|
|
@@ -846,4 +919,4 @@ export {
|
|
|
846
919
|
startSpan,
|
|
847
920
|
restore
|
|
848
921
|
};
|
|
849
|
-
//# sourceMappingURL=chunk-
|
|
922
|
+
//# sourceMappingURL=chunk-UI6QVWA4.mjs.map
|
package/dist/cli/index.js
CHANGED
|
@@ -18080,11 +18080,15 @@ async function _runInit() {
|
|
|
18080
18080
|
}
|
|
18081
18081
|
const serviceConfigs = [];
|
|
18082
18082
|
if (allDetected.length > 0) {
|
|
18083
|
-
console.log(
|
|
18084
|
-
`
|
|
18083
|
+
console.log(
|
|
18084
|
+
source_default.bold(` Auto-detected ${allDetected.length} services:
|
|
18085
|
+
`)
|
|
18086
|
+
);
|
|
18085
18087
|
for (const svc of allDetected) {
|
|
18086
18088
|
const tag = svc.autopriced ? source_default.dim("auto-priced") : source_default.yellow("needs plan");
|
|
18087
|
-
console.log(
|
|
18089
|
+
console.log(
|
|
18090
|
+
` ${source_default.green(" \u2713")} ${svc.displayName.padEnd(20)} ${tag}`
|
|
18091
|
+
);
|
|
18088
18092
|
}
|
|
18089
18093
|
console.log();
|
|
18090
18094
|
const fixedTier = allDetected.filter((s) => !s.autopriced);
|
|
@@ -18101,7 +18105,11 @@ async function _runInit() {
|
|
|
18101
18105
|
});
|
|
18102
18106
|
if (plan !== "skip") {
|
|
18103
18107
|
const selected = entry.plans.find((p) => p.value === plan);
|
|
18104
|
-
serviceConfigs.push({
|
|
18108
|
+
serviceConfigs.push({
|
|
18109
|
+
name: svc.name,
|
|
18110
|
+
plan,
|
|
18111
|
+
monthlyCost: selected?.monthly
|
|
18112
|
+
});
|
|
18105
18113
|
} else {
|
|
18106
18114
|
serviceConfigs.push({ name: svc.name });
|
|
18107
18115
|
}
|
|
@@ -18120,18 +18128,38 @@ async function _runInit() {
|
|
|
18120
18128
|
});
|
|
18121
18129
|
if (addMore) {
|
|
18122
18130
|
const alreadyAdded = new Set(serviceConfigs.map((s) => s.name));
|
|
18123
|
-
const additionalServices = SERVICE_CATALOG.filter(
|
|
18131
|
+
const additionalServices = SERVICE_CATALOG.filter(
|
|
18132
|
+
(s) => !alreadyAdded.has(s.name)
|
|
18133
|
+
);
|
|
18124
18134
|
const llmChoices = additionalServices.filter((s) => s.category === "llm").map((s) => ({ name: s.displayName, value: s.name }));
|
|
18125
18135
|
const apiChoices = additionalServices.filter((s) => s.category === "api").map((s) => ({ name: s.displayName, value: s.name }));
|
|
18126
18136
|
const infraChoices = additionalServices.filter((s) => s.category === "infra").map((s) => ({ name: s.displayName, value: s.name }));
|
|
18127
18137
|
const additional = await esm_default2({
|
|
18128
18138
|
message: "Select services:",
|
|
18129
18139
|
choices: [
|
|
18130
|
-
...llmChoices.length ? [
|
|
18140
|
+
...llmChoices.length ? [
|
|
18141
|
+
{
|
|
18142
|
+
name: source_default.bold.blue("\u2500\u2500 LLM Providers \u2500\u2500"),
|
|
18143
|
+
value: "__sep",
|
|
18144
|
+
disabled: true
|
|
18145
|
+
}
|
|
18146
|
+
] : [],
|
|
18131
18147
|
...llmChoices,
|
|
18132
|
-
...apiChoices.length ? [
|
|
18148
|
+
...apiChoices.length ? [
|
|
18149
|
+
{
|
|
18150
|
+
name: source_default.bold.magenta("\u2500\u2500 API Services \u2500\u2500"),
|
|
18151
|
+
value: "__sep2",
|
|
18152
|
+
disabled: true
|
|
18153
|
+
}
|
|
18154
|
+
] : [],
|
|
18133
18155
|
...apiChoices,
|
|
18134
|
-
...infraChoices.length ? [
|
|
18156
|
+
...infraChoices.length ? [
|
|
18157
|
+
{
|
|
18158
|
+
name: source_default.bold.yellow("\u2500\u2500 Infrastructure \u2500\u2500"),
|
|
18159
|
+
value: "__sep3",
|
|
18160
|
+
disabled: true
|
|
18161
|
+
}
|
|
18162
|
+
] : [],
|
|
18135
18163
|
...infraChoices
|
|
18136
18164
|
]
|
|
18137
18165
|
});
|
|
@@ -18159,7 +18187,9 @@ async function _runInit() {
|
|
|
18159
18187
|
}
|
|
18160
18188
|
let projectName = "my-project";
|
|
18161
18189
|
try {
|
|
18162
|
-
const pkg = JSON.parse(
|
|
18190
|
+
const pkg = JSON.parse(
|
|
18191
|
+
import_node_fs5.default.readFileSync(import_node_path6.default.join(cwd, "package.json"), "utf-8")
|
|
18192
|
+
);
|
|
18163
18193
|
if (pkg.name) projectName = pkg.name;
|
|
18164
18194
|
} catch {
|
|
18165
18195
|
}
|
|
@@ -18174,12 +18204,12 @@ async function _runInit() {
|
|
|
18174
18204
|
});
|
|
18175
18205
|
if (apiKey) {
|
|
18176
18206
|
try {
|
|
18177
|
-
const apiUrl =
|
|
18207
|
+
const apiUrl = "https://burn0-server-production.up.railway.app";
|
|
18178
18208
|
const res = await fetch(`${apiUrl}/v1/projects/config`, {
|
|
18179
18209
|
method: "POST",
|
|
18180
18210
|
headers: {
|
|
18181
18211
|
"Content-Type": "application/json",
|
|
18182
|
-
|
|
18212
|
+
Authorization: `Bearer ${apiKey}`
|
|
18183
18213
|
},
|
|
18184
18214
|
body: JSON.stringify({
|
|
18185
18215
|
services: serviceConfigs.map((s) => ({
|
|
@@ -18206,7 +18236,9 @@ async function _runInit() {
|
|
|
18206
18236
|
const gitignoreLines = gitignoreContent.split("\n").map((l) => l.trim());
|
|
18207
18237
|
if (!gitignoreLines.includes(".env")) {
|
|
18208
18238
|
ensureGitignore(cwd, ".env");
|
|
18209
|
-
console.log(
|
|
18239
|
+
console.log(
|
|
18240
|
+
source_default.green(" \u2713 Added .env to .gitignore (protects your API keys)")
|
|
18241
|
+
);
|
|
18210
18242
|
}
|
|
18211
18243
|
console.log("");
|
|
18212
18244
|
console.log(source_default.green(" \u2713 Setup complete"));
|
|
@@ -18318,7 +18350,7 @@ var init_connect = __esm({
|
|
|
18318
18350
|
});
|
|
18319
18351
|
|
|
18320
18352
|
// src/transport/local.ts
|
|
18321
|
-
var import_node_fs6, import_node_path7, BURN0_DIR, LEDGER_FILE, MAX_FILE_SIZE, MAX_AGE_MS, LocalLedger;
|
|
18353
|
+
var import_node_fs6, import_node_path7, BURN0_DIR, LEDGER_FILE, SYNC_MARKER_FILE, MAX_FILE_SIZE, MAX_AGE_MS, LocalLedger;
|
|
18322
18354
|
var init_local = __esm({
|
|
18323
18355
|
"src/transport/local.ts"() {
|
|
18324
18356
|
"use strict";
|
|
@@ -18326,6 +18358,7 @@ var init_local = __esm({
|
|
|
18326
18358
|
import_node_path7 = __toESM(require("path"));
|
|
18327
18359
|
BURN0_DIR = ".burn0";
|
|
18328
18360
|
LEDGER_FILE = "costs.jsonl";
|
|
18361
|
+
SYNC_MARKER_FILE = "last-sync.txt";
|
|
18329
18362
|
MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
18330
18363
|
MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
18331
18364
|
LocalLedger = class {
|
|
@@ -18349,6 +18382,24 @@ var init_local = __esm({
|
|
|
18349
18382
|
return [];
|
|
18350
18383
|
}
|
|
18351
18384
|
}
|
|
18385
|
+
readUnsynced() {
|
|
18386
|
+
const all = this.read();
|
|
18387
|
+
const lastSync = this.getLastSyncTime();
|
|
18388
|
+
if (!lastSync) return all;
|
|
18389
|
+
return all.filter((e) => new Date(e.timestamp).getTime() > lastSync);
|
|
18390
|
+
}
|
|
18391
|
+
markSynced() {
|
|
18392
|
+
this.ensureDir();
|
|
18393
|
+
import_node_fs6.default.writeFileSync(import_node_path7.default.join(this.dirPath, SYNC_MARKER_FILE), (/* @__PURE__ */ new Date()).toISOString());
|
|
18394
|
+
}
|
|
18395
|
+
getLastSyncTime() {
|
|
18396
|
+
try {
|
|
18397
|
+
const ts = import_node_fs6.default.readFileSync(import_node_path7.default.join(this.dirPath, SYNC_MARKER_FILE), "utf-8").trim();
|
|
18398
|
+
return new Date(ts).getTime();
|
|
18399
|
+
} catch {
|
|
18400
|
+
return null;
|
|
18401
|
+
}
|
|
18402
|
+
}
|
|
18352
18403
|
ensureDir() {
|
|
18353
18404
|
if (!import_node_fs6.default.existsSync(this.dirPath)) import_node_fs6.default.mkdirSync(this.dirPath, { recursive: true });
|
|
18354
18405
|
}
|
|
@@ -18483,7 +18534,20 @@ function formatCost(cost) {
|
|
|
18483
18534
|
}
|
|
18484
18535
|
function formatDateLabel(dateStr) {
|
|
18485
18536
|
const d = /* @__PURE__ */ new Date(dateStr + "T12:00:00");
|
|
18486
|
-
const months = [
|
|
18537
|
+
const months = [
|
|
18538
|
+
"Jan",
|
|
18539
|
+
"Feb",
|
|
18540
|
+
"Mar",
|
|
18541
|
+
"Apr",
|
|
18542
|
+
"May",
|
|
18543
|
+
"Jun",
|
|
18544
|
+
"Jul",
|
|
18545
|
+
"Aug",
|
|
18546
|
+
"Sep",
|
|
18547
|
+
"Oct",
|
|
18548
|
+
"Nov",
|
|
18549
|
+
"Dec"
|
|
18550
|
+
];
|
|
18487
18551
|
return `${months[d.getMonth()]} ${String(d.getDate()).padStart(2, " ")}`;
|
|
18488
18552
|
}
|
|
18489
18553
|
function makeBar(value, max, width) {
|
|
@@ -18512,10 +18576,12 @@ function aggregateLocal(events, days) {
|
|
|
18512
18576
|
const estimate = estimateLocalCost(event);
|
|
18513
18577
|
if (estimate.type === "priced" && estimate.cost > 0) {
|
|
18514
18578
|
totalCost += estimate.cost;
|
|
18515
|
-
if (!serviceCosts[event.service])
|
|
18579
|
+
if (!serviceCosts[event.service])
|
|
18580
|
+
serviceCosts[event.service] = { cost: 0, calls: 0 };
|
|
18516
18581
|
serviceCosts[event.service].cost += estimate.cost;
|
|
18517
18582
|
serviceCosts[event.service].calls++;
|
|
18518
|
-
if (!dayCosts[eventDateStr])
|
|
18583
|
+
if (!dayCosts[eventDateStr])
|
|
18584
|
+
dayCosts[eventDateStr] = { cost: 0, calls: 0, services: {} };
|
|
18519
18585
|
dayCosts[eventDateStr].cost += estimate.cost;
|
|
18520
18586
|
dayCosts[eventDateStr].calls++;
|
|
18521
18587
|
dayCosts[eventDateStr].services[event.service] = (dayCosts[eventDateStr].services[event.service] ?? 0) + estimate.cost;
|
|
@@ -18543,17 +18609,24 @@ function aggregateLocal(events, days) {
|
|
|
18543
18609
|
}
|
|
18544
18610
|
function renderCallCountOnly(data) {
|
|
18545
18611
|
const maxCalls = data.allServiceCalls.length > 0 ? data.allServiceCalls[0].calls : 0;
|
|
18546
|
-
const maxNameLen = Math.max(
|
|
18612
|
+
const maxNameLen = Math.max(
|
|
18613
|
+
...data.allServiceCalls.map((s) => s.name.length),
|
|
18614
|
+
8
|
|
18615
|
+
);
|
|
18547
18616
|
for (const svc of data.allServiceCalls) {
|
|
18548
18617
|
const bar = makeBar(svc.calls, maxCalls, 20);
|
|
18549
|
-
console.log(
|
|
18618
|
+
console.log(
|
|
18619
|
+
` ${svc.name.padEnd(maxNameLen)} ${source_default.gray(`${String(svc.calls).padStart(5)} calls`)} ${source_default.cyan(bar)}`
|
|
18620
|
+
);
|
|
18550
18621
|
}
|
|
18551
18622
|
console.log();
|
|
18552
18623
|
}
|
|
18553
18624
|
function renderCostReport(data, label, showDaily, isToday) {
|
|
18554
|
-
console.log(
|
|
18625
|
+
console.log(
|
|
18626
|
+
`
|
|
18555
18627
|
${source_default.hex("#FA5D19").bold("burn0 report")} ${source_default.gray(`\u2500\u2500 ${label}`)}
|
|
18556
|
-
`
|
|
18628
|
+
`
|
|
18629
|
+
);
|
|
18557
18630
|
if (data.total.calls === 0) {
|
|
18558
18631
|
const msg = isToday ? "No calls today." : `No cost data yet. Run your app with \`import '@burn0/burn0'\` to start tracking.`;
|
|
18559
18632
|
console.log(source_default.dim(` ${msg}
|
|
@@ -18561,50 +18634,70 @@ function renderCostReport(data, label, showDaily, isToday) {
|
|
|
18561
18634
|
return;
|
|
18562
18635
|
}
|
|
18563
18636
|
if (!data.pricingAvailable) {
|
|
18564
|
-
console.log(
|
|
18565
|
-
|
|
18637
|
+
console.log(
|
|
18638
|
+
source_default.dim(
|
|
18639
|
+
` ${data.total.calls} calls tracked (pricing data not available)
|
|
18640
|
+
`
|
|
18641
|
+
)
|
|
18642
|
+
);
|
|
18566
18643
|
renderCallCountOnly(data);
|
|
18567
18644
|
return;
|
|
18568
18645
|
}
|
|
18569
18646
|
if (data.total.cost === 0 && data.total.calls > 0) {
|
|
18570
|
-
console.log(
|
|
18571
|
-
|
|
18647
|
+
console.log(
|
|
18648
|
+
source_default.dim(
|
|
18649
|
+
` ${data.total.calls} calls tracked (no pricing data available)
|
|
18650
|
+
`
|
|
18651
|
+
)
|
|
18652
|
+
);
|
|
18572
18653
|
renderCallCountOnly(data);
|
|
18573
18654
|
return;
|
|
18574
18655
|
}
|
|
18575
|
-
console.log(
|
|
18576
|
-
`)
|
|
18656
|
+
console.log(
|
|
18657
|
+
` ${source_default.bold("Total:")} ${source_default.green(formatCost(data.total.cost))} ${source_default.gray(`(${data.total.calls} calls)`)}
|
|
18658
|
+
`
|
|
18659
|
+
);
|
|
18577
18660
|
const maxCost = data.byService.length > 0 ? data.byService[0].cost : 0;
|
|
18578
18661
|
const maxNameLen = Math.max(...data.byService.map((s) => s.name.length), 8);
|
|
18579
18662
|
for (const svc of data.byService) {
|
|
18580
18663
|
const pct = data.total.cost > 0 ? Math.round(svc.cost / data.total.cost * 100) : 0;
|
|
18581
18664
|
const bar = makeBar(svc.cost, maxCost, 20);
|
|
18582
|
-
console.log(
|
|
18665
|
+
console.log(
|
|
18666
|
+
` ${svc.name.padEnd(maxNameLen)} ${source_default.green(formatCost(svc.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.gray(`${String(pct).padStart(3)}%`)}`
|
|
18667
|
+
);
|
|
18583
18668
|
}
|
|
18584
18669
|
if (data.unpricedCount > 0) {
|
|
18585
18670
|
console.log(source_default.dim(`
|
|
18586
18671
|
+ ${data.unpricedCount} calls not priced`));
|
|
18587
18672
|
}
|
|
18588
18673
|
if (showDaily && data.byDay.length > 0) {
|
|
18589
|
-
console.log(
|
|
18674
|
+
console.log(
|
|
18675
|
+
`
|
|
18590
18676
|
${source_default.gray("\u2500\u2500 daily \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
18591
|
-
`
|
|
18677
|
+
`
|
|
18678
|
+
);
|
|
18592
18679
|
const maxDayCost = Math.max(...data.byDay.map((d) => d.cost));
|
|
18593
18680
|
for (const day of data.byDay) {
|
|
18594
18681
|
const dateLabel = formatDateLabel(day.date);
|
|
18595
18682
|
const bar = makeBar(day.cost, maxDayCost, 12);
|
|
18596
18683
|
const top2 = day.topServices.slice(0, 2).map((s) => `${s.name} ${formatCost(s.cost)}`).join(" \xB7 ");
|
|
18597
18684
|
const more = day.topServices.length > 2 ? ` +${day.topServices.length - 2} more` : "";
|
|
18598
|
-
console.log(
|
|
18685
|
+
console.log(
|
|
18686
|
+
` ${source_default.gray(dateLabel)} ${source_default.green(formatCost(day.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.dim(top2 + more)}`
|
|
18687
|
+
);
|
|
18599
18688
|
}
|
|
18600
18689
|
}
|
|
18601
18690
|
if (data.total.cost > 0) {
|
|
18602
18691
|
const daysInPeriod = showDaily ? 7 : 1;
|
|
18603
18692
|
const dailyRate = data.total.cost / daysInPeriod;
|
|
18604
18693
|
const monthly = dailyRate * 30;
|
|
18605
|
-
console.log(
|
|
18606
|
-
|
|
18607
|
-
|
|
18694
|
+
console.log(
|
|
18695
|
+
`
|
|
18696
|
+
${source_default.gray("\u2500\u2500 projection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`
|
|
18697
|
+
);
|
|
18698
|
+
console.log(
|
|
18699
|
+
` ${source_default.gray("~")}${source_default.green(formatCost(monthly))}${source_default.gray("/mo estimated")} ${source_default.dim(`(based on ${isToday ? "today" : "last 7 days"})`)}`
|
|
18700
|
+
);
|
|
18608
18701
|
}
|
|
18609
18702
|
console.log();
|
|
18610
18703
|
}
|
|
@@ -18612,10 +18705,16 @@ async function fetchBackendReport(apiKey, days) {
|
|
|
18612
18705
|
try {
|
|
18613
18706
|
const controller = new AbortController();
|
|
18614
18707
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
18615
|
-
const response = await globalThis.fetch(
|
|
18616
|
-
|
|
18617
|
-
|
|
18618
|
-
|
|
18708
|
+
const response = await globalThis.fetch(
|
|
18709
|
+
`${BURN0_API_URL}/v1/report?days=${days}`,
|
|
18710
|
+
{
|
|
18711
|
+
headers: {
|
|
18712
|
+
Accept: "application/json",
|
|
18713
|
+
Authorization: `Bearer ${apiKey}`
|
|
18714
|
+
},
|
|
18715
|
+
signal: controller.signal
|
|
18716
|
+
}
|
|
18717
|
+
);
|
|
18619
18718
|
clearTimeout(timeout);
|
|
18620
18719
|
if (!response.ok) return null;
|
|
18621
18720
|
const data = await response.json();
|
|
@@ -18623,7 +18722,10 @@ async function fetchBackendReport(apiKey, days) {
|
|
|
18623
18722
|
total: data.total ?? { cost: 0, calls: 0 },
|
|
18624
18723
|
byService: data.byService ?? [],
|
|
18625
18724
|
byDay: data.byDay ?? [],
|
|
18626
|
-
allServiceCalls: (data.byService ?? []).map((s) => ({
|
|
18725
|
+
allServiceCalls: (data.byService ?? []).map((s) => ({
|
|
18726
|
+
name: s.name,
|
|
18727
|
+
calls: s.calls
|
|
18728
|
+
})),
|
|
18627
18729
|
unpricedCount: 0,
|
|
18628
18730
|
pricingAvailable: true
|
|
18629
18731
|
};
|
|
@@ -18657,7 +18759,7 @@ var init_report = __esm({
|
|
|
18657
18759
|
init_local();
|
|
18658
18760
|
init_local_pricing();
|
|
18659
18761
|
init_env();
|
|
18660
|
-
BURN0_API_URL =
|
|
18762
|
+
BURN0_API_URL = "https://burn0-server-production.up.railway.app";
|
|
18661
18763
|
}
|
|
18662
18764
|
});
|
|
18663
18765
|
|
package/dist/index.js
CHANGED
|
@@ -458,32 +458,10 @@ function createRestorer(deps) {
|
|
|
458
458
|
// src/transport/dispatcher.ts
|
|
459
459
|
function createDispatcher(mode2, deps) {
|
|
460
460
|
return (event) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
break;
|
|
466
|
-
case "dev-cloud":
|
|
467
|
-
deps.logEvent?.(event);
|
|
468
|
-
deps.writeLedger?.(event);
|
|
469
|
-
deps.addToBatch?.(event);
|
|
470
|
-
break;
|
|
471
|
-
case "prod-cloud":
|
|
472
|
-
deps.logEvent?.(event);
|
|
473
|
-
deps.writeLedger?.(event);
|
|
474
|
-
deps.addToBatch?.(event);
|
|
475
|
-
break;
|
|
476
|
-
case "prod-local":
|
|
477
|
-
deps.logEvent?.(event);
|
|
478
|
-
break;
|
|
479
|
-
case "test-enabled":
|
|
480
|
-
deps.logEvent?.(event);
|
|
481
|
-
deps.writeLedger?.(event);
|
|
482
|
-
deps.addToBatch?.(event);
|
|
483
|
-
break;
|
|
484
|
-
case "test-disabled":
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
461
|
+
if (mode2 === "test-disabled") return;
|
|
462
|
+
deps.logEvent?.(event);
|
|
463
|
+
deps.writeLedger?.(event);
|
|
464
|
+
deps.addToBatch?.(event);
|
|
487
465
|
};
|
|
488
466
|
}
|
|
489
467
|
|
|
@@ -529,6 +507,7 @@ var import_node_fs = __toESM(require("fs"));
|
|
|
529
507
|
var import_node_path = __toESM(require("path"));
|
|
530
508
|
var BURN0_DIR = ".burn0";
|
|
531
509
|
var LEDGER_FILE = "costs.jsonl";
|
|
510
|
+
var SYNC_MARKER_FILE = "last-sync.txt";
|
|
532
511
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
533
512
|
var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
534
513
|
var LocalLedger = class {
|
|
@@ -552,6 +531,24 @@ var LocalLedger = class {
|
|
|
552
531
|
return [];
|
|
553
532
|
}
|
|
554
533
|
}
|
|
534
|
+
readUnsynced() {
|
|
535
|
+
const all = this.read();
|
|
536
|
+
const lastSync = this.getLastSyncTime();
|
|
537
|
+
if (!lastSync) return all;
|
|
538
|
+
return all.filter((e) => new Date(e.timestamp).getTime() > lastSync);
|
|
539
|
+
}
|
|
540
|
+
markSynced() {
|
|
541
|
+
this.ensureDir();
|
|
542
|
+
import_node_fs.default.writeFileSync(import_node_path.default.join(this.dirPath, SYNC_MARKER_FILE), (/* @__PURE__ */ new Date()).toISOString());
|
|
543
|
+
}
|
|
544
|
+
getLastSyncTime() {
|
|
545
|
+
try {
|
|
546
|
+
const ts = import_node_fs.default.readFileSync(import_node_path.default.join(this.dirPath, SYNC_MARKER_FILE), "utf-8").trim();
|
|
547
|
+
return new Date(ts).getTime();
|
|
548
|
+
} catch {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
555
552
|
ensureDir() {
|
|
556
553
|
if (!import_node_fs.default.existsSync(this.dirPath)) import_node_fs.default.mkdirSync(this.dirPath, { recursive: true });
|
|
557
554
|
}
|
|
@@ -596,10 +593,17 @@ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch)
|
|
|
596
593
|
},
|
|
597
594
|
body: JSON.stringify({ events, sdk_version: SDK_VERSION })
|
|
598
595
|
});
|
|
599
|
-
if (response.ok)
|
|
596
|
+
if (response.ok) {
|
|
597
|
+
if (isDebug()) console.log(`[burn0] Shipped ${events.length} events`);
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
if (isDebug()) {
|
|
601
|
+
const body = await response.text().catch(() => "");
|
|
602
|
+
console.warn(`[burn0] Shipping rejected: ${response.status} ${body}`);
|
|
603
|
+
}
|
|
600
604
|
} catch (err) {
|
|
601
605
|
if (isDebug()) {
|
|
602
|
-
console.warn("[burn0]
|
|
606
|
+
console.warn("[burn0] Shipping failed:", err.message);
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
609
|
if (attempt < maxAttempts - 1) {
|
|
@@ -857,31 +861,100 @@ try {
|
|
|
857
861
|
}
|
|
858
862
|
var ticker = createTicker({ todayCost, todayCalls, perServiceCosts });
|
|
859
863
|
var batch = null;
|
|
860
|
-
|
|
861
|
-
|
|
864
|
+
var lateInitDone = false;
|
|
865
|
+
var failedEvents = [];
|
|
866
|
+
function createBatch(key) {
|
|
867
|
+
return new BatchBuffer({
|
|
862
868
|
sizeThreshold: 50,
|
|
863
869
|
timeThresholdMs: 1e4,
|
|
864
870
|
maxSize: 500,
|
|
865
871
|
onFlush: (events) => {
|
|
866
|
-
|
|
872
|
+
const toShip = failedEvents.length > 0 ? [...failedEvents, ...events] : events;
|
|
873
|
+
failedEvents = [];
|
|
874
|
+
shipEvents(toShip, key, BURN0_API_URL, originalFetch2).then((ok) => {
|
|
875
|
+
if (!ok) {
|
|
876
|
+
failedEvents = toShip.slice(-500);
|
|
877
|
+
}
|
|
878
|
+
}).catch(() => {
|
|
879
|
+
failedEvents = toShip.slice(-500);
|
|
867
880
|
});
|
|
868
881
|
}
|
|
869
882
|
});
|
|
870
883
|
}
|
|
884
|
+
if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
|
|
885
|
+
batch = createBatch(apiKey);
|
|
886
|
+
}
|
|
887
|
+
var pendingEvents = [];
|
|
888
|
+
function lateInit(event) {
|
|
889
|
+
if (batch) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const lateKey = getApiKey();
|
|
893
|
+
if (!lateKey) {
|
|
894
|
+
if (event) pendingEvents.push(event);
|
|
895
|
+
if (!lateInitDone) {
|
|
896
|
+
lateInitDone = true;
|
|
897
|
+
setTimeout(() => {
|
|
898
|
+
lateInitDone = false;
|
|
899
|
+
if (pendingEvents.length > 0) {
|
|
900
|
+
const e = pendingEvents.shift();
|
|
901
|
+
lateInit(e);
|
|
902
|
+
} else {
|
|
903
|
+
lateInit();
|
|
904
|
+
}
|
|
905
|
+
}, 0);
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
lateInitDone = true;
|
|
910
|
+
apiKey = lateKey;
|
|
911
|
+
mode = detectMode({ isTTY: isTTY(), apiKey });
|
|
912
|
+
batch = createBatch(lateKey);
|
|
913
|
+
fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
|
|
914
|
+
});
|
|
915
|
+
for (const e of pendingEvents) {
|
|
916
|
+
batch.add(e);
|
|
917
|
+
}
|
|
918
|
+
pendingEvents.length = 0;
|
|
919
|
+
syncLedger(lateKey);
|
|
920
|
+
}
|
|
921
|
+
function syncLedger(key) {
|
|
922
|
+
try {
|
|
923
|
+
const unsynced = ledger.readUnsynced();
|
|
924
|
+
if (unsynced.length === 0) {
|
|
925
|
+
ledger.markSynced();
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const promises = [];
|
|
929
|
+
for (let i = 0; i < unsynced.length; i += 500) {
|
|
930
|
+
const chunk = unsynced.slice(i, i + 500);
|
|
931
|
+
promises.push(shipEvents(chunk, key, BURN0_API_URL, originalFetch2));
|
|
932
|
+
}
|
|
933
|
+
Promise.all(promises).then((results) => {
|
|
934
|
+
if (results.every(Boolean)) {
|
|
935
|
+
ledger.markSynced();
|
|
936
|
+
}
|
|
937
|
+
}).catch(() => {
|
|
938
|
+
});
|
|
939
|
+
} catch {
|
|
940
|
+
}
|
|
941
|
+
}
|
|
871
942
|
var shouldWriteLedger = mode !== "test-disabled" && mode !== "prod-local";
|
|
872
943
|
var dispatch = createDispatcher(mode, {
|
|
873
944
|
logEvent: (e) => ticker.tick(e),
|
|
874
945
|
writeLedger: shouldWriteLedger ? (e) => ledger.write(e) : void 0,
|
|
875
|
-
addToBatch:
|
|
946
|
+
addToBatch: (e) => {
|
|
947
|
+
lateInit();
|
|
948
|
+
batch?.add(e);
|
|
949
|
+
}
|
|
876
950
|
});
|
|
877
951
|
var preloaded = checkImportOrder();
|
|
878
952
|
if (preloaded.length > 0) {
|
|
879
|
-
console.warn(
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
console.warn("[burn0] No API key \u2014 costs not tracked. Get one free at burn0.dev/api");
|
|
953
|
+
console.warn(
|
|
954
|
+
`[burn0] Warning: These SDKs were imported before burn0 and may not be tracked: ${preloaded.join(", ")}. Move \`import '@burn0/burn0'\` to the top of your entry file.`
|
|
955
|
+
);
|
|
883
956
|
}
|
|
884
|
-
if (canPatch() && mode !== "test-disabled"
|
|
957
|
+
if (canPatch() && mode !== "test-disabled") {
|
|
885
958
|
const onEvent = (event) => {
|
|
886
959
|
const enriched = enrichEvent(event);
|
|
887
960
|
dispatch(enriched);
|
package/dist/index.mjs
CHANGED
package/dist/register.js
CHANGED
|
@@ -444,32 +444,10 @@ function createRestorer(deps) {
|
|
|
444
444
|
// src/transport/dispatcher.ts
|
|
445
445
|
function createDispatcher(mode2, deps) {
|
|
446
446
|
return (event) => {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
break;
|
|
452
|
-
case "dev-cloud":
|
|
453
|
-
deps.logEvent?.(event);
|
|
454
|
-
deps.writeLedger?.(event);
|
|
455
|
-
deps.addToBatch?.(event);
|
|
456
|
-
break;
|
|
457
|
-
case "prod-cloud":
|
|
458
|
-
deps.logEvent?.(event);
|
|
459
|
-
deps.writeLedger?.(event);
|
|
460
|
-
deps.addToBatch?.(event);
|
|
461
|
-
break;
|
|
462
|
-
case "prod-local":
|
|
463
|
-
deps.logEvent?.(event);
|
|
464
|
-
break;
|
|
465
|
-
case "test-enabled":
|
|
466
|
-
deps.logEvent?.(event);
|
|
467
|
-
deps.writeLedger?.(event);
|
|
468
|
-
deps.addToBatch?.(event);
|
|
469
|
-
break;
|
|
470
|
-
case "test-disabled":
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
447
|
+
if (mode2 === "test-disabled") return;
|
|
448
|
+
deps.logEvent?.(event);
|
|
449
|
+
deps.writeLedger?.(event);
|
|
450
|
+
deps.addToBatch?.(event);
|
|
473
451
|
};
|
|
474
452
|
}
|
|
475
453
|
|
|
@@ -515,6 +493,7 @@ var import_node_fs = __toESM(require("fs"));
|
|
|
515
493
|
var import_node_path = __toESM(require("path"));
|
|
516
494
|
var BURN0_DIR = ".burn0";
|
|
517
495
|
var LEDGER_FILE = "costs.jsonl";
|
|
496
|
+
var SYNC_MARKER_FILE = "last-sync.txt";
|
|
518
497
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
519
498
|
var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
520
499
|
var LocalLedger = class {
|
|
@@ -538,6 +517,24 @@ var LocalLedger = class {
|
|
|
538
517
|
return [];
|
|
539
518
|
}
|
|
540
519
|
}
|
|
520
|
+
readUnsynced() {
|
|
521
|
+
const all = this.read();
|
|
522
|
+
const lastSync = this.getLastSyncTime();
|
|
523
|
+
if (!lastSync) return all;
|
|
524
|
+
return all.filter((e) => new Date(e.timestamp).getTime() > lastSync);
|
|
525
|
+
}
|
|
526
|
+
markSynced() {
|
|
527
|
+
this.ensureDir();
|
|
528
|
+
import_node_fs.default.writeFileSync(import_node_path.default.join(this.dirPath, SYNC_MARKER_FILE), (/* @__PURE__ */ new Date()).toISOString());
|
|
529
|
+
}
|
|
530
|
+
getLastSyncTime() {
|
|
531
|
+
try {
|
|
532
|
+
const ts = import_node_fs.default.readFileSync(import_node_path.default.join(this.dirPath, SYNC_MARKER_FILE), "utf-8").trim();
|
|
533
|
+
return new Date(ts).getTime();
|
|
534
|
+
} catch {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
541
538
|
ensureDir() {
|
|
542
539
|
if (!import_node_fs.default.existsSync(this.dirPath)) import_node_fs.default.mkdirSync(this.dirPath, { recursive: true });
|
|
543
540
|
}
|
|
@@ -582,10 +579,17 @@ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch)
|
|
|
582
579
|
},
|
|
583
580
|
body: JSON.stringify({ events, sdk_version: SDK_VERSION })
|
|
584
581
|
});
|
|
585
|
-
if (response.ok)
|
|
582
|
+
if (response.ok) {
|
|
583
|
+
if (isDebug()) console.log(`[burn0] Shipped ${events.length} events`);
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
if (isDebug()) {
|
|
587
|
+
const body = await response.text().catch(() => "");
|
|
588
|
+
console.warn(`[burn0] Shipping rejected: ${response.status} ${body}`);
|
|
589
|
+
}
|
|
586
590
|
} catch (err) {
|
|
587
591
|
if (isDebug()) {
|
|
588
|
-
console.warn("[burn0]
|
|
592
|
+
console.warn("[burn0] Shipping failed:", err.message);
|
|
589
593
|
}
|
|
590
594
|
}
|
|
591
595
|
if (attempt < maxAttempts - 1) {
|
|
@@ -843,31 +847,100 @@ try {
|
|
|
843
847
|
}
|
|
844
848
|
var ticker = createTicker({ todayCost, todayCalls, perServiceCosts });
|
|
845
849
|
var batch = null;
|
|
846
|
-
|
|
847
|
-
|
|
850
|
+
var lateInitDone = false;
|
|
851
|
+
var failedEvents = [];
|
|
852
|
+
function createBatch(key) {
|
|
853
|
+
return new BatchBuffer({
|
|
848
854
|
sizeThreshold: 50,
|
|
849
855
|
timeThresholdMs: 1e4,
|
|
850
856
|
maxSize: 500,
|
|
851
857
|
onFlush: (events) => {
|
|
852
|
-
|
|
858
|
+
const toShip = failedEvents.length > 0 ? [...failedEvents, ...events] : events;
|
|
859
|
+
failedEvents = [];
|
|
860
|
+
shipEvents(toShip, key, BURN0_API_URL, originalFetch2).then((ok) => {
|
|
861
|
+
if (!ok) {
|
|
862
|
+
failedEvents = toShip.slice(-500);
|
|
863
|
+
}
|
|
864
|
+
}).catch(() => {
|
|
865
|
+
failedEvents = toShip.slice(-500);
|
|
853
866
|
});
|
|
854
867
|
}
|
|
855
868
|
});
|
|
856
869
|
}
|
|
870
|
+
if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
|
|
871
|
+
batch = createBatch(apiKey);
|
|
872
|
+
}
|
|
873
|
+
var pendingEvents = [];
|
|
874
|
+
function lateInit(event) {
|
|
875
|
+
if (batch) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const lateKey = getApiKey();
|
|
879
|
+
if (!lateKey) {
|
|
880
|
+
if (event) pendingEvents.push(event);
|
|
881
|
+
if (!lateInitDone) {
|
|
882
|
+
lateInitDone = true;
|
|
883
|
+
setTimeout(() => {
|
|
884
|
+
lateInitDone = false;
|
|
885
|
+
if (pendingEvents.length > 0) {
|
|
886
|
+
const e = pendingEvents.shift();
|
|
887
|
+
lateInit(e);
|
|
888
|
+
} else {
|
|
889
|
+
lateInit();
|
|
890
|
+
}
|
|
891
|
+
}, 0);
|
|
892
|
+
}
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
lateInitDone = true;
|
|
896
|
+
apiKey = lateKey;
|
|
897
|
+
mode = detectMode({ isTTY: isTTY(), apiKey });
|
|
898
|
+
batch = createBatch(lateKey);
|
|
899
|
+
fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
|
|
900
|
+
});
|
|
901
|
+
for (const e of pendingEvents) {
|
|
902
|
+
batch.add(e);
|
|
903
|
+
}
|
|
904
|
+
pendingEvents.length = 0;
|
|
905
|
+
syncLedger(lateKey);
|
|
906
|
+
}
|
|
907
|
+
function syncLedger(key) {
|
|
908
|
+
try {
|
|
909
|
+
const unsynced = ledger.readUnsynced();
|
|
910
|
+
if (unsynced.length === 0) {
|
|
911
|
+
ledger.markSynced();
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const promises = [];
|
|
915
|
+
for (let i = 0; i < unsynced.length; i += 500) {
|
|
916
|
+
const chunk = unsynced.slice(i, i + 500);
|
|
917
|
+
promises.push(shipEvents(chunk, key, BURN0_API_URL, originalFetch2));
|
|
918
|
+
}
|
|
919
|
+
Promise.all(promises).then((results) => {
|
|
920
|
+
if (results.every(Boolean)) {
|
|
921
|
+
ledger.markSynced();
|
|
922
|
+
}
|
|
923
|
+
}).catch(() => {
|
|
924
|
+
});
|
|
925
|
+
} catch {
|
|
926
|
+
}
|
|
927
|
+
}
|
|
857
928
|
var shouldWriteLedger = mode !== "test-disabled" && mode !== "prod-local";
|
|
858
929
|
var dispatch = createDispatcher(mode, {
|
|
859
930
|
logEvent: (e) => ticker.tick(e),
|
|
860
931
|
writeLedger: shouldWriteLedger ? (e) => ledger.write(e) : void 0,
|
|
861
|
-
addToBatch:
|
|
932
|
+
addToBatch: (e) => {
|
|
933
|
+
lateInit();
|
|
934
|
+
batch?.add(e);
|
|
935
|
+
}
|
|
862
936
|
});
|
|
863
937
|
var preloaded = checkImportOrder();
|
|
864
938
|
if (preloaded.length > 0) {
|
|
865
|
-
console.warn(
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
console.warn("[burn0] No API key \u2014 costs not tracked. Get one free at burn0.dev/api");
|
|
939
|
+
console.warn(
|
|
940
|
+
`[burn0] Warning: These SDKs were imported before burn0 and may not be tracked: ${preloaded.join(", ")}. Move \`import '@burn0/burn0'\` to the top of your entry file.`
|
|
941
|
+
);
|
|
869
942
|
}
|
|
870
|
-
if (canPatch() && mode !== "test-disabled"
|
|
943
|
+
if (canPatch() && mode !== "test-disabled") {
|
|
871
944
|
const onEvent = (event) => {
|
|
872
945
|
const enriched = enrichEvent(event);
|
|
873
946
|
dispatch(enriched);
|
package/dist/register.mjs
CHANGED