@bitfab/sdk 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
@@ -88,7 +88,7 @@ var __version__;
88
88
  var init_version_generated = __esm({
89
89
  "src/version.generated.ts"() {
90
90
  "use strict";
91
- __version__ = "0.34.1";
91
+ __version__ = "0.36.0";
92
92
  }
93
93
  });
94
94
 
@@ -102,6 +102,87 @@ var init_constants = __esm({
102
102
  }
103
103
  });
104
104
 
105
+ // src/readEnv.ts
106
+ function readEnv(name) {
107
+ if (typeof process !== "undefined" && process.env) {
108
+ return process.env[name];
109
+ }
110
+ return void 0;
111
+ }
112
+ var init_readEnv = __esm({
113
+ "src/readEnv.ts"() {
114
+ "use strict";
115
+ }
116
+ });
117
+
118
+ // src/compress.ts
119
+ function toArrayBuffer(view) {
120
+ return view.buffer.slice(
121
+ view.byteOffset,
122
+ view.byteOffset + view.byteLength
123
+ );
124
+ }
125
+ async function gzipViaStream(bytes) {
126
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
127
+ return await new Response(stream).arrayBuffer();
128
+ }
129
+ function encodeRequestBody(body) {
130
+ if (readEnv(DISABLE_COMPRESSION_ENV)) {
131
+ return { body };
132
+ }
133
+ const bytes = new TextEncoder().encode(body);
134
+ if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
135
+ return { body };
136
+ }
137
+ if (gzipNode) {
138
+ return gzipNode(bytes).then(
139
+ (compressed) => ({
140
+ body: toArrayBuffer(compressed),
141
+ contentEncoding: "gzip"
142
+ }),
143
+ () => ({ body })
144
+ );
145
+ }
146
+ if (typeof CompressionStream === "undefined") {
147
+ return { body };
148
+ }
149
+ return gzipViaStream(bytes).then(
150
+ (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
151
+ () => ({ body })
152
+ );
153
+ }
154
+ var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
155
+ var init_compress = __esm({
156
+ "src/compress.ts"() {
157
+ "use strict";
158
+ init_readEnv();
159
+ DISABLE_COMPRESSION_ENV = "BITFAB_DISABLE_COMPRESSION";
160
+ MIN_COMPRESSED_BYTES = 8192;
161
+ _nodeGzipReady = (typeof process !== "undefined" && process.versions?.node ? (
162
+ // The join trick hides "node:zlib" from static analysis so bundlers that
163
+ // ban Node.js built-ins don't fail at build time. webpackIgnore tells
164
+ // webpack/turbopack to emit a native import() so Node.js can resolve the
165
+ // module at runtime. Same pattern as `asyncStorage.ts`.
166
+ import(
167
+ /* webpackIgnore: true */
168
+ ["node", "zlib"].join(":")
169
+ ).then(({ gzip }) => {
170
+ gzipNode = (data) => new Promise((resolve, reject) => {
171
+ gzip(data, (error, result) => {
172
+ if (error) {
173
+ reject(error);
174
+ } else {
175
+ resolve(result);
176
+ }
177
+ });
178
+ });
179
+ }).catch(() => {
180
+ })
181
+ ) : Promise.resolve()).then(() => {
182
+ });
183
+ }
184
+ });
185
+
105
186
  // src/errors.ts
106
187
  var BitfabError;
107
188
  var init_errors = __esm({
@@ -151,6 +232,131 @@ var init_replayContext = __esm({
151
232
  }
152
233
  });
153
234
 
235
+ // src/payloadBudget.ts
236
+ function byteLength(value) {
237
+ return textEncoder ? textEncoder.encode(value).length : value.length;
238
+ }
239
+ function carrierByteLength(body) {
240
+ return carrierBytesOf(textEncoder ? textEncoder.encode(body) : null, body);
241
+ }
242
+ function carrierBytesOf(encoded, body) {
243
+ if (!encoded) {
244
+ return body.length + 2;
245
+ }
246
+ let extra = 2;
247
+ for (let i = 0; i < encoded.length; i++) {
248
+ const byte = encoded[i];
249
+ if (byte === 34 || byte === 92) {
250
+ extra += 1;
251
+ } else if (byte < 32) {
252
+ extra += byte === 8 || byte === 9 || byte === 10 || byte === 12 || byte === 13 ? 1 : 5;
253
+ }
254
+ }
255
+ return encoded.length + extra;
256
+ }
257
+ function fitsCarrierBudget(body) {
258
+ const units = body.length;
259
+ if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
260
+ return true;
261
+ }
262
+ if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
263
+ return false;
264
+ }
265
+ return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
266
+ }
267
+ function asRecord(value) {
268
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
269
+ }
270
+ function cloneTrimmable(payload) {
271
+ const copy = { ...payload };
272
+ const containers = [];
273
+ const spanData = asRecord(copy.span_data);
274
+ if (spanData) {
275
+ const clone = { ...spanData };
276
+ copy.span_data = clone;
277
+ containers.push(clone);
278
+ }
279
+ const rawSpan = asRecord(copy.rawSpan);
280
+ const rawSpanData = rawSpan && asRecord(rawSpan.span_data);
281
+ if (rawSpan && rawSpanData) {
282
+ const clone = { ...rawSpanData };
283
+ copy.rawSpan = { ...rawSpan, span_data: clone };
284
+ containers.push(clone);
285
+ }
286
+ if (containers.length === 0) {
287
+ containers.push(copy);
288
+ }
289
+ return { copy, containers };
290
+ }
291
+ function collectCandidates(containers) {
292
+ const candidates = [];
293
+ for (const container of containers) {
294
+ for (const [key, value] of Object.entries(container)) {
295
+ if (STRUCTURAL_SPAN_KEYS.has(key) || value == null) {
296
+ continue;
297
+ }
298
+ let size;
299
+ try {
300
+ size = byteLength(JSON.stringify(value) ?? "");
301
+ } catch {
302
+ continue;
303
+ }
304
+ candidates.push({ container, key, size });
305
+ }
306
+ }
307
+ return candidates.sort((a, b) => b.size - a.size);
308
+ }
309
+ function trimPayloadToBudget(payload, encode) {
310
+ const { copy, containers } = cloneTrimmable(payload);
311
+ const candidates = collectCandidates(containers);
312
+ if (candidates.length === 0) {
313
+ return void 0;
314
+ }
315
+ const trimmed = [];
316
+ for (const candidate of candidates) {
317
+ candidate.container[candidate.key] = `<unserializable: too_large_${candidate.size}_bytes>`;
318
+ trimmed.push(candidate.key);
319
+ let body;
320
+ try {
321
+ body = encode(copy);
322
+ } catch {
323
+ return void 0;
324
+ }
325
+ if (fitsCarrierBudget(body)) {
326
+ return { value: copy, trimmed };
327
+ }
328
+ }
329
+ return void 0;
330
+ }
331
+ function markPayloadTrimmed(value, trimmed) {
332
+ const existing = Array.isArray(value.errors) ? value.errors : [];
333
+ value.errors = [
334
+ ...existing,
335
+ {
336
+ source: "sdk",
337
+ step: "payload_budget",
338
+ error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
339
+ ...new Set(trimmed)
340
+ ].join(", ")}`
341
+ }
342
+ ];
343
+ }
344
+ var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
345
+ var init_payloadBudget = __esm({
346
+ "src/payloadBudget.ts"() {
347
+ "use strict";
348
+ MAX_SPAN_CARRIER_BYTES = 28e5;
349
+ textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
350
+ MAX_BYTES_PER_UNIT = 3;
351
+ STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
352
+ "name",
353
+ "type",
354
+ "function_name",
355
+ "error_source"
356
+ ]);
357
+ }
358
+ });
359
+
154
360
  // src/warnOnce.ts
155
361
  function warnOnce(key, message) {
156
362
  if (warned.has(key)) {
@@ -172,8 +378,37 @@ var init_warnOnce = __esm({
172
378
 
173
379
  // src/serializePayload.ts
174
380
  function serializePayloadBody(payload) {
381
+ const encoded = encodePayloadBody(payload);
382
+ if (fitsCarrierBudget(encoded.body)) {
383
+ return { body: encoded.body, dropped: encoded.dropped };
384
+ }
385
+ return applyPayloadBudget(encoded);
386
+ }
387
+ function applyPayloadBudget(encoded) {
388
+ const result = encoded.value ? trimPayloadToBudget(
389
+ encoded.value,
390
+ (value) => encodePayloadBody(value).body
391
+ ) : void 0;
392
+ if (!result) {
393
+ return { body: encoded.body, dropped: encoded.dropped };
394
+ }
395
+ warnOnce(
396
+ "payload:over-budget",
397
+ `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
398
+ ...new Set(result.trimmed)
399
+ ].join(
400
+ ", "
401
+ )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
402
+ );
403
+ markPayloadTrimmed(result.value, result.trimmed);
404
+ return {
405
+ body: encodePayloadBody(result.value).body,
406
+ dropped: encoded.dropped
407
+ };
408
+ }
409
+ function encodePayloadBody(payload) {
175
410
  try {
176
- return { body: JSON.stringify(payload), dropped: [] };
411
+ return { body: JSON.stringify(payload), dropped: [], value: payload };
177
412
  } catch {
178
413
  const dropped = [];
179
414
  const sanitize = (value, seen) => {
@@ -238,12 +473,11 @@ function serializePayloadBody(payload) {
238
473
  sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
239
474
  } catch (error) {
240
475
  const message = error instanceof Error ? error.message : String(error);
241
- return {
242
- body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
243
- dropped
244
- };
476
+ const marker = { error: `payload_serialize_failed: ${message}` };
477
+ return { body: JSON.stringify(marker), dropped, value: marker };
245
478
  }
246
- if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
479
+ const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
480
+ if (dropped.length > 0 && isRecord) {
247
481
  const obj = sanitized;
248
482
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
249
483
  obj.errors = [
@@ -257,29 +491,21 @@ function serializePayloadBody(payload) {
257
491
  }
258
492
  ];
259
493
  }
260
- return { body: JSON.stringify(sanitized), dropped };
494
+ return {
495
+ body: JSON.stringify(sanitized),
496
+ dropped,
497
+ value: isRecord ? sanitized : void 0
498
+ };
261
499
  }
262
500
  }
263
501
  var init_serializePayload = __esm({
264
502
  "src/serializePayload.ts"() {
265
503
  "use strict";
504
+ init_payloadBudget();
266
505
  init_warnOnce();
267
506
  }
268
507
  });
269
508
 
270
- // src/readEnv.ts
271
- function readEnv(name) {
272
- if (typeof process !== "undefined" && process.env) {
273
- return process.env[name];
274
- }
275
- return void 0;
276
- }
277
- var init_readEnv = __esm({
278
- "src/readEnv.ts"() {
279
- "use strict";
280
- }
281
- });
282
-
283
509
  // src/unrefTimer.ts
284
510
  function unrefTimer(timer) {
285
511
  const handle = timer;
@@ -325,7 +551,7 @@ function recordTraceSubmission(operation, payload) {
325
551
  return;
326
552
  }
327
553
  if (operation === "external_span") {
328
- const rawSpan = asRecord(payload.rawSpan);
554
+ const rawSpan = asRecord2(payload.rawSpan);
329
555
  if (typeof rawSpan?.id !== "string") {
330
556
  submissionCounter += 1;
331
557
  }
@@ -362,14 +588,14 @@ function takeReplaySpanCounts(traceIds) {
362
588
  }
363
589
  return counts;
364
590
  }
365
- function asRecord(value) {
591
+ function asRecord2(value) {
366
592
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
367
593
  }
368
594
  function resolveSourceTraceId(payload) {
369
595
  if (typeof payload.sourceTraceId === "string") {
370
596
  return payload.sourceTraceId;
371
597
  }
372
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
598
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
373
599
  return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
374
600
  }
375
601
  function otlpValue(value) {
@@ -427,32 +653,27 @@ function spanToOtlp(span) {
427
653
  }
428
654
  return result;
429
655
  }
430
- function buildOtlpRequest(first, spans) {
656
+ function encodeSpan(span) {
657
+ const json = JSON.stringify(spanToOtlp(span));
658
+ return { json, size: byteLength(json) };
659
+ }
660
+ function requestEnvelope(first) {
431
661
  const scope = first.instrumentationScope;
432
- return {
433
- resourceSpans: [
434
- {
435
- resource: {
436
- attributes: otlpAttributes(
437
- first.resource.attributes
438
- )
439
- },
440
- scopeSpans: [
441
- {
442
- scope: { name: scope.name, version: scope.version ?? "" },
443
- spans
444
- }
445
- ]
446
- }
447
- ]
448
- };
662
+ const resource = JSON.stringify({
663
+ attributes: otlpAttributes(
664
+ first.resource.attributes
665
+ )
666
+ });
667
+ const scopeJson = JSON.stringify({
668
+ name: scope.name,
669
+ version: scope.version ?? ""
670
+ });
671
+ const head = `{"resourceSpans":[{"resource":${resource},"scopeSpans":[{"scope":${scopeJson},"spans":[`;
672
+ const tail = "]}]}]}";
673
+ return { head, tail, size: byteLength(head) + byteLength(tail) };
449
674
  }
450
- function encodedSize(value) {
451
- const json = JSON.stringify(value);
452
- if (typeof TextEncoder !== "undefined") {
453
- return new TextEncoder().encode(json).length;
454
- }
455
- return json.length;
675
+ function encodeRequest(envelope, spans) {
676
+ return envelope.head + spans.map((span) => span.json).join(",") + envelope.tail;
456
677
  }
457
678
  function delay(ms) {
458
679
  return new Promise((resolve) => {
@@ -502,16 +723,12 @@ function isRetryable(error) {
502
723
  }
503
724
  return RETRYABLE_STATUSES.has(status) || status >= 500;
504
725
  }
505
- function normalizeCollectorEndpoint(endpoint) {
506
- const trimmed = endpoint.replace(/\/+$/, "");
507
- return trimmed.endsWith("/v1/traces") ? trimmed : `${trimmed}/v1/traces`;
508
- }
509
726
  function endSpan(span, endTime) {
510
727
  span.end(endTime);
511
728
  }
512
729
  function spanName(operation, payload) {
513
730
  if (operation === "external_span") {
514
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
731
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
515
732
  if (typeof spanData?.name === "string") {
516
733
  return spanData.name;
517
734
  }
@@ -522,8 +739,8 @@ function spanName(operation, payload) {
522
739
  return `bitfab.${operation}`;
523
740
  }
524
741
  function payloadTimestamp(payload, field) {
525
- const rawSpan = asRecord(payload.rawSpan);
526
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
742
+ const rawSpan = asRecord2(payload.rawSpan);
743
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
527
744
  const raw = rawSpan?.[field] ?? rawTrace?.[field];
528
745
  if (typeof raw !== "string") {
529
746
  return void 0;
@@ -532,7 +749,7 @@ function payloadTimestamp(payload, field) {
532
749
  return Number.isNaN(parsed) ? void 0 : parsed;
533
750
  }
534
751
  function hasError(payload) {
535
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
752
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
536
753
  if (spanData?.error != null) {
537
754
  return true;
538
755
  }
@@ -542,7 +759,6 @@ function hasError(payload) {
542
759
  function createOtelTransport(options) {
543
760
  return new OtelBatchTransport({
544
761
  ...options,
545
- collectorEndpoint: readEnv(COLLECTOR_ENDPOINT_ENV) || void 0,
546
762
  exportConcurrency: readBoundedIntEnv(
547
763
  EXPORT_CONCURRENCY_ENV,
548
764
  MAX_EXPORT_CONCURRENCY,
@@ -577,7 +793,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
577
793
  (transport, remaining) => transport.shutdown(remaining)
578
794
  );
579
795
  }
580
- 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;
796
+ 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;
581
797
  var init_otel = __esm({
582
798
  "src/otel.ts"() {
583
799
  "use strict";
@@ -587,6 +803,7 @@ var init_otel = __esm({
587
803
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
588
804
  init_constants();
589
805
  init_errors();
806
+ init_payloadBudget();
590
807
  init_readEnv();
591
808
  init_serializePayload();
592
809
  init_unrefTimer();
@@ -597,10 +814,8 @@ var init_otel = __esm({
597
814
  MAX_EXPORT_REQUEST_BYTES = 3e6;
598
815
  MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
599
816
  EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
600
- COLLECTOR_ENDPOINT_ENV = "BITFAB_OTEL_EXPORTER_ENDPOINT";
601
817
  MAX_QUEUE_SIZE = 8192;
602
818
  DIRECT_MAX_EXPORT_BATCH_SIZE = 512;
603
- COLLECTOR_MAX_EXPORT_BATCH_SIZE = 32;
604
819
  DIRECT_MAX_REQUEST_BATCH_SIZE = 8;
605
820
  DEFAULT_EXPORT_CONCURRENCY = 32;
606
821
  MAX_EXPORT_CONCURRENCY = 64;
@@ -614,6 +829,7 @@ var init_otel = __esm({
614
829
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
615
830
  replayTraceSubmissions = /* @__PURE__ */ new Set();
616
831
  submissionCounter = 0;
832
+ SPAN_SEPARATOR_BYTES = 1;
617
833
  OtlpPayloadTooLargeError = class extends Error {
618
834
  };
619
835
  OtlpPartialSuccessError = class extends Error {
@@ -642,57 +858,55 @@ var init_otel = __esm({
642
858
  return true;
643
859
  }
644
860
  let encoded;
861
+ let envelope;
645
862
  try {
646
- encoded = spans.map(spanToOtlp);
863
+ encoded = spans.map(encodeSpan);
864
+ envelope = requestEnvelope(spans[0]);
647
865
  } catch (error) {
648
866
  logError("failed to encode an OpenTelemetry span batch", error);
649
867
  return false;
650
868
  }
651
- const first = spans[0];
652
- const batches = this.buildRequestBatches(first, encoded);
869
+ const batches = this.buildRequestBatches(envelope, encoded);
653
870
  const results = await mapWithConcurrency(
654
871
  batches,
655
872
  this.exportConcurrency,
656
- (batch) => this.send(first, batch)
873
+ (batch) => this.send(envelope, batch)
657
874
  );
658
875
  return results.every(Boolean);
659
876
  }
660
- buildRequestBatches(first, spans) {
877
+ buildRequestBatches(envelope, spans) {
661
878
  const batches = [];
662
879
  let current = [];
880
+ let size = envelope.size;
663
881
  for (const span of spans) {
664
- if (current.length >= this.maxRequestBatchSize) {
665
- batches.push(current);
882
+ const addition = span.size + (current.length > 0 ? SPAN_SEPARATOR_BYTES : 0);
883
+ if (current.length > 0 && (current.length >= this.maxRequestBatchSize || size + addition > this.maxRequestBytes)) {
884
+ batches.push({ spans: current, size });
666
885
  current = [];
886
+ size = envelope.size;
667
887
  }
668
- const candidate = [...current, span];
669
- if (current.length > 0 && encodedSize(buildOtlpRequest(first, candidate)) > this.maxRequestBytes) {
670
- batches.push(current);
671
- current = [span];
672
- } else {
673
- current = candidate;
674
- }
888
+ current.push(span);
889
+ size += span.size + (current.length > 1 ? SPAN_SEPARATOR_BYTES : 0);
675
890
  }
676
891
  if (current.length > 0) {
677
- batches.push(current);
892
+ batches.push({ spans: current, size });
678
893
  }
679
894
  return batches;
680
895
  }
681
- async send(first, spans) {
682
- const payload = buildOtlpRequest(first, spans);
683
- if (encodedSize(payload) > this.maxRequestBytes) {
896
+ async send(envelope, batch) {
897
+ if (batch.size > this.maxRequestBytes) {
684
898
  logError(
685
899
  "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
686
900
  );
687
901
  return false;
688
902
  }
689
903
  try {
690
- await this.sendWithRetries(payload);
904
+ await this.sendWithRetries(encodeRequest(envelope, batch.spans));
691
905
  return true;
692
906
  } catch (error) {
693
907
  if (error instanceof OtlpPayloadTooLargeError) {
694
908
  logError(
695
- 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"
909
+ 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"
696
910
  );
697
911
  return false;
698
912
  }
@@ -716,15 +930,15 @@ var init_otel = __esm({
716
930
  * the server does not yet understand. The fix is a client-supplied
717
931
  * idempotency key that ingestion dedupes on.
718
932
  */
719
- async sendWithRetries(payload) {
933
+ async sendWithRetries(body) {
720
934
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
721
935
  try {
722
936
  const response = await this.directSender(
723
937
  OTLP_TRACES_ENDPOINT,
724
- payload,
938
+ body,
725
939
  EXPORT_TIMEOUT_MILLIS
726
940
  );
727
- const partialSuccess = asRecord(response?.partialSuccess);
941
+ const partialSuccess = asRecord2(response?.partialSuccess);
728
942
  const rejected = partialSuccess?.rejectedSpans;
729
943
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
730
944
  logError(
@@ -752,115 +966,6 @@ var init_otel = __esm({
752
966
  async forceFlush() {
753
967
  }
754
968
  };
755
- CollectorSpanExporter = class {
756
- constructor(endpoint, apiKey, maxRequestBytes) {
757
- this.endpoint = endpoint;
758
- this.apiKey = apiKey;
759
- this.maxRequestBytes = maxRequestBytes;
760
- }
761
- /**
762
- * Loaded through a dynamic import rather than a top-level one so bundlers
763
- * code-split it: Collector delivery is opt-in, and a consumer who never sets
764
- * an endpoint should not pay for the exporter in their initial bundle. It is
765
- * a hard dependency, so this cannot fail for want of the package.
766
- */
767
- loadExporterModule() {
768
- if (!this.pendingModule) {
769
- this.pendingModule = import("@opentelemetry/exporter-trace-otlp-proto");
770
- }
771
- return this.pendingModule;
772
- }
773
- export(spans, resultCallback) {
774
- void this.exportAsync(spans).then(
775
- (succeeded) => {
776
- resultCallback({
777
- code: succeeded ? import_core.ExportResultCode.SUCCESS : import_core.ExportResultCode.FAILED
778
- });
779
- },
780
- (error) => {
781
- resultCallback({ code: import_core.ExportResultCode.FAILED, error });
782
- }
783
- );
784
- }
785
- async exportAsync(spans) {
786
- if (spans.length === 0) {
787
- return true;
788
- }
789
- let delegate;
790
- try {
791
- delegate = await this.resolveDelegate();
792
- } catch (error) {
793
- logError("failed to build the OTLP Collector exporter", error);
794
- return false;
795
- }
796
- const results = await Promise.all(
797
- this.partition(spans).map(
798
- (batch) => new Promise((resolve) => {
799
- try {
800
- delegate.export(batch, (result) => {
801
- resolve(result.code === import_core.ExportResultCode.SUCCESS);
802
- });
803
- } catch (error) {
804
- logError("Collector export threw", error);
805
- resolve(false);
806
- }
807
- })
808
- )
809
- );
810
- return results.every(Boolean);
811
- }
812
- /**
813
- * Partition by the encoded JSON size of each carrier rather than its encoded
814
- * protobuf size. Protobuf is strictly smaller than the equivalent JSON for
815
- * these payloads, so the JSON figure is a conservative bound that keeps every
816
- * request under the target without pulling `@opentelemetry/otlp-transformer`
817
- * into the dependency set purely to measure bytes.
818
- */
819
- partition(spans) {
820
- const batches = [];
821
- let current = [];
822
- let currentSize = 0;
823
- for (const span of spans) {
824
- const size = encodedSize(spanToOtlp(span));
825
- if (current.length > 0 && currentSize + size > this.maxRequestBytes) {
826
- batches.push(current);
827
- current = [];
828
- currentSize = 0;
829
- }
830
- current.push(span);
831
- currentSize += size;
832
- }
833
- if (current.length > 0) {
834
- batches.push(current);
835
- }
836
- return batches;
837
- }
838
- async resolveDelegate() {
839
- const apiKey = this.apiKey() ?? "";
840
- if (this.delegate && this.delegateApiKey === apiKey) {
841
- return this.delegate;
842
- }
843
- const { OTLPTraceExporter } = await this.loadExporterModule();
844
- const previous = this.delegate;
845
- this.delegate = new OTLPTraceExporter({
846
- url: this.endpoint,
847
- headers: { Authorization: `Bearer ${apiKey}` },
848
- timeoutMillis: EXPORT_TIMEOUT_MILLIS
849
- });
850
- this.delegateApiKey = apiKey;
851
- if (previous) {
852
- void previous.shutdown().catch(() => {
853
- });
854
- }
855
- return this.delegate;
856
- }
857
- async shutdown() {
858
- await this.delegate?.shutdown();
859
- }
860
- async forceFlush() {
861
- await this.delegate?.forceFlush?.();
862
- }
863
- };
864
969
  DeliveryTrackingExporter = class {
865
970
  constructor(exporter) {
866
971
  this.exporter = exporter;
@@ -903,27 +1008,22 @@ var init_otel = __esm({
903
1008
  OtelBatchTransport = class {
904
1009
  constructor(options) {
905
1010
  this.closed = false;
906
- const collectorEndpoint = options.collectorEndpoint;
907
1011
  const maxRequestBytes = options.maxRequestBytes ?? MAX_EXPORT_REQUEST_BYTES;
908
1012
  const maxRequestBatchSize = options.maxRequestBatchSize ?? DIRECT_MAX_REQUEST_BATCH_SIZE;
909
1013
  if (maxRequestBatchSize <= 0) {
910
1014
  throw new BitfabError("maxRequestBatchSize must be a positive integer");
911
1015
  }
912
1016
  this.deliveryTracker = new DeliveryTrackingExporter(
913
- collectorEndpoint === void 0 ? new BitfabSpanExporter(
1017
+ new BitfabSpanExporter(
914
1018
  options.directSender,
915
1019
  maxRequestBytes,
916
1020
  maxRequestBatchSize,
917
1021
  options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
918
- ) : new CollectorSpanExporter(
919
- normalizeCollectorEndpoint(collectorEndpoint),
920
- options.apiKey,
921
- maxRequestBytes
922
1022
  )
923
1023
  );
924
1024
  this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
925
1025
  maxQueueSize: options.maxQueueSize ?? MAX_QUEUE_SIZE,
926
- maxExportBatchSize: options.maxExportBatchSize ?? (collectorEndpoint === void 0 ? DIRECT_MAX_EXPORT_BATCH_SIZE : COLLECTOR_MAX_EXPORT_BATCH_SIZE),
1026
+ maxExportBatchSize: options.maxExportBatchSize ?? DIRECT_MAX_EXPORT_BATCH_SIZE,
927
1027
  scheduledDelayMillis: SCHEDULE_DELAY_MILLIS,
928
1028
  exportTimeoutMillis: options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
929
1029
  });
@@ -1076,6 +1176,7 @@ var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCL
1076
1176
  var init_http = __esm({
1077
1177
  "src/http.ts"() {
1078
1178
  "use strict";
1179
+ init_compress();
1079
1180
  init_constants();
1080
1181
  init_errors();
1081
1182
  init_replayContext();
@@ -1140,8 +1241,7 @@ var init_http = __esm({
1140
1241
  }
1141
1242
  if (!this.traceTransport) {
1142
1243
  this.traceTransport = createTraceTransport({
1143
- apiKey: () => this.resolveApiKey(),
1144
- directSender: (endpoint, payload, timeoutMs) => this.request(endpoint, payload, {
1244
+ directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1145
1245
  timeout: timeoutMs
1146
1246
  })
1147
1247
  });
@@ -1211,11 +1311,6 @@ var init_http = __esm({
1211
1311
  * @throws {BitfabError} If the request fails
1212
1312
  */
1213
1313
  async request(endpoint, payload, options) {
1214
- const url = `${this.serviceUrl}${endpoint}`;
1215
- const timeout = options?.timeout ?? this.timeout;
1216
- const method = options?.method ?? "POST";
1217
- const controller = new AbortController();
1218
- const timeoutId = setTimeout(() => controller.abort(), timeout);
1219
1314
  const { body, dropped } = serializePayloadBody(payload);
1220
1315
  if (dropped.length > 0) {
1221
1316
  try {
@@ -1225,14 +1320,33 @@ var init_http = __esm({
1225
1320
  } catch {
1226
1321
  }
1227
1322
  }
1323
+ return this.sendEncoded(endpoint, body, options);
1324
+ }
1325
+ /**
1326
+ * POST an already-encoded body. The span transport encodes its own batches,
1327
+ * so routing them back through {@link HttpClient.request} would encode the
1328
+ * same data twice.
1329
+ */
1330
+ async sendEncoded(endpoint, body, options) {
1331
+ const url = `${this.serviceUrl}${endpoint}`;
1332
+ const timeout = options?.timeout ?? this.timeout;
1333
+ const method = options?.method ?? "POST";
1334
+ const controller = new AbortController();
1335
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1336
+ const prepared = encodeRequestBody(body);
1337
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1338
+ const headers = {
1339
+ "Content-Type": "application/json",
1340
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1341
+ };
1342
+ if (encoded.contentEncoding) {
1343
+ headers["Content-Encoding"] = encoded.contentEncoding;
1344
+ }
1228
1345
  try {
1229
1346
  const response = await fetch(url, {
1230
1347
  method,
1231
- headers: {
1232
- "Content-Type": "application/json",
1233
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1234
- },
1235
- body,
1348
+ headers,
1349
+ body: encoded.body,
1236
1350
  signal: controller.signal
1237
1351
  });
1238
1352
  if (!response.ok) {
@@ -1681,9 +1795,10 @@ var init_serialize = __esm({
1681
1795
  "src/serialize.ts"() {
1682
1796
  "use strict";
1683
1797
  import_superjson = __toESM(require("superjson"), 1);
1798
+ init_payloadBudget();
1684
1799
  init_warnOnce();
1685
- MAX_SERIALIZED_BYTES = 512e3;
1686
- MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
1800
+ MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1801
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1687
1802
  MAX_SAFE_DEPTH = 6;
1688
1803
  }
1689
1804
  });