@bitfab/sdk 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
@@ -35,7 +35,7 @@ var __version__;
35
35
  var init_version_generated = __esm({
36
36
  "src/version.generated.ts"() {
37
37
  "use strict";
38
- __version__ = "0.13.3";
38
+ __version__ = "0.13.4";
39
39
  }
40
40
  });
41
41
 
@@ -49,6 +49,21 @@ var init_constants = __esm({
49
49
  }
50
50
  });
51
51
 
52
+ // src/errors.ts
53
+ var BitfabError;
54
+ var init_errors = __esm({
55
+ "src/errors.ts"() {
56
+ "use strict";
57
+ BitfabError = class extends Error {
58
+ constructor(message, url) {
59
+ super(message);
60
+ this.url = url;
61
+ this.name = "BitfabError";
62
+ }
63
+ };
64
+ }
65
+ });
66
+
52
67
  // src/http.ts
53
68
  function awaitOnExit(promise) {
54
69
  pendingTracePromises.add(promise);
@@ -67,18 +82,12 @@ async function flushTraces(timeoutMs = 5e3) {
67
82
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
68
83
  ]);
69
84
  }
70
- var BitfabError, pendingTracePromises, HttpClient;
85
+ var pendingTracePromises, HttpClient;
71
86
  var init_http = __esm({
72
87
  "src/http.ts"() {
73
88
  "use strict";
74
89
  init_constants();
75
- BitfabError = class extends Error {
76
- constructor(message, url) {
77
- super(message);
78
- this.url = url;
79
- this.name = "BitfabError";
80
- }
81
- };
90
+ init_errors();
82
91
  pendingTracePromises = /* @__PURE__ */ new Set();
83
92
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
84
93
  let isFlushing = false;
@@ -257,7 +266,7 @@ var init_http = __esm({
257
266
  * Start a replay session by fetching historical traces.
258
267
  * Blocking call — creates a test run and returns lightweight item references.
259
268
  */
260
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
269
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
261
270
  const payload = { traceFunctionKey, limit };
262
271
  if (traceIds) {
263
272
  payload.traceIds = traceIds;
@@ -268,8 +277,12 @@ var init_http = __esm({
268
277
  if (codeChangeFiles !== void 0) {
269
278
  payload.codeChangeFiles = codeChangeFiles;
270
279
  }
280
+ if (includeDbBranchLease) {
281
+ payload.includeDbBranchLease = true;
282
+ }
283
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
271
284
  return this.request("/api/sdk/replay/start", payload, {
272
- timeout: 3e4
285
+ timeout
273
286
  });
274
287
  }
275
288
  /**
@@ -355,6 +368,27 @@ var init_http = __esm({
355
368
  { timeout: 3e4 }
356
369
  );
357
370
  }
371
+ /**
372
+ * Ask the server to materialize a per-trace DB branch lease from a
373
+ * captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
374
+ * snapshot + preview branch and polls operations to readiness, which
375
+ * can take seconds.
376
+ */
377
+ async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
378
+ return this.request(
379
+ "/api/sdk/replay/resolveDbBranchLease",
380
+ { testRunId, traceId, dbSnapshotRef },
381
+ { timeout: 9e4 }
382
+ );
383
+ }
384
+ /** Release a previously-resolved DB branch lease. Idempotent server-side. */
385
+ async releaseDbBranchLease(leaseId) {
386
+ await this.request(
387
+ "/api/sdk/replay/releaseDbBranchLease",
388
+ { leaseId },
389
+ { timeout: 3e4 }
390
+ );
391
+ }
358
392
  };
359
393
  }
360
394
  });
@@ -532,33 +566,53 @@ function buildMockTree(rootNode) {
532
566
  }
533
567
  return { spans };
534
568
  }
535
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy) {
536
- const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
537
- const spanData = span.rawData?.span_data ?? {};
538
- const inputs = deserializeInputs(spanData);
539
- const originalOutput = deserializeOutput(spanData);
540
- let mockTree;
541
- if (mockStrategy === "all" || mockStrategy === "marked") {
542
- const treeResponse = await httpClient.getSpanTree(serverItem.externalSpanId);
543
- mockTree = buildMockTree(treeResponse.root);
544
- }
569
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment) {
570
+ const lease = environment ? serverItem.dbBranchLease : void 0;
571
+ let inputs = [];
572
+ let originalOutput;
545
573
  let result;
546
574
  let error = null;
547
575
  try {
576
+ const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
577
+ const spanData = span.rawData?.span_data ?? {};
578
+ inputs = deserializeInputs(spanData);
579
+ originalOutput = deserializeOutput(spanData);
580
+ let mockTree;
581
+ if (mockStrategy === "all" || mockStrategy === "marked") {
582
+ const treeResponse = await httpClient.getSpanTree(
583
+ serverItem.externalSpanId
584
+ );
585
+ mockTree = buildMockTree(treeResponse.root);
586
+ }
548
587
  const maybePromise = runWithReplayContext(
549
588
  {
550
589
  testRunId,
551
590
  inputSourceSpanId: span.id,
552
591
  inputSourceTraceId: span.externalTraceId,
592
+ sourceBitfabTraceId: serverItem.traceId,
553
593
  mockTree,
554
594
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
555
- mockStrategy
595
+ mockStrategy,
596
+ dbBranchLease: lease
556
597
  },
557
598
  () => fn(...inputs)
558
599
  );
559
600
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
560
601
  } catch (e) {
561
602
  error = e instanceof Error ? e.message : String(e);
603
+ } finally {
604
+ if (lease) {
605
+ try {
606
+ await httpClient.releaseDbBranchLease(lease.leaseId);
607
+ } catch (e) {
608
+ try {
609
+ console.warn(
610
+ `Bitfab: failed to release DB branch lease ${lease.leaseId} (TTL janitor will catch it): ${e instanceof Error ? e.message : String(e)}`
611
+ );
612
+ } catch {
613
+ }
614
+ }
615
+ }
562
616
  }
563
617
  return {
564
618
  input: inputs,
@@ -567,7 +621,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
567
621
  error,
568
622
  durationMs: serverItem.durationMs ?? null,
569
623
  tokens: serverItem.tokens ?? null,
570
- model: serverItem.model ?? null
624
+ model: serverItem.model ?? null,
625
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
571
626
  };
572
627
  }
573
628
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -597,12 +652,21 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
597
652
  options?.limit ?? 5,
598
653
  options?.traceIds,
599
654
  options?.codeChangeDescription,
600
- options?.codeChangeFiles
655
+ options?.codeChangeFiles,
656
+ options?.environment !== void 0
657
+ // includeDbBranchLease
601
658
  );
602
659
  const mockStrategy = options?.mock ?? "none";
603
660
  const maxConcurrency = options?.maxConcurrency ?? 10;
604
661
  const tasks = serverItems.map(
605
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
662
+ (serverItem) => () => processItem(
663
+ httpClient,
664
+ serverItem,
665
+ fn,
666
+ testRunId,
667
+ mockStrategy,
668
+ options?.environment
669
+ )
606
670
  );
607
671
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
608
672
  await flushTraces();
@@ -639,6 +703,8 @@ __export(index_exports, {
639
703
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
640
704
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
641
705
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
706
+ ReplayEnvironment: () => ReplayEnvironment,
707
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
642
708
  __version__: () => __version__,
643
709
  flushTraces: () => flushTraces,
644
710
  getCurrentSpan: () => getCurrentSpan,
@@ -1415,6 +1481,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1415
1481
 
1416
1482
  // src/client.ts
1417
1483
  init_constants();
1484
+
1485
+ // src/dbSnapshot.ts
1486
+ init_errors();
1487
+ var SUPPORTED_PROVIDERS = [
1488
+ "neon",
1489
+ "ardent",
1490
+ "dolt",
1491
+ "gfs",
1492
+ "mongodb-atlas",
1493
+ "dynamodb",
1494
+ "snowflake",
1495
+ "bigquery"
1496
+ ];
1497
+ function validateDbSnapshotConfig(config) {
1498
+ if (!SUPPORTED_PROVIDERS.includes(config.provider)) {
1499
+ throw new BitfabError(
1500
+ `dbSnapshot.provider "${config.provider}" is not supported. Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}.`
1501
+ );
1502
+ }
1503
+ }
1504
+ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1505
+ return {
1506
+ provider: config.provider,
1507
+ sdkWallClockBeforeFn
1508
+ };
1509
+ }
1510
+
1511
+ // src/client.ts
1418
1512
  init_http();
1419
1513
 
1420
1514
  // src/langgraph.ts
@@ -1895,6 +1989,84 @@ var BitfabLangGraphCallbackHandler = class {
1895
1989
 
1896
1990
  // src/client.ts
1897
1991
  init_replayContext();
1992
+
1993
+ // src/replayEnvironment.ts
1994
+ init_replayContext();
1995
+ var ReplayEnvironment = class {
1996
+ /**
1997
+ * The per-trace branch URL for the item currently being replayed.
1998
+ * Throws if read outside a replay item.
1999
+ */
2000
+ get databaseUrl() {
2001
+ return this.require().databaseUrl;
2002
+ }
2003
+ /** When the per-trace branch URL stops being valid. ISO-8601. */
2004
+ get expiresAt() {
2005
+ return this.require().expiresAt;
2006
+ }
2007
+ /** Deep link to the branch in the provider console, if available. */
2008
+ get providerConsoleUrl() {
2009
+ return this.require().providerConsoleUrl;
2010
+ }
2011
+ /**
2012
+ * True if the branch is read-only. Customer code can use this to skip
2013
+ * write operations during replay when the provider returned a read-only
2014
+ * lease.
2015
+ */
2016
+ get readOnly() {
2017
+ return this.require().readOnly;
2018
+ }
2019
+ /** The historical trace ID that produced the input for this replay item. */
2020
+ get traceId() {
2021
+ return this.require().traceId;
2022
+ }
2023
+ /**
2024
+ * How the resolver pinned this branch.
2025
+ * - "timestamp": snapshot at SDK wall clock; bounded by replication lag.
2026
+ * - "lsn": customer LSN mapped to a replica snapshot (future).
2027
+ */
2028
+ get precision() {
2029
+ return this.require().precision;
2030
+ }
2031
+ /** True when read inside a replay item that has a resolved branch. */
2032
+ get active() {
2033
+ return this.read() !== null;
2034
+ }
2035
+ /** Non-throwing variant for callers that handle the inactive case. */
2036
+ snapshot() {
2037
+ return this.read();
2038
+ }
2039
+ read() {
2040
+ const ctx = getReplayContext();
2041
+ if (!ctx?.dbBranchLease) {
2042
+ return null;
2043
+ }
2044
+ const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId;
2045
+ if (!traceId) {
2046
+ return null;
2047
+ }
2048
+ const lease = ctx.dbBranchLease;
2049
+ return {
2050
+ databaseUrl: lease.databaseUrl,
2051
+ expiresAt: lease.expiresAt,
2052
+ providerConsoleUrl: lease.providerConsoleUrl,
2053
+ readOnly: lease.readOnly,
2054
+ traceId,
2055
+ precision: lease.precision
2056
+ };
2057
+ }
2058
+ require() {
2059
+ const snapshot = this.read();
2060
+ if (!snapshot) {
2061
+ throw new Error(
2062
+ "ReplayEnvironment accessed outside of a replay item. Pass it to bitfab.replay({ environment }) and only read it inside the replayed function."
2063
+ );
2064
+ }
2065
+ return snapshot;
2066
+ }
2067
+ };
2068
+
2069
+ // src/client.ts
1898
2070
  init_serialize();
1899
2071
 
1900
2072
  // src/tracing.ts
@@ -2390,6 +2562,10 @@ var Bitfab = class {
2390
2562
  this.enabled = enabled;
2391
2563
  }
2392
2564
  this.bamlClient = config.bamlClient ?? null;
2565
+ if (config.dbSnapshot) {
2566
+ validateDbSnapshotConfig(config.dbSnapshot);
2567
+ }
2568
+ this.dbSnapshot = config.dbSnapshot;
2393
2569
  this.httpClient = new HttpClient({
2394
2570
  apiKey: this.apiKey,
2395
2571
  serviceUrl: this.serviceUrl,
@@ -2696,6 +2872,7 @@ var Bitfab = class {
2696
2872
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2697
2873
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2698
2874
  const replayCtxAtRoot = getReplayContext();
2875
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2699
2876
  activeTraceStates.set(traceId, {
2700
2877
  traceId,
2701
2878
  startedAt,
@@ -2705,7 +2882,8 @@ var Bitfab = class {
2705
2882
  },
2706
2883
  ...replayCtxAtRoot?.inputSourceTraceId && {
2707
2884
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2708
- }
2885
+ },
2886
+ ...dbSnapshotRef && { dbSnapshotRef }
2709
2887
  });
2710
2888
  pendingSpanPromises.set(traceId, []);
2711
2889
  }
@@ -2725,6 +2903,7 @@ var Bitfab = class {
2725
2903
  try {
2726
2904
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2727
2905
  const replayCtx = getReplayContext();
2906
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2728
2907
  const spanPromise = self.sendWrapperSpan({
2729
2908
  ...baseSpanParams,
2730
2909
  ...params,
@@ -2734,6 +2913,9 @@ var Bitfab = class {
2734
2913
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2735
2914
  ...replayCtx?.inputSourceSpanId && {
2736
2915
  inputSourceSpanId: replayCtx.inputSourceSpanId
2916
+ },
2917
+ ...dbSnapshotRefForSpan && {
2918
+ dbSnapshotRef: dbSnapshotRefForSpan
2737
2919
  }
2738
2920
  });
2739
2921
  if (isRootSpan) {
@@ -2754,7 +2936,8 @@ var Bitfab = class {
2754
2936
  metadata: traceState?.metadata,
2755
2937
  contexts: traceState?.contexts ?? [],
2756
2938
  testRunId: traceState?.testRunId,
2757
- inputSourceTraceId: traceState?.inputSourceTraceId
2939
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2940
+ dbSnapshotRef: traceState?.dbSnapshotRef
2758
2941
  });
2759
2942
  activeTraceStates.delete(traceId);
2760
2943
  } else {
@@ -2915,6 +3098,9 @@ var Bitfab = class {
2915
3098
  if (params.inputSourceTraceId) {
2916
3099
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2917
3100
  }
3101
+ if (params.dbSnapshotRef) {
3102
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3103
+ }
2918
3104
  this.httpClient.sendExternalTrace({
2919
3105
  type: "sdk-function",
2920
3106
  source: "typescript-sdk-function",
@@ -2957,7 +3143,10 @@ var Bitfab = class {
2957
3143
  ...params.contexts && params.contexts.length > 0 && {
2958
3144
  contexts: params.contexts
2959
3145
  },
2960
- ...params.prompt !== void 0 && { prompt: params.prompt }
3146
+ ...params.prompt !== void 0 && { prompt: params.prompt },
3147
+ ...params.dbSnapshotRef && {
3148
+ db_snapshot_ref: params.dbSnapshotRef
3149
+ }
2961
3150
  }
2962
3151
  };
2963
3152
  if (params.parentSpanId) {
@@ -3000,6 +3189,12 @@ var Bitfab = class {
3000
3189
  );
3001
3190
  }
3002
3191
  };
3192
+ /**
3193
+ * Per-trace environment for `replay({ environment })`. Construct one,
3194
+ * pass it to replay, and read `env.databaseUrl` inside the replayed
3195
+ * function to pick up the per-trace branch URL.
3196
+ */
3197
+ Bitfab.ReplayEnvironment = ReplayEnvironment;
3003
3198
  var BitfabFunction = class {
3004
3199
  constructor(client, traceFunctionKey) {
3005
3200
  this.client = client;
@@ -3061,6 +3256,8 @@ init_http();
3061
3256
  BitfabLangGraphCallbackHandler,
3062
3257
  BitfabOpenAITracingProcessor,
3063
3258
  DEFAULT_SERVICE_URL,
3259
+ ReplayEnvironment,
3260
+ SUPPORTED_PROVIDERS,
3064
3261
  __version__,
3065
3262
  flushTraces,
3066
3263
  getCurrentSpan,