@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.
@@ -16,7 +16,7 @@ import {
16
16
  toJsonSafe,
17
17
  toJsonSafeReport,
18
18
  warnOnce
19
- } from "./chunk-MBKY42QC.js";
19
+ } from "./chunk-EKOAKLZC.js";
20
20
 
21
21
  // src/processorPayload.ts
22
22
  var SERIALIZATION_DEGRADED_STEP = "serialization_degraded";
@@ -3371,7 +3371,7 @@ var Bitfab = class {
3371
3371
  `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3372
3372
  );
3373
3373
  }
3374
- const { replay: doReplay } = await import("./replay-MH5QFXET.js");
3374
+ const { replay: doReplay } = await import("./replay-TW3ONJ3W.js");
3375
3375
  return doReplay(
3376
3376
  this.httpClient,
3377
3377
  this.serviceUrl,
@@ -3609,4 +3609,4 @@ export {
3609
3609
  BitfabFunction,
3610
3610
  finalizers
3611
3611
  };
3612
- //# sourceMappingURL=chunk-TJAFKZ4X.js.map
3612
+ //# sourceMappingURL=chunk-VDFPZYLH.js.map
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.36.9";
45
+ __version__ = "0.36.11";
46
46
  }
47
47
  });
48
48
 
@@ -1435,6 +1435,10 @@ var init_http = __esm({
1435
1435
  ...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
1436
1436
  });
1437
1437
  }
1438
+ const serverTraceIds = asPayloadRecord(response?.traceIds);
1439
+ if (serverTraceIds !== void 0) {
1440
+ this.recordServerTraceIds(serverTraceIds);
1441
+ }
1438
1442
  const partialSuccess = asPayloadRecord(response?.partialSuccess);
1439
1443
  const rejected = partialSuccess?.rejectedSpans;
1440
1444
  if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
@@ -1459,6 +1463,14 @@ var init_http = __esm({
1459
1463
  }
1460
1464
  }
1461
1465
  }
1466
+ /**
1467
+ * The server's assigned `traces.id` for a tracked trace if it has already
1468
+ * been read back off an ingest response, without stopping tracking. Lets a
1469
+ * replay surface the id mid-run for items whose spans already landed.
1470
+ */
1471
+ peekServerTraceId(traceId) {
1472
+ return this.traceDeliveries.get(traceId)?.serverTraceId;
1473
+ }
1462
1474
  /** Whether any tracked trace has had its closing carrier submitted. */
1463
1475
  hasClosedDeliveries(traceIds) {
1464
1476
  return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
@@ -1485,7 +1497,8 @@ var init_http = __esm({
1485
1497
  closed: delivery.closed,
1486
1498
  delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
1487
1499
  (spanId) => delivery.ackedSpanIds.has(spanId)
1488
- )
1500
+ ),
1501
+ serverTraceId: delivery.serverTraceId
1489
1502
  };
1490
1503
  }
1491
1504
  return reports;
@@ -1514,6 +1527,23 @@ var init_http = __esm({
1514
1527
  * delivered ref is proof its row exists: the same fact the replay status
1515
1528
  * endpoint would report, already in hand.
1516
1529
  */
1530
+ /**
1531
+ * Record the server's assigned `traces.id` for each tracked source trace,
1532
+ * read back from the OTLP ingest response. Keyed by source trace id, the same
1533
+ * key the delivery ledger uses. Untracked ids are ignored.
1534
+ */
1535
+ recordServerTraceIds(map) {
1536
+ for (const [sourceTraceId, serverTraceId] of Object.entries(map)) {
1537
+ if (typeof serverTraceId !== "string") {
1538
+ continue;
1539
+ }
1540
+ const delivery = this.traceDeliveries.get(sourceTraceId);
1541
+ if (delivery === void 0) {
1542
+ continue;
1543
+ }
1544
+ delivery.serverTraceId = serverTraceId;
1545
+ }
1546
+ }
1517
1547
  recordDeliveredCarriers(refs) {
1518
1548
  for (const ref of refs) {
1519
1549
  const delivery = this.traceDeliveries.get(ref.traceId);
@@ -2230,7 +2260,7 @@ async function captureCodeChangeFromGit(cwd, label) {
2230
2260
  const tracked = await git(root, [
2231
2261
  "diff",
2232
2262
  "--name-status",
2233
- "--no-renames",
2263
+ "--find-renames",
2234
2264
  "-z",
2235
2265
  base,
2236
2266
  "--",
@@ -2246,23 +2276,23 @@ async function captureCodeChangeFromGit(cwd, label) {
2246
2276
  ]);
2247
2277
  const entries = [
2248
2278
  ...parseNameStatusZ(tracked ?? ""),
2249
- ...(untracked ?? "").split(NUL).filter((p) => p.length > 0).map((path) => ({ status: "A", path }))
2279
+ ...(untracked ?? "").split(NUL).filter((p) => p.length > 0).map((path) => ({ status: "A", beforePath: path, path }))
2250
2280
  ];
2251
2281
  if (entries.length === 0) {
2252
2282
  return null;
2253
2283
  }
2254
2284
  const files = [];
2255
2285
  let totalBytes = 0;
2256
- for (const { status, path } of entries) {
2286
+ for (const { status, beforePath, path } of entries) {
2257
2287
  if (files.length >= MAX_FILES) {
2258
2288
  break;
2259
2289
  }
2260
- const beforeBytes = status === "A" ? 0 : await blobBytes(base, path);
2290
+ const beforeBytes = status === "A" ? 0 : await blobBytes(base, beforePath);
2261
2291
  const afterBytes = status === "D" ? 0 : await workingBytes(path);
2262
2292
  if (beforeBytes > MAX_FILE_BYTES || afterBytes > MAX_FILE_BYTES) {
2263
2293
  continue;
2264
2294
  }
2265
- const before = (status === "A" ? "" : await git(root, ["show", `${base}:${path}`]) ?? "").replace(/\r\n/g, "\n");
2295
+ const before = (status === "A" ? "" : await git(root, ["show", `${base}:${beforePath}`]) ?? "").replace(/\r\n/g, "\n");
2266
2296
  const after = (status === "D" ? "" : await readWorkingFile(readFile, root, path)).replace(/\r\n/g, "\n");
2267
2297
  if (before === after) {
2268
2298
  continue;
@@ -2320,8 +2350,18 @@ async function readWorkingFile(readFile, root, path) {
2320
2350
  function parseNameStatusZ(raw) {
2321
2351
  const parts = raw.split(NUL).filter((p) => p.length > 0);
2322
2352
  const out = [];
2323
- for (let i = 0; i + 1 < parts.length; i += 2) {
2324
- out.push({ status: parts[i].charAt(0), path: parts[i + 1] });
2353
+ let i = 0;
2354
+ while (i + 1 < parts.length) {
2355
+ const status = parts[i].charAt(0);
2356
+ i += 1;
2357
+ const beforePath = parts[i];
2358
+ i += 1;
2359
+ if ((status === "R" || status === "C") && i < parts.length) {
2360
+ out.push({ status, beforePath, path: parts[i] });
2361
+ i += 1;
2362
+ } else {
2363
+ out.push({ status, beforePath, path: beforePath });
2364
+ }
2325
2365
  }
2326
2366
  return out;
2327
2367
  }
@@ -2651,13 +2691,17 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
2651
2691
  }
2652
2692
  if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
2653
2693
  httpClient.takeTraceDeliveries(replayedTraceIds);
2654
- return;
2694
+ return {};
2655
2695
  }
2656
2696
  const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
2657
2697
  const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
2658
2698
  const expectedSpanCounts = {};
2699
+ const readBackTraceIds = {};
2659
2700
  let allDelivered = true;
2660
2701
  for (const [traceId, delivery] of Object.entries(deliveries)) {
2702
+ if (delivery.serverTraceId !== void 0) {
2703
+ readBackTraceIds[traceId] = delivery.serverTraceId;
2704
+ }
2661
2705
  if (!delivery.closed) {
2662
2706
  continue;
2663
2707
  }
@@ -2665,7 +2709,7 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
2665
2709
  allDelivered = allDelivered && delivery.delivered;
2666
2710
  }
2667
2711
  if (allDelivered) {
2668
- return;
2712
+ return readBackTraceIds;
2669
2713
  }
2670
2714
  const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
2671
2715
  let missing = Object.keys(expectedSpanCounts).length;
@@ -2679,7 +2723,7 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
2679
2723
  (traceId) => ready[traceId] === void 0
2680
2724
  ).length;
2681
2725
  if (missing === 0) {
2682
- return;
2726
+ return readBackTraceIds;
2683
2727
  }
2684
2728
  if (Date.now() >= deadline) {
2685
2729
  break;
@@ -2707,7 +2751,7 @@ async function mapWithConcurrency2(tasks, maxConcurrency, onSettled, onStarted)
2707
2751
  onStarted?.(index);
2708
2752
  const result = await tasks[index]();
2709
2753
  results[index] = result;
2710
- onSettled?.(result, index);
2754
+ await onSettled?.(result, index);
2711
2755
  }
2712
2756
  }
2713
2757
  const workers = Array.from(
@@ -2797,10 +2841,36 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2797
2841
  let started = 0;
2798
2842
  let succeeded = 0;
2799
2843
  let errored = 0;
2844
+ let flushInFlight = null;
2845
+ let flushPending = false;
2846
+ const flushFinishedItemTrace = () => {
2847
+ flushPending = true;
2848
+ if (!flushInFlight) {
2849
+ flushInFlight = (async () => {
2850
+ try {
2851
+ while (flushPending) {
2852
+ flushPending = false;
2853
+ await httpClient.settleDeferredWork(REPLAY_PERSISTENCE_TIMEOUT_MS);
2854
+ await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
2855
+ }
2856
+ } finally {
2857
+ flushInFlight = null;
2858
+ }
2859
+ })();
2860
+ }
2861
+ return flushInFlight;
2862
+ };
2800
2863
  const resultItems = await mapWithConcurrency2(
2801
2864
  tasks,
2802
2865
  maxConcurrency,
2803
- (item) => {
2866
+ async (item, index) => {
2867
+ let serverTraceId = null;
2868
+ try {
2869
+ await flushFinishedItemTrace();
2870
+ serverTraceId = httpClient.peekServerTraceId(replayedTraceIds[index]) ?? null;
2871
+ } catch {
2872
+ }
2873
+ item.traceId = serverTraceId;
2804
2874
  completed += 1;
2805
2875
  if (item.error === null) {
2806
2876
  succeeded += 1;
@@ -2815,11 +2885,12 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2815
2885
  succeeded,
2816
2886
  errored,
2817
2887
  item: {
2818
- // The server replay trace id isn't known until completeReplay runs
2819
- // (below), so it can't be reported mid-run and we never emit the
2820
- // client-side placeholder. originalTraceId (the historical trace)
2821
- // is known now and is what a UI keys on to identify what settled.
2822
- traceId: null,
2888
+ // The server's assigned traces.id, read back off the ingest
2889
+ // response and surfaced as this item finishes. Null only if the
2890
+ // per-item flush could not confirm delivery in time; the end-of-run
2891
+ // barrier then fills the returned ReplayItem. The client-side
2892
+ // placeholder is never surfaced.
2893
+ traceId: serverTraceId,
2823
2894
  originalTraceId: item.originalTraceId ?? null,
2824
2895
  originalSpanId: item.originalSpanId ?? null,
2825
2896
  // Deprecated aliases for originalTraceId/originalSpanId.
@@ -2865,12 +2936,19 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2865
2936
  }
2866
2937
  } : void 0
2867
2938
  );
2868
- await preserveReplayFailure(
2939
+ const deliveredTraceIds = await preserveReplayFailure(
2869
2940
  () => waitForReplayPersistence(httpClient, testRunId, replayedTraceIds),
2870
2941
  resultItems,
2871
2942
  testRunId,
2872
2943
  fullTestRunUrl
2873
2944
  );
2945
+ for (let index = 0; index < resultItems.length; index += 1) {
2946
+ const localId = replayedTraceIds[index];
2947
+ const readBack = localId ? deliveredTraceIds[localId] : void 0;
2948
+ if (readBack !== void 0) {
2949
+ resultItems[index].traceId = readBack;
2950
+ }
2951
+ }
2874
2952
  const completeResult = await preserveReplayFailure(
2875
2953
  () => httpClient.completeReplay(testRunId),
2876
2954
  resultItems,
@@ -2886,7 +2964,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
2886
2964
  const item = resultItems[index];
2887
2965
  const localId = replayedTraceIds[index];
2888
2966
  const mapped = localId ? serverTraceIds[localId] : void 0;
2889
- item.traceId = mapped ?? null;
2967
+ item.traceId = item.traceId ?? mapped ?? null;
2890
2968
  if (item.error === null) {
2891
2969
  completedCount += 1;
2892
2970
  if (mapped === void 0) {