bitfab 0.13.3 → 0.13.4

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.4";
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,33 +575,53 @@ 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;
556
584
  try {
585
+ const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
586
+ const spanData = span.rawData?.span_data ?? {};
587
+ inputs = deserializeInputs(spanData);
588
+ originalOutput = deserializeOutput(spanData);
589
+ let mockTree;
590
+ if (mockStrategy === "all" || mockStrategy === "marked") {
591
+ const treeResponse = await httpClient.getSpanTree(
592
+ serverItem.externalSpanId
593
+ );
594
+ mockTree = buildMockTree(treeResponse.root);
595
+ }
557
596
  const maybePromise = runWithReplayContext(
558
597
  {
559
598
  testRunId,
560
599
  inputSourceSpanId: span.id,
561
600
  inputSourceTraceId: span.externalTraceId,
601
+ sourceBitfabTraceId: serverItem.traceId,
562
602
  mockTree,
563
603
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
564
- mockStrategy
604
+ mockStrategy,
605
+ dbBranchLease: lease
565
606
  },
566
607
  () => fn(...inputs)
567
608
  );
568
609
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
569
610
  } catch (e) {
570
611
  error = e instanceof Error ? e.message : String(e);
612
+ } finally {
613
+ if (lease) {
614
+ try {
615
+ await httpClient.releaseDbBranchLease(lease.leaseId);
616
+ } catch (e) {
617
+ try {
618
+ console.warn(
619
+ `Bitfab: failed to release DB branch lease ${lease.leaseId} (TTL janitor will catch it): ${e instanceof Error ? e.message : String(e)}`
620
+ );
621
+ } catch {
622
+ }
623
+ }
624
+ }
571
625
  }
572
626
  return {
573
627
  input: inputs,
@@ -576,7 +630,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
576
630
  error,
577
631
  durationMs: serverItem.durationMs ?? null,
578
632
  tokens: serverItem.tokens ?? null,
579
- model: serverItem.model ?? null
633
+ model: serverItem.model ?? null,
634
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
580
635
  };
581
636
  }
582
637
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -606,12 +661,21 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
606
661
  options?.limit ?? 5,
607
662
  options?.traceIds,
608
663
  options?.codeChangeDescription,
609
- options?.codeChangeFiles
664
+ options?.codeChangeFiles,
665
+ options?.environment !== void 0
666
+ // includeDbBranchLease
610
667
  );
611
668
  const mockStrategy = options?.mock ?? "none";
612
669
  const maxConcurrency = options?.maxConcurrency ?? 10;
613
670
  const tasks = serverItems.map(
614
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
671
+ (serverItem) => () => processItem(
672
+ httpClient,
673
+ serverItem,
674
+ fn,
675
+ testRunId,
676
+ mockStrategy,
677
+ options?.environment
678
+ )
615
679
  );
616
680
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
617
681
  await flushTraces();
@@ -648,6 +712,8 @@ __export(index_exports, {
648
712
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
649
713
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
650
714
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
715
+ ReplayEnvironment: () => ReplayEnvironment,
716
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
651
717
  __version__: () => __version__,
652
718
  flushTraces: () => flushTraces,
653
719
  getCurrentSpan: () => getCurrentSpan,
@@ -1424,6 +1490,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1424
1490
 
1425
1491
  // src/client.ts
1426
1492
  init_constants();
1493
+
1494
+ // src/dbSnapshot.ts
1495
+ init_errors();
1496
+ var SUPPORTED_PROVIDERS = [
1497
+ "neon",
1498
+ "ardent",
1499
+ "dolt",
1500
+ "gfs",
1501
+ "mongodb-atlas",
1502
+ "dynamodb",
1503
+ "snowflake",
1504
+ "bigquery"
1505
+ ];
1506
+ function validateDbSnapshotConfig(config) {
1507
+ if (!SUPPORTED_PROVIDERS.includes(config.provider)) {
1508
+ throw new BitfabError(
1509
+ `dbSnapshot.provider "${config.provider}" is not supported. Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}.`
1510
+ );
1511
+ }
1512
+ }
1513
+ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1514
+ return {
1515
+ provider: config.provider,
1516
+ sdkWallClockBeforeFn
1517
+ };
1518
+ }
1519
+
1520
+ // src/client.ts
1427
1521
  init_http();
1428
1522
 
1429
1523
  // src/langgraph.ts
@@ -1904,6 +1998,84 @@ var BitfabLangGraphCallbackHandler = class {
1904
1998
 
1905
1999
  // src/client.ts
1906
2000
  init_replayContext();
2001
+
2002
+ // src/replayEnvironment.ts
2003
+ init_replayContext();
2004
+ var ReplayEnvironment = class {
2005
+ /**
2006
+ * The per-trace branch URL for the item currently being replayed.
2007
+ * Throws if read outside a replay item.
2008
+ */
2009
+ get databaseUrl() {
2010
+ return this.require().databaseUrl;
2011
+ }
2012
+ /** When the per-trace branch URL stops being valid. ISO-8601. */
2013
+ get expiresAt() {
2014
+ return this.require().expiresAt;
2015
+ }
2016
+ /** Deep link to the branch in the provider console, if available. */
2017
+ get providerConsoleUrl() {
2018
+ return this.require().providerConsoleUrl;
2019
+ }
2020
+ /**
2021
+ * True if the branch is read-only. Customer code can use this to skip
2022
+ * write operations during replay when the provider returned a read-only
2023
+ * lease.
2024
+ */
2025
+ get readOnly() {
2026
+ return this.require().readOnly;
2027
+ }
2028
+ /** The historical trace ID that produced the input for this replay item. */
2029
+ get traceId() {
2030
+ return this.require().traceId;
2031
+ }
2032
+ /**
2033
+ * How the resolver pinned this branch.
2034
+ * - "timestamp": snapshot at SDK wall clock; bounded by replication lag.
2035
+ * - "lsn": customer LSN mapped to a replica snapshot (future).
2036
+ */
2037
+ get precision() {
2038
+ return this.require().precision;
2039
+ }
2040
+ /** True when read inside a replay item that has a resolved branch. */
2041
+ get active() {
2042
+ return this.read() !== null;
2043
+ }
2044
+ /** Non-throwing variant for callers that handle the inactive case. */
2045
+ snapshot() {
2046
+ return this.read();
2047
+ }
2048
+ read() {
2049
+ const ctx = getReplayContext();
2050
+ if (!ctx?.dbBranchLease) {
2051
+ return null;
2052
+ }
2053
+ const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId;
2054
+ if (!traceId) {
2055
+ return null;
2056
+ }
2057
+ const lease = ctx.dbBranchLease;
2058
+ return {
2059
+ databaseUrl: lease.databaseUrl,
2060
+ expiresAt: lease.expiresAt,
2061
+ providerConsoleUrl: lease.providerConsoleUrl,
2062
+ readOnly: lease.readOnly,
2063
+ traceId,
2064
+ precision: lease.precision
2065
+ };
2066
+ }
2067
+ require() {
2068
+ const snapshot = this.read();
2069
+ if (!snapshot) {
2070
+ throw new Error(
2071
+ "ReplayEnvironment accessed outside of a replay item. Pass it to bitfab.replay({ environment }) and only read it inside the replayed function."
2072
+ );
2073
+ }
2074
+ return snapshot;
2075
+ }
2076
+ };
2077
+
2078
+ // src/client.ts
1907
2079
  init_serialize();
1908
2080
 
1909
2081
  // src/tracing.ts
@@ -2399,6 +2571,10 @@ var Bitfab = class {
2399
2571
  this.enabled = enabled;
2400
2572
  }
2401
2573
  this.bamlClient = config.bamlClient ?? null;
2574
+ if (config.dbSnapshot) {
2575
+ validateDbSnapshotConfig(config.dbSnapshot);
2576
+ }
2577
+ this.dbSnapshot = config.dbSnapshot;
2402
2578
  this.httpClient = new HttpClient({
2403
2579
  apiKey: this.apiKey,
2404
2580
  serviceUrl: this.serviceUrl,
@@ -2705,6 +2881,7 @@ var Bitfab = class {
2705
2881
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2706
2882
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2707
2883
  const replayCtxAtRoot = getReplayContext();
2884
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2708
2885
  activeTraceStates.set(traceId, {
2709
2886
  traceId,
2710
2887
  startedAt,
@@ -2714,7 +2891,8 @@ var Bitfab = class {
2714
2891
  },
2715
2892
  ...replayCtxAtRoot?.inputSourceTraceId && {
2716
2893
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2717
- }
2894
+ },
2895
+ ...dbSnapshotRef && { dbSnapshotRef }
2718
2896
  });
2719
2897
  pendingSpanPromises.set(traceId, []);
2720
2898
  }
@@ -2734,6 +2912,7 @@ var Bitfab = class {
2734
2912
  try {
2735
2913
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2736
2914
  const replayCtx = getReplayContext();
2915
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2737
2916
  const spanPromise = self.sendWrapperSpan({
2738
2917
  ...baseSpanParams,
2739
2918
  ...params,
@@ -2743,6 +2922,9 @@ var Bitfab = class {
2743
2922
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2744
2923
  ...replayCtx?.inputSourceSpanId && {
2745
2924
  inputSourceSpanId: replayCtx.inputSourceSpanId
2925
+ },
2926
+ ...dbSnapshotRefForSpan && {
2927
+ dbSnapshotRef: dbSnapshotRefForSpan
2746
2928
  }
2747
2929
  });
2748
2930
  if (isRootSpan) {
@@ -2763,7 +2945,8 @@ var Bitfab = class {
2763
2945
  metadata: traceState?.metadata,
2764
2946
  contexts: traceState?.contexts ?? [],
2765
2947
  testRunId: traceState?.testRunId,
2766
- inputSourceTraceId: traceState?.inputSourceTraceId
2948
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2949
+ dbSnapshotRef: traceState?.dbSnapshotRef
2767
2950
  });
2768
2951
  activeTraceStates.delete(traceId);
2769
2952
  } else {
@@ -2924,6 +3107,9 @@ var Bitfab = class {
2924
3107
  if (params.inputSourceTraceId) {
2925
3108
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2926
3109
  }
3110
+ if (params.dbSnapshotRef) {
3111
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3112
+ }
2927
3113
  this.httpClient.sendExternalTrace({
2928
3114
  type: "sdk-function",
2929
3115
  source: "typescript-sdk-function",
@@ -2966,7 +3152,10 @@ var Bitfab = class {
2966
3152
  ...params.contexts && params.contexts.length > 0 && {
2967
3153
  contexts: params.contexts
2968
3154
  },
2969
- ...params.prompt !== void 0 && { prompt: params.prompt }
3155
+ ...params.prompt !== void 0 && { prompt: params.prompt },
3156
+ ...params.dbSnapshotRef && {
3157
+ db_snapshot_ref: params.dbSnapshotRef
3158
+ }
2970
3159
  }
2971
3160
  };
2972
3161
  if (params.parentSpanId) {
@@ -3009,6 +3198,12 @@ var Bitfab = class {
3009
3198
  );
3010
3199
  }
3011
3200
  };
3201
+ /**
3202
+ * Per-trace environment for `replay({ environment })`. Construct one,
3203
+ * pass it to replay, and read `env.databaseUrl` inside the replayed
3204
+ * function to pick up the per-trace branch URL.
3205
+ */
3206
+ Bitfab.ReplayEnvironment = ReplayEnvironment;
3012
3207
  var BitfabFunction = class {
3013
3208
  constructor(client, traceFunctionKey) {
3014
3209
  this.client = client;
@@ -3070,6 +3265,8 @@ init_http();
3070
3265
  BitfabLangGraphCallbackHandler,
3071
3266
  BitfabOpenAITracingProcessor,
3072
3267
  DEFAULT_SERVICE_URL,
3268
+ ReplayEnvironment,
3269
+ SUPPORTED_PROVIDERS,
3073
3270
  __version__,
3074
3271
  flushTraces,
3075
3272
  getCurrentSpan,