bitfab 0.34.2 → 0.36.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/dist/index.cjs CHANGED
@@ -51,7 +51,7 @@ var __version__;
51
51
  var init_version_generated = __esm({
52
52
  "src/version.generated.ts"() {
53
53
  "use strict";
54
- __version__ = "0.34.2";
54
+ __version__ = "0.36.0";
55
55
  }
56
56
  });
57
57
 
@@ -65,6 +65,87 @@ var init_constants = __esm({
65
65
  }
66
66
  });
67
67
 
68
+ // src/readEnv.ts
69
+ function readEnv(name) {
70
+ if (typeof process !== "undefined" && process.env) {
71
+ return process.env[name];
72
+ }
73
+ return void 0;
74
+ }
75
+ var init_readEnv = __esm({
76
+ "src/readEnv.ts"() {
77
+ "use strict";
78
+ }
79
+ });
80
+
81
+ // src/compress.ts
82
+ function toArrayBuffer(view) {
83
+ return view.buffer.slice(
84
+ view.byteOffset,
85
+ view.byteOffset + view.byteLength
86
+ );
87
+ }
88
+ async function gzipViaStream(bytes) {
89
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
90
+ return await new Response(stream).arrayBuffer();
91
+ }
92
+ function encodeRequestBody(body) {
93
+ if (readEnv(DISABLE_COMPRESSION_ENV)) {
94
+ return { body };
95
+ }
96
+ const bytes = new TextEncoder().encode(body);
97
+ if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
98
+ return { body };
99
+ }
100
+ if (gzipNode) {
101
+ return gzipNode(bytes).then(
102
+ (compressed) => ({
103
+ body: toArrayBuffer(compressed),
104
+ contentEncoding: "gzip"
105
+ }),
106
+ () => ({ body })
107
+ );
108
+ }
109
+ if (typeof CompressionStream === "undefined") {
110
+ return { body };
111
+ }
112
+ return gzipViaStream(bytes).then(
113
+ (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
114
+ () => ({ body })
115
+ );
116
+ }
117
+ var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
118
+ var init_compress = __esm({
119
+ "src/compress.ts"() {
120
+ "use strict";
121
+ init_readEnv();
122
+ DISABLE_COMPRESSION_ENV = "BITFAB_DISABLE_COMPRESSION";
123
+ MIN_COMPRESSED_BYTES = 8192;
124
+ _nodeGzipReady = (typeof process !== "undefined" && process.versions?.node ? (
125
+ // The join trick hides "node:zlib" from static analysis so bundlers that
126
+ // ban Node.js built-ins don't fail at build time. webpackIgnore tells
127
+ // webpack/turbopack to emit a native import() so Node.js can resolve the
128
+ // module at runtime. Same pattern as `asyncStorage.ts`.
129
+ import(
130
+ /* webpackIgnore: true */
131
+ ["node", "zlib"].join(":")
132
+ ).then(({ gzip }) => {
133
+ gzipNode = (data) => new Promise((resolve, reject) => {
134
+ gzip(data, (error, result) => {
135
+ if (error) {
136
+ reject(error);
137
+ } else {
138
+ resolve(result);
139
+ }
140
+ });
141
+ });
142
+ }).catch(() => {
143
+ })
144
+ ) : Promise.resolve()).then(() => {
145
+ });
146
+ }
147
+ });
148
+
68
149
  // src/errors.ts
69
150
  var BitfabError;
70
151
  var init_errors = __esm({
@@ -153,6 +234,131 @@ var init_replayContext = __esm({
153
234
  }
154
235
  });
155
236
 
237
+ // src/payloadBudget.ts
238
+ function byteLength(value) {
239
+ return textEncoder ? textEncoder.encode(value).length : value.length;
240
+ }
241
+ function carrierByteLength(body) {
242
+ return carrierBytesOf(textEncoder ? textEncoder.encode(body) : null, body);
243
+ }
244
+ function carrierBytesOf(encoded, body) {
245
+ if (!encoded) {
246
+ return body.length + 2;
247
+ }
248
+ let extra = 2;
249
+ for (let i = 0; i < encoded.length; i++) {
250
+ const byte = encoded[i];
251
+ if (byte === 34 || byte === 92) {
252
+ extra += 1;
253
+ } else if (byte < 32) {
254
+ extra += byte === 8 || byte === 9 || byte === 10 || byte === 12 || byte === 13 ? 1 : 5;
255
+ }
256
+ }
257
+ return encoded.length + extra;
258
+ }
259
+ function fitsCarrierBudget(body) {
260
+ const units = body.length;
261
+ if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
262
+ return true;
263
+ }
264
+ if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
265
+ return false;
266
+ }
267
+ return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
268
+ }
269
+ function asRecord(value) {
270
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
271
+ }
272
+ function cloneTrimmable(payload) {
273
+ const copy = { ...payload };
274
+ const containers = [];
275
+ const spanData = asRecord(copy.span_data);
276
+ if (spanData) {
277
+ const clone = { ...spanData };
278
+ copy.span_data = clone;
279
+ containers.push(clone);
280
+ }
281
+ const rawSpan = asRecord(copy.rawSpan);
282
+ const rawSpanData = rawSpan && asRecord(rawSpan.span_data);
283
+ if (rawSpan && rawSpanData) {
284
+ const clone = { ...rawSpanData };
285
+ copy.rawSpan = { ...rawSpan, span_data: clone };
286
+ containers.push(clone);
287
+ }
288
+ if (containers.length === 0) {
289
+ containers.push(copy);
290
+ }
291
+ return { copy, containers };
292
+ }
293
+ function collectCandidates(containers) {
294
+ const candidates = [];
295
+ for (const container of containers) {
296
+ for (const [key, value] of Object.entries(container)) {
297
+ if (STRUCTURAL_SPAN_KEYS.has(key) || value == null) {
298
+ continue;
299
+ }
300
+ let size;
301
+ try {
302
+ size = byteLength(JSON.stringify(value) ?? "");
303
+ } catch {
304
+ continue;
305
+ }
306
+ candidates.push({ container, key, size });
307
+ }
308
+ }
309
+ return candidates.sort((a, b) => b.size - a.size);
310
+ }
311
+ function trimPayloadToBudget(payload, encode) {
312
+ const { copy, containers } = cloneTrimmable(payload);
313
+ const candidates = collectCandidates(containers);
314
+ if (candidates.length === 0) {
315
+ return void 0;
316
+ }
317
+ const trimmed = [];
318
+ for (const candidate of candidates) {
319
+ candidate.container[candidate.key] = `<unserializable: too_large_${candidate.size}_bytes>`;
320
+ trimmed.push(candidate.key);
321
+ let body;
322
+ try {
323
+ body = encode(copy);
324
+ } catch {
325
+ return void 0;
326
+ }
327
+ if (fitsCarrierBudget(body)) {
328
+ return { value: copy, trimmed };
329
+ }
330
+ }
331
+ return void 0;
332
+ }
333
+ function markPayloadTrimmed(value, trimmed) {
334
+ const existing = Array.isArray(value.errors) ? value.errors : [];
335
+ value.errors = [
336
+ ...existing,
337
+ {
338
+ source: "sdk",
339
+ step: "payload_budget",
340
+ error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
341
+ ...new Set(trimmed)
342
+ ].join(", ")}`
343
+ }
344
+ ];
345
+ }
346
+ var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
347
+ var init_payloadBudget = __esm({
348
+ "src/payloadBudget.ts"() {
349
+ "use strict";
350
+ MAX_SPAN_CARRIER_BYTES = 28e5;
351
+ textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
352
+ MAX_BYTES_PER_UNIT = 3;
353
+ STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
354
+ "name",
355
+ "type",
356
+ "function_name",
357
+ "error_source"
358
+ ]);
359
+ }
360
+ });
361
+
156
362
  // src/warnOnce.ts
157
363
  function warnOnce(key, message) {
158
364
  if (warned.has(key)) {
@@ -174,8 +380,37 @@ var init_warnOnce = __esm({
174
380
 
175
381
  // src/serializePayload.ts
176
382
  function serializePayloadBody(payload) {
383
+ const encoded = encodePayloadBody(payload);
384
+ if (fitsCarrierBudget(encoded.body)) {
385
+ return { body: encoded.body, dropped: encoded.dropped };
386
+ }
387
+ return applyPayloadBudget(encoded);
388
+ }
389
+ function applyPayloadBudget(encoded) {
390
+ const result = encoded.value ? trimPayloadToBudget(
391
+ encoded.value,
392
+ (value) => encodePayloadBody(value).body
393
+ ) : void 0;
394
+ if (!result) {
395
+ return { body: encoded.body, dropped: encoded.dropped };
396
+ }
397
+ warnOnce(
398
+ "payload:over-budget",
399
+ `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
400
+ ...new Set(result.trimmed)
401
+ ].join(
402
+ ", "
403
+ )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
404
+ );
405
+ markPayloadTrimmed(result.value, result.trimmed);
406
+ return {
407
+ body: encodePayloadBody(result.value).body,
408
+ dropped: encoded.dropped
409
+ };
410
+ }
411
+ function encodePayloadBody(payload) {
177
412
  try {
178
- return { body: JSON.stringify(payload), dropped: [] };
413
+ return { body: JSON.stringify(payload), dropped: [], value: payload };
179
414
  } catch {
180
415
  const dropped = [];
181
416
  const sanitize = (value, seen) => {
@@ -240,12 +475,11 @@ function serializePayloadBody(payload) {
240
475
  sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
241
476
  } catch (error) {
242
477
  const message = error instanceof Error ? error.message : String(error);
243
- return {
244
- body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
245
- dropped
246
- };
478
+ const marker = { error: `payload_serialize_failed: ${message}` };
479
+ return { body: JSON.stringify(marker), dropped, value: marker };
247
480
  }
248
- if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
481
+ const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
482
+ if (dropped.length > 0 && isRecord) {
249
483
  const obj = sanitized;
250
484
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
251
485
  obj.errors = [
@@ -259,29 +493,21 @@ function serializePayloadBody(payload) {
259
493
  }
260
494
  ];
261
495
  }
262
- return { body: JSON.stringify(sanitized), dropped };
496
+ return {
497
+ body: JSON.stringify(sanitized),
498
+ dropped,
499
+ value: isRecord ? sanitized : void 0
500
+ };
263
501
  }
264
502
  }
265
503
  var init_serializePayload = __esm({
266
504
  "src/serializePayload.ts"() {
267
505
  "use strict";
506
+ init_payloadBudget();
268
507
  init_warnOnce();
269
508
  }
270
509
  });
271
510
 
272
- // src/readEnv.ts
273
- function readEnv(name) {
274
- if (typeof process !== "undefined" && process.env) {
275
- return process.env[name];
276
- }
277
- return void 0;
278
- }
279
- var init_readEnv = __esm({
280
- "src/readEnv.ts"() {
281
- "use strict";
282
- }
283
- });
284
-
285
511
  // src/unrefTimer.ts
286
512
  function unrefTimer(timer) {
287
513
  const handle = timer;
@@ -327,7 +553,7 @@ function recordTraceSubmission(operation, payload) {
327
553
  return;
328
554
  }
329
555
  if (operation === "external_span") {
330
- const rawSpan = asRecord(payload.rawSpan);
556
+ const rawSpan = asRecord2(payload.rawSpan);
331
557
  if (typeof rawSpan?.id !== "string") {
332
558
  submissionCounter += 1;
333
559
  }
@@ -364,14 +590,14 @@ function takeReplaySpanCounts(traceIds) {
364
590
  }
365
591
  return counts;
366
592
  }
367
- function asRecord(value) {
593
+ function asRecord2(value) {
368
594
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
369
595
  }
370
596
  function resolveSourceTraceId(payload) {
371
597
  if (typeof payload.sourceTraceId === "string") {
372
598
  return payload.sourceTraceId;
373
599
  }
374
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
600
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
375
601
  return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
376
602
  }
377
603
  function otlpValue(value) {
@@ -429,9 +655,6 @@ function spanToOtlp(span) {
429
655
  }
430
656
  return result;
431
657
  }
432
- function byteLength(value) {
433
- return textEncoder ? textEncoder.encode(value).length : value.length;
434
- }
435
658
  function encodeSpan(span) {
436
659
  const json = JSON.stringify(spanToOtlp(span));
437
660
  return { json, size: byteLength(json) };
@@ -507,7 +730,7 @@ function endSpan(span, endTime) {
507
730
  }
508
731
  function spanName(operation, payload) {
509
732
  if (operation === "external_span") {
510
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
733
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
511
734
  if (typeof spanData?.name === "string") {
512
735
  return spanData.name;
513
736
  }
@@ -518,8 +741,8 @@ function spanName(operation, payload) {
518
741
  return `bitfab.${operation}`;
519
742
  }
520
743
  function payloadTimestamp(payload, field) {
521
- const rawSpan = asRecord(payload.rawSpan);
522
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
744
+ const rawSpan = asRecord2(payload.rawSpan);
745
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
523
746
  const raw = rawSpan?.[field] ?? rawTrace?.[field];
524
747
  if (typeof raw !== "string") {
525
748
  return void 0;
@@ -528,7 +751,7 @@ function payloadTimestamp(payload, field) {
528
751
  return Number.isNaN(parsed) ? void 0 : parsed;
529
752
  }
530
753
  function hasError(payload) {
531
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
754
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
532
755
  if (spanData?.error != null) {
533
756
  return true;
534
757
  }
@@ -572,7 +795,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
572
795
  (transport, remaining) => transport.shutdown(remaining)
573
796
  );
574
797
  }
575
- var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, OTLP_TRACES_ENDPOINT, MAX_EXPORT_REQUEST_BYTES, MAX_REQUEST_BYTES_ENV, EXPORT_CONCURRENCY_ENV, MAX_QUEUE_SIZE, DIRECT_MAX_EXPORT_BATCH_SIZE, DIRECT_MAX_REQUEST_BATCH_SIZE, DEFAULT_EXPORT_CONCURRENCY, MAX_EXPORT_CONCURRENCY, SCHEDULE_DELAY_MILLIS, EXPORT_TIMEOUT_MILLIS, RETRY_DELAY_MILLIS, MAX_SEND_ATTEMPTS, DEFAULT_LIFECYCLE_TIMEOUT_MS, RETRYABLE_STATUSES, liveTransports, traceSubmissionSpanIds, replayTraceSubmissions, submissionCounter, textEncoder, SPAN_SEPARATOR_BYTES, OtlpPayloadTooLargeError, OtlpPartialSuccessError, BitfabSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
798
+ var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, OTLP_TRACES_ENDPOINT, MAX_EXPORT_REQUEST_BYTES, MAX_REQUEST_BYTES_ENV, EXPORT_CONCURRENCY_ENV, MAX_QUEUE_SIZE, DIRECT_MAX_EXPORT_BATCH_SIZE, DIRECT_MAX_REQUEST_BATCH_SIZE, DEFAULT_EXPORT_CONCURRENCY, MAX_EXPORT_CONCURRENCY, SCHEDULE_DELAY_MILLIS, EXPORT_TIMEOUT_MILLIS, RETRY_DELAY_MILLIS, MAX_SEND_ATTEMPTS, DEFAULT_LIFECYCLE_TIMEOUT_MS, RETRYABLE_STATUSES, liveTransports, traceSubmissionSpanIds, replayTraceSubmissions, submissionCounter, SPAN_SEPARATOR_BYTES, OtlpPayloadTooLargeError, OtlpPartialSuccessError, BitfabSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
576
799
  var init_otel = __esm({
577
800
  "src/otel.ts"() {
578
801
  "use strict";
@@ -582,6 +805,7 @@ var init_otel = __esm({
582
805
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
583
806
  init_constants();
584
807
  init_errors();
808
+ init_payloadBudget();
585
809
  init_readEnv();
586
810
  init_serializePayload();
587
811
  init_unrefTimer();
@@ -607,7 +831,6 @@ var init_otel = __esm({
607
831
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
608
832
  replayTraceSubmissions = /* @__PURE__ */ new Set();
609
833
  submissionCounter = 0;
610
- textEncoder = typeof TextEncoder === "undefined" ? void 0 : new TextEncoder();
611
834
  SPAN_SEPARATOR_BYTES = 1;
612
835
  OtlpPayloadTooLargeError = class extends Error {
613
836
  };
@@ -717,7 +940,7 @@ var init_otel = __esm({
717
940
  body,
718
941
  EXPORT_TIMEOUT_MILLIS
719
942
  );
720
- const partialSuccess = asRecord(response?.partialSuccess);
943
+ const partialSuccess = asRecord2(response?.partialSuccess);
721
944
  const rejected = partialSuccess?.rejectedSpans;
722
945
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
723
946
  logError(
@@ -955,6 +1178,7 @@ var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCL
955
1178
  var init_http = __esm({
956
1179
  "src/http.ts"() {
957
1180
  "use strict";
1181
+ init_compress();
958
1182
  init_constants();
959
1183
  init_errors();
960
1184
  init_replayContext();
@@ -1111,14 +1335,20 @@ var init_http = __esm({
1111
1335
  const method = options?.method ?? "POST";
1112
1336
  const controller = new AbortController();
1113
1337
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1338
+ const prepared = encodeRequestBody(body);
1339
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1340
+ const headers = {
1341
+ "Content-Type": "application/json",
1342
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1343
+ };
1344
+ if (encoded.contentEncoding) {
1345
+ headers["Content-Encoding"] = encoded.contentEncoding;
1346
+ }
1114
1347
  try {
1115
1348
  const response = await fetch(url, {
1116
1349
  method,
1117
- headers: {
1118
- "Content-Type": "application/json",
1119
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1120
- },
1121
- body,
1350
+ headers,
1351
+ body: encoded.body,
1122
1352
  signal: controller.signal
1123
1353
  });
1124
1354
  if (!response.ok) {
@@ -1567,9 +1797,10 @@ var init_serialize = __esm({
1567
1797
  "src/serialize.ts"() {
1568
1798
  "use strict";
1569
1799
  import_superjson = __toESM(require("superjson"), 1);
1800
+ init_payloadBudget();
1570
1801
  init_warnOnce();
1571
- MAX_SERIALIZED_BYTES = 512e3;
1572
- MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
1802
+ MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1803
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1573
1804
  MAX_SAFE_DEPTH = 6;
1574
1805
  }
1575
1806
  });