@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.
package/dist/index.cjs CHANGED
@@ -42,7 +42,7 @@ var __version__;
42
42
  var init_version_generated = __esm({
43
43
  "src/version.generated.ts"() {
44
44
  "use strict";
45
- __version__ = "0.36.6";
45
+ __version__ = "0.36.7";
46
46
  }
47
47
  });
48
48
 
@@ -76,33 +76,58 @@ function toArrayBuffer(view) {
76
76
  view.byteOffset + view.byteLength
77
77
  );
78
78
  }
79
+ function compressedRequest(body, rawBytes, compressed) {
80
+ if (compressed.byteLength >= rawBytes) {
81
+ return { body, rawBytes, wireBytes: rawBytes };
82
+ }
83
+ return {
84
+ body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
85
+ contentEncoding: "gzip",
86
+ rawBytes,
87
+ wireBytes: compressed.byteLength
88
+ };
89
+ }
79
90
  async function gzipViaStream(bytes) {
80
91
  const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
81
92
  return await new Response(stream).arrayBuffer();
82
93
  }
83
94
  function encodeRequestBody(body) {
84
95
  if (readEnv(DISABLE_COMPRESSION_ENV)) {
85
- return { body };
96
+ const rawBytes = new TextEncoder().encode(body).byteLength;
97
+ return { body, rawBytes, wireBytes: rawBytes };
86
98
  }
87
99
  const bytes = new TextEncoder().encode(body);
88
100
  if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
89
- return { body };
101
+ return {
102
+ body,
103
+ rawBytes: bytes.byteLength,
104
+ wireBytes: bytes.byteLength
105
+ };
90
106
  }
91
107
  if (gzipNode) {
92
108
  return gzipNode(bytes).then(
93
- (compressed) => ({
94
- body: toArrayBuffer(compressed),
95
- contentEncoding: "gzip"
96
- }),
97
- () => ({ body })
109
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
110
+ () => ({
111
+ body,
112
+ rawBytes: bytes.byteLength,
113
+ wireBytes: bytes.byteLength
114
+ })
98
115
  );
99
116
  }
100
117
  if (typeof CompressionStream === "undefined") {
101
- return { body };
118
+ return {
119
+ body,
120
+ rawBytes: bytes.byteLength,
121
+ wireBytes: bytes.byteLength
122
+ };
102
123
  }
103
124
  return gzipViaStream(bytes).then(
104
- (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
105
- () => ({ body })
125
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
126
+ () => ({
127
+ body,
128
+ rawBytes: bytes.byteLength,
129
+ wireBytes: bytes.byteLength
130
+ })
106
131
  );
107
132
  }
108
133
  var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
@@ -247,15 +272,15 @@ function carrierBytesOf(encoded, body) {
247
272
  }
248
273
  return encoded.length + extra;
249
274
  }
250
- function fitsCarrierBudget(body) {
275
+ function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
251
276
  const units = body.length;
252
- if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
277
+ if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
253
278
  return true;
254
279
  }
255
- if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
280
+ if (units + 2 > maxBytes) {
256
281
  return false;
257
282
  }
258
- return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
283
+ return carrierByteLength(body) <= maxBytes;
259
284
  }
260
285
  function asRecord(value) {
261
286
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -299,7 +324,7 @@ function collectCandidates(containers) {
299
324
  }
300
325
  return candidates.sort((a, b) => b.size - a.size);
301
326
  }
302
- function trimPayloadToBudget(payload, encode) {
327
+ function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
303
328
  const { copy, containers } = cloneTrimmable(payload);
304
329
  const candidates = collectCandidates(containers);
305
330
  if (candidates.length === 0) {
@@ -315,30 +340,31 @@ function trimPayloadToBudget(payload, encode) {
315
340
  } catch {
316
341
  return void 0;
317
342
  }
318
- if (fitsCarrierBudget(body)) {
343
+ if (fitsCarrierBudget(body, maxBytes)) {
319
344
  return { value: copy, trimmed };
320
345
  }
321
346
  }
322
347
  return void 0;
323
348
  }
324
- function markPayloadTrimmed(value, trimmed) {
349
+ function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
325
350
  const existing = Array.isArray(value.errors) ? value.errors : [];
326
351
  value.errors = [
327
352
  ...existing,
328
353
  {
329
354
  source: "sdk",
330
355
  step: "payload_budget",
331
- error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
356
+ error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
332
357
  ...new Set(trimmed)
333
358
  ].join(", ")}`
334
359
  }
335
360
  ];
336
361
  }
337
- var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
362
+ var MAX_SPAN_CARRIER_BYTES, MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
338
363
  var init_payloadBudget = __esm({
339
364
  "src/payloadBudget.ts"() {
340
365
  "use strict";
341
366
  MAX_SPAN_CARRIER_BYTES = 28e5;
367
+ MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
342
368
  textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
343
369
  MAX_BYTES_PER_UNIT = 3;
344
370
  STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
@@ -370,30 +396,31 @@ var init_warnOnce = __esm({
370
396
  });
371
397
 
372
398
  // src/serializePayload.ts
373
- function serializePayloadBody(payload) {
399
+ function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
374
400
  const encoded = encodePayloadBody(payload);
375
- if (fitsCarrierBudget(encoded.body)) {
401
+ if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
376
402
  return { body: encoded.body, dropped: encoded.dropped };
377
403
  }
378
- return applyPayloadBudget(encoded);
404
+ return applyPayloadBudget(encoded, maxCarrierBytes);
379
405
  }
380
- function applyPayloadBudget(encoded) {
406
+ function applyPayloadBudget(encoded, maxCarrierBytes) {
381
407
  const result = encoded.value ? trimPayloadToBudget(
382
408
  encoded.value,
383
- (value) => encodePayloadBody(value).body
409
+ (value) => encodePayloadBody(value).body,
410
+ maxCarrierBytes
384
411
  ) : void 0;
385
412
  if (!result) {
386
413
  return { body: encoded.body, dropped: encoded.dropped };
387
414
  }
388
415
  warnOnce(
389
416
  "payload:over-budget",
390
- `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
417
+ `a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
391
418
  ...new Set(result.trimmed)
392
419
  ].join(
393
420
  ", "
394
421
  )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
395
422
  );
396
- markPayloadTrimmed(result.value, result.trimmed);
423
+ markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
397
424
  return {
398
425
  body: encodePayloadBody(result.value).body,
399
426
  dropped: encoded.dropped
@@ -650,6 +677,31 @@ function encodeSpan(span) {
650
677
  const json = JSON.stringify(spanToOtlp(span));
651
678
  return { json, size: byteLength(json) };
652
679
  }
680
+ function trimEncodedSpan(span) {
681
+ try {
682
+ const carrier = JSON.parse(span.json);
683
+ const attribute = carrier.attributes?.find(
684
+ (entry) => entry.key === PAYLOAD_ATTRIBUTE
685
+ );
686
+ const payloadBody = attribute?.value?.stringValue;
687
+ if (!attribute?.value || payloadBody === void 0) {
688
+ return void 0;
689
+ }
690
+ const payload = JSON.parse(payloadBody);
691
+ attribute.value.stringValue = serializePayloadBody(
692
+ payload,
693
+ MAX_SPAN_CARRIER_BYTES
694
+ ).body;
695
+ const json = JSON.stringify(carrier);
696
+ return { json, size: byteLength(json) };
697
+ } catch {
698
+ return void 0;
699
+ }
700
+ }
701
+ async function prepareRequest(body) {
702
+ const prepared = encodeRequestBody(body);
703
+ return prepared instanceof Promise ? await prepared : prepared;
704
+ }
653
705
  function requestEnvelope(first) {
654
706
  const scope = first.instrumentationScope;
655
707
  const resource = JSON.stringify({
@@ -786,7 +838,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
786
838
  (transport, remaining) => transport.shutdown(remaining)
787
839
  );
788
840
  }
789
- 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;
841
+ var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, OTLP_TRACES_ENDPOINT, MAX_EXPORT_REQUEST_BYTES, MAX_DECOMPRESSED_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;
790
842
  var init_otel = __esm({
791
843
  "src/otel.ts"() {
792
844
  "use strict";
@@ -794,6 +846,7 @@ var init_otel = __esm({
794
846
  import_core = require("@opentelemetry/core");
795
847
  import_resources = require("@opentelemetry/resources");
796
848
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
849
+ init_compress();
797
850
  init_constants();
798
851
  init_errors();
799
852
  init_payloadBudget();
@@ -805,6 +858,7 @@ var init_otel = __esm({
805
858
  PAYLOAD_ATTRIBUTE = "bitfab.payload";
806
859
  OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
807
860
  MAX_EXPORT_REQUEST_BYTES = 3e6;
861
+ MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
808
862
  MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
809
863
  EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
810
864
  MAX_QUEUE_SIZE = 8192;
@@ -887,15 +941,43 @@ var init_otel = __esm({
887
941
  return batches;
888
942
  }
889
943
  async send(envelope, batch) {
890
- if (batch.size > this.maxRequestBytes) {
891
- logError(
892
- "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
893
- );
894
- return false;
895
- }
896
944
  try {
897
- await this.sendWithRetries(encodeRequest(envelope, batch.spans));
898
- return true;
945
+ let requestSpans = batch.spans;
946
+ let requestRawBytes = batch.size;
947
+ let alreadyTrimmed = false;
948
+ while (true) {
949
+ if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
950
+ const prepared = await prepareRequest(
951
+ encodeRequest(envelope, requestSpans)
952
+ );
953
+ if (prepared.wireBytes <= this.maxRequestBytes) {
954
+ await this.sendWithRetries(prepared);
955
+ return true;
956
+ }
957
+ }
958
+ if (batch.spans.length !== 1) {
959
+ logError(
960
+ "an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
961
+ );
962
+ return false;
963
+ }
964
+ if (alreadyTrimmed) {
965
+ logError(
966
+ "a single OpenTelemetry span exceeded the configured request-size target after trimming"
967
+ );
968
+ return false;
969
+ }
970
+ const trimmed = trimEncodedSpan(batch.spans[0]);
971
+ if (!trimmed) {
972
+ logError(
973
+ "a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
974
+ );
975
+ return false;
976
+ }
977
+ requestSpans = [trimmed];
978
+ requestRawBytes = envelope.size + trimmed.size;
979
+ alreadyTrimmed = true;
980
+ }
899
981
  } catch (error) {
900
982
  if (error instanceof OtlpPayloadTooLargeError) {
901
983
  logError(
@@ -923,12 +1005,12 @@ var init_otel = __esm({
923
1005
  * the server does not yet understand. The fix is a client-supplied
924
1006
  * idempotency key that ingestion dedupes on.
925
1007
  */
926
- async sendWithRetries(body) {
1008
+ async sendWithRetries(request) {
927
1009
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
928
1010
  try {
929
1011
  const response = await this.directSender(
930
1012
  OTLP_TRACES_ENDPOINT,
931
- body,
1013
+ request,
932
1014
  EXPORT_TIMEOUT_MILLIS
933
1015
  );
934
1016
  const partialSuccess = asRecord2(response?.partialSuccess);
@@ -1045,7 +1127,10 @@ var init_otel = __esm({
1045
1127
  return;
1046
1128
  }
1047
1129
  try {
1048
- const { body, dropped } = serializePayloadBody(payload);
1130
+ const { body, dropped } = serializePayloadBody(
1131
+ payload,
1132
+ MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
1133
+ );
1049
1134
  if (dropped.length > 0) {
1050
1135
  warnOnce(
1051
1136
  "otel-carrier-payload-stubbed",
@@ -1234,7 +1319,7 @@ var init_http = __esm({
1234
1319
  }
1235
1320
  if (!this.traceTransport) {
1236
1321
  this.traceTransport = createTraceTransport({
1237
- directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1322
+ directSender: (endpoint, request, timeoutMs) => this.sendPrepared(endpoint, request, {
1238
1323
  timeout: timeoutMs
1239
1324
  })
1240
1325
  });
@@ -1321,13 +1406,16 @@ var init_http = __esm({
1321
1406
  * same data twice.
1322
1407
  */
1323
1408
  async sendEncoded(endpoint, body, options) {
1409
+ const prepared = encodeRequestBody(body);
1410
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1411
+ return this.sendPrepared(endpoint, encoded, options);
1412
+ }
1413
+ async sendPrepared(endpoint, encoded, options) {
1324
1414
  const url = `${this.serviceUrl}${endpoint}`;
1325
1415
  const timeout = options?.timeout ?? this.timeout;
1326
1416
  const method = options?.method ?? "POST";
1327
1417
  const controller = new AbortController();
1328
1418
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1329
- const prepared = encodeRequestBody(body);
1330
- const encoded = prepared instanceof Promise ? await prepared : prepared;
1331
1419
  const headers = {
1332
1420
  "Content-Type": "application/json",
1333
1421
  Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
@@ -1801,8 +1889,8 @@ var init_serialize = __esm({
1801
1889
  import_superjson = __toESM(require("superjson"), 1);
1802
1890
  init_payloadBudget();
1803
1891
  init_warnOnce();
1804
- MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1805
- MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1892
+ MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1893
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1806
1894
  MAX_SAFE_DEPTH = 6;
1807
1895
  }
1808
1896
  });