bitfab 0.13.3 → 0.13.5

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
@@ -44,7 +44,7 @@ var __version__;
44
44
  var init_version_generated = __esm({
45
45
  "src/version.generated.ts"() {
46
46
  "use strict";
47
- __version__ = "0.13.3";
47
+ __version__ = "0.13.5";
48
48
  }
49
49
  });
50
50
 
@@ -58,6 +58,21 @@ var init_constants = __esm({
58
58
  }
59
59
  });
60
60
 
61
+ // src/errors.ts
62
+ var BitfabError;
63
+ var init_errors = __esm({
64
+ "src/errors.ts"() {
65
+ "use strict";
66
+ BitfabError = class extends Error {
67
+ constructor(message, url) {
68
+ super(message);
69
+ this.url = url;
70
+ this.name = "BitfabError";
71
+ }
72
+ };
73
+ }
74
+ });
75
+
61
76
  // src/http.ts
62
77
  function awaitOnExit(promise) {
63
78
  pendingTracePromises.add(promise);
@@ -76,18 +91,12 @@ async function flushTraces(timeoutMs = 5e3) {
76
91
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
77
92
  ]);
78
93
  }
79
- var BitfabError, pendingTracePromises, HttpClient;
94
+ var pendingTracePromises, HttpClient;
80
95
  var init_http = __esm({
81
96
  "src/http.ts"() {
82
97
  "use strict";
83
98
  init_constants();
84
- BitfabError = class extends Error {
85
- constructor(message, url) {
86
- super(message);
87
- this.url = url;
88
- this.name = "BitfabError";
89
- }
90
- };
99
+ init_errors();
91
100
  pendingTracePromises = /* @__PURE__ */ new Set();
92
101
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
93
102
  let isFlushing = false;
@@ -266,7 +275,7 @@ var init_http = __esm({
266
275
  * Start a replay session by fetching historical traces.
267
276
  * Blocking call — creates a test run and returns lightweight item references.
268
277
  */
269
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
278
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
270
279
  const payload = { traceFunctionKey, limit };
271
280
  if (traceIds) {
272
281
  payload.traceIds = traceIds;
@@ -277,8 +286,12 @@ var init_http = __esm({
277
286
  if (codeChangeFiles !== void 0) {
278
287
  payload.codeChangeFiles = codeChangeFiles;
279
288
  }
289
+ if (includeDbBranchLease) {
290
+ payload.includeDbBranchLease = true;
291
+ }
292
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
280
293
  return this.request("/api/sdk/replay/start", payload, {
281
- timeout: 3e4
294
+ timeout
282
295
  });
283
296
  }
284
297
  /**
@@ -364,6 +377,27 @@ var init_http = __esm({
364
377
  { timeout: 3e4 }
365
378
  );
366
379
  }
380
+ /**
381
+ * Ask the server to materialize a per-trace DB branch lease from a
382
+ * captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
383
+ * snapshot + preview branch and polls operations to readiness, which
384
+ * can take seconds.
385
+ */
386
+ async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
387
+ return this.request(
388
+ "/api/sdk/replay/resolveDbBranchLease",
389
+ { testRunId, traceId, dbSnapshotRef },
390
+ { timeout: 9e4 }
391
+ );
392
+ }
393
+ /** Release a previously-resolved DB branch lease. Idempotent server-side. */
394
+ async releaseDbBranchLease(leaseId) {
395
+ await this.request(
396
+ "/api/sdk/replay/releaseDbBranchLease",
397
+ { leaseId },
398
+ { timeout: 3e4 }
399
+ );
400
+ }
367
401
  };
368
402
  }
369
403
  });
@@ -541,42 +575,66 @@ function buildMockTree(rootNode) {
541
575
  }
542
576
  return { spans };
543
577
  }
544
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy) {
545
- const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
546
- const spanData = span.rawData?.span_data ?? {};
547
- const inputs = deserializeInputs(spanData);
548
- const originalOutput = deserializeOutput(spanData);
549
- let mockTree;
550
- if (mockStrategy === "all" || mockStrategy === "marked") {
551
- const treeResponse = await httpClient.getSpanTree(serverItem.externalSpanId);
552
- mockTree = buildMockTree(treeResponse.root);
553
- }
578
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment) {
579
+ const lease = environment ? serverItem.dbBranchLease : void 0;
580
+ let inputs = [];
581
+ let originalOutput;
554
582
  let result;
555
583
  let error = null;
584
+ const replayedTraceId = crypto.randomUUID();
556
585
  try {
586
+ const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
587
+ const spanData = span.rawData?.span_data ?? {};
588
+ inputs = deserializeInputs(spanData);
589
+ originalOutput = deserializeOutput(spanData);
590
+ let mockTree;
591
+ if (mockStrategy === "all" || mockStrategy === "marked") {
592
+ const treeResponse = await httpClient.getSpanTree(
593
+ serverItem.externalSpanId
594
+ );
595
+ mockTree = buildMockTree(treeResponse.root);
596
+ }
557
597
  const maybePromise = runWithReplayContext(
558
598
  {
559
599
  testRunId,
600
+ traceId: replayedTraceId,
560
601
  inputSourceSpanId: span.id,
561
602
  inputSourceTraceId: span.externalTraceId,
603
+ sourceBitfabTraceId: serverItem.traceId,
562
604
  mockTree,
563
605
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
564
- mockStrategy
606
+ mockStrategy,
607
+ dbBranchLease: lease
565
608
  },
566
609
  () => fn(...inputs)
567
610
  );
568
611
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
569
612
  } catch (e) {
570
613
  error = e instanceof Error ? e.message : String(e);
614
+ } finally {
615
+ if (lease) {
616
+ try {
617
+ await httpClient.releaseDbBranchLease(lease.leaseId);
618
+ } catch (e) {
619
+ try {
620
+ console.warn(
621
+ `Bitfab: failed to release DB branch lease ${lease.leaseId} (TTL janitor will catch it): ${e instanceof Error ? e.message : String(e)}`
622
+ );
623
+ } catch {
624
+ }
625
+ }
626
+ }
571
627
  }
572
628
  return {
629
+ traceId: replayedTraceId,
573
630
  input: inputs,
574
631
  result,
575
632
  originalOutput,
576
633
  error,
577
634
  durationMs: serverItem.durationMs ?? null,
578
635
  tokens: serverItem.tokens ?? null,
579
- model: serverItem.model ?? null
636
+ model: serverItem.model ?? null,
637
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
580
638
  };
581
639
  }
582
640
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -606,23 +664,39 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
606
664
  options?.limit ?? 5,
607
665
  options?.traceIds,
608
666
  options?.codeChangeDescription,
609
- options?.codeChangeFiles
667
+ options?.codeChangeFiles,
668
+ options?.environment !== void 0
669
+ // includeDbBranchLease
610
670
  );
611
671
  const mockStrategy = options?.mock ?? "none";
612
672
  const maxConcurrency = options?.maxConcurrency ?? 10;
613
673
  const tasks = serverItems.map(
614
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
674
+ (serverItem) => () => processItem(
675
+ httpClient,
676
+ serverItem,
677
+ fn,
678
+ testRunId,
679
+ mockStrategy,
680
+ options?.environment
681
+ )
615
682
  );
616
683
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
617
684
  await flushTraces();
685
+ let serverTraceIds = {};
618
686
  try {
619
- await httpClient.completeReplay(testRunId);
687
+ const completeResult = await httpClient.completeReplay(testRunId);
688
+ serverTraceIds = completeResult.traceIds ?? {};
620
689
  } catch (e) {
621
690
  try {
622
691
  console.error("Bitfab: Failed to complete replay:", e);
623
692
  } catch {
624
693
  }
625
694
  }
695
+ for (const item of resultItems) {
696
+ if (item.traceId) {
697
+ item.traceId = serverTraceIds[item.traceId] ?? null;
698
+ }
699
+ }
626
700
  return {
627
701
  items: resultItems,
628
702
  testRunId,
@@ -648,6 +722,8 @@ __export(index_exports, {
648
722
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
649
723
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
650
724
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
725
+ ReplayEnvironment: () => ReplayEnvironment,
726
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
651
727
  __version__: () => __version__,
652
728
  flushTraces: () => flushTraces,
653
729
  getCurrentSpan: () => getCurrentSpan,
@@ -1424,6 +1500,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1424
1500
 
1425
1501
  // src/client.ts
1426
1502
  init_constants();
1503
+
1504
+ // src/dbSnapshot.ts
1505
+ init_errors();
1506
+ var SUPPORTED_PROVIDERS = [
1507
+ "neon",
1508
+ "ardent",
1509
+ "dolt",
1510
+ "gfs",
1511
+ "mongodb-atlas",
1512
+ "dynamodb",
1513
+ "snowflake",
1514
+ "bigquery"
1515
+ ];
1516
+ function validateDbSnapshotConfig(config) {
1517
+ if (!SUPPORTED_PROVIDERS.includes(config.provider)) {
1518
+ throw new BitfabError(
1519
+ `dbSnapshot.provider "${config.provider}" is not supported. Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}.`
1520
+ );
1521
+ }
1522
+ }
1523
+ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1524
+ return {
1525
+ provider: config.provider,
1526
+ sdkWallClockBeforeFn
1527
+ };
1528
+ }
1529
+
1530
+ // src/client.ts
1427
1531
  init_http();
1428
1532
 
1429
1533
  // src/langgraph.ts
@@ -1904,6 +2008,84 @@ var BitfabLangGraphCallbackHandler = class {
1904
2008
 
1905
2009
  // src/client.ts
1906
2010
  init_replayContext();
2011
+
2012
+ // src/replayEnvironment.ts
2013
+ init_replayContext();
2014
+ var ReplayEnvironment = class {
2015
+ /**
2016
+ * The per-trace branch URL for the item currently being replayed.
2017
+ * Throws if read outside a replay item.
2018
+ */
2019
+ get databaseUrl() {
2020
+ return this.require().databaseUrl;
2021
+ }
2022
+ /** When the per-trace branch URL stops being valid. ISO-8601. */
2023
+ get expiresAt() {
2024
+ return this.require().expiresAt;
2025
+ }
2026
+ /** Deep link to the branch in the provider console, if available. */
2027
+ get providerConsoleUrl() {
2028
+ return this.require().providerConsoleUrl;
2029
+ }
2030
+ /**
2031
+ * True if the branch is read-only. Customer code can use this to skip
2032
+ * write operations during replay when the provider returned a read-only
2033
+ * lease.
2034
+ */
2035
+ get readOnly() {
2036
+ return this.require().readOnly;
2037
+ }
2038
+ /** The historical trace ID that produced the input for this replay item. */
2039
+ get traceId() {
2040
+ return this.require().traceId;
2041
+ }
2042
+ /**
2043
+ * How the resolver pinned this branch.
2044
+ * - "timestamp": snapshot at SDK wall clock; bounded by replication lag.
2045
+ * - "lsn": customer LSN mapped to a replica snapshot (future).
2046
+ */
2047
+ get precision() {
2048
+ return this.require().precision;
2049
+ }
2050
+ /** True when read inside a replay item that has a resolved branch. */
2051
+ get active() {
2052
+ return this.read() !== null;
2053
+ }
2054
+ /** Non-throwing variant for callers that handle the inactive case. */
2055
+ snapshot() {
2056
+ return this.read();
2057
+ }
2058
+ read() {
2059
+ const ctx = getReplayContext();
2060
+ if (!ctx?.dbBranchLease) {
2061
+ return null;
2062
+ }
2063
+ const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId;
2064
+ if (!traceId) {
2065
+ return null;
2066
+ }
2067
+ const lease = ctx.dbBranchLease;
2068
+ return {
2069
+ databaseUrl: lease.databaseUrl,
2070
+ expiresAt: lease.expiresAt,
2071
+ providerConsoleUrl: lease.providerConsoleUrl,
2072
+ readOnly: lease.readOnly,
2073
+ traceId,
2074
+ precision: lease.precision
2075
+ };
2076
+ }
2077
+ require() {
2078
+ const snapshot = this.read();
2079
+ if (!snapshot) {
2080
+ throw new Error(
2081
+ "ReplayEnvironment accessed outside of a replay item. Pass it to bitfab.replay({ environment }) and only read it inside the replayed function."
2082
+ );
2083
+ }
2084
+ return snapshot;
2085
+ }
2086
+ };
2087
+
2088
+ // src/client.ts
1907
2089
  init_serialize();
1908
2090
 
1909
2091
  // src/tracing.ts
@@ -2399,6 +2581,10 @@ var Bitfab = class {
2399
2581
  this.enabled = enabled;
2400
2582
  }
2401
2583
  this.bamlClient = config.bamlClient ?? null;
2584
+ if (config.dbSnapshot) {
2585
+ validateDbSnapshotConfig(config.dbSnapshot);
2586
+ }
2587
+ this.dbSnapshot = config.dbSnapshot;
2402
2588
  this.httpClient = new HttpClient({
2403
2589
  apiKey: this.apiKey,
2404
2590
  serviceUrl: this.serviceUrl,
@@ -2695,7 +2881,8 @@ var Bitfab = class {
2695
2881
  }
2696
2882
  const currentStack = getSpanStack();
2697
2883
  const parentContext = currentStack[currentStack.length - 1];
2698
- const traceId = parentContext?.traceId ?? crypto.randomUUID();
2884
+ const replayCtxForTraceId = parentContext ? null : getReplayContext();
2885
+ const traceId = parentContext?.traceId ?? replayCtxForTraceId?.traceId ?? crypto.randomUUID();
2699
2886
  const spanId = crypto.randomUUID();
2700
2887
  const parentSpanId = parentContext?.spanId ?? null;
2701
2888
  const isRootSpan = parentSpanId === null;
@@ -2705,6 +2892,7 @@ var Bitfab = class {
2705
2892
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2706
2893
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2707
2894
  const replayCtxAtRoot = getReplayContext();
2895
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2708
2896
  activeTraceStates.set(traceId, {
2709
2897
  traceId,
2710
2898
  startedAt,
@@ -2714,7 +2902,8 @@ var Bitfab = class {
2714
2902
  },
2715
2903
  ...replayCtxAtRoot?.inputSourceTraceId && {
2716
2904
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2717
- }
2905
+ },
2906
+ ...dbSnapshotRef && { dbSnapshotRef }
2718
2907
  });
2719
2908
  pendingSpanPromises.set(traceId, []);
2720
2909
  }
@@ -2734,6 +2923,7 @@ var Bitfab = class {
2734
2923
  try {
2735
2924
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2736
2925
  const replayCtx = getReplayContext();
2926
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2737
2927
  const spanPromise = self.sendWrapperSpan({
2738
2928
  ...baseSpanParams,
2739
2929
  ...params,
@@ -2743,6 +2933,9 @@ var Bitfab = class {
2743
2933
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2744
2934
  ...replayCtx?.inputSourceSpanId && {
2745
2935
  inputSourceSpanId: replayCtx.inputSourceSpanId
2936
+ },
2937
+ ...dbSnapshotRefForSpan && {
2938
+ dbSnapshotRef: dbSnapshotRefForSpan
2746
2939
  }
2747
2940
  });
2748
2941
  if (isRootSpan) {
@@ -2763,7 +2956,8 @@ var Bitfab = class {
2763
2956
  metadata: traceState?.metadata,
2764
2957
  contexts: traceState?.contexts ?? [],
2765
2958
  testRunId: traceState?.testRunId,
2766
- inputSourceTraceId: traceState?.inputSourceTraceId
2959
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2960
+ dbSnapshotRef: traceState?.dbSnapshotRef
2767
2961
  });
2768
2962
  activeTraceStates.delete(traceId);
2769
2963
  } else {
@@ -2924,6 +3118,9 @@ var Bitfab = class {
2924
3118
  if (params.inputSourceTraceId) {
2925
3119
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2926
3120
  }
3121
+ if (params.dbSnapshotRef) {
3122
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3123
+ }
2927
3124
  this.httpClient.sendExternalTrace({
2928
3125
  type: "sdk-function",
2929
3126
  source: "typescript-sdk-function",
@@ -2966,7 +3163,10 @@ var Bitfab = class {
2966
3163
  ...params.contexts && params.contexts.length > 0 && {
2967
3164
  contexts: params.contexts
2968
3165
  },
2969
- ...params.prompt !== void 0 && { prompt: params.prompt }
3166
+ ...params.prompt !== void 0 && { prompt: params.prompt },
3167
+ ...params.dbSnapshotRef && {
3168
+ db_snapshot_ref: params.dbSnapshotRef
3169
+ }
2970
3170
  }
2971
3171
  };
2972
3172
  if (params.parentSpanId) {
@@ -3009,6 +3209,12 @@ var Bitfab = class {
3009
3209
  );
3010
3210
  }
3011
3211
  };
3212
+ /**
3213
+ * Per-trace environment for `replay({ environment })`. Construct one,
3214
+ * pass it to replay, and read `env.databaseUrl` inside the replayed
3215
+ * function to pick up the per-trace branch URL.
3216
+ */
3217
+ Bitfab.ReplayEnvironment = ReplayEnvironment;
3012
3218
  var BitfabFunction = class {
3013
3219
  constructor(client, traceFunctionKey) {
3014
3220
  this.client = client;
@@ -3070,6 +3276,8 @@ init_http();
3070
3276
  BitfabLangGraphCallbackHandler,
3071
3277
  BitfabOpenAITracingProcessor,
3072
3278
  DEFAULT_SERVICE_URL,
3279
+ ReplayEnvironment,
3280
+ SUPPORTED_PROVIDERS,
3073
3281
  __version__,
3074
3282
  flushTraces,
3075
3283
  getCurrentSpan,