@bitfab/sdk 0.36.5 → 0.36.7

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.
@@ -247,38 +247,63 @@ function toArrayBuffer(view) {
247
247
  view.byteOffset + view.byteLength
248
248
  );
249
249
  }
250
+ function compressedRequest(body, rawBytes, compressed) {
251
+ if (compressed.byteLength >= rawBytes) {
252
+ return { body, rawBytes, wireBytes: rawBytes };
253
+ }
254
+ return {
255
+ body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
256
+ contentEncoding: "gzip",
257
+ rawBytes,
258
+ wireBytes: compressed.byteLength
259
+ };
260
+ }
250
261
  async function gzipViaStream(bytes) {
251
262
  const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
252
263
  return await new Response(stream).arrayBuffer();
253
264
  }
254
265
  function encodeRequestBody(body) {
255
266
  if (readEnv(DISABLE_COMPRESSION_ENV)) {
256
- return { body };
267
+ const rawBytes = new TextEncoder().encode(body).byteLength;
268
+ return { body, rawBytes, wireBytes: rawBytes };
257
269
  }
258
270
  const bytes = new TextEncoder().encode(body);
259
271
  if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
260
- return { body };
272
+ return {
273
+ body,
274
+ rawBytes: bytes.byteLength,
275
+ wireBytes: bytes.byteLength
276
+ };
261
277
  }
262
278
  if (gzipNode) {
263
279
  return gzipNode(bytes).then(
264
- (compressed) => ({
265
- body: toArrayBuffer(compressed),
266
- contentEncoding: "gzip"
267
- }),
268
- () => ({ body })
280
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
281
+ () => ({
282
+ body,
283
+ rawBytes: bytes.byteLength,
284
+ wireBytes: bytes.byteLength
285
+ })
269
286
  );
270
287
  }
271
288
  if (typeof CompressionStream === "undefined") {
272
- return { body };
289
+ return {
290
+ body,
291
+ rawBytes: bytes.byteLength,
292
+ wireBytes: bytes.byteLength
293
+ };
273
294
  }
274
295
  return gzipViaStream(bytes).then(
275
- (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
276
- () => ({ body })
296
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
297
+ () => ({
298
+ body,
299
+ rawBytes: bytes.byteLength,
300
+ wireBytes: bytes.byteLength
301
+ })
277
302
  );
278
303
  }
279
304
 
280
305
  // src/version.generated.ts
281
- var __version__ = "0.36.5";
306
+ var __version__ = "0.36.7";
282
307
 
283
308
  // src/constants.ts
284
309
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -351,6 +376,7 @@ function runWithReplayContext(ctx, fn) {
351
376
 
352
377
  // src/payloadBudget.ts
353
378
  var MAX_SPAN_CARRIER_BYTES = 28e5;
379
+ var MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
354
380
  var textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
355
381
  function byteLength(value) {
356
382
  return textEncoder ? textEncoder.encode(value).length : value.length;
@@ -374,15 +400,15 @@ function carrierBytesOf(encoded, body) {
374
400
  return encoded.length + extra;
375
401
  }
376
402
  var MAX_BYTES_PER_UNIT = 3;
377
- function fitsCarrierBudget(body) {
403
+ function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
378
404
  const units = body.length;
379
- if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
405
+ if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
380
406
  return true;
381
407
  }
382
- if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
408
+ if (units + 2 > maxBytes) {
383
409
  return false;
384
410
  }
385
- return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
411
+ return carrierByteLength(body) <= maxBytes;
386
412
  }
387
413
  var STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
388
414
  "name",
@@ -432,7 +458,7 @@ function collectCandidates(containers) {
432
458
  }
433
459
  return candidates.sort((a, b) => b.size - a.size);
434
460
  }
435
- function trimPayloadToBudget(payload, encode) {
461
+ function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
436
462
  const { copy, containers } = cloneTrimmable(payload);
437
463
  const candidates = collectCandidates(containers);
438
464
  if (candidates.length === 0) {
@@ -448,20 +474,20 @@ function trimPayloadToBudget(payload, encode) {
448
474
  } catch {
449
475
  return void 0;
450
476
  }
451
- if (fitsCarrierBudget(body)) {
477
+ if (fitsCarrierBudget(body, maxBytes)) {
452
478
  return { value: copy, trimmed };
453
479
  }
454
480
  }
455
481
  return void 0;
456
482
  }
457
- function markPayloadTrimmed(value, trimmed) {
483
+ function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
458
484
  const existing = Array.isArray(value.errors) ? value.errors : [];
459
485
  value.errors = [
460
486
  ...existing,
461
487
  {
462
488
  source: "sdk",
463
489
  step: "payload_budget",
464
- error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
490
+ error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
465
491
  ...new Set(trimmed)
466
492
  ].join(", ")}`
467
493
  }
@@ -482,30 +508,31 @@ function warnOnce(key, message) {
482
508
  }
483
509
 
484
510
  // src/serializePayload.ts
485
- function serializePayloadBody(payload) {
511
+ function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
486
512
  const encoded = encodePayloadBody(payload);
487
- if (fitsCarrierBudget(encoded.body)) {
513
+ if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
488
514
  return { body: encoded.body, dropped: encoded.dropped };
489
515
  }
490
- return applyPayloadBudget(encoded);
516
+ return applyPayloadBudget(encoded, maxCarrierBytes);
491
517
  }
492
- function applyPayloadBudget(encoded) {
518
+ function applyPayloadBudget(encoded, maxCarrierBytes) {
493
519
  const result = encoded.value ? trimPayloadToBudget(
494
520
  encoded.value,
495
- (value) => encodePayloadBody(value).body
521
+ (value) => encodePayloadBody(value).body,
522
+ maxCarrierBytes
496
523
  ) : void 0;
497
524
  if (!result) {
498
525
  return { body: encoded.body, dropped: encoded.dropped };
499
526
  }
500
527
  warnOnce(
501
528
  "payload:over-budget",
502
- `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
529
+ `a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
503
530
  ...new Set(result.trimmed)
504
531
  ].join(
505
532
  ", "
506
533
  )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
507
534
  );
508
- markPayloadTrimmed(result.value, result.trimmed);
535
+ markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
509
536
  return {
510
537
  body: encodePayloadBody(result.value).body,
511
538
  dropped: encoded.dropped
@@ -629,6 +656,7 @@ var OPERATION_ATTRIBUTE = "bitfab.operation";
629
656
  var PAYLOAD_ATTRIBUTE = "bitfab.payload";
630
657
  var OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
631
658
  var MAX_EXPORT_REQUEST_BYTES = 3e6;
659
+ var MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
632
660
  var MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
633
661
  var EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
634
662
  var MAX_QUEUE_SIZE = 8192;
@@ -784,6 +812,31 @@ function encodeSpan(span) {
784
812
  const json = JSON.stringify(spanToOtlp(span));
785
813
  return { json, size: byteLength(json) };
786
814
  }
815
+ function trimEncodedSpan(span) {
816
+ try {
817
+ const carrier = JSON.parse(span.json);
818
+ const attribute = carrier.attributes?.find(
819
+ (entry) => entry.key === PAYLOAD_ATTRIBUTE
820
+ );
821
+ const payloadBody = attribute?.value?.stringValue;
822
+ if (!attribute?.value || payloadBody === void 0) {
823
+ return void 0;
824
+ }
825
+ const payload = JSON.parse(payloadBody);
826
+ attribute.value.stringValue = serializePayloadBody(
827
+ payload,
828
+ MAX_SPAN_CARRIER_BYTES
829
+ ).body;
830
+ const json = JSON.stringify(carrier);
831
+ return { json, size: byteLength(json) };
832
+ } catch {
833
+ return void 0;
834
+ }
835
+ }
836
+ async function prepareRequest(body) {
837
+ const prepared = encodeRequestBody(body);
838
+ return prepared instanceof Promise ? await prepared : prepared;
839
+ }
787
840
  function requestEnvelope(first) {
788
841
  const scope = first.instrumentationScope;
789
842
  const resource = JSON.stringify({
@@ -914,15 +967,43 @@ var BitfabSpanExporter = class {
914
967
  return batches;
915
968
  }
916
969
  async send(envelope, batch) {
917
- if (batch.size > this.maxRequestBytes) {
918
- logError(
919
- "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
920
- );
921
- return false;
922
- }
923
970
  try {
924
- await this.sendWithRetries(encodeRequest(envelope, batch.spans));
925
- return true;
971
+ let requestSpans = batch.spans;
972
+ let requestRawBytes = batch.size;
973
+ let alreadyTrimmed = false;
974
+ while (true) {
975
+ if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
976
+ const prepared = await prepareRequest(
977
+ encodeRequest(envelope, requestSpans)
978
+ );
979
+ if (prepared.wireBytes <= this.maxRequestBytes) {
980
+ await this.sendWithRetries(prepared);
981
+ return true;
982
+ }
983
+ }
984
+ if (batch.spans.length !== 1) {
985
+ logError(
986
+ "an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
987
+ );
988
+ return false;
989
+ }
990
+ if (alreadyTrimmed) {
991
+ logError(
992
+ "a single OpenTelemetry span exceeded the configured request-size target after trimming"
993
+ );
994
+ return false;
995
+ }
996
+ const trimmed = trimEncodedSpan(batch.spans[0]);
997
+ if (!trimmed) {
998
+ logError(
999
+ "a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
1000
+ );
1001
+ return false;
1002
+ }
1003
+ requestSpans = [trimmed];
1004
+ requestRawBytes = envelope.size + trimmed.size;
1005
+ alreadyTrimmed = true;
1006
+ }
926
1007
  } catch (error) {
927
1008
  if (error instanceof OtlpPayloadTooLargeError) {
928
1009
  logError(
@@ -950,12 +1031,12 @@ var BitfabSpanExporter = class {
950
1031
  * the server does not yet understand. The fix is a client-supplied
951
1032
  * idempotency key that ingestion dedupes on.
952
1033
  */
953
- async sendWithRetries(body) {
1034
+ async sendWithRetries(request) {
954
1035
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
955
1036
  try {
956
1037
  const response = await this.directSender(
957
1038
  OTLP_TRACES_ENDPOINT,
958
- body,
1039
+ request,
959
1040
  EXPORT_TIMEOUT_MILLIS
960
1041
  );
961
1042
  const partialSuccess = asRecord2(response?.partialSuccess);
@@ -1072,7 +1153,10 @@ var OtelBatchTransport = class {
1072
1153
  return;
1073
1154
  }
1074
1155
  try {
1075
- const { body, dropped } = serializePayloadBody(payload);
1156
+ const { body, dropped } = serializePayloadBody(
1157
+ payload,
1158
+ MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
1159
+ );
1076
1160
  if (dropped.length > 0) {
1077
1161
  warnOnce(
1078
1162
  "otel-carrier-payload-stubbed",
@@ -1311,7 +1395,7 @@ var HttpClient = class {
1311
1395
  }
1312
1396
  if (!this.traceTransport) {
1313
1397
  this.traceTransport = createTraceTransport({
1314
- directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1398
+ directSender: (endpoint, request, timeoutMs) => this.sendPrepared(endpoint, request, {
1315
1399
  timeout: timeoutMs
1316
1400
  })
1317
1401
  });
@@ -1398,13 +1482,16 @@ var HttpClient = class {
1398
1482
  * same data twice.
1399
1483
  */
1400
1484
  async sendEncoded(endpoint, body, options) {
1485
+ const prepared = encodeRequestBody(body);
1486
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1487
+ return this.sendPrepared(endpoint, encoded, options);
1488
+ }
1489
+ async sendPrepared(endpoint, encoded, options) {
1401
1490
  const url = `${this.serviceUrl}${endpoint}`;
1402
1491
  const timeout = options?.timeout ?? this.timeout;
1403
1492
  const method = options?.method ?? "POST";
1404
1493
  const controller = new AbortController();
1405
1494
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1406
- const prepared = encodeRequestBody(body);
1407
- const encoded = prepared instanceof Promise ? await prepared : prepared;
1408
1495
  const headers = {
1409
1496
  "Content-Type": "application/json",
1410
1497
  Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
@@ -1767,8 +1854,8 @@ function fallbackUuidV4() {
1767
1854
 
1768
1855
  // src/serialize.ts
1769
1856
  import superjson from "superjson";
1770
- var MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1771
- var MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1857
+ var MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1858
+ var MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1772
1859
  function describeValue(value) {
1773
1860
  try {
1774
1861
  const ctorName = value?.constructor?.name;
@@ -2255,12 +2342,13 @@ function sleep(ms) {
2255
2342
  unrefTimer(timer);
2256
2343
  });
2257
2344
  }
2258
- async function mapWithConcurrency2(tasks, maxConcurrency, onSettled) {
2345
+ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted) {
2259
2346
  const results = new Array(tasks.length);
2260
2347
  let nextIndex = 0;
2261
2348
  async function worker() {
2262
2349
  while (nextIndex < tasks.length) {
2263
2350
  const index = nextIndex++;
2351
+ onStarted?.(index);
2264
2352
  const result = await tasks[index]();
2265
2353
  results[index] = result;
2266
2354
  onSettled?.(result, index);
@@ -2347,13 +2435,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2347
2435
  )
2348
2436
  );
2349
2437
  const total = tasks.length;
2438
+ const onItemFinish = options?.onItemFinish ?? options?.onProgress;
2350
2439
  let completed = 0;
2440
+ let started = 0;
2351
2441
  let succeeded = 0;
2352
2442
  let errored = 0;
2353
2443
  const resultItems = await mapWithConcurrency2(
2354
2444
  tasks,
2355
2445
  maxConcurrency,
2356
- options?.onProgress ? (item) => {
2446
+ (item) => {
2357
2447
  completed += 1;
2358
2448
  if (item.error === null) {
2359
2449
  succeeded += 1;
@@ -2361,18 +2451,17 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2361
2451
  errored += 1;
2362
2452
  }
2363
2453
  try {
2364
- options?.onProgress?.({
2454
+ onItemFinish?.({
2365
2455
  testRunId,
2366
2456
  completed,
2367
2457
  total,
2368
2458
  succeeded,
2369
2459
  errored,
2370
2460
  item: {
2371
- // The server replay trace id isn't known until completeReplay
2372
- // runs (below), so it can't be reported mid-run and we never
2373
- // emit the client-side placeholder. originalTraceId (the
2374
- // historical trace) is known now and is what a UI keys on to
2375
- // identify what just settled.
2461
+ // The server replay trace id isn't known until completeReplay runs
2462
+ // (below), so it can't be reported mid-run and we never emit the
2463
+ // client-side placeholder. originalTraceId (the historical trace)
2464
+ // is known now and is what a UI keys on to identify what settled.
2376
2465
  traceId: null,
2377
2466
  originalTraceId: item.originalTraceId ?? null,
2378
2467
  originalSpanId: item.originalSpanId ?? null,
@@ -2393,6 +2482,30 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2393
2482
  });
2394
2483
  } catch {
2395
2484
  }
2485
+ },
2486
+ options?.onItemStart ? (index) => {
2487
+ started += 1;
2488
+ const serverItem = serverItems[index];
2489
+ const originalTraceId = serverItem.originalTraceId ?? serverItem.sourceTraceId;
2490
+ const originalSpanId = serverItem.originalSpanId ?? serverItem.sourceSpanId;
2491
+ try {
2492
+ options.onItemStart?.({
2493
+ type: "started",
2494
+ testRunId,
2495
+ started,
2496
+ completed,
2497
+ total,
2498
+ succeeded,
2499
+ errored,
2500
+ item: {
2501
+ originalTraceId,
2502
+ originalSpanId,
2503
+ sourceTraceId: originalTraceId,
2504
+ sourceSpanId: originalSpanId
2505
+ }
2506
+ });
2507
+ } catch {
2508
+ }
2396
2509
  } : void 0
2397
2510
  );
2398
2511
  await preserveReplayFailure(
@@ -2455,17 +2568,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2455
2568
  testRunUrl: fullTestRunUrl
2456
2569
  };
2457
2570
  await writeReplayResultFile(result);
2458
- try {
2459
- options?.onProgress?.({
2460
- type: "complete",
2461
- testRunId,
2462
- completed: total,
2463
- total,
2464
- succeeded,
2465
- errored,
2466
- result
2467
- });
2468
- } catch {
2571
+ if (!options?.onItemFinish) {
2572
+ try {
2573
+ options?.onProgress?.({
2574
+ type: "complete",
2575
+ testRunId,
2576
+ completed: total,
2577
+ total,
2578
+ succeeded,
2579
+ errored,
2580
+ result
2581
+ });
2582
+ } catch {
2583
+ }
2469
2584
  }
2470
2585
  return result;
2471
2586
  }
@@ -2521,4 +2636,4 @@ export {
2521
2636
  serializeReplayResult,
2522
2637
  replay
2523
2638
  };
2524
- //# sourceMappingURL=chunk-FLP6CNRE.js.map
2639
+ //# sourceMappingURL=chunk-CFECTUDU.js.map