bitfab 0.34.0 → 0.34.2

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.0";
54
+ __version__ = "0.34.2";
55
55
  }
56
56
  });
57
57
 
@@ -429,32 +429,30 @@ function spanToOtlp(span) {
429
429
  }
430
430
  return result;
431
431
  }
432
- function buildOtlpRequest(first, spans) {
432
+ function byteLength(value) {
433
+ return textEncoder ? textEncoder.encode(value).length : value.length;
434
+ }
435
+ function encodeSpan(span) {
436
+ const json = JSON.stringify(spanToOtlp(span));
437
+ return { json, size: byteLength(json) };
438
+ }
439
+ function requestEnvelope(first) {
433
440
  const scope = first.instrumentationScope;
434
- return {
435
- resourceSpans: [
436
- {
437
- resource: {
438
- attributes: otlpAttributes(
439
- first.resource.attributes
440
- )
441
- },
442
- scopeSpans: [
443
- {
444
- scope: { name: scope.name, version: scope.version ?? "" },
445
- spans
446
- }
447
- ]
448
- }
449
- ]
450
- };
441
+ const resource = JSON.stringify({
442
+ attributes: otlpAttributes(
443
+ first.resource.attributes
444
+ )
445
+ });
446
+ const scopeJson = JSON.stringify({
447
+ name: scope.name,
448
+ version: scope.version ?? ""
449
+ });
450
+ const head = `{"resourceSpans":[{"resource":${resource},"scopeSpans":[{"scope":${scopeJson},"spans":[`;
451
+ const tail = "]}]}]}";
452
+ return { head, tail, size: byteLength(head) + byteLength(tail) };
451
453
  }
452
- function encodedSize(value) {
453
- const json = JSON.stringify(value);
454
- if (typeof TextEncoder !== "undefined") {
455
- return new TextEncoder().encode(json).length;
456
- }
457
- return json.length;
454
+ function encodeRequest(envelope, spans) {
455
+ return envelope.head + spans.map((span) => span.json).join(",") + envelope.tail;
458
456
  }
459
457
  function delay(ms) {
460
458
  return new Promise((resolve) => {
@@ -504,10 +502,6 @@ function isRetryable(error) {
504
502
  }
505
503
  return RETRYABLE_STATUSES.has(status) || status >= 500;
506
504
  }
507
- function normalizeCollectorEndpoint(endpoint) {
508
- const trimmed = endpoint.replace(/\/+$/, "");
509
- return trimmed.endsWith("/v1/traces") ? trimmed : `${trimmed}/v1/traces`;
510
- }
511
505
  function endSpan(span, endTime) {
512
506
  span.end(endTime);
513
507
  }
@@ -544,7 +538,6 @@ function hasError(payload) {
544
538
  function createOtelTransport(options) {
545
539
  return new OtelBatchTransport({
546
540
  ...options,
547
- collectorEndpoint: readEnv(COLLECTOR_ENDPOINT_ENV) || void 0,
548
541
  exportConcurrency: readBoundedIntEnv(
549
542
  EXPORT_CONCURRENCY_ENV,
550
543
  MAX_EXPORT_CONCURRENCY,
@@ -579,7 +572,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
579
572
  (transport, remaining) => transport.shutdown(remaining)
580
573
  );
581
574
  }
582
- 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, COLLECTOR_ENDPOINT_ENV, MAX_QUEUE_SIZE, DIRECT_MAX_EXPORT_BATCH_SIZE, COLLECTOR_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, OtlpPayloadTooLargeError, OtlpPartialSuccessError, BitfabSpanExporter, CollectorSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
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;
583
576
  var init_otel = __esm({
584
577
  "src/otel.ts"() {
585
578
  "use strict";
@@ -599,10 +592,8 @@ var init_otel = __esm({
599
592
  MAX_EXPORT_REQUEST_BYTES = 3e6;
600
593
  MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
601
594
  EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
602
- COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT";
603
595
  MAX_QUEUE_SIZE = 8192;
604
596
  DIRECT_MAX_EXPORT_BATCH_SIZE = 512;
605
- COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32;
606
597
  DIRECT_MAX_REQUEST_BATCH_SIZE = 8;
607
598
  DEFAULT_EXPORT_CONCURRENCY = 32;
608
599
  MAX_EXPORT_CONCURRENCY = 64;
@@ -616,6 +607,8 @@ var init_otel = __esm({
616
607
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
617
608
  replayTraceSubmissions = /* @__PURE__ */ new Set();
618
609
  submissionCounter = 0;
610
+ textEncoder = typeof TextEncoder === "undefined" ? void 0 : new TextEncoder();
611
+ SPAN_SEPARATOR_BYTES = 1;
619
612
  OtlpPayloadTooLargeError = class extends Error {
620
613
  };
621
614
  OtlpPartialSuccessError = class extends Error {
@@ -644,57 +637,55 @@ var init_otel = __esm({
644
637
  return true;
645
638
  }
646
639
  let encoded;
640
+ let envelope;
647
641
  try {
648
- encoded = spans.map(spanToOtlp);
642
+ encoded = spans.map(encodeSpan);
643
+ envelope = requestEnvelope(spans[0]);
649
644
  } catch (error) {
650
645
  logError("failed to encode an OpenTelemetry span batch", error);
651
646
  return false;
652
647
  }
653
- const first = spans[0];
654
- const batches = this.buildRequestBatches(first, encoded);
648
+ const batches = this.buildRequestBatches(envelope, encoded);
655
649
  const results = await mapWithConcurrency(
656
650
  batches,
657
651
  this.exportConcurrency,
658
- (batch) => this.send(first, batch)
652
+ (batch) => this.send(envelope, batch)
659
653
  );
660
654
  return results.every(Boolean);
661
655
  }
662
- buildRequestBatches(first, spans) {
656
+ buildRequestBatches(envelope, spans) {
663
657
  const batches = [];
664
658
  let current = [];
659
+ let size = envelope.size;
665
660
  for (const span of spans) {
666
- if (current.length >= this.maxRequestBatchSize) {
667
- batches.push(current);
661
+ const addition = span.size + (current.length > 0 ? SPAN_SEPARATOR_BYTES : 0);
662
+ if (current.length > 0 && (current.length >= this.maxRequestBatchSize || size + addition > this.maxRequestBytes)) {
663
+ batches.push({ spans: current, size });
668
664
  current = [];
665
+ size = envelope.size;
669
666
  }
670
- const candidate = [...current, span];
671
- if (current.length > 0 && encodedSize(buildOtlpRequest(first, candidate)) > this.maxRequestBytes) {
672
- batches.push(current);
673
- current = [span];
674
- } else {
675
- current = candidate;
676
- }
667
+ current.push(span);
668
+ size += span.size + (current.length > 1 ? SPAN_SEPARATOR_BYTES : 0);
677
669
  }
678
670
  if (current.length > 0) {
679
- batches.push(current);
671
+ batches.push({ spans: current, size });
680
672
  }
681
673
  return batches;
682
674
  }
683
- async send(first, spans) {
684
- const payload = buildOtlpRequest(first, spans);
685
- if (encodedSize(payload) > this.maxRequestBytes) {
675
+ async send(envelope, batch) {
676
+ if (batch.size > this.maxRequestBytes) {
686
677
  logError(
687
678
  "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
688
679
  );
689
680
  return false;
690
681
  }
691
682
  try {
692
- await this.sendWithRetries(payload);
683
+ await this.sendWithRetries(encodeRequest(envelope, batch.spans));
693
684
  return true;
694
685
  } catch (error) {
695
686
  if (error instanceof OtlpPayloadTooLargeError) {
696
687
  logError(
697
- spans.length === 1 ? "a single OpenTelemetry span exceeded the ingestion request limit and could not be exported" : "an OpenTelemetry span batch exceeded the ingestion request limit and could not be exported"
688
+ batch.spans.length === 1 ? "a single OpenTelemetry span exceeded the ingestion request limit and could not be exported" : "an OpenTelemetry span batch exceeded the ingestion request limit and could not be exported"
698
689
  );
699
690
  return false;
700
691
  }
@@ -718,12 +709,12 @@ var init_otel = __esm({
718
709
  * the server does not yet understand. The fix is a client-supplied
719
710
  * idempotency key that ingestion dedupes on.
720
711
  */
721
- async sendWithRetries(payload) {
712
+ async sendWithRetries(body) {
722
713
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
723
714
  try {
724
715
  const response = await this.directSender(
725
716
  OTLP_TRACES_ENDPOINT,
726
- payload,
717
+ body,
727
718
  EXPORT_TIMEOUT_MILLIS
728
719
  );
729
720
  const partialSuccess = asRecord(response?.partialSuccess);
@@ -754,115 +745,6 @@ var init_otel = __esm({
754
745
  async forceFlush() {
755
746
  }
756
747
  };
757
- CollectorSpanExporter = class {
758
- constructor(endpoint, apiKey, maxRequestBytes) {
759
- this.endpoint = endpoint;
760
- this.apiKey = apiKey;
761
- this.maxRequestBytes = maxRequestBytes;
762
- }
763
- /**
764
- * Loaded through a dynamic import rather than a top-level one so bundlers
765
- * code-split it: Collector delivery is opt-in, and a consumer who never sets
766
- * an endpoint should not pay for the exporter in their initial bundle. It is
767
- * a hard dependency, so this cannot fail for want of the package.
768
- */
769
- loadExporterModule() {
770
- if (!this.pendingModule) {
771
- this.pendingModule = import("@opentelemetry/exporter-trace-otlp-proto");
772
- }
773
- return this.pendingModule;
774
- }
775
- export(spans, resultCallback) {
776
- void this.exportAsync(spans).then(
777
- (succeeded) => {
778
- resultCallback({
779
- code: succeeded ? import_core.ExportResultCode.SUCCESS : import_core.ExportResultCode.FAILED
780
- });
781
- },
782
- (error) => {
783
- resultCallback({ code: import_core.ExportResultCode.FAILED, error });
784
- }
785
- );
786
- }
787
- async exportAsync(spans) {
788
- if (spans.length === 0) {
789
- return true;
790
- }
791
- let delegate;
792
- try {
793
- delegate = await this.resolveDelegate();
794
- } catch (error) {
795
- logError("failed to build the OTLP Collector exporter", error);
796
- return false;
797
- }
798
- const results = await Promise.all(
799
- this.partition(spans).map(
800
- (batch) => new Promise((resolve) => {
801
- try {
802
- delegate.export(batch, (result) => {
803
- resolve(result.code === import_core.ExportResultCode.SUCCESS);
804
- });
805
- } catch (error) {
806
- logError("Collector export threw", error);
807
- resolve(false);
808
- }
809
- })
810
- )
811
- );
812
- return results.every(Boolean);
813
- }
814
- /**
815
- * Partition by the encoded JSON size of each carrier rather than its encoded
816
- * protobuf size. Protobuf is strictly smaller than the equivalent JSON for
817
- * these payloads, so the JSON figure is a conservative bound that keeps every
818
- * request under the target without pulling `@opentelemetry/otlp-transformer`
819
- * into the dependency set purely to measure bytes.
820
- */
821
- partition(spans) {
822
- const batches = [];
823
- let current = [];
824
- let currentSize = 0;
825
- for (const span of spans) {
826
- const size = encodedSize(spanToOtlp(span));
827
- if (current.length > 0 && currentSize + size > this.maxRequestBytes) {
828
- batches.push(current);
829
- current = [];
830
- currentSize = 0;
831
- }
832
- current.push(span);
833
- currentSize += size;
834
- }
835
- if (current.length > 0) {
836
- batches.push(current);
837
- }
838
- return batches;
839
- }
840
- async resolveDelegate() {
841
- const apiKey = this.apiKey() ?? "";
842
- if (this.delegate && this.delegateApiKey === apiKey) {
843
- return this.delegate;
844
- }
845
- const { OTLPTraceExporter } = await this.loadExporterModule();
846
- const previous = this.delegate;
847
- this.delegate = new OTLPTraceExporter({
848
- url: this.endpoint,
849
- headers: { Authorization: `Bearer ${apiKey}` },
850
- timeoutMillis: EXPORT_TIMEOUT_MILLIS
851
- });
852
- this.delegateApiKey = apiKey;
853
- if (previous) {
854
- void previous.shutdown().catch(() => {
855
- });
856
- }
857
- return this.delegate;
858
- }
859
- async shutdown() {
860
- await this.delegate?.shutdown();
861
- }
862
- async forceFlush() {
863
- await this.delegate?.forceFlush?.();
864
- }
865
- };
866
748
  DeliveryTrackingExporter = class {
867
749
  constructor(exporter) {
868
750
  this.exporter = exporter;
@@ -905,27 +787,22 @@ var init_otel = __esm({
905
787
  OtelBatchTransport = class {
906
788
  constructor(options) {
907
789
  this.closed = false;
908
- const collectorEndpoint = options.collectorEndpoint;
909
790
  const maxRequestBytes = options.maxRequestBytes ?? MAX_EXPORT_REQUEST_BYTES;
910
791
  const maxRequestBatchSize = options.maxRequestBatchSize ?? DIRECT_MAX_REQUEST_BATCH_SIZE;
911
792
  if (maxRequestBatchSize <= 0) {
912
793
  throw new BitfabError("maxRequestBatchSize must be a positive integer");
913
794
  }
914
795
  this.deliveryTracker = new DeliveryTrackingExporter(
915
- collectorEndpoint === void 0 ? new BitfabSpanExporter(
796
+ new BitfabSpanExporter(
916
797
  options.directSender,
917
798
  maxRequestBytes,
918
799
  maxRequestBatchSize,
919
800
  options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
920
- ) : new CollectorSpanExporter(
921
- normalizeCollectorEndpoint(collectorEndpoint),
922
- options.apiKey,
923
- maxRequestBytes
924
801
  )
925
802
  );
926
803
  this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
927
804
  maxQueueSize: options.maxQueueSize ?? MAX_QUEUE_SIZE,
928
- maxExportBatchSize: options.maxExportBatchSize ?? (collectorEndpoint === void 0 ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE),
805
+ maxExportBatchSize: options.maxExportBatchSize ?? DIRECT_MAX_EXPORT_BATCH_SIZE,
929
806
  scheduledDelayMillis: SCHEDULE_DELAY_MILLIS,
930
807
  exportTimeoutMillis: options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
931
808
  });
@@ -1142,8 +1019,7 @@ var init_http = __esm({
1142
1019
  }
1143
1020
  if (!this.traceTransport) {
1144
1021
  this.traceTransport = createTraceTransport({
1145
- apiKey: () => this.resolveApiKey(),
1146
- directSender: (endpoint, payload, timeoutMs) => this.request(endpoint, payload, {
1022
+ directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1147
1023
  timeout: timeoutMs
1148
1024
  })
1149
1025
  });
@@ -1213,11 +1089,6 @@ var init_http = __esm({
1213
1089
  * @throws {BitfabError} If the request fails
1214
1090
  */
1215
1091
  async request(endpoint, payload, options) {
1216
- const url = `${this.serviceUrl}${endpoint}`;
1217
- const timeout = options?.timeout ?? this.timeout;
1218
- const method = options?.method ?? "POST";
1219
- const controller = new AbortController();
1220
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1221
1092
  const { body, dropped } = serializePayloadBody(payload);
1222
1093
  if (dropped.length > 0) {
1223
1094
  try {
@@ -1227,6 +1098,19 @@ var init_http = __esm({
1227
1098
  } catch {
1228
1099
  }
1229
1100
  }
1101
+ return this.sendEncoded(endpoint, body, options);
1102
+ }
1103
+ /**
1104
+ * POST an already-encoded body. The span transport encodes its own batches,
1105
+ * so routing them back through {@link HttpClient.request} would encode the
1106
+ * same data twice.
1107
+ */
1108
+ async sendEncoded(endpoint, body, options) {
1109
+ const url = `${this.serviceUrl}${endpoint}`;
1110
+ const timeout = options?.timeout ?? this.timeout;
1111
+ const method = options?.method ?? "POST";
1112
+ const controller = new AbortController();
1113
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1230
1114
  try {
1231
1115
  const response = await fetch(url, {
1232
1116
  method,
@@ -2439,6 +2323,7 @@ __export(index_exports, {
2439
2323
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
2440
2324
  BitfabVercelAiHandler: () => BitfabVercelAiHandler,
2441
2325
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
2326
+ HttpClient: () => HttpClient,
2442
2327
  SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
2443
2328
  __version__: () => __version__,
2444
2329
  finalizers: () => finalizers,
@@ -6015,6 +5900,7 @@ init_replay();
6015
5900
  BitfabOpenAITracingProcessor,
6016
5901
  BitfabVercelAiHandler,
6017
5902
  DEFAULT_SERVICE_URL,
5903
+ HttpClient,
6018
5904
  SUPPORTED_PROVIDERS,
6019
5905
  __version__,
6020
5906
  finalizers,