bitfab 0.34.1 → 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.d.cts CHANGED
@@ -288,9 +288,7 @@ interface ReplayContext {
288
288
  *
289
289
  * Returns `false` when delivery failed or the deadline expired, so a caller
290
290
  * that depends on persistence (replay does) can react instead of assuming a
291
- * drained queue means the server has the data. When delivery goes through a
292
- * Collector, `true` means the Collector accepted the spans; it is not proof
293
- * that Bitfab committed them.
291
+ * drained queue means the server has the data.
294
292
  *
295
293
  * @param timeoutMs - Maximum total time to wait in milliseconds (default: 5000)
296
294
  */
@@ -399,6 +397,15 @@ declare class HttpClient {
399
397
  timeout?: number;
400
398
  method?: "POST" | "PATCH" | "PUT";
401
399
  }): Promise<T>;
400
+ /**
401
+ * POST an already-encoded body. The span transport encodes its own batches,
402
+ * so routing them back through {@link HttpClient.request} would encode the
403
+ * same data twice.
404
+ */
405
+ sendEncoded<T>(endpoint: string, body: string, options?: {
406
+ timeout?: number;
407
+ method?: "POST" | "PATCH" | "PUT";
408
+ }): Promise<T>;
402
409
  /**
403
410
  * Look up a function by name.
404
411
  * Blocks until complete - needed for function execution.
@@ -2329,7 +2336,7 @@ declare class BitfabFunction {
2329
2336
  /**
2330
2337
  * SDK version from package.json (injected at build time)
2331
2338
  */
2332
- declare const __version__ = "0.34.1";
2339
+ declare const __version__ = "0.34.2";
2333
2340
 
2334
2341
  /**
2335
2342
  * Constants for the Bitfab SDK.
package/dist/index.d.ts CHANGED
@@ -288,9 +288,7 @@ interface ReplayContext {
288
288
  *
289
289
  * Returns `false` when delivery failed or the deadline expired, so a caller
290
290
  * that depends on persistence (replay does) can react instead of assuming a
291
- * drained queue means the server has the data. When delivery goes through a
292
- * Collector, `true` means the Collector accepted the spans; it is not proof
293
- * that Bitfab committed them.
291
+ * drained queue means the server has the data.
294
292
  *
295
293
  * @param timeoutMs - Maximum total time to wait in milliseconds (default: 5000)
296
294
  */
@@ -399,6 +397,15 @@ declare class HttpClient {
399
397
  timeout?: number;
400
398
  method?: "POST" | "PATCH" | "PUT";
401
399
  }): Promise<T>;
400
+ /**
401
+ * POST an already-encoded body. The span transport encodes its own batches,
402
+ * so routing them back through {@link HttpClient.request} would encode the
403
+ * same data twice.
404
+ */
405
+ sendEncoded<T>(endpoint: string, body: string, options?: {
406
+ timeout?: number;
407
+ method?: "POST" | "PATCH" | "PUT";
408
+ }): Promise<T>;
402
409
  /**
403
410
  * Look up a function by name.
404
411
  * Blocks until complete - needed for function execution.
@@ -2329,7 +2336,7 @@ declare class BitfabFunction {
2329
2336
  /**
2330
2337
  * SDK version from package.json (injected at build time)
2331
2338
  */
2332
- declare const __version__ = "0.34.1";
2339
+ declare const __version__ = "0.34.2";
2333
2340
 
2334
2341
  /**
2335
2342
  * Constants for the Bitfab SDK.
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  getCurrentReplayBranch,
21
21
  getCurrentSpan,
22
22
  getCurrentTrace
23
- } from "./chunk-KO373TDH.js";
23
+ } from "./chunk-KQB42J3S.js";
24
24
  import {
25
25
  BITFAB_PROGRESS_PREFIX,
26
26
  BitfabError,
@@ -29,7 +29,7 @@ import {
29
29
  __version__,
30
30
  flushTraces,
31
31
  reportReplayProgress
32
- } from "./chunk-5ZMEY5NX.js";
32
+ } from "./chunk-SBQQOFA5.js";
33
33
  export {
34
34
  BITFAB_PROGRESS_PREFIX,
35
35
  Bitfab,
package/dist/node.cjs CHANGED
@@ -97,7 +97,7 @@ var __version__;
97
97
  var init_version_generated = __esm({
98
98
  "src/version.generated.ts"() {
99
99
  "use strict";
100
- __version__ = "0.34.1";
100
+ __version__ = "0.34.2";
101
101
  }
102
102
  });
103
103
 
@@ -436,32 +436,30 @@ function spanToOtlp(span) {
436
436
  }
437
437
  return result;
438
438
  }
439
- function buildOtlpRequest(first, spans) {
439
+ function byteLength(value) {
440
+ return textEncoder ? textEncoder.encode(value).length : value.length;
441
+ }
442
+ function encodeSpan(span) {
443
+ const json = JSON.stringify(spanToOtlp(span));
444
+ return { json, size: byteLength(json) };
445
+ }
446
+ function requestEnvelope(first) {
440
447
  const scope = first.instrumentationScope;
441
- return {
442
- resourceSpans: [
443
- {
444
- resource: {
445
- attributes: otlpAttributes(
446
- first.resource.attributes
447
- )
448
- },
449
- scopeSpans: [
450
- {
451
- scope: { name: scope.name, version: scope.version ?? "" },
452
- spans
453
- }
454
- ]
455
- }
456
- ]
457
- };
448
+ const resource = JSON.stringify({
449
+ attributes: otlpAttributes(
450
+ first.resource.attributes
451
+ )
452
+ });
453
+ const scopeJson = JSON.stringify({
454
+ name: scope.name,
455
+ version: scope.version ?? ""
456
+ });
457
+ const head = `{"resourceSpans":[{"resource":${resource},"scopeSpans":[{"scope":${scopeJson},"spans":[`;
458
+ const tail = "]}]}]}";
459
+ return { head, tail, size: byteLength(head) + byteLength(tail) };
458
460
  }
459
- function encodedSize(value) {
460
- const json = JSON.stringify(value);
461
- if (typeof TextEncoder !== "undefined") {
462
- return new TextEncoder().encode(json).length;
463
- }
464
- return json.length;
461
+ function encodeRequest(envelope, spans) {
462
+ return envelope.head + spans.map((span) => span.json).join(",") + envelope.tail;
465
463
  }
466
464
  function delay(ms) {
467
465
  return new Promise((resolve) => {
@@ -511,10 +509,6 @@ function isRetryable(error) {
511
509
  }
512
510
  return RETRYABLE_STATUSES.has(status) || status >= 500;
513
511
  }
514
- function normalizeCollectorEndpoint(endpoint) {
515
- const trimmed = endpoint.replace(/\/+$/, "");
516
- return trimmed.endsWith("/v1/traces") ? trimmed : `${trimmed}/v1/traces`;
517
- }
518
512
  function endSpan(span, endTime) {
519
513
  span.end(endTime);
520
514
  }
@@ -551,7 +545,6 @@ function hasError(payload) {
551
545
  function createOtelTransport(options) {
552
546
  return new OtelBatchTransport({
553
547
  ...options,
554
- collectorEndpoint: readEnv(COLLECTOR_ENDPOINT_ENV) || void 0,
555
548
  exportConcurrency: readBoundedIntEnv(
556
549
  EXPORT_CONCURRENCY_ENV,
557
550
  MAX_EXPORT_CONCURRENCY,
@@ -586,7 +579,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
586
579
  (transport, remaining) => transport.shutdown(remaining)
587
580
  );
588
581
  }
589
- 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;
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, 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;
590
583
  var init_otel = __esm({
591
584
  "src/otel.ts"() {
592
585
  "use strict";
@@ -606,10 +599,8 @@ var init_otel = __esm({
606
599
  MAX_EXPORT_REQUEST_BYTES = 3e6;
607
600
  MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
608
601
  EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
609
- COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT";
610
602
  MAX_QUEUE_SIZE = 8192;
611
603
  DIRECT_MAX_EXPORT_BATCH_SIZE = 512;
612
- COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32;
613
604
  DIRECT_MAX_REQUEST_BATCH_SIZE = 8;
614
605
  DEFAULT_EXPORT_CONCURRENCY = 32;
615
606
  MAX_EXPORT_CONCURRENCY = 64;
@@ -623,6 +614,8 @@ var init_otel = __esm({
623
614
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
624
615
  replayTraceSubmissions = /* @__PURE__ */ new Set();
625
616
  submissionCounter = 0;
617
+ textEncoder = typeof TextEncoder === "undefined" ? void 0 : new TextEncoder();
618
+ SPAN_SEPARATOR_BYTES = 1;
626
619
  OtlpPayloadTooLargeError = class extends Error {
627
620
  };
628
621
  OtlpPartialSuccessError = class extends Error {
@@ -651,57 +644,55 @@ var init_otel = __esm({
651
644
  return true;
652
645
  }
653
646
  let encoded;
647
+ let envelope;
654
648
  try {
655
- encoded = spans.map(spanToOtlp);
649
+ encoded = spans.map(encodeSpan);
650
+ envelope = requestEnvelope(spans[0]);
656
651
  } catch (error) {
657
652
  logError("failed to encode an OpenTelemetry span batch", error);
658
653
  return false;
659
654
  }
660
- const first = spans[0];
661
- const batches = this.buildRequestBatches(first, encoded);
655
+ const batches = this.buildRequestBatches(envelope, encoded);
662
656
  const results = await mapWithConcurrency(
663
657
  batches,
664
658
  this.exportConcurrency,
665
- (batch) => this.send(first, batch)
659
+ (batch) => this.send(envelope, batch)
666
660
  );
667
661
  return results.every(Boolean);
668
662
  }
669
- buildRequestBatches(first, spans) {
663
+ buildRequestBatches(envelope, spans) {
670
664
  const batches = [];
671
665
  let current = [];
666
+ let size = envelope.size;
672
667
  for (const span of spans) {
673
- if (current.length >= this.maxRequestBatchSize) {
674
- batches.push(current);
668
+ const addition = span.size + (current.length > 0 ? SPAN_SEPARATOR_BYTES : 0);
669
+ if (current.length > 0 && (current.length >= this.maxRequestBatchSize || size + addition > this.maxRequestBytes)) {
670
+ batches.push({ spans: current, size });
675
671
  current = [];
672
+ size = envelope.size;
676
673
  }
677
- const candidate = [...current, span];
678
- if (current.length > 0 && encodedSize(buildOtlpRequest(first, candidate)) > this.maxRequestBytes) {
679
- batches.push(current);
680
- current = [span];
681
- } else {
682
- current = candidate;
683
- }
674
+ current.push(span);
675
+ size += span.size + (current.length > 1 ? SPAN_SEPARATOR_BYTES : 0);
684
676
  }
685
677
  if (current.length > 0) {
686
- batches.push(current);
678
+ batches.push({ spans: current, size });
687
679
  }
688
680
  return batches;
689
681
  }
690
- async send(first, spans) {
691
- const payload = buildOtlpRequest(first, spans);
692
- if (encodedSize(payload) > this.maxRequestBytes) {
682
+ async send(envelope, batch) {
683
+ if (batch.size > this.maxRequestBytes) {
693
684
  logError(
694
685
  "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
695
686
  );
696
687
  return false;
697
688
  }
698
689
  try {
699
- await this.sendWithRetries(payload);
690
+ await this.sendWithRetries(encodeRequest(envelope, batch.spans));
700
691
  return true;
701
692
  } catch (error) {
702
693
  if (error instanceof OtlpPayloadTooLargeError) {
703
694
  logError(
704
- 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"
695
+ 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"
705
696
  );
706
697
  return false;
707
698
  }
@@ -725,12 +716,12 @@ var init_otel = __esm({
725
716
  * the server does not yet understand. The fix is a client-supplied
726
717
  * idempotency key that ingestion dedupes on.
727
718
  */
728
- async sendWithRetries(payload) {
719
+ async sendWithRetries(body) {
729
720
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
730
721
  try {
731
722
  const response = await this.directSender(
732
723
  OTLP_TRACES_ENDPOINT,
733
- payload,
724
+ body,
734
725
  EXPORT_TIMEOUT_MILLIS
735
726
  );
736
727
  const partialSuccess = asRecord(response?.partialSuccess);
@@ -761,115 +752,6 @@ var init_otel = __esm({
761
752
  async forceFlush() {
762
753
  }
763
754
  };
764
- CollectorSpanExporter = class {
765
- constructor(endpoint, apiKey, maxRequestBytes) {
766
- this.endpoint = endpoint;
767
- this.apiKey = apiKey;
768
- this.maxRequestBytes = maxRequestBytes;
769
- }
770
- /**
771
- * Loaded through a dynamic import rather than a top-level one so bundlers
772
- * code-split it: Collector delivery is opt-in, and a consumer who never sets
773
- * an endpoint should not pay for the exporter in their initial bundle. It is
774
- * a hard dependency, so this cannot fail for want of the package.
775
- */
776
- loadExporterModule() {
777
- if (!this.pendingModule) {
778
- this.pendingModule = import("@opentelemetry/exporter-trace-otlp-proto");
779
- }
780
- return this.pendingModule;
781
- }
782
- export(spans, resultCallback) {
783
- void this.exportAsync(spans).then(
784
- (succeeded) => {
785
- resultCallback({
786
- code: succeeded ? import_core.ExportResultCode.SUCCESS : import_core.ExportResultCode.FAILED
787
- });
788
- },
789
- (error) => {
790
- resultCallback({ code: import_core.ExportResultCode.FAILED, error });
791
- }
792
- );
793
- }
794
- async exportAsync(spans) {
795
- if (spans.length === 0) {
796
- return true;
797
- }
798
- let delegate;
799
- try {
800
- delegate = await this.resolveDelegate();
801
- } catch (error) {
802
- logError("failed to build the OTLP Collector exporter", error);
803
- return false;
804
- }
805
- const results = await Promise.all(
806
- this.partition(spans).map(
807
- (batch) => new Promise((resolve) => {
808
- try {
809
- delegate.export(batch, (result) => {
810
- resolve(result.code === import_core.ExportResultCode.SUCCESS);
811
- });
812
- } catch (error) {
813
- logError("Collector export threw", error);
814
- resolve(false);
815
- }
816
- })
817
- )
818
- );
819
- return results.every(Boolean);
820
- }
821
- /**
822
- * Partition by the encoded JSON size of each carrier rather than its encoded
823
- * protobuf size. Protobuf is strictly smaller than the equivalent JSON for
824
- * these payloads, so the JSON figure is a conservative bound that keeps every
825
- * request under the target without pulling `@opentelemetry/otlp-transformer`
826
- * into the dependency set purely to measure bytes.
827
- */
828
- partition(spans) {
829
- const batches = [];
830
- let current = [];
831
- let currentSize = 0;
832
- for (const span of spans) {
833
- const size = encodedSize(spanToOtlp(span));
834
- if (current.length > 0 && currentSize + size > this.maxRequestBytes) {
835
- batches.push(current);
836
- current = [];
837
- currentSize = 0;
838
- }
839
- current.push(span);
840
- currentSize += size;
841
- }
842
- if (current.length > 0) {
843
- batches.push(current);
844
- }
845
- return batches;
846
- }
847
- async resolveDelegate() {
848
- const apiKey = this.apiKey() ?? "";
849
- if (this.delegate && this.delegateApiKey === apiKey) {
850
- return this.delegate;
851
- }
852
- const { OTLPTraceExporter } = await this.loadExporterModule();
853
- const previous = this.delegate;
854
- this.delegate = new OTLPTraceExporter({
855
- url: this.endpoint,
856
- headers: { Authorization: `Bearer ${apiKey}` },
857
- timeoutMillis: EXPORT_TIMEOUT_MILLIS
858
- });
859
- this.delegateApiKey = apiKey;
860
- if (previous) {
861
- void previous.shutdown().catch(() => {
862
- });
863
- }
864
- return this.delegate;
865
- }
866
- async shutdown() {
867
- await this.delegate?.shutdown();
868
- }
869
- async forceFlush() {
870
- await this.delegate?.forceFlush?.();
871
- }
872
- };
873
755
  DeliveryTrackingExporter = class {
874
756
  constructor(exporter) {
875
757
  this.exporter = exporter;
@@ -912,27 +794,22 @@ var init_otel = __esm({
912
794
  OtelBatchTransport = class {
913
795
  constructor(options) {
914
796
  this.closed = false;
915
- const collectorEndpoint = options.collectorEndpoint;
916
797
  const maxRequestBytes = options.maxRequestBytes ?? MAX_EXPORT_REQUEST_BYTES;
917
798
  const maxRequestBatchSize = options.maxRequestBatchSize ?? DIRECT_MAX_REQUEST_BATCH_SIZE;
918
799
  if (maxRequestBatchSize <= 0) {
919
800
  throw new BitfabError("maxRequestBatchSize must be a positive integer");
920
801
  }
921
802
  this.deliveryTracker = new DeliveryTrackingExporter(
922
- collectorEndpoint === void 0 ? new BitfabSpanExporter(
803
+ new BitfabSpanExporter(
923
804
  options.directSender,
924
805
  maxRequestBytes,
925
806
  maxRequestBatchSize,
926
807
  options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
927
- ) : new CollectorSpanExporter(
928
- normalizeCollectorEndpoint(collectorEndpoint),
929
- options.apiKey,
930
- maxRequestBytes
931
808
  )
932
809
  );
933
810
  this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
934
811
  maxQueueSize: options.maxQueueSize ?? MAX_QUEUE_SIZE,
935
- maxExportBatchSize: options.maxExportBatchSize ?? (collectorEndpoint === void 0 ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE),
812
+ maxExportBatchSize: options.maxExportBatchSize ?? DIRECT_MAX_EXPORT_BATCH_SIZE,
936
813
  scheduledDelayMillis: SCHEDULE_DELAY_MILLIS,
937
814
  exportTimeoutMillis: options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
938
815
  });
@@ -1149,8 +1026,7 @@ var init_http = __esm({
1149
1026
  }
1150
1027
  if (!this.traceTransport) {
1151
1028
  this.traceTransport = createTraceTransport({
1152
- apiKey: () => this.resolveApiKey(),
1153
- directSender: (endpoint, payload, timeoutMs) => this.request(endpoint, payload, {
1029
+ directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1154
1030
  timeout: timeoutMs
1155
1031
  })
1156
1032
  });
@@ -1220,11 +1096,6 @@ var init_http = __esm({
1220
1096
  * @throws {BitfabError} If the request fails
1221
1097
  */
1222
1098
  async request(endpoint, payload, options) {
1223
- const url = `${this.serviceUrl}${endpoint}`;
1224
- const timeout = options?.timeout ?? this.timeout;
1225
- const method = options?.method ?? "POST";
1226
- const controller = new AbortController();
1227
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1228
1099
  const { body, dropped } = serializePayloadBody(payload);
1229
1100
  if (dropped.length > 0) {
1230
1101
  try {
@@ -1234,6 +1105,19 @@ var init_http = __esm({
1234
1105
  } catch {
1235
1106
  }
1236
1107
  }
1108
+ return this.sendEncoded(endpoint, body, options);
1109
+ }
1110
+ /**
1111
+ * POST an already-encoded body. The span transport encodes its own batches,
1112
+ * so routing them back through {@link HttpClient.request} would encode the
1113
+ * same data twice.
1114
+ */
1115
+ async sendEncoded(endpoint, body, options) {
1116
+ const url = `${this.serviceUrl}${endpoint}`;
1117
+ const timeout = options?.timeout ?? this.timeout;
1118
+ const method = options?.method ?? "POST";
1119
+ const controller = new AbortController();
1120
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1237
1121
  try {
1238
1122
  const response = await fetch(url, {
1239
1123
  method,