@bitfab/sdk 0.36.9 → 0.36.11

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
@@ -353,6 +353,12 @@ interface DeliveryReport {
353
353
  closed: boolean;
354
354
  /** Every carrier submitted under this trace came back accepted. */
355
355
  delivered: boolean;
356
+ /**
357
+ * The server's assigned `traces.id`, read back from the OTLP ingest response.
358
+ * Absent when talking to a server that predates this field, or before any
359
+ * carrier for the trace has been acked.
360
+ */
361
+ serverTraceId?: string;
356
362
  }
357
363
  declare class HttpClient {
358
364
  private readonly apiKey;
@@ -390,6 +396,12 @@ declare class HttpClient {
390
396
  * that was never tracked, so ordinary tracing costs no bookkeeping at all.
391
397
  */
392
398
  trackTraceDeliveries(traceIds: string[]): void;
399
+ /**
400
+ * The server's assigned `traces.id` for a tracked trace if it has already
401
+ * been read back off an ingest response, without stopping tracking. Lets a
402
+ * replay surface the id mid-run for items whose spans already landed.
403
+ */
404
+ peekServerTraceId(traceId: string): string | undefined;
393
405
  /** Whether any tracked trace has had its closing carrier submitted. */
394
406
  hasClosedDeliveries(traceIds: string[]): boolean;
395
407
  /**
@@ -410,6 +422,12 @@ declare class HttpClient {
410
422
  * delivered ref is proof its row exists: the same fact the replay status
411
423
  * endpoint would report, already in hand.
412
424
  */
425
+ /**
426
+ * Record the server's assigned `traces.id` for each tracked source trace,
427
+ * read back from the OTLP ingest response. Keyed by source trace id, the same
428
+ * key the delivery ledger uses. Untracked ids are ignored.
429
+ */
430
+ private recordServerTraceIds;
413
431
  private recordDeliveredCarriers;
414
432
  /**
415
433
  * Track deferred span work so this client's own lifecycle waits for it, and
@@ -2557,7 +2575,7 @@ declare class BitfabFunction {
2557
2575
  /**
2558
2576
  * SDK version from package.json (injected at build time)
2559
2577
  */
2560
- declare const __version__ = "0.36.9";
2578
+ declare const __version__ = "0.36.11";
2561
2579
 
2562
2580
  /**
2563
2581
  * Constants for the Bitfab SDK.
package/dist/index.d.ts CHANGED
@@ -353,6 +353,12 @@ interface DeliveryReport {
353
353
  closed: boolean;
354
354
  /** Every carrier submitted under this trace came back accepted. */
355
355
  delivered: boolean;
356
+ /**
357
+ * The server's assigned `traces.id`, read back from the OTLP ingest response.
358
+ * Absent when talking to a server that predates this field, or before any
359
+ * carrier for the trace has been acked.
360
+ */
361
+ serverTraceId?: string;
356
362
  }
357
363
  declare class HttpClient {
358
364
  private readonly apiKey;
@@ -390,6 +396,12 @@ declare class HttpClient {
390
396
  * that was never tracked, so ordinary tracing costs no bookkeeping at all.
391
397
  */
392
398
  trackTraceDeliveries(traceIds: string[]): void;
399
+ /**
400
+ * The server's assigned `traces.id` for a tracked trace if it has already
401
+ * been read back off an ingest response, without stopping tracking. Lets a
402
+ * replay surface the id mid-run for items whose spans already landed.
403
+ */
404
+ peekServerTraceId(traceId: string): string | undefined;
393
405
  /** Whether any tracked trace has had its closing carrier submitted. */
394
406
  hasClosedDeliveries(traceIds: string[]): boolean;
395
407
  /**
@@ -410,6 +422,12 @@ declare class HttpClient {
410
422
  * delivered ref is proof its row exists: the same fact the replay status
411
423
  * endpoint would report, already in hand.
412
424
  */
425
+ /**
426
+ * Record the server's assigned `traces.id` for each tracked source trace,
427
+ * read back from the OTLP ingest response. Keyed by source trace id, the same
428
+ * key the delivery ledger uses. Untracked ids are ignored.
429
+ */
430
+ private recordServerTraceIds;
413
431
  private recordDeliveredCarriers;
414
432
  /**
415
433
  * Track deferred span work so this client's own lifecycle waits for it, and
@@ -2557,7 +2575,7 @@ declare class BitfabFunction {
2557
2575
  /**
2558
2576
  * SDK version from package.json (injected at build time)
2559
2577
  */
2560
- declare const __version__ = "0.36.9";
2578
+ declare const __version__ = "0.36.11";
2561
2579
 
2562
2580
  /**
2563
2581
  * Constants for the Bitfab SDK.
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getCurrentReplayBranch,
12
12
  getCurrentSpan,
13
13
  getCurrentTrace
14
- } from "./chunk-TJAFKZ4X.js";
14
+ } from "./chunk-VDFPZYLH.js";
15
15
  import {
16
16
  BITFAB_PROGRESS_PREFIX,
17
17
  BitfabError,
@@ -23,7 +23,7 @@ import {
23
23
  flushTraces,
24
24
  reportReplayProgress,
25
25
  serializeReplayResult
26
- } from "./chunk-MBKY42QC.js";
26
+ } from "./chunk-EKOAKLZC.js";
27
27
  export {
28
28
  BITFAB_PROGRESS_PREFIX,
29
29
  Bitfab,
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.36.9";
91
+ __version__ = "0.36.11";
92
92
  }
93
93
  });
94
94
 
@@ -1442,6 +1442,10 @@ var init_http = __esm({
1442
1442
  ...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
1443
1443
  });
1444
1444
  }
1445
+ const serverTraceIds = asPayloadRecord(response?.traceIds);
1446
+ if (serverTraceIds !== void 0) {
1447
+ this.recordServerTraceIds(serverTraceIds);
1448
+ }
1445
1449
  const partialSuccess = asPayloadRecord(response?.partialSuccess);
1446
1450
  const rejected = partialSuccess?.rejectedSpans;
1447
1451
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
@@ -1466,6 +1470,14 @@ var init_http = __esm({
1466
1470
  }
1467
1471
  }
1468
1472
  }
1473
+ /**
1474
+ * The server's assigned `traces.id` for a tracked trace if it has already
1475
+ * been read back off an ingest response, without stopping tracking. Lets a
1476
+ * replay surface the id mid-run for items whose spans already landed.
1477
+ */
1478
+ peekServerTraceId(traceId) {
1479
+ return this.traceDeliveries.get(traceId)?.serverTraceId;
1480
+ }
1469
1481
  /** Whether any tracked trace has had its closing carrier submitted. */
1470
1482
  hasClosedDeliveries(traceIds) {
1471
1483
  return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
@@ -1492,7 +1504,8 @@ var init_http = __esm({
1492
1504
  closed: delivery.closed,
1493
1505
  delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
1494
1506
  (spanId) => delivery.ackedSpanIds.has(spanId)
1495
- )
1507
+ ),
1508
+ serverTraceId: delivery.serverTraceId
1496
1509
  };
1497
1510
  }
1498
1511
  return reports;
@@ -1521,6 +1534,23 @@ var init_http = __esm({
1521
1534
  * delivered ref is proof its row exists: the same fact the replay status
1522
1535
  * endpoint would report, already in hand.
1523
1536
  */
1537
+ /**
1538
+ * Record the server's assigned `traces.id` for each tracked source trace,
1539
+ * read back from the OTLP ingest response. Keyed by source trace id, the same
1540
+ * key the delivery ledger uses. Untracked ids are ignored.
1541
+ */
1542
+ recordServerTraceIds(map) {
1543
+ for (const [sourceTraceId, serverTraceId] of Object.entries(map)) {
1544
+ if (typeof serverTraceId !== "string") {
1545
+ continue;
1546
+ }
1547
+ const delivery = this.traceDeliveries.get(sourceTraceId);
1548
+ if (delivery === void 0) {
1549
+ continue;
1550
+ }
1551
+ delivery.serverTraceId = serverTraceId;
1552
+ }
1553
+ }
1524
1554
  recordDeliveredCarriers(refs) {
1525
1555
  for (const ref of refs) {
1526
1556
  const delivery = this.traceDeliveries.get(ref.traceId);
@@ -2237,7 +2267,7 @@ async function captureCodeChangeFromGit(cwd, label) {
2237
2267
  const tracked = await git(root, [
2238
2268
  "diff",
2239
2269
  "--name-status",
2240
- "--no-renames",
2270
+ "--find-renames",
2241
2271
  "-z",
2242
2272
  base,
2243
2273
  "--",
@@ -2253,23 +2283,23 @@ async function captureCodeChangeFromGit(cwd, label) {
2253
2283
  ]);
2254
2284
  const entries = [
2255
2285
  ...parseNameStatusZ(tracked ?? ""),
2256
- ...(untracked ?? "").split(NUL).filter((p) => p.length > 0).map((path) => ({ status: "A", path }))
2286
+ ...(untracked ?? "").split(NUL).filter((p) => p.length > 0).map((path) => ({ status: "A", beforePath: path, path }))
2257
2287
  ];
2258
2288
  if (entries.length === 0) {
2259
2289
  return null;
2260
2290
  }
2261
2291
  const files = [];
2262
2292
  let totalBytes = 0;
2263
- for (const { status, path } of entries) {
2293
+ for (const { status, beforePath, path } of entries) {
2264
2294
  if (files.length >= MAX_FILES) {
2265
2295
  break;
2266
2296
  }
2267
- const beforeBytes = status === "A" ? 0 : await blobBytes(base, path);
2297
+ const beforeBytes = status === "A" ? 0 : await blobBytes(base, beforePath);
2268
2298
  const afterBytes = status === "D" ? 0 : await workingBytes(path);
2269
2299
  if (beforeBytes > MAX_FILE_BYTES || afterBytes > MAX_FILE_BYTES) {
2270
2300
  continue;
2271
2301
  }
2272
- const before = (status === "A" ? "" : await git(root, ["show", `${base}:${path}`]) ?? "").replace(/\r\n/g, "\n");
2302
+ const before = (status === "A" ? "" : await git(root, ["show", `${base}:${beforePath}`]) ?? "").replace(/\r\n/g, "\n");
2273
2303
  const after = (status === "D" ? "" : await readWorkingFile(readFile, root, path)).replace(/\r\n/g, "\n");
2274
2304
  if (before === after) {
2275
2305
  continue;
@@ -2327,8 +2357,18 @@ async function readWorkingFile(readFile, root, path) {
2327
2357
  function parseNameStatusZ(raw) {
2328
2358
  const parts = raw.split(NUL).filter((p) => p.length > 0);
2329
2359
  const out = [];
2330
- for (let i = 0; i + 1 < parts.length; i += 2) {
2331
- out.push({ status: parts[i].charAt(0), path: parts[i + 1] });
2360
+ let i = 0;
2361
+ while (i + 1 < parts.length) {
2362
+ const status = parts[i].charAt(0);
2363
+ i += 1;
2364
+ const beforePath = parts[i];
2365
+ i += 1;
2366
+ if ((status === "R" || status === "C") && i < parts.length) {
2367
+ out.push({ status, beforePath, path: parts[i] });
2368
+ i += 1;
2369
+ } else {
2370
+ out.push({ status, beforePath, path: beforePath });
2371
+ }
2332
2372
  }
2333
2373
  return out;
2334
2374
  }
@@ -2658,13 +2698,17 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
2658
2698
  }
2659
2699
  if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
2660
2700
  httpClient.takeTraceDeliveries(replayedTraceIds);
2661
- return;
2701
+ return {};
2662
2702
  }
2663
2703
  const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
2664
2704
  const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
2665
2705
  const expectedSpanCounts = {};
2706
+ const readBackTraceIds = {};
2666
2707
  let allDelivered = true;
2667
2708
  for (const [traceId, delivery] of Object.entries(deliveries)) {
2709
+ if (delivery.serverTraceId !== void 0) {
2710
+ readBackTraceIds[traceId] = delivery.serverTraceId;
2711
+ }
2668
2712
  if (!delivery.closed) {
2669
2713
  continue;
2670
2714
  }
@@ -2672,7 +2716,7 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
2672
2716
  allDelivered = allDelivered && delivery.delivered;
2673
2717
  }
2674
2718
  if (allDelivered) {
2675
- return;
2719
+ return readBackTraceIds;
2676
2720
  }
2677
2721
  const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
2678
2722
  let missing = Object.keys(expectedSpanCounts).length;
@@ -2686,7 +2730,7 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
2686
2730
  (traceId) => ready[traceId] === void 0
2687
2731
  ).length;
2688
2732
  if (missing === 0) {
2689
- return;
2733
+ return readBackTraceIds;
2690
2734
  }
2691
2735
  if (Date.now() >= deadline) {
2692
2736
  break;
@@ -2714,7 +2758,7 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
2714
2758
  onStarted?.(index);
2715
2759
  const result = await tasks[index]();
2716
2760
  results[index] = result;
2717
- onSettled?.(result, index);
2761
+ await onSettled?.(result, index);
2718
2762
  }
2719
2763
  }
2720
2764
  const workers = Array.from(
@@ -2804,10 +2848,36 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2804
2848
  let started = 0;
2805
2849
  let succeeded = 0;
2806
2850
  let errored = 0;
2851
+ let flushInFlight = null;
2852
+ let flushPending = false;
2853
+ const flushFinishedItemTrace = () => {
2854
+ flushPending = true;
2855
+ if (!flushInFlight) {
2856
+ flushInFlight = (async () => {
2857
+ try {
2858
+ while (flushPending) {
2859
+ flushPending = false;
2860
+ await httpClient.settleDeferredWork(REPLAY_PERSISTENCE_TIMEOUT_MS);
2861
+ await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
2862
+ }
2863
+ } finally {
2864
+ flushInFlight = null;
2865
+ }
2866
+ })();
2867
+ }
2868
+ return flushInFlight;
2869
+ };
2807
2870
  const resultItems = await mapWithConcurrency2(
2808
2871
  tasks,
2809
2872
  maxConcurrency,
2810
- (item) => {
2873
+ async (item, index) => {
2874
+ let serverTraceId = null;
2875
+ try {
2876
+ await flushFinishedItemTrace();
2877
+ serverTraceId = httpClient.peekServerTraceId(replayedTraceIds[index]) ?? null;
2878
+ } catch {
2879
+ }
2880
+ item.traceId = serverTraceId;
2811
2881
  completed += 1;
2812
2882
  if (item.error === null) {
2813
2883
  succeeded += 1;
@@ -2822,11 +2892,12 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2822
2892
  succeeded,
2823
2893
  errored,
2824
2894
  item: {
2825
- // The server replay trace id isn't known until completeReplay runs
2826
- // (below), so it can't be reported mid-run and we never emit the
2827
- // client-side placeholder. originalTraceId (the historical trace)
2828
- // is known now and is what a UI keys on to identify what settled.
2829
- traceId: null,
2895
+ // The server's assigned traces.id, read back off the ingest
2896
+ // response and surfaced as this item finishes. Null only if the
2897
+ // per-item flush could not confirm delivery in time; the end-of-run
2898
+ // barrier then fills the returned ReplayItem. The client-side
2899
+ // placeholder is never surfaced.
2900
+ traceId: serverTraceId,
2830
2901
  originalTraceId: item.originalTraceId ?? null,
2831
2902
  originalSpanId: item.originalSpanId ?? null,
2832
2903
  // Deprecated aliases for originalTraceId/originalSpanId.
@@ -2872,12 +2943,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2872
2943
  }
2873
2944
  } : void 0
2874
2945
  );
2875
- await preserveReplayFailure(
2946
+ const deliveredTraceIds = await preserveReplayFailure(
2876
2947
  () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2877
2948
  resultItems,
2878
2949
  testRunId,
2879
2950
  fullTestRunUrl
2880
2951
  );
2952
+ for (let index = 0; index < resultItems.length; index += 1) {
2953
+ const localId = replayedTraceIds[index];
2954
+ const readBack = localId ? deliveredTraceIds[localId] : void 0;
2955
+ if (readBack !== void 0) {
2956
+ resultItems[index].traceId = readBack;
2957
+ }
2958
+ }
2881
2959
  const completeResult = await preserveReplayFailure(
2882
2960
  () => httpClient.completeReplay(testRunId),
2883
2961
  resultItems,
@@ -2893,7 +2971,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2893
2971
  const item = resultItems[index];
2894
2972
  const localId = replayedTraceIds[index];
2895
2973
  const mapped = localId ? serverTraceIds[localId] : void 0;
2896
- item.traceId = mapped ?? null;
2974
+ item.traceId = item.traceId ?? mapped ?? null;
2897
2975
  if (item.error === null) {
2898
2976
  completedCount += 1;
2899
2977
  if (mapped === void 0) {