@bitfab/sdk 0.36.6 → 0.36.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.
@@ -247,38 +247,63 @@ function toArrayBuffer(view) {
247
247
  view.byteOffset + view.byteLength
248
248
  );
249
249
  }
250
+ function compressedRequest(body, rawBytes, compressed) {
251
+ if (compressed.byteLength >= rawBytes) {
252
+ return { body, rawBytes, wireBytes: rawBytes };
253
+ }
254
+ return {
255
+ body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
256
+ contentEncoding: "gzip",
257
+ rawBytes,
258
+ wireBytes: compressed.byteLength
259
+ };
260
+ }
250
261
  async function gzipViaStream(bytes) {
251
262
  const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
252
263
  return await new Response(stream).arrayBuffer();
253
264
  }
254
265
  function encodeRequestBody(body) {
255
266
  if (readEnv(DISABLE_COMPRESSION_ENV)) {
256
- return { body };
267
+ const rawBytes = new TextEncoder().encode(body).byteLength;
268
+ return { body, rawBytes, wireBytes: rawBytes };
257
269
  }
258
270
  const bytes = new TextEncoder().encode(body);
259
271
  if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
260
- return { body };
272
+ return {
273
+ body,
274
+ rawBytes: bytes.byteLength,
275
+ wireBytes: bytes.byteLength
276
+ };
261
277
  }
262
278
  if (gzipNode) {
263
279
  return gzipNode(bytes).then(
264
- (compressed) => ({
265
- body: toArrayBuffer(compressed),
266
- contentEncoding: "gzip"
267
- }),
268
- () => ({ body })
280
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
281
+ () => ({
282
+ body,
283
+ rawBytes: bytes.byteLength,
284
+ wireBytes: bytes.byteLength
285
+ })
269
286
  );
270
287
  }
271
288
  if (typeof CompressionStream === "undefined") {
272
- return { body };
289
+ return {
290
+ body,
291
+ rawBytes: bytes.byteLength,
292
+ wireBytes: bytes.byteLength
293
+ };
273
294
  }
274
295
  return gzipViaStream(bytes).then(
275
- (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
276
- () => ({ body })
296
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
297
+ () => ({
298
+ body,
299
+ rawBytes: bytes.byteLength,
300
+ wireBytes: bytes.byteLength
301
+ })
277
302
  );
278
303
  }
279
304
 
280
305
  // src/version.generated.ts
281
- var __version__ = "0.36.6";
306
+ var __version__ = "0.36.7";
282
307
 
283
308
  // src/constants.ts
284
309
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -351,6 +376,7 @@ function runWithReplayContext(ctx, fn) {
351
376
 
352
377
  // src/payloadBudget.ts
353
378
  var MAX_SPAN_CARRIER_BYTES = 28e5;
379
+ var MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
354
380
  var textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
355
381
  function byteLength(value) {
356
382
  return textEncoder ? textEncoder.encode(value).length : value.length;
@@ -374,15 +400,15 @@ function carrierBytesOf(encoded, body) {
374
400
  return encoded.length + extra;
375
401
  }
376
402
  var MAX_BYTES_PER_UNIT = 3;
377
- function fitsCarrierBudget(body) {
403
+ function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
378
404
  const units = body.length;
379
- if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
405
+ if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
380
406
  return true;
381
407
  }
382
- if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
408
+ if (units + 2 > maxBytes) {
383
409
  return false;
384
410
  }
385
- return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
411
+ return carrierByteLength(body) <= maxBytes;
386
412
  }
387
413
  var STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
388
414
  "name",
@@ -432,7 +458,7 @@ function collectCandidates(containers) {
432
458
  }
433
459
  return candidates.sort((a, b) => b.size - a.size);
434
460
  }
435
- function trimPayloadToBudget(payload, encode) {
461
+ function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
436
462
  const { copy, containers } = cloneTrimmable(payload);
437
463
  const candidates = collectCandidates(containers);
438
464
  if (candidates.length === 0) {
@@ -448,20 +474,20 @@ function trimPayloadToBudget(payload, encode) {
448
474
  } catch {
449
475
  return void 0;
450
476
  }
451
- if (fitsCarrierBudget(body)) {
477
+ if (fitsCarrierBudget(body, maxBytes)) {
452
478
  return { value: copy, trimmed };
453
479
  }
454
480
  }
455
481
  return void 0;
456
482
  }
457
- function markPayloadTrimmed(value, trimmed) {
483
+ function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
458
484
  const existing = Array.isArray(value.errors) ? value.errors : [];
459
485
  value.errors = [
460
486
  ...existing,
461
487
  {
462
488
  source: "sdk",
463
489
  step: "payload_budget",
464
- error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
490
+ error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
465
491
  ...new Set(trimmed)
466
492
  ].join(", ")}`
467
493
  }
@@ -482,30 +508,31 @@ function warnOnce(key, message) {
482
508
  }
483
509
 
484
510
  // src/serializePayload.ts
485
- function serializePayloadBody(payload) {
511
+ function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
486
512
  const encoded = encodePayloadBody(payload);
487
- if (fitsCarrierBudget(encoded.body)) {
513
+ if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
488
514
  return { body: encoded.body, dropped: encoded.dropped };
489
515
  }
490
- return applyPayloadBudget(encoded);
516
+ return applyPayloadBudget(encoded, maxCarrierBytes);
491
517
  }
492
- function applyPayloadBudget(encoded) {
518
+ function applyPayloadBudget(encoded, maxCarrierBytes) {
493
519
  const result = encoded.value ? trimPayloadToBudget(
494
520
  encoded.value,
495
- (value) => encodePayloadBody(value).body
521
+ (value) => encodePayloadBody(value).body,
522
+ maxCarrierBytes
496
523
  ) : void 0;
497
524
  if (!result) {
498
525
  return { body: encoded.body, dropped: encoded.dropped };
499
526
  }
500
527
  warnOnce(
501
528
  "payload:over-budget",
502
- `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
529
+ `a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
503
530
  ...new Set(result.trimmed)
504
531
  ].join(
505
532
  ", "
506
533
  )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
507
534
  );
508
- markPayloadTrimmed(result.value, result.trimmed);
535
+ markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
509
536
  return {
510
537
  body: encodePayloadBody(result.value).body,
511
538
  dropped: encoded.dropped
@@ -629,6 +656,7 @@ var OPERATION_ATTRIBUTE = "bitfab.operation";
629
656
  var PAYLOAD_ATTRIBUTE = "bitfab.payload";
630
657
  var OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
631
658
  var MAX_EXPORT_REQUEST_BYTES = 3e6;
659
+ var MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
632
660
  var MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
633
661
  var EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
634
662
  var MAX_QUEUE_SIZE = 8192;
@@ -784,6 +812,31 @@ function encodeSpan(span) {
784
812
  const json = JSON.stringify(spanToOtlp(span));
785
813
  return { json, size: byteLength(json) };
786
814
  }
815
+ function trimEncodedSpan(span) {
816
+ try {
817
+ const carrier = JSON.parse(span.json);
818
+ const attribute = carrier.attributes?.find(
819
+ (entry) => entry.key === PAYLOAD_ATTRIBUTE
820
+ );
821
+ const payloadBody = attribute?.value?.stringValue;
822
+ if (!attribute?.value || payloadBody === void 0) {
823
+ return void 0;
824
+ }
825
+ const payload = JSON.parse(payloadBody);
826
+ attribute.value.stringValue = serializePayloadBody(
827
+ payload,
828
+ MAX_SPAN_CARRIER_BYTES
829
+ ).body;
830
+ const json = JSON.stringify(carrier);
831
+ return { json, size: byteLength(json) };
832
+ } catch {
833
+ return void 0;
834
+ }
835
+ }
836
+ async function prepareRequest(body) {
837
+ const prepared = encodeRequestBody(body);
838
+ return prepared instanceof Promise ? await prepared : prepared;
839
+ }
787
840
  function requestEnvelope(first) {
788
841
  const scope = first.instrumentationScope;
789
842
  const resource = JSON.stringify({
@@ -914,15 +967,43 @@ var BitfabSpanExporter = class {
914
967
  return batches;
915
968
  }
916
969
  async send(envelope, batch) {
917
- if (batch.size > this.maxRequestBytes) {
918
- logError(
919
- "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
920
- );
921
- return false;
922
- }
923
970
  try {
924
- await this.sendWithRetries(encodeRequest(envelope, batch.spans));
925
- return true;
971
+ let requestSpans = batch.spans;
972
+ let requestRawBytes = batch.size;
973
+ let alreadyTrimmed = false;
974
+ while (true) {
975
+ if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
976
+ const prepared = await prepareRequest(
977
+ encodeRequest(envelope, requestSpans)
978
+ );
979
+ if (prepared.wireBytes <= this.maxRequestBytes) {
980
+ await this.sendWithRetries(prepared);
981
+ return true;
982
+ }
983
+ }
984
+ if (batch.spans.length !== 1) {
985
+ logError(
986
+ "an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
987
+ );
988
+ return false;
989
+ }
990
+ if (alreadyTrimmed) {
991
+ logError(
992
+ "a single OpenTelemetry span exceeded the configured request-size target after trimming"
993
+ );
994
+ return false;
995
+ }
996
+ const trimmed = trimEncodedSpan(batch.spans[0]);
997
+ if (!trimmed) {
998
+ logError(
999
+ "a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
1000
+ );
1001
+ return false;
1002
+ }
1003
+ requestSpans = [trimmed];
1004
+ requestRawBytes = envelope.size + trimmed.size;
1005
+ alreadyTrimmed = true;
1006
+ }
926
1007
  } catch (error) {
927
1008
  if (error instanceof OtlpPayloadTooLargeError) {
928
1009
  logError(
@@ -950,12 +1031,12 @@ var BitfabSpanExporter = class {
950
1031
  * the server does not yet understand. The fix is a client-supplied
951
1032
  * idempotency key that ingestion dedupes on.
952
1033
  */
953
- async sendWithRetries(body) {
1034
+ async sendWithRetries(request) {
954
1035
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
955
1036
  try {
956
1037
  const response = await this.directSender(
957
1038
  OTLP_TRACES_ENDPOINT,
958
- body,
1039
+ request,
959
1040
  EXPORT_TIMEOUT_MILLIS
960
1041
  );
961
1042
  const partialSuccess = asRecord2(response?.partialSuccess);
@@ -1072,7 +1153,10 @@ var OtelBatchTransport = class {
1072
1153
  return;
1073
1154
  }
1074
1155
  try {
1075
- const { body, dropped } = serializePayloadBody(payload);
1156
+ const { body, dropped } = serializePayloadBody(
1157
+ payload,
1158
+ MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
1159
+ );
1076
1160
  if (dropped.length > 0) {
1077
1161
  warnOnce(
1078
1162
  "otel-carrier-payload-stubbed",
@@ -1311,7 +1395,7 @@ var HttpClient = class {
1311
1395
  }
1312
1396
  if (!this.traceTransport) {
1313
1397
  this.traceTransport = createTraceTransport({
1314
- directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1398
+ directSender: (endpoint, request, timeoutMs) => this.sendPrepared(endpoint, request, {
1315
1399
  timeout: timeoutMs
1316
1400
  })
1317
1401
  });
@@ -1398,13 +1482,16 @@ var HttpClient = class {
1398
1482
  * same data twice.
1399
1483
  */
1400
1484
  async sendEncoded(endpoint, body, options) {
1485
+ const prepared = encodeRequestBody(body);
1486
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1487
+ return this.sendPrepared(endpoint, encoded, options);
1488
+ }
1489
+ async sendPrepared(endpoint, encoded, options) {
1401
1490
  const url = `${this.serviceUrl}${endpoint}`;
1402
1491
  const timeout = options?.timeout ?? this.timeout;
1403
1492
  const method = options?.method ?? "POST";
1404
1493
  const controller = new AbortController();
1405
1494
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1406
- const prepared = encodeRequestBody(body);
1407
- const encoded = prepared instanceof Promise ? await prepared : prepared;
1408
1495
  const headers = {
1409
1496
  "Content-Type": "application/json",
1410
1497
  Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
@@ -1767,8 +1854,8 @@ function fallbackUuidV4() {
1767
1854
 
1768
1855
  // src/serialize.ts
1769
1856
  import superjson from "superjson";
1770
- var MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1771
- var MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1857
+ var MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1858
+ var MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1772
1859
  function describeValue(value) {
1773
1860
  try {
1774
1861
  const ctorName = value?.constructor?.name;
@@ -2549,4 +2636,4 @@ export {
2549
2636
  serializeReplayResult,
2550
2637
  replay
2551
2638
  };
2552
- //# sourceMappingURL=chunk-ENOQ2K2H.js.map
2639
+ //# sourceMappingURL=chunk-CFECTUDU.js.map