bitfab 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.d.cts CHANGED
@@ -2336,7 +2336,7 @@ declare class BitfabFunction {
2336
2336
  /**
2337
2337
  * SDK version from package.json (injected at build time)
2338
2338
  */
2339
- declare const __version__ = "0.34.2";
2339
+ declare const __version__ = "0.36.0";
2340
2340
 
2341
2341
  /**
2342
2342
  * Constants for the Bitfab SDK.
package/dist/index.d.ts CHANGED
@@ -2336,7 +2336,7 @@ declare class BitfabFunction {
2336
2336
  /**
2337
2337
  * SDK version from package.json (injected at build time)
2338
2338
  */
2339
- declare const __version__ = "0.34.2";
2339
+ declare const __version__ = "0.36.0";
2340
2340
 
2341
2341
  /**
2342
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-KQB42J3S.js";
23
+ } from "./chunk-JECWMVJG.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-SBQQOFA5.js";
32
+ } from "./chunk-4YTIVUYX.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.2";
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,9 +662,6 @@ function spanToOtlp(span) {
436
662
  }
437
663
  return result;
438
664
  }
439
- function byteLength(value) {
440
- return textEncoder ? textEncoder.encode(value).length : value.length;
441
- }
442
665
  function encodeSpan(span) {
443
666
  const json = JSON.stringify(spanToOtlp(span));
444
667
  return { json, size: byteLength(json) };
@@ -514,7 +737,7 @@ function endSpan(span, endTime) {
514
737
  }
515
738
  function spanName(operation, payload) {
516
739
  if (operation === "external_span") {
517
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
740
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
518
741
  if (typeof spanData?.name === "string") {
519
742
  return spanData.name;
520
743
  }
@@ -525,8 +748,8 @@ function spanName(operation, payload) {
525
748
  return `bitfab.${operation}`;
526
749
  }
527
750
  function payloadTimestamp(payload, field) {
528
- const rawSpan = asRecord(payload.rawSpan);
529
- const rawTrace = asRecord(payload.externalTrace) ?? asRecord(payload.rawTrace);
751
+ const rawSpan = asRecord2(payload.rawSpan);
752
+ const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
530
753
  const raw = rawSpan?.[field] ?? rawTrace?.[field];
531
754
  if (typeof raw !== "string") {
532
755
  return void 0;
@@ -535,7 +758,7 @@ function payloadTimestamp(payload, field) {
535
758
  return Number.isNaN(parsed) ? void 0 : parsed;
536
759
  }
537
760
  function hasError(payload) {
538
- const spanData = asRecord(asRecord(payload.rawSpan)?.span_data);
761
+ const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
539
762
  if (spanData?.error != null) {
540
763
  return true;
541
764
  }
@@ -579,7 +802,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
579
802
  (transport, remaining) => transport.shutdown(remaining)
580
803
  );
581
804
  }
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;
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;
583
806
  var init_otel = __esm({
584
807
  "src/otel.ts"() {
585
808
  "use strict";
@@ -589,6 +812,7 @@ var init_otel = __esm({
589
812
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
590
813
  init_constants();
591
814
  init_errors();
815
+ init_payloadBudget();
592
816
  init_readEnv();
593
817
  init_serializePayload();
594
818
  init_unrefTimer();
@@ -614,7 +838,6 @@ var init_otel = __esm({
614
838
  traceSubmissionSpanIds = /* @__PURE__ */ new Map();
615
839
  replayTraceSubmissions = /* @__PURE__ */ new Set();
616
840
  submissionCounter = 0;
617
- textEncoder = typeof TextEncoder === "undefined" ? void 0 : new TextEncoder();
618
841
  SPAN_SEPARATOR_BYTES = 1;
619
842
  OtlpPayloadTooLargeError = class extends Error {
620
843
  };
@@ -724,7 +947,7 @@ var init_otel = __esm({
724
947
  body,
725
948
  EXPORT_TIMEOUT_MILLIS
726
949
  );
727
- const partialSuccess = asRecord(response?.partialSuccess);
950
+ const partialSuccess = asRecord2(response?.partialSuccess);
728
951
  const rejected = partialSuccess?.rejectedSpans;
729
952
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
730
953
  logError(
@@ -962,6 +1185,7 @@ var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCL
962
1185
  var init_http = __esm({
963
1186
  "src/http.ts"() {
964
1187
  "use strict";
1188
+ init_compress();
965
1189
  init_constants();
966
1190
  init_errors();
967
1191
  init_replayContext();
@@ -1118,14 +1342,20 @@ var init_http = __esm({
1118
1342
  const method = options?.method ?? "POST";
1119
1343
  const controller = new AbortController();
1120
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
+ }
1121
1354
  try {
1122
1355
  const response = await fetch(url, {
1123
1356
  method,
1124
- headers: {
1125
- "Content-Type": "application/json",
1126
- Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
1127
- },
1128
- body,
1357
+ headers,
1358
+ body: encoded.body,
1129
1359
  signal: controller.signal
1130
1360
  });
1131
1361
  if (!response.ok) {
@@ -1574,9 +1804,10 @@ var init_serialize = __esm({
1574
1804
  "src/serialize.ts"() {
1575
1805
  "use strict";
1576
1806
  import_superjson = __toESM(require("superjson"), 1);
1807
+ init_payloadBudget();
1577
1808
  init_warnOnce();
1578
- MAX_SERIALIZED_BYTES = 512e3;
1579
- MAX_FRAMEWORK_SERIALIZED_BYTES = 2e6;
1809
+ MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1810
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1580
1811
  MAX_SAFE_DEPTH = 6;
1581
1812
  }
1582
1813
  });