@burn0/burn0 0.2.5 → 0.2.6

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
@@ -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
- switch (mode2) {
401
- case "dev-local":
402
- deps.logEvent?.(event);
403
- deps.writeLedger?.(event);
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
 
@@ -535,10 +513,17 @@ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch)
535
513
  },
536
514
  body: JSON.stringify({ events, sdk_version: SDK_VERSION })
537
515
  });
538
- if (response.ok) return true;
516
+ if (response.ok) {
517
+ if (isDebug()) console.log(`[burn0] Shipped ${events.length} events`);
518
+ return true;
519
+ }
520
+ if (isDebug()) {
521
+ const body = await response.text().catch(() => "");
522
+ console.warn(`[burn0] Shipping rejected: ${response.status} ${body}`);
523
+ }
539
524
  } catch (err) {
540
525
  if (isDebug()) {
541
- console.warn("[burn0] Event shipping failed:", err.message);
526
+ console.warn("[burn0] Shipping failed:", err.message);
542
527
  }
543
528
  }
544
529
  if (attempt < maxAttempts - 1) {
@@ -796,31 +781,70 @@ try {
796
781
  }
797
782
  var ticker = createTicker({ todayCost, todayCalls, perServiceCosts });
798
783
  var batch = null;
799
- if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
800
- batch = new BatchBuffer({
784
+ var lateInitDone = false;
785
+ function createBatch(key) {
786
+ return new BatchBuffer({
801
787
  sizeThreshold: 50,
802
788
  timeThresholdMs: 1e4,
803
789
  maxSize: 500,
804
790
  onFlush: (events) => {
805
- shipEvents(events, apiKey, BURN0_API_URL, originalFetch2).catch(() => {
791
+ shipEvents(events, key, BURN0_API_URL, originalFetch2).catch(() => {
806
792
  });
807
793
  }
808
794
  });
809
795
  }
796
+ if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
797
+ batch = createBatch(apiKey);
798
+ }
799
+ var pendingEvents = [];
800
+ function lateInit(event) {
801
+ if (batch) {
802
+ return;
803
+ }
804
+ const lateKey = getApiKey();
805
+ if (!lateKey) {
806
+ if (event) pendingEvents.push(event);
807
+ if (!lateInitDone) {
808
+ lateInitDone = true;
809
+ setTimeout(() => {
810
+ lateInitDone = false;
811
+ if (pendingEvents.length > 0) {
812
+ const e = pendingEvents.shift();
813
+ lateInit(e);
814
+ } else {
815
+ lateInit();
816
+ }
817
+ }, 0);
818
+ }
819
+ return;
820
+ }
821
+ lateInitDone = true;
822
+ apiKey = lateKey;
823
+ mode = detectMode({ isTTY: isTTY(), apiKey });
824
+ batch = createBatch(lateKey);
825
+ fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
826
+ });
827
+ for (const e of pendingEvents) {
828
+ batch.add(e);
829
+ }
830
+ pendingEvents.length = 0;
831
+ }
810
832
  var shouldWriteLedger = mode !== "test-disabled" && mode !== "prod-local";
811
833
  var dispatch = createDispatcher(mode, {
812
834
  logEvent: (e) => ticker.tick(e),
813
835
  writeLedger: shouldWriteLedger ? (e) => ledger.write(e) : void 0,
814
- addToBatch: batch ? (e) => batch.add(e) : void 0
836
+ addToBatch: (e) => {
837
+ lateInit();
838
+ batch?.add(e);
839
+ }
815
840
  });
816
841
  var preloaded = checkImportOrder();
817
842
  if (preloaded.length > 0) {
818
- console.warn(`[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.`);
819
- }
820
- if (mode === "prod-local") {
821
- console.warn("[burn0] No API key \u2014 costs not tracked. Get one free at burn0.dev/api");
843
+ console.warn(
844
+ `[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.`
845
+ );
822
846
  }
823
- if (canPatch() && mode !== "test-disabled" && mode !== "prod-local") {
847
+ if (canPatch() && mode !== "test-disabled") {
824
848
  const onEvent = (event) => {
825
849
  const enriched = enrichEvent(event);
826
850
  dispatch(enriched);
@@ -846,4 +870,4 @@ export {
846
870
  startSpan,
847
871
  restore
848
872
  };
849
- //# sourceMappingURL=chunk-H3A5NM5C.mjs.map
873
+ //# sourceMappingURL=chunk-PPEPVHNM.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(source_default.bold(` Auto-detected ${allDetected.length} services:
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(` ${source_default.green(" \u2713")} ${svc.displayName.padEnd(20)} ${tag}`);
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({ name: svc.name, plan, monthlyCost: selected?.monthly });
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((s) => !alreadyAdded.has(s.name));
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 ? [{ name: source_default.bold.blue("\u2500\u2500 LLM Providers \u2500\u2500"), value: "__sep", disabled: true }] : [],
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 ? [{ name: source_default.bold.magenta("\u2500\u2500 API Services \u2500\u2500"), value: "__sep2", disabled: true }] : [],
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 ? [{ name: source_default.bold.yellow("\u2500\u2500 Infrastructure \u2500\u2500"), value: "__sep3", disabled: true }] : [],
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(import_node_fs5.default.readFileSync(import_node_path6.default.join(cwd, "package.json"), "utf-8"));
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 = process.env.BURN0_API_URL ?? "https://burn0-server-production.up.railway.app";
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
- "Authorization": `Bearer ${apiKey}`
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(source_default.green(" \u2713 Added .env to .gitignore (protects your API keys)"));
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"));
@@ -18483,7 +18515,20 @@ function formatCost(cost) {
18483
18515
  }
18484
18516
  function formatDateLabel(dateStr) {
18485
18517
  const d = /* @__PURE__ */ new Date(dateStr + "T12:00:00");
18486
- const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
18518
+ const months = [
18519
+ "Jan",
18520
+ "Feb",
18521
+ "Mar",
18522
+ "Apr",
18523
+ "May",
18524
+ "Jun",
18525
+ "Jul",
18526
+ "Aug",
18527
+ "Sep",
18528
+ "Oct",
18529
+ "Nov",
18530
+ "Dec"
18531
+ ];
18487
18532
  return `${months[d.getMonth()]} ${String(d.getDate()).padStart(2, " ")}`;
18488
18533
  }
18489
18534
  function makeBar(value, max, width) {
@@ -18512,10 +18557,12 @@ function aggregateLocal(events, days) {
18512
18557
  const estimate = estimateLocalCost(event);
18513
18558
  if (estimate.type === "priced" && estimate.cost > 0) {
18514
18559
  totalCost += estimate.cost;
18515
- if (!serviceCosts[event.service]) serviceCosts[event.service] = { cost: 0, calls: 0 };
18560
+ if (!serviceCosts[event.service])
18561
+ serviceCosts[event.service] = { cost: 0, calls: 0 };
18516
18562
  serviceCosts[event.service].cost += estimate.cost;
18517
18563
  serviceCosts[event.service].calls++;
18518
- if (!dayCosts[eventDateStr]) dayCosts[eventDateStr] = { cost: 0, calls: 0, services: {} };
18564
+ if (!dayCosts[eventDateStr])
18565
+ dayCosts[eventDateStr] = { cost: 0, calls: 0, services: {} };
18519
18566
  dayCosts[eventDateStr].cost += estimate.cost;
18520
18567
  dayCosts[eventDateStr].calls++;
18521
18568
  dayCosts[eventDateStr].services[event.service] = (dayCosts[eventDateStr].services[event.service] ?? 0) + estimate.cost;
@@ -18543,17 +18590,24 @@ function aggregateLocal(events, days) {
18543
18590
  }
18544
18591
  function renderCallCountOnly(data) {
18545
18592
  const maxCalls = data.allServiceCalls.length > 0 ? data.allServiceCalls[0].calls : 0;
18546
- const maxNameLen = Math.max(...data.allServiceCalls.map((s) => s.name.length), 8);
18593
+ const maxNameLen = Math.max(
18594
+ ...data.allServiceCalls.map((s) => s.name.length),
18595
+ 8
18596
+ );
18547
18597
  for (const svc of data.allServiceCalls) {
18548
18598
  const bar = makeBar(svc.calls, maxCalls, 20);
18549
- console.log(` ${svc.name.padEnd(maxNameLen)} ${source_default.gray(`${String(svc.calls).padStart(5)} calls`)} ${source_default.cyan(bar)}`);
18599
+ console.log(
18600
+ ` ${svc.name.padEnd(maxNameLen)} ${source_default.gray(`${String(svc.calls).padStart(5)} calls`)} ${source_default.cyan(bar)}`
18601
+ );
18550
18602
  }
18551
18603
  console.log();
18552
18604
  }
18553
18605
  function renderCostReport(data, label, showDaily, isToday) {
18554
- console.log(`
18606
+ console.log(
18607
+ `
18555
18608
  ${source_default.hex("#FA5D19").bold("burn0 report")} ${source_default.gray(`\u2500\u2500 ${label}`)}
18556
- `);
18609
+ `
18610
+ );
18557
18611
  if (data.total.calls === 0) {
18558
18612
  const msg = isToday ? "No calls today." : `No cost data yet. Run your app with \`import '@burn0/burn0'\` to start tracking.`;
18559
18613
  console.log(source_default.dim(` ${msg}
@@ -18561,50 +18615,70 @@ function renderCostReport(data, label, showDaily, isToday) {
18561
18615
  return;
18562
18616
  }
18563
18617
  if (!data.pricingAvailable) {
18564
- console.log(source_default.dim(` ${data.total.calls} calls tracked (pricing data not available)
18565
- `));
18618
+ console.log(
18619
+ source_default.dim(
18620
+ ` ${data.total.calls} calls tracked (pricing data not available)
18621
+ `
18622
+ )
18623
+ );
18566
18624
  renderCallCountOnly(data);
18567
18625
  return;
18568
18626
  }
18569
18627
  if (data.total.cost === 0 && data.total.calls > 0) {
18570
- console.log(source_default.dim(` ${data.total.calls} calls tracked (no pricing data available)
18571
- `));
18628
+ console.log(
18629
+ source_default.dim(
18630
+ ` ${data.total.calls} calls tracked (no pricing data available)
18631
+ `
18632
+ )
18633
+ );
18572
18634
  renderCallCountOnly(data);
18573
18635
  return;
18574
18636
  }
18575
- console.log(` ${source_default.bold("Total:")} ${source_default.green(formatCost(data.total.cost))} ${source_default.gray(`(${data.total.calls} calls)`)}
18576
- `);
18637
+ console.log(
18638
+ ` ${source_default.bold("Total:")} ${source_default.green(formatCost(data.total.cost))} ${source_default.gray(`(${data.total.calls} calls)`)}
18639
+ `
18640
+ );
18577
18641
  const maxCost = data.byService.length > 0 ? data.byService[0].cost : 0;
18578
18642
  const maxNameLen = Math.max(...data.byService.map((s) => s.name.length), 8);
18579
18643
  for (const svc of data.byService) {
18580
18644
  const pct = data.total.cost > 0 ? Math.round(svc.cost / data.total.cost * 100) : 0;
18581
18645
  const bar = makeBar(svc.cost, maxCost, 20);
18582
- console.log(` ${svc.name.padEnd(maxNameLen)} ${source_default.green(formatCost(svc.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.gray(`${String(pct).padStart(3)}%`)}`);
18646
+ console.log(
18647
+ ` ${svc.name.padEnd(maxNameLen)} ${source_default.green(formatCost(svc.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.gray(`${String(pct).padStart(3)}%`)}`
18648
+ );
18583
18649
  }
18584
18650
  if (data.unpricedCount > 0) {
18585
18651
  console.log(source_default.dim(`
18586
18652
  + ${data.unpricedCount} calls not priced`));
18587
18653
  }
18588
18654
  if (showDaily && data.byDay.length > 0) {
18589
- console.log(`
18655
+ console.log(
18656
+ `
18590
18657
  ${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
- `);
18658
+ `
18659
+ );
18592
18660
  const maxDayCost = Math.max(...data.byDay.map((d) => d.cost));
18593
18661
  for (const day of data.byDay) {
18594
18662
  const dateLabel = formatDateLabel(day.date);
18595
18663
  const bar = makeBar(day.cost, maxDayCost, 12);
18596
18664
  const top2 = day.topServices.slice(0, 2).map((s) => `${s.name} ${formatCost(s.cost)}`).join(" \xB7 ");
18597
18665
  const more = day.topServices.length > 2 ? ` +${day.topServices.length - 2} more` : "";
18598
- console.log(` ${source_default.gray(dateLabel)} ${source_default.green(formatCost(day.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.dim(top2 + more)}`);
18666
+ console.log(
18667
+ ` ${source_default.gray(dateLabel)} ${source_default.green(formatCost(day.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.dim(top2 + more)}`
18668
+ );
18599
18669
  }
18600
18670
  }
18601
18671
  if (data.total.cost > 0) {
18602
18672
  const daysInPeriod = showDaily ? 7 : 1;
18603
18673
  const dailyRate = data.total.cost / daysInPeriod;
18604
18674
  const monthly = dailyRate * 30;
18605
- console.log(`
18606
- ${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")}`);
18607
- console.log(` ${source_default.gray("~")}${source_default.green(formatCost(monthly))}${source_default.gray("/mo estimated")} ${source_default.dim(`(based on ${isToday ? "today" : "last 7 days"})`)}`);
18675
+ console.log(
18676
+ `
18677
+ ${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")}`
18678
+ );
18679
+ console.log(
18680
+ ` ${source_default.gray("~")}${source_default.green(formatCost(monthly))}${source_default.gray("/mo estimated")} ${source_default.dim(`(based on ${isToday ? "today" : "last 7 days"})`)}`
18681
+ );
18608
18682
  }
18609
18683
  console.log();
18610
18684
  }
@@ -18612,10 +18686,16 @@ async function fetchBackendReport(apiKey, days) {
18612
18686
  try {
18613
18687
  const controller = new AbortController();
18614
18688
  const timeout = setTimeout(() => controller.abort(), 5e3);
18615
- const response = await globalThis.fetch(`${BURN0_API_URL}/v1/report?days=${days}`, {
18616
- headers: { "Accept": "application/json", "Authorization": `Bearer ${apiKey}` },
18617
- signal: controller.signal
18618
- });
18689
+ const response = await globalThis.fetch(
18690
+ `${BURN0_API_URL}/v1/report?days=${days}`,
18691
+ {
18692
+ headers: {
18693
+ Accept: "application/json",
18694
+ Authorization: `Bearer ${apiKey}`
18695
+ },
18696
+ signal: controller.signal
18697
+ }
18698
+ );
18619
18699
  clearTimeout(timeout);
18620
18700
  if (!response.ok) return null;
18621
18701
  const data = await response.json();
@@ -18623,7 +18703,10 @@ async function fetchBackendReport(apiKey, days) {
18623
18703
  total: data.total ?? { cost: 0, calls: 0 },
18624
18704
  byService: data.byService ?? [],
18625
18705
  byDay: data.byDay ?? [],
18626
- allServiceCalls: (data.byService ?? []).map((s) => ({ name: s.name, calls: s.calls })),
18706
+ allServiceCalls: (data.byService ?? []).map((s) => ({
18707
+ name: s.name,
18708
+ calls: s.calls
18709
+ })),
18627
18710
  unpricedCount: 0,
18628
18711
  pricingAvailable: true
18629
18712
  };
@@ -18657,7 +18740,7 @@ var init_report = __esm({
18657
18740
  init_local();
18658
18741
  init_local_pricing();
18659
18742
  init_env();
18660
- BURN0_API_URL = process.env.BURN0_API_URL ?? "https://burn0-server-production.up.railway.app";
18743
+ BURN0_API_URL = "https://burn0-server-production.up.railway.app";
18661
18744
  }
18662
18745
  });
18663
18746
 
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
- switch (mode2) {
462
- case "dev-local":
463
- deps.logEvent?.(event);
464
- deps.writeLedger?.(event);
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
 
@@ -596,10 +574,17 @@ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch)
596
574
  },
597
575
  body: JSON.stringify({ events, sdk_version: SDK_VERSION })
598
576
  });
599
- if (response.ok) return true;
577
+ if (response.ok) {
578
+ if (isDebug()) console.log(`[burn0] Shipped ${events.length} events`);
579
+ return true;
580
+ }
581
+ if (isDebug()) {
582
+ const body = await response.text().catch(() => "");
583
+ console.warn(`[burn0] Shipping rejected: ${response.status} ${body}`);
584
+ }
600
585
  } catch (err) {
601
586
  if (isDebug()) {
602
- console.warn("[burn0] Event shipping failed:", err.message);
587
+ console.warn("[burn0] Shipping failed:", err.message);
603
588
  }
604
589
  }
605
590
  if (attempt < maxAttempts - 1) {
@@ -857,31 +842,70 @@ try {
857
842
  }
858
843
  var ticker = createTicker({ todayCost, todayCalls, perServiceCosts });
859
844
  var batch = null;
860
- if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
861
- batch = new BatchBuffer({
845
+ var lateInitDone = false;
846
+ function createBatch(key) {
847
+ return new BatchBuffer({
862
848
  sizeThreshold: 50,
863
849
  timeThresholdMs: 1e4,
864
850
  maxSize: 500,
865
851
  onFlush: (events) => {
866
- shipEvents(events, apiKey, BURN0_API_URL, originalFetch2).catch(() => {
852
+ shipEvents(events, key, BURN0_API_URL, originalFetch2).catch(() => {
867
853
  });
868
854
  }
869
855
  });
870
856
  }
857
+ if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
858
+ batch = createBatch(apiKey);
859
+ }
860
+ var pendingEvents = [];
861
+ function lateInit(event) {
862
+ if (batch) {
863
+ return;
864
+ }
865
+ const lateKey = getApiKey();
866
+ if (!lateKey) {
867
+ if (event) pendingEvents.push(event);
868
+ if (!lateInitDone) {
869
+ lateInitDone = true;
870
+ setTimeout(() => {
871
+ lateInitDone = false;
872
+ if (pendingEvents.length > 0) {
873
+ const e = pendingEvents.shift();
874
+ lateInit(e);
875
+ } else {
876
+ lateInit();
877
+ }
878
+ }, 0);
879
+ }
880
+ return;
881
+ }
882
+ lateInitDone = true;
883
+ apiKey = lateKey;
884
+ mode = detectMode({ isTTY: isTTY(), apiKey });
885
+ batch = createBatch(lateKey);
886
+ fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
887
+ });
888
+ for (const e of pendingEvents) {
889
+ batch.add(e);
890
+ }
891
+ pendingEvents.length = 0;
892
+ }
871
893
  var shouldWriteLedger = mode !== "test-disabled" && mode !== "prod-local";
872
894
  var dispatch = createDispatcher(mode, {
873
895
  logEvent: (e) => ticker.tick(e),
874
896
  writeLedger: shouldWriteLedger ? (e) => ledger.write(e) : void 0,
875
- addToBatch: batch ? (e) => batch.add(e) : void 0
897
+ addToBatch: (e) => {
898
+ lateInit();
899
+ batch?.add(e);
900
+ }
876
901
  });
877
902
  var preloaded = checkImportOrder();
878
903
  if (preloaded.length > 0) {
879
- console.warn(`[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.`);
880
- }
881
- if (mode === "prod-local") {
882
- console.warn("[burn0] No API key \u2014 costs not tracked. Get one free at burn0.dev/api");
904
+ console.warn(
905
+ `[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.`
906
+ );
883
907
  }
884
- if (canPatch() && mode !== "test-disabled" && mode !== "prod-local") {
908
+ if (canPatch() && mode !== "test-disabled") {
885
909
  const onEvent = (event) => {
886
910
  const enriched = enrichEvent(event);
887
911
  dispatch(enriched);
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  restore,
3
3
  startSpan,
4
4
  track
5
- } from "./chunk-H3A5NM5C.mjs";
5
+ } from "./chunk-PPEPVHNM.mjs";
6
6
  import "./chunk-DJ72YN4C.mjs";
7
7
  export {
8
8
  restore,
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
- switch (mode2) {
448
- case "dev-local":
449
- deps.logEvent?.(event);
450
- deps.writeLedger?.(event);
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
 
@@ -582,10 +560,17 @@ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch)
582
560
  },
583
561
  body: JSON.stringify({ events, sdk_version: SDK_VERSION })
584
562
  });
585
- if (response.ok) return true;
563
+ if (response.ok) {
564
+ if (isDebug()) console.log(`[burn0] Shipped ${events.length} events`);
565
+ return true;
566
+ }
567
+ if (isDebug()) {
568
+ const body = await response.text().catch(() => "");
569
+ console.warn(`[burn0] Shipping rejected: ${response.status} ${body}`);
570
+ }
586
571
  } catch (err) {
587
572
  if (isDebug()) {
588
- console.warn("[burn0] Event shipping failed:", err.message);
573
+ console.warn("[burn0] Shipping failed:", err.message);
589
574
  }
590
575
  }
591
576
  if (attempt < maxAttempts - 1) {
@@ -843,31 +828,70 @@ try {
843
828
  }
844
829
  var ticker = createTicker({ todayCost, todayCalls, perServiceCosts });
845
830
  var batch = null;
846
- if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
847
- batch = new BatchBuffer({
831
+ var lateInitDone = false;
832
+ function createBatch(key) {
833
+ return new BatchBuffer({
848
834
  sizeThreshold: 50,
849
835
  timeThresholdMs: 1e4,
850
836
  maxSize: 500,
851
837
  onFlush: (events) => {
852
- shipEvents(events, apiKey, BURN0_API_URL, originalFetch2).catch(() => {
838
+ shipEvents(events, key, BURN0_API_URL, originalFetch2).catch(() => {
853
839
  });
854
840
  }
855
841
  });
856
842
  }
843
+ if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
844
+ batch = createBatch(apiKey);
845
+ }
846
+ var pendingEvents = [];
847
+ function lateInit(event) {
848
+ if (batch) {
849
+ return;
850
+ }
851
+ const lateKey = getApiKey();
852
+ if (!lateKey) {
853
+ if (event) pendingEvents.push(event);
854
+ if (!lateInitDone) {
855
+ lateInitDone = true;
856
+ setTimeout(() => {
857
+ lateInitDone = false;
858
+ if (pendingEvents.length > 0) {
859
+ const e = pendingEvents.shift();
860
+ lateInit(e);
861
+ } else {
862
+ lateInit();
863
+ }
864
+ }, 0);
865
+ }
866
+ return;
867
+ }
868
+ lateInitDone = true;
869
+ apiKey = lateKey;
870
+ mode = detectMode({ isTTY: isTTY(), apiKey });
871
+ batch = createBatch(lateKey);
872
+ fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
873
+ });
874
+ for (const e of pendingEvents) {
875
+ batch.add(e);
876
+ }
877
+ pendingEvents.length = 0;
878
+ }
857
879
  var shouldWriteLedger = mode !== "test-disabled" && mode !== "prod-local";
858
880
  var dispatch = createDispatcher(mode, {
859
881
  logEvent: (e) => ticker.tick(e),
860
882
  writeLedger: shouldWriteLedger ? (e) => ledger.write(e) : void 0,
861
- addToBatch: batch ? (e) => batch.add(e) : void 0
883
+ addToBatch: (e) => {
884
+ lateInit();
885
+ batch?.add(e);
886
+ }
862
887
  });
863
888
  var preloaded = checkImportOrder();
864
889
  if (preloaded.length > 0) {
865
- console.warn(`[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.`);
866
- }
867
- if (mode === "prod-local") {
868
- console.warn("[burn0] No API key \u2014 costs not tracked. Get one free at burn0.dev/api");
890
+ console.warn(
891
+ `[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.`
892
+ );
869
893
  }
870
- if (canPatch() && mode !== "test-disabled" && mode !== "prod-local") {
894
+ if (canPatch() && mode !== "test-disabled") {
871
895
  const onEvent = (event) => {
872
896
  const enriched = enrichEvent(event);
873
897
  dispatch(enriched);
package/dist/register.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import "./chunk-H3A5NM5C.mjs";
1
+ import "./chunk-PPEPVHNM.mjs";
2
2
  import "./chunk-DJ72YN4C.mjs";
3
3
  //# sourceMappingURL=register.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burn0/burn0",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Lightweight cost observability for every API call in your stack",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",