bitfab 0.34.1 → 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/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.36.0";
101
101
  }
102
102
  });
103
103
 
@@ -111,6 +111,87 @@ var init_constants = __esm({
111
111
  }
112
112
  });
113
113
 
114
+ // src/readEnv.ts
115
+ function readEnv(name) {
116
+ if (typeof process !== "undefined" && process.env) {
117
+ return process.env[name];
118
+ }
119
+ return void 0;
120
+ }
121
+ var init_readEnv = __esm({
122
+ "src/readEnv.ts"() {
123
+ "use strict";
124
+ }
125
+ });
126
+
127
+ // src/compress.ts
128
+ function toArrayBuffer(view) {
129
+ return view.buffer.slice(
130
+ view.byteOffset,
131
+ view.byteOffset + view.byteLength
132
+ );
133
+ }
134
+ async function gzipViaStream(bytes) {
135
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
136
+ return await new Response(stream).arrayBuffer();
137
+ }
138
+ function encodeRequestBody(body) {
139
+ if (readEnv(DISABLE_COMPRESSION_ENV)) {
140
+ return { body };
141
+ }
142
+ const bytes = new TextEncoder().encode(body);
143
+ if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
144
+ return { body };
145
+ }
146
+ if (gzipNode) {
147
+ return gzipNode(bytes).then(
148
+ (compressed) => ({
149
+ body: toArrayBuffer(compressed),
150
+ contentEncoding: "gzip"
151
+ }),
152
+ () => ({ body })
153
+ );
154
+ }
155
+ if (typeof CompressionStream === "undefined") {
156
+ return { body };
157
+ }
158
+ return gzipViaStream(bytes).then(
159
+ (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
160
+ () => ({ body })
161
+ );
162
+ }
163
+ var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
164
+ var init_compress = __esm({
165
+ "src/compress.ts"() {
166
+ "use strict";
167
+ init_readEnv();
168
+ DISABLE_COMPRESSION_ENV = "BITFAB_DISABLE_COMPRESSION";
169
+ MIN_COMPRESSED_BYTES = 8192;
170
+ _nodeGzipReady = (typeof process !== "undefined" && process.versions?.node ? (
171
+ // The join trick hides "node:zlib" from static analysis so bundlers that
172
+ // ban Node.js built-ins don't fail at build time. webpackIgnore tells
173
+ // webpack/turbopack to emit a native import() so Node.js can resolve the
174
+ // module at runtime. Same pattern as `asyncStorage.ts`.
175
+ import(
176
+ /* webpackIgnore: true */
177
+ ["node", "zlib"].join(":")
178
+ ).then(({ gzip }) => {
179
+ gzipNode = (data) => new Promise((resolve, reject) => {
180
+ gzip(data, (error, result) => {
181
+ if (error) {
182
+ reject(error);
183
+ } else {
184
+ resolve(result);
185
+ }
186
+ });
187
+ });
188
+ }).catch(() => {
189
+ })
190
+ ) : Promise.resolve()).then(() => {
191
+ });
192
+ }
193
+ });
194
+
114
195
  // src/errors.ts
115
196
  var BitfabError;
116
197
  var init_errors = __esm({
@@ -160,6 +241,131 @@ var init_replayContext = __esm({
160
241
  }
161
242
  });
162
243
 
244
+ // src/payloadBudget.ts
245
+ function byteLength(value) {
246
+ return textEncoder ? textEncoder.encode(value).length : value.length;
247
+ }
248
+ function carrierByteLength(body) {
249
+ return carrierBytesOf(textEncoder ? textEncoder.encode(body) : null, body);
250
+ }
251
+ function carrierBytesOf(encoded, body) {
252
+ if (!encoded) {
253
+ return body.length + 2;
254
+ }
255
+ let extra = 2;
256
+ for (let i = 0; i < encoded.length; i++) {
257
+ const byte = encoded[i];
258
+ if (byte === 34 || byte === 92) {
259
+ extra += 1;
260
+ } else if (byte < 32) {
261
+ extra += byte === 8 || byte === 9 || byte === 10 || byte === 12 || byte === 13 ? 1 : 5;
262
+ }
263
+ }
264
+ return encoded.length + extra;
265
+ }
266
+ function fitsCarrierBudget(body) {
267
+ const units = body.length;
268
+ if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
269
+ return true;
270
+ }
271
+ if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
272
+ return false;
273
+ }
274
+ return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
275
+ }
276
+ function asRecord(value) {
277
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
278
+ }
279
+ function cloneTrimmable(payload) {
280
+ const copy = { ...payload };
281
+ const containers = [];
282
+ const spanData = asRecord(copy.span_data);
283
+ if (spanData) {
284
+ const clone = { ...spanData };
285
+ copy.span_data = clone;
286
+ containers.push(clone);
287
+ }
288
+ const rawSpan = asRecord(copy.rawSpan);
289
+ const rawSpanData = rawSpan && asRecord(rawSpan.span_data);
290
+ if (rawSpan && rawSpanData) {
291
+ const clone = { ...rawSpanData };
292
+ copy.rawSpan = { ...rawSpan, span_data: clone };
293
+ containers.push(clone);
294
+ }
295
+ if (containers.length === 0) {
296
+ containers.push(copy);
297
+ }
298
+ return { copy, containers };
299
+ }
300
+ function collectCandidates(containers) {
301
+ const candidates = [];
302
+ for (const container of containers) {
303
+ for (const [key, value] of Object.entries(container)) {
304
+ if (STRUCTURAL_SPAN_KEYS.has(key) || value == null) {
305
+ continue;
306
+ }
307
+ let size;
308
+ try {
309
+ size = byteLength(JSON.stringify(value) ?? "");
310
+ } catch {
311
+ continue;
312
+ }
313
+ candidates.push({ container, key, size });
314
+ }
315
+ }
316
+ return candidates.sort((a, b) => b.size - a.size);
317
+ }
318
+ function trimPayloadToBudget(payload, encode) {
319
+ const { copy, containers } = cloneTrimmable(payload);
320
+ const candidates = collectCandidates(containers);
321
+ if (candidates.length === 0) {
322
+ return void 0;
323
+ }
324
+ const trimmed = [];
325
+ for (const candidate of candidates) {
326
+ candidate.container[candidate.key] = `<unserializable: too_large_${candidate.size}_bytes>`;
327
+ trimmed.push(candidate.key);
328
+ let body;
329
+ try {
330
+ body = encode(copy);
331
+ } catch {
332
+ return void 0;
333
+ }
334
+ if (fitsCarrierBudget(body)) {
335
+ return { value: copy, trimmed };
336
+ }
337
+ }
338
+ return void 0;
339
+ }
340
+ function markPayloadTrimmed(value, trimmed) {
341
+ const existing = Array.isArray(value.errors) ? value.errors : [];
342
+ value.errors = [
343
+ ...existing,
344
+ {
345
+ source: "sdk",
346
+ step: "payload_budget",
347
+ error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
348
+ ...new Set(trimmed)
349
+ ].join(", ")}`
350
+ }
351
+ ];
352
+ }
353
+ var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
354
+ var init_payloadBudget = __esm({
355
+ "src/payloadBudget.ts"() {
356
+ "use strict";
357
+ MAX_SPAN_CARRIER_BYTES = 28e5;
358
+ textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
359
+ MAX_BYTES_PER_UNIT = 3;
360
+ STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
361
+ "name",
362
+ "type",
363
+ "function_name",
364
+ "error_source"
365
+ ]);
366
+ }
367
+ });
368
+
163
369
  // src/warnOnce.ts
164
370
  function warnOnce(key, message) {
165
371
  if (warned.has(key)) {
@@ -181,8 +387,37 @@ var init_warnOnce = __esm({
181
387
 
182
388
  // src/serializePayload.ts
183
389
  function serializePayloadBody(payload) {
390
+ const encoded = encodePayloadBody(payload);
391
+ if (fitsCarrierBudget(encoded.body)) {
392
+ return { body: encoded.body, dropped: encoded.dropped };
393
+ }
394
+ return applyPayloadBudget(encoded);
395
+ }
396
+ function applyPayloadBudget(encoded) {
397
+ const result = encoded.value ? trimPayloadToBudget(
398
+ encoded.value,
399
+ (value) => encodePayloadBody(value).body
400
+ ) : void 0;
401
+ if (!result) {
402
+ return { body: encoded.body, dropped: encoded.dropped };
403
+ }
404
+ warnOnce(
405
+ "payload:over-budget",
406
+ `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
407
+ ...new Set(result.trimmed)
408
+ ].join(
409
+ ", "
410
+ )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
411
+ );
412
+ markPayloadTrimmed(result.value, result.trimmed);
413
+ return {
414
+ body: encodePayloadBody(result.value).body,
415
+ dropped: encoded.dropped
416
+ };
417
+ }
418
+ function encodePayloadBody(payload) {
184
419
  try {
185
- return { body: JSON.stringify(payload), dropped: [] };
420
+ return { body: JSON.stringify(payload), dropped: [], value: payload };
186
421
  } catch {
187
422
  const dropped = [];
188
423
  const sanitize = (value, seen) => {
@@ -247,12 +482,11 @@ function serializePayloadBody(payload) {
247
482
  sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
248
483
  } catch (error) {
249
484
  const message = error instanceof Error ? error.message : String(error);
250
- return {
251
- body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
252
- dropped
253
- };
485
+ const marker = { error: `payload_serialize_failed: ${message}` };
486
+ return { body: JSON.stringify(marker), dropped, value: marker };
254
487
  }
255
- if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
488
+ const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
489
+ if (dropped.length > 0 && isRecord) {
256
490
  const obj = sanitized;
257
491
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
258
492
  obj.errors = [
@@ -266,29 +500,21 @@ function serializePayloadBody(payload) {
266
500
  }
267
501
  ];
268
502
  }
269
- return { body: JSON.stringify(sanitized), dropped };
503
+ return {
504
+ body: JSON.stringify(sanitized),
505
+ dropped,
506
+ value: isRecord ? sanitized : void 0
507
+ };
270
508
  }
271
509
  }
272
510
  var init_serializePayload = __esm({
273
511
  "src/serializePayload.ts"() {
274
512
  "use strict";
513
+ init_payloadBudget();
275
514
  init_warnOnce();
276
515
  }
277
516
  });
278
517
 
279
- // src/readEnv.ts
280
- function readEnv(name) {
281
- if (typeof process !== "undefined" && process.env) {
282
- return process.env[name];
283
- }
284
- return void 0;
285
- }
286
- var init_readEnv = __esm({
287
- "src/readEnv.ts"() {
288
- "use strict";
289
- }
290
- });
291
-
292
518
  // src/unrefTimer.ts
293
519
  function unrefTimer(timer) {
294
520
  const handle = timer;
@@ -334,7 +560,7 @@ function recordTraceSubmission(operation, payload) {
334
560
  return;
335
561
  }
336
562
  if (operation === "external_span") {
337
- const rawSpan = asRecord(payload.rawSpan);
563
+ const rawSpan = asRecord2(payload.rawSpan);
338
564
  if (typeof rawSpan?.id !== "string") {
339
565
  submissionCounter += 1;
340
566
  }
@@ -371,14 +597,14 @@ function takeReplaySpanCounts(traceIds) {
371
597
  }
372
598
  return counts;
373
599
  }
374
- function asRecord(value) {
600
+ function asRecord2(value) {
375
601
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
376
602
  }
377
603
  function resolveSourceTraceId(payload) {
378
604
  if (typeof payload.sourceTraceId === "string") {
379
605
  return payload.sourceTraceId;
380
606
  }
381
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
607
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
382
608
  return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
383
609
  }
384
610
  function otlpValue(value) {
@@ -436,32 +662,27 @@ function spanToOtlp(span) {
436
662
  }
437
663
  return result;
438
664
  }
439
- function buildOtlpRequest(first, spans) {
665
+ function encodeSpan(span) {
666
+ const json = JSON.stringify(spanToOtlp(span));
667
+ return { json, size: byteLength(json) };
668
+ }
669
+ function requestEnvelope(first) {
440
670
  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
- };
671
+ const resource = JSON.stringify({
672
+ attributes: otlpAttributes(
673
+ first.resource.attributes
674
+ )
675
+ });
676
+ const scopeJson = JSON.stringify({
677
+ name: scope.name,
678
+ version: scope.version ?? ""
679
+ });
680
+ const head = `{"resourceSpans":[{"resource":${resource},"scopeSpans":[{"scope":${scopeJson},"spans":[`;
681
+ const tail = "]}]}]}";
682
+ return { head, tail, size: byteLength(head) + byteLength(tail) };
458
683
  }
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;
684
+ function encodeRequest(envelope, spans) {
685
+ return envelope.head + spans.map((span) => span.json).join(",") + envelope.tail;
465
686
  }
466
687
  function delay(ms) {
467
688
  return new Promise((resolve) => {
@@ -511,16 +732,12 @@ function isRetryable(error) {
511
732
  }
512
733
  return RETRYABLE_STATUSES.has(status) || status >= 500;
513
734
  }
514
- function normalizeCollectorEndpoint(endpoint) {
515
- const trimmed = endpoint.replace(/\/+$/, "");
516
- return trimmed.endsWith("/v1/traces") ? trimmed : `${trimmed}/v1/traces`;
517
- }
518
735
  function endSpan(span, endTime) {
519
736
  span.end(endTime);
520
737
  }
521
738
  function spanName(operation, payload) {
522
739
  if (operation === "external_span") {
523
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
740
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
524
741
  if (typeof spanData?.name === "string") {
525
742
  return spanData.name;
526
743
  }
@@ -531,8 +748,8 @@ function spanName(operation, payload) {
531
748
  return `bitfab.${operation}`;
532
749
  }
533
750
  function payloadTimestamp(payload, field) {
534
- const rawSpan = asRecord(payload.rawSpan);
535
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
751
+ const rawSpan = asRecord2(payload.rawSpan);
752
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
536
753
  const raw = rawSpan?.[field] ?? rawTrace?.[field];
537
754
  if (typeof raw !== "string") {
538
755
  return void 0;
@@ -541,7 +758,7 @@ function payloadTimestamp(payload, field) {
541
758
  return Number.isNaN(parsed) ? void 0 : parsed;
542
759
  }
543
760
  function hasError(payload) {
544
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
761
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
545
762
  if (spanData?.error != null) {
546
763
  return true;
547
764
  }
@@ -551,7 +768,6 @@ function hasError(payload) {
551
768
  function createOtelTransport(options) {
552
769
  return new OtelBatchTransport({
553
770
  ...options,
554
- collectorEndpoint: readEnv(COLLECTOR_ENDPOINT_ENV) || void 0,
555
771
  exportConcurrency: readBoundedIntEnv(
556
772
  EXPORT_CONCURRENCY_ENV,
557
773
  MAX_EXPORT_CONCURRENCY,
@@ -586,7 +802,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
586
802
  (transport, remaining) => transport.shutdown(remaining)
587
803
  );
588
804
  }
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;
805
+ 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;
590
806
  var init_otel = __esm({
591
807
  "src/otel.ts"() {
592
808
  "use strict";
@@ -596,6 +812,7 @@ var init_otel = __esm({
596
812
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
597
813
  init_constants();
598
814
  init_errors();
815
+ init_payloadBudget();
599
816
  init_readEnv();
600
817
  init_serializePayload();
601
818
  init_unrefTimer();
@@ -606,10 +823,8 @@ var init_otel = __esm({
606
823
  MAX_EXPORT_REQUEST_BYTES = 3e6;
607
824
  MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
608
825
  EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
609
- COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT";
610
826
  MAX_QUEUE_SIZE = 8192;
611
827
  DIRECT_MAX_EXPORT_BATCH_SIZE = 512;
612
- COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32;
613
828
  DIRECT_MAX_REQUEST_BATCH_SIZE = 8;
614
829
  DEFAULT_EXPORT_CONCURRENCY = 32;
615
830
  MAX_EXPORT_CONCURRENCY = 64;
@@ -623,6 +838,7 @@ var init_otel = __esm({
623
838
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
624
839
  replayTraceSubmissions = /* @__PURE__ */ new Set();
625
840
  submissionCounter = 0;
841
+ SPAN_SEPARATOR_BYTES = 1;
626
842
  OtlpPayloadTooLargeError = class extends Error {
627
843
  };
628
844
  OtlpPartialSuccessError = class extends Error {
@@ -651,57 +867,55 @@ var init_otel = __esm({
651
867
  return true;
652
868
  }
653
869
  let encoded;
870
+ let envelope;
654
871
  try {
655
- encoded = spans.map(spanToOtlp);
872
+ encoded = spans.map(encodeSpan);
873
+ envelope = requestEnvelope(spans[0]);
656
874
  } catch (error) {
657
875
  logError("failed to encode an OpenTelemetry span batch", error);
658
876
  return false;
659
877
  }
660
- const first = spans[0];
661
- const batches = this.buildRequestBatches(first, encoded);
878
+ const batches = this.buildRequestBatches(envelope, encoded);
662
879
  const results = await mapWithConcurrency(
663
880
  batches,
664
881
  this.exportConcurrency,
665
- (batch) => this.send(first, batch)
882
+ (batch) => this.send(envelope, batch)
666
883
  );
667
884
  return results.every(Boolean);
668
885
  }
669
- buildRequestBatches(first, spans) {
886
+ buildRequestBatches(envelope, spans) {
670
887
  const batches = [];
671
888
  let current = [];
889
+ let size = envelope.size;
672
890
  for (const span of spans) {
673
- if (current.length >= this.maxRequestBatchSize) {
674
- batches.push(current);
891
+ const addition = span.size + (current.length > 0 ? SPAN_SEPARATOR_BYTES : 0);
892
+ if (current.length > 0 && (current.length >= this.maxRequestBatchSize || size + addition > this.maxRequestBytes)) {
893
+ batches.push({ spans: current, size });
675
894
  current = [];
895
+ size = envelope.size;
676
896
  }
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
- }
897
+ current.push(span);
898
+ size += span.size + (current.length > 1 ? SPAN_SEPARATOR_BYTES : 0);
684
899
  }
685
900
  if (current.length > 0) {
686
- batches.push(current);
901
+ batches.push({ spans: current, size });
687
902
  }
688
903
  return batches;
689
904
  }
690
- async send(first, spans) {
691
- const payload = buildOtlpRequest(first, spans);
692
- if (encodedSize(payload) > this.maxRequestBytes) {
905
+ async send(envelope, batch) {
906
+ if (batch.size > this.maxRequestBytes) {
693
907
  logError(
694
908
  "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
695
909
  );
696
910
  return false;
697
911
  }
698
912
  try {
699
- await this.sendWithRetries(payload);
913
+ await this.sendWithRetries(encodeRequest(envelope, batch.spans));
700
914
  return true;
701
915
  } catch (error) {
702
916
  if (error instanceof OtlpPayloadTooLargeError) {
703
917
  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"
918
+ 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
919
  );
706
920
  return false;
707
921
  }
@@ -725,15 +939,15 @@ var init_otel = __esm({
725
939
  * the server does not yet understand. The fix is a client-supplied
726
940
  * idempotency key that ingestion dedupes on.
727
941
  */
728
- async sendWithRetries(payload) {
942
+ async sendWithRetries(body) {
729
943
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
730
944
  try {
731
945
  const response = await this.directSender(
732
946
  OTLP_TRACES_ENDPOINT,
733
- payload,
947
+ body,
734
948
  EXPORT_TIMEOUT_MILLIS
735
949
  );
736
- const partialSuccess = asRecord(response?.partialSuccess);
950
+ const partialSuccess = asRecord2(response?.partialSuccess);
737
951
  const rejected = partialSuccess?.rejectedSpans;
738
952
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
739
953
  logError(
@@ -761,115 +975,6 @@ var init_otel = __esm({
761
975
  async forceFlush() {
762
976
  }
763
977
  };
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
978
  DeliveryTrackingExporter = class {
874
979
  constructor(exporter) {
875
980
  this.exporter = exporter;
@@ -912,27 +1017,22 @@ var init_otel = __esm({
912
1017
  OtelBatchTransport = class {
913
1018
  constructor(options) {
914
1019
  this.closed = false;
915
- const collectorEndpoint = options.collectorEndpoint;
916
1020
  const maxRequestBytes = options.maxRequestBytes ?? MAX_EXPORT_REQUEST_BYTES;
917
1021
  const maxRequestBatchSize = options.maxRequestBatchSize ?? DIRECT_MAX_REQUEST_BATCH_SIZE;
918
1022
  if (maxRequestBatchSize <= 0) {
919
1023
  throw new BitfabError("maxRequestBatchSize must be a positive integer");
920
1024
  }
921
1025
  this.deliveryTracker = new DeliveryTrackingExporter(
922
- collectorEndpoint === void 0 ? new BitfabSpanExporter(
1026
+ new BitfabSpanExporter(
923
1027
  options.directSender,
924
1028
  maxRequestBytes,
925
1029
  maxRequestBatchSize,
926
1030
  options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
927
- ) : new CollectorSpanExporter(
928
- normalizeCollectorEndpoint(collectorEndpoint),
929
- options.apiKey,
930
- maxRequestBytes
931
1031
  )
932
1032
  );
933
1033
  this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
934
1034
  maxQueueSize: options.maxQueueSize ?? MAX_QUEUE_SIZE,
935
- maxExportBatchSize: options.maxExportBatchSize ?? (collectorEndpoint === void 0 ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE),
1035
+ maxExportBatchSize: options.maxExportBatchSize ?? DIRECT_MAX_EXPORT_BATCH_SIZE,
936
1036
  scheduledDelayMillis: SCHEDULE_DELAY_MILLIS,
937
1037
  exportTimeoutMillis: options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
938
1038
  });
@@ -1085,6 +1185,7 @@ var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCL
1085
1185
  var init_http = __esm({
1086
1186
  "src/http.ts"() {
1087
1187
  "use strict";
1188
+ init_compress();
1088
1189
  init_constants();
1089
1190
  init_errors();
1090
1191
  init_replayContext();
@@ -1149,8 +1250,7 @@ var init_http = __esm({
1149
1250
  }
1150
1251
  if (!this.traceTransport) {
1151
1252
  this.traceTransport = createTraceTransport({
1152
- apiKey: () => this.resolveApiKey(),
1153
- directSender: (endpoint, payload, timeoutMs) => this.request(endpoint, payload, {
1253
+ directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1154
1254
  timeout: timeoutMs
1155
1255
  })
1156
1256
  });
@@ -1220,11 +1320,6 @@ var init_http = __esm({
1220
1320
  * @throws {BitfabError} If the request fails
1221
1321
  */
1222
1322
  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
1323
  const { body, dropped } = serializePayloadBody(payload);
1229
1324
  if (dropped.length > 0) {
1230
1325
  try {
@@ -1234,14 +1329,33 @@ var init_http = __esm({
1234
1329
  } catch {
1235
1330
  }
1236
1331
  }
1332
+ return this.sendEncoded(endpoint, body, options);
1333
+ }
1334
+ /**
1335
+ * POST an already-encoded body. The span transport encodes its own batches,
1336
+ * so routing them back through {@link HttpClient.request} would encode the
1337
+ * same data twice.
1338
+ */
1339
+ async sendEncoded(endpoint, body, options) {
1340
+ const url = `${this.serviceUrl}${endpoint}`;
1341
+ const timeout = options?.timeout ?? this.timeout;
1342
+ const method = options?.method ?? "POST";
1343
+ const controller = new AbortController();
1344
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1345
+ const prepared = encodeRequestBody(body);
1346
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1347
+ const headers = {
1348
+ "Content-Type": "application/json",
1349
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1350
+ };
1351
+ if (encoded.contentEncoding) {
1352
+ headers["Content-Encoding"] = encoded.contentEncoding;
1353
+ }
1237
1354
  try {
1238
1355
  const response = await fetch(url, {
1239
1356
  method,
1240
- headers: {
1241
- "Content-Type": "application/json",
1242
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1243
- },
1244
- body,
1357
+ headers,
1358
+ body: encoded.body,
1245
1359
  signal: controller.signal
1246
1360
  });
1247
1361
  if (!response.ok) {
@@ -1690,9 +1804,10 @@ var init_serialize = __esm({
1690
1804
  "src/serialize.ts"() {
1691
1805
  "use strict";
1692
1806
  import_superjson = __toESM(require("superjson"), 1);
1807
+ init_payloadBudget();
1693
1808
  init_warnOnce();
1694
- MAX_SERIALIZED_BYTES = 512e3;
1695
- MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
1809
+ MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1810
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1696
1811
  MAX_SAFE_DEPTH = 6;
1697
1812
  }
1698
1813
  });