@bitfab/sdk 0.34.2 → 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/index.cjs CHANGED
@@ -42,7 +42,7 @@ var __version__;
42
42
  var init_version_generated = __esm({
43
43
  "src/version.generated.ts"() {
44
44
  "use strict";
45
- __version__ = "0.34.2";
45
+ __version__ = "0.36.0";
46
46
  }
47
47
  });
48
48
 
@@ -56,6 +56,87 @@ var init_constants = __esm({
56
56
  }
57
57
  });
58
58
 
59
+ // src/readEnv.ts
60
+ function readEnv(name) {
61
+ if (typeof process !== "undefined" && process.env) {
62
+ return process.env[name];
63
+ }
64
+ return void 0;
65
+ }
66
+ var init_readEnv = __esm({
67
+ "src/readEnv.ts"() {
68
+ "use strict";
69
+ }
70
+ });
71
+
72
+ // src/compress.ts
73
+ function toArrayBuffer(view) {
74
+ return view.buffer.slice(
75
+ view.byteOffset,
76
+ view.byteOffset + view.byteLength
77
+ );
78
+ }
79
+ async function gzipViaStream(bytes) {
80
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
81
+ return await new Response(stream).arrayBuffer();
82
+ }
83
+ function encodeRequestBody(body) {
84
+ if (readEnv(DISABLE_COMPRESSION_ENV)) {
85
+ return { body };
86
+ }
87
+ const bytes = new TextEncoder().encode(body);
88
+ if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
89
+ return { body };
90
+ }
91
+ if (gzipNode) {
92
+ return gzipNode(bytes).then(
93
+ (compressed) => ({
94
+ body: toArrayBuffer(compressed),
95
+ contentEncoding: "gzip"
96
+ }),
97
+ () => ({ body })
98
+ );
99
+ }
100
+ if (typeof CompressionStream === "undefined") {
101
+ return { body };
102
+ }
103
+ return gzipViaStream(bytes).then(
104
+ (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
105
+ () => ({ body })
106
+ );
107
+ }
108
+ var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
109
+ var init_compress = __esm({
110
+ "src/compress.ts"() {
111
+ "use strict";
112
+ init_readEnv();
113
+ DISABLE_COMPRESSION_ENV = "BITFAB_DISABLE_COMPRESSION";
114
+ MIN_COMPRESSED_BYTES = 8192;
115
+ _nodeGzipReady = (typeof process !== "undefined" && process.versions?.node ? (
116
+ // The join trick hides "node:zlib" from static analysis so bundlers that
117
+ // ban Node.js built-ins don't fail at build time. webpackIgnore tells
118
+ // webpack/turbopack to emit a native import() so Node.js can resolve the
119
+ // module at runtime. Same pattern as `asyncStorage.ts`.
120
+ import(
121
+ /* webpackIgnore: true */
122
+ ["node", "zlib"].join(":")
123
+ ).then(({ gzip }) => {
124
+ gzipNode = (data) => new Promise((resolve, reject) => {
125
+ gzip(data, (error, result) => {
126
+ if (error) {
127
+ reject(error);
128
+ } else {
129
+ resolve(result);
130
+ }
131
+ });
132
+ });
133
+ }).catch(() => {
134
+ })
135
+ ) : Promise.resolve()).then(() => {
136
+ });
137
+ }
138
+ });
139
+
59
140
  // src/errors.ts
60
141
  var BitfabError;
61
142
  var init_errors = __esm({
@@ -144,6 +225,131 @@ var init_replayContext = __esm({
144
225
  }
145
226
  });
146
227
 
228
+ // src/payloadBudget.ts
229
+ function byteLength(value) {
230
+ return textEncoder ? textEncoder.encode(value).length : value.length;
231
+ }
232
+ function carrierByteLength(body) {
233
+ return carrierBytesOf(textEncoder ? textEncoder.encode(body) : null, body);
234
+ }
235
+ function carrierBytesOf(encoded, body) {
236
+ if (!encoded) {
237
+ return body.length + 2;
238
+ }
239
+ let extra = 2;
240
+ for (let i = 0; i < encoded.length; i++) {
241
+ const byte = encoded[i];
242
+ if (byte === 34 || byte === 92) {
243
+ extra += 1;
244
+ } else if (byte < 32) {
245
+ extra += byte === 8 || byte === 9 || byte === 10 || byte === 12 || byte === 13 ? 1 : 5;
246
+ }
247
+ }
248
+ return encoded.length + extra;
249
+ }
250
+ function fitsCarrierBudget(body) {
251
+ const units = body.length;
252
+ if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
253
+ return true;
254
+ }
255
+ if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
256
+ return false;
257
+ }
258
+ return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
259
+ }
260
+ function asRecord(value) {
261
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
262
+ }
263
+ function cloneTrimmable(payload) {
264
+ const copy = { ...payload };
265
+ const containers = [];
266
+ const spanData = asRecord(copy.span_data);
267
+ if (spanData) {
268
+ const clone = { ...spanData };
269
+ copy.span_data = clone;
270
+ containers.push(clone);
271
+ }
272
+ const rawSpan = asRecord(copy.rawSpan);
273
+ const rawSpanData = rawSpan && asRecord(rawSpan.span_data);
274
+ if (rawSpan && rawSpanData) {
275
+ const clone = { ...rawSpanData };
276
+ copy.rawSpan = { ...rawSpan, span_data: clone };
277
+ containers.push(clone);
278
+ }
279
+ if (containers.length === 0) {
280
+ containers.push(copy);
281
+ }
282
+ return { copy, containers };
283
+ }
284
+ function collectCandidates(containers) {
285
+ const candidates = [];
286
+ for (const container of containers) {
287
+ for (const [key, value] of Object.entries(container)) {
288
+ if (STRUCTURAL_SPAN_KEYS.has(key) || value == null) {
289
+ continue;
290
+ }
291
+ let size;
292
+ try {
293
+ size = byteLength(JSON.stringify(value) ?? "");
294
+ } catch {
295
+ continue;
296
+ }
297
+ candidates.push({ container, key, size });
298
+ }
299
+ }
300
+ return candidates.sort((a, b) => b.size - a.size);
301
+ }
302
+ function trimPayloadToBudget(payload, encode) {
303
+ const { copy, containers } = cloneTrimmable(payload);
304
+ const candidates = collectCandidates(containers);
305
+ if (candidates.length === 0) {
306
+ return void 0;
307
+ }
308
+ const trimmed = [];
309
+ for (const candidate of candidates) {
310
+ candidate.container[candidate.key] = `<unserializable: too_large_${candidate.size}_bytes>`;
311
+ trimmed.push(candidate.key);
312
+ let body;
313
+ try {
314
+ body = encode(copy);
315
+ } catch {
316
+ return void 0;
317
+ }
318
+ if (fitsCarrierBudget(body)) {
319
+ return { value: copy, trimmed };
320
+ }
321
+ }
322
+ return void 0;
323
+ }
324
+ function markPayloadTrimmed(value, trimmed) {
325
+ const existing = Array.isArray(value.errors) ? value.errors : [];
326
+ value.errors = [
327
+ ...existing,
328
+ {
329
+ source: "sdk",
330
+ step: "payload_budget",
331
+ error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
332
+ ...new Set(trimmed)
333
+ ].join(", ")}`
334
+ }
335
+ ];
336
+ }
337
+ var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
338
+ var init_payloadBudget = __esm({
339
+ "src/payloadBudget.ts"() {
340
+ "use strict";
341
+ MAX_SPAN_CARRIER_BYTES = 28e5;
342
+ textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
343
+ MAX_BYTES_PER_UNIT = 3;
344
+ STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
345
+ "name",
346
+ "type",
347
+ "function_name",
348
+ "error_source"
349
+ ]);
350
+ }
351
+ });
352
+
147
353
  // src/warnOnce.ts
148
354
  function warnOnce(key, message) {
149
355
  if (warned.has(key)) {
@@ -165,8 +371,37 @@ var init_warnOnce = __esm({
165
371
 
166
372
  // src/serializePayload.ts
167
373
  function serializePayloadBody(payload) {
374
+ const encoded = encodePayloadBody(payload);
375
+ if (fitsCarrierBudget(encoded.body)) {
376
+ return { body: encoded.body, dropped: encoded.dropped };
377
+ }
378
+ return applyPayloadBudget(encoded);
379
+ }
380
+ function applyPayloadBudget(encoded) {
381
+ const result = encoded.value ? trimPayloadToBudget(
382
+ encoded.value,
383
+ (value) => encodePayloadBody(value).body
384
+ ) : void 0;
385
+ if (!result) {
386
+ return { body: encoded.body, dropped: encoded.dropped };
387
+ }
388
+ warnOnce(
389
+ "payload:over-budget",
390
+ `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
391
+ ...new Set(result.trimmed)
392
+ ].join(
393
+ ", "
394
+ )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
395
+ );
396
+ markPayloadTrimmed(result.value, result.trimmed);
397
+ return {
398
+ body: encodePayloadBody(result.value).body,
399
+ dropped: encoded.dropped
400
+ };
401
+ }
402
+ function encodePayloadBody(payload) {
168
403
  try {
169
- return { body: JSON.stringify(payload), dropped: [] };
404
+ return { body: JSON.stringify(payload), dropped: [], value: payload };
170
405
  } catch {
171
406
  const dropped = [];
172
407
  const sanitize = (value, seen) => {
@@ -231,12 +466,11 @@ function serializePayloadBody(payload) {
231
466
  sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
232
467
  } catch (error) {
233
468
  const message = error instanceof Error ? error.message : String(error);
234
- return {
235
- body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
236
- dropped
237
- };
469
+ const marker = { error: `payload_serialize_failed: ${message}` };
470
+ return { body: JSON.stringify(marker), dropped, value: marker };
238
471
  }
239
- if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
472
+ const isRecord = typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized);
473
+ if (dropped.length > 0 && isRecord) {
240
474
  const obj = sanitized;
241
475
  const existing = Array.isArray(obj.errors) ? obj.errors : [];
242
476
  obj.errors = [
@@ -250,29 +484,21 @@ function serializePayloadBody(payload) {
250
484
  }
251
485
  ];
252
486
  }
253
- return { body: JSON.stringify(sanitized), dropped };
487
+ return {
488
+ body: JSON.stringify(sanitized),
489
+ dropped,
490
+ value: isRecord ? sanitized : void 0
491
+ };
254
492
  }
255
493
  }
256
494
  var init_serializePayload = __esm({
257
495
  "src/serializePayload.ts"() {
258
496
  "use strict";
497
+ init_payloadBudget();
259
498
  init_warnOnce();
260
499
  }
261
500
  });
262
501
 
263
- // src/readEnv.ts
264
- function readEnv(name) {
265
- if (typeof process !== "undefined" && process.env) {
266
- return process.env[name];
267
- }
268
- return void 0;
269
- }
270
- var init_readEnv = __esm({
271
- "src/readEnv.ts"() {
272
- "use strict";
273
- }
274
- });
275
-
276
502
  // src/unrefTimer.ts
277
503
  function unrefTimer(timer) {
278
504
  const handle = timer;
@@ -318,7 +544,7 @@ function recordTraceSubmission(operation, payload) {
318
544
  return;
319
545
  }
320
546
  if (operation === "external_span") {
321
- const rawSpan = asRecord(payload.rawSpan);
547
+ const rawSpan = asRecord2(payload.rawSpan);
322
548
  if (typeof rawSpan?.id !== "string") {
323
549
  submissionCounter += 1;
324
550
  }
@@ -355,14 +581,14 @@ function takeReplaySpanCounts(traceIds) {
355
581
  }
356
582
  return counts;
357
583
  }
358
- function asRecord(value) {
584
+ function asRecord2(value) {
359
585
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
360
586
  }
361
587
  function resolveSourceTraceId(payload) {
362
588
  if (typeof payload.sourceTraceId === "string") {
363
589
  return payload.sourceTraceId;
364
590
  }
365
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
591
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
366
592
  return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
367
593
  }
368
594
  function otlpValue(value) {
@@ -420,9 +646,6 @@ function spanToOtlp(span) {
420
646
  }
421
647
  return result;
422
648
  }
423
- function byteLength(value) {
424
- return textEncoder ? textEncoder.encode(value).length : value.length;
425
- }
426
649
  function encodeSpan(span) {
427
650
  const json = JSON.stringify(spanToOtlp(span));
428
651
  return { json, size: byteLength(json) };
@@ -498,7 +721,7 @@ function endSpan(span, endTime) {
498
721
  }
499
722
  function spanName(operation, payload) {
500
723
  if (operation === "external_span") {
501
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
724
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
502
725
  if (typeof spanData?.name === "string") {
503
726
  return spanData.name;
504
727
  }
@@ -509,8 +732,8 @@ function spanName(operation, payload) {
509
732
  return `bitfab.${operation}`;
510
733
  }
511
734
  function payloadTimestamp(payload, field) {
512
- const rawSpan = asRecord(payload.rawSpan);
513
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
735
+ const rawSpan = asRecord2(payload.rawSpan);
736
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
514
737
  const raw = rawSpan?.[field] ?? rawTrace?.[field];
515
738
  if (typeof raw !== "string") {
516
739
  return void 0;
@@ -519,7 +742,7 @@ function payloadTimestamp(payload, field) {
519
742
  return Number.isNaN(parsed) ? void 0 : parsed;
520
743
  }
521
744
  function hasError(payload) {
522
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
745
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
523
746
  if (spanData?.error != null) {
524
747
  return true;
525
748
  }
@@ -563,7 +786,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
563
786
  (transport, remaining) => transport.shutdown(remaining)
564
787
  );
565
788
  }
566
- 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;
789
+ var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, OTLP_TRACES_ENDPOINT, MAX_EXPORT_REQUEST_BYTES, MAX_REQUEST_BYTES_ENV, EXPORT_CONCURRENCY_ENV, MAX_QUEUE_SIZE, DIRECT_MAX_EXPORT_BATCH_SIZE, DIRECT_MAX_REQUEST_BATCH_SIZE, DEFAULT_EXPORT_CONCURRENCY, MAX_EXPORT_CONCURRENCY, SCHEDULE_DELAY_MILLIS, EXPORT_TIMEOUT_MILLIS, RETRY_DELAY_MILLIS, MAX_SEND_ATTEMPTS, DEFAULT_LIFECYCLE_TIMEOUT_MS, RETRYABLE_STATUSES, liveTransports, traceSubmissionSpanIds, replayTraceSubmissions, submissionCounter, SPAN_SEPARATOR_BYTES, OtlpPayloadTooLargeError, OtlpPartialSuccessError, BitfabSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
567
790
  var init_otel = __esm({
568
791
  "src/otel.ts"() {
569
792
  "use strict";
@@ -573,6 +796,7 @@ var init_otel = __esm({
573
796
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
574
797
  init_constants();
575
798
  init_errors();
799
+ init_payloadBudget();
576
800
  init_readEnv();
577
801
  init_serializePayload();
578
802
  init_unrefTimer();
@@ -598,7 +822,6 @@ var init_otel = __esm({
598
822
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
599
823
  replayTraceSubmissions = /* @__PURE__ */ new Set();
600
824
  submissionCounter = 0;
601
- textEncoder = typeof TextEncoder === "undefined" ? void 0 : new TextEncoder();
602
825
  SPAN_SEPARATOR_BYTES = 1;
603
826
  OtlpPayloadTooLargeError = class extends Error {
604
827
  };
@@ -708,7 +931,7 @@ var init_otel = __esm({
708
931
  body,
709
932
  EXPORT_TIMEOUT_MILLIS
710
933
  );
711
- const partialSuccess = asRecord(response?.partialSuccess);
934
+ const partialSuccess = asRecord2(response?.partialSuccess);
712
935
  const rejected = partialSuccess?.rejectedSpans;
713
936
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
714
937
  logError(
@@ -946,6 +1169,7 @@ var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCL
946
1169
  var init_http = __esm({
947
1170
  "src/http.ts"() {
948
1171
  "use strict";
1172
+ init_compress();
949
1173
  init_constants();
950
1174
  init_errors();
951
1175
  init_replayContext();
@@ -1102,14 +1326,20 @@ var init_http = __esm({
1102
1326
  const method = options?.method ?? "POST";
1103
1327
  const controller = new AbortController();
1104
1328
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1329
+ const prepared = encodeRequestBody(body);
1330
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1331
+ const headers = {
1332
+ "Content-Type": "application/json",
1333
+ Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1334
+ };
1335
+ if (encoded.contentEncoding) {
1336
+ headers["Content-Encoding"] = encoded.contentEncoding;
1337
+ }
1105
1338
  try {
1106
1339
  const response = await fetch(url, {
1107
1340
  method,
1108
- headers: {
1109
- "Content-Type": "application/json",
1110
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1111
- },
1112
- body,
1341
+ headers,
1342
+ body: encoded.body,
1113
1343
  signal: controller.signal
1114
1344
  });
1115
1345
  if (!response.ok) {
@@ -1558,9 +1788,10 @@ var init_serialize = __esm({
1558
1788
  "src/serialize.ts"() {
1559
1789
  "use strict";
1560
1790
  import_superjson = __toESM(require("superjson"), 1);
1791
+ init_payloadBudget();
1561
1792
  init_warnOnce();
1562
- MAX_SERIALIZED_BYTES = 512e3;
1563
- MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
1793
+ MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1794
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1564
1795
  MAX_SAFE_DEPTH = 6;
1565
1796
  }
1566
1797
  });