@bitfab/sdk 0.13.2 → 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/node.cjs CHANGED
@@ -81,7 +81,7 @@ var __version__;
81
81
  var init_version_generated = __esm({
82
82
  "src/version.generated.ts"() {
83
83
  "use strict";
84
- __version__ = "0.13.2";
84
+ __version__ = "0.13.4";
85
85
  }
86
86
  });
87
87
 
@@ -95,6 +95,21 @@ var init_constants = __esm({
95
95
  }
96
96
  });
97
97
 
98
+ // src/errors.ts
99
+ var BitfabError;
100
+ var init_errors = __esm({
101
+ "src/errors.ts"() {
102
+ "use strict";
103
+ BitfabError = class extends Error {
104
+ constructor(message, url) {
105
+ super(message);
106
+ this.url = url;
107
+ this.name = "BitfabError";
108
+ }
109
+ };
110
+ }
111
+ });
112
+
98
113
  // src/http.ts
99
114
  function awaitOnExit(promise) {
100
115
  pendingTracePromises.add(promise);
@@ -113,18 +128,12 @@ async function flushTraces(timeoutMs = 5e3) {
113
128
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
114
129
  ]);
115
130
  }
116
- var BitfabError, pendingTracePromises, HttpClient;
131
+ var pendingTracePromises, HttpClient;
117
132
  var init_http = __esm({
118
133
  "src/http.ts"() {
119
134
  "use strict";
120
135
  init_constants();
121
- BitfabError = class extends Error {
122
- constructor(message, url) {
123
- super(message);
124
- this.url = url;
125
- this.name = "BitfabError";
126
- }
127
- };
136
+ init_errors();
128
137
  pendingTracePromises = /* @__PURE__ */ new Set();
129
138
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
130
139
  let isFlushing = false;
@@ -303,7 +312,7 @@ var init_http = __esm({
303
312
  * Start a replay session by fetching historical traces.
304
313
  * Blocking call — creates a test run and returns lightweight item references.
305
314
  */
306
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
315
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
307
316
  const payload = { traceFunctionKey, limit };
308
317
  if (traceIds) {
309
318
  payload.traceIds = traceIds;
@@ -314,8 +323,12 @@ var init_http = __esm({
314
323
  if (codeChangeFiles !== void 0) {
315
324
  payload.codeChangeFiles = codeChangeFiles;
316
325
  }
326
+ if (includeDbBranchLease) {
327
+ payload.includeDbBranchLease = true;
328
+ }
329
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
317
330
  return this.request("/api/sdk/replay/start", payload, {
318
- timeout: 3e4
331
+ timeout
319
332
  });
320
333
  }
321
334
  /**
@@ -401,6 +414,27 @@ var init_http = __esm({
401
414
  { timeout: 3e4 }
402
415
  );
403
416
  }
417
+ /**
418
+ * Ask the server to materialize a per-trace DB branch lease from a
419
+ * captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
420
+ * snapshot + preview branch and polls operations to readiness, which
421
+ * can take seconds.
422
+ */
423
+ async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
424
+ return this.request(
425
+ "/api/sdk/replay/resolveDbBranchLease",
426
+ { testRunId, traceId, dbSnapshotRef },
427
+ { timeout: 9e4 }
428
+ );
429
+ }
430
+ /** Release a previously-resolved DB branch lease. Idempotent server-side. */
431
+ async releaseDbBranchLease(leaseId) {
432
+ await this.request(
433
+ "/api/sdk/replay/releaseDbBranchLease",
434
+ { leaseId },
435
+ { timeout: 3e4 }
436
+ );
437
+ }
404
438
  };
405
439
  }
406
440
  });
@@ -539,33 +573,53 @@ function buildMockTree(rootNode) {
539
573
  }
540
574
  return { spans };
541
575
  }
542
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy) {
543
- const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
544
- const spanData = span.rawData?.span_data ?? {};
545
- const inputs = deserializeInputs(spanData);
546
- const originalOutput = deserializeOutput(spanData);
547
- let mockTree;
548
- if (mockStrategy === "all" || mockStrategy === "marked") {
549
- const treeResponse = await httpClient.getSpanTree(serverItem.externalSpanId);
550
- mockTree = buildMockTree(treeResponse.root);
551
- }
576
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment) {
577
+ const lease = environment ? serverItem.dbBranchLease : void 0;
578
+ let inputs = [];
579
+ let originalOutput;
552
580
  let result;
553
581
  let error = null;
554
582
  try {
583
+ const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
584
+ const spanData = span.rawData?.span_data ?? {};
585
+ inputs = deserializeInputs(spanData);
586
+ originalOutput = deserializeOutput(spanData);
587
+ let mockTree;
588
+ if (mockStrategy === "all" || mockStrategy === "marked") {
589
+ const treeResponse = await httpClient.getSpanTree(
590
+ serverItem.externalSpanId
591
+ );
592
+ mockTree = buildMockTree(treeResponse.root);
593
+ }
555
594
  const maybePromise = runWithReplayContext(
556
595
  {
557
596
  testRunId,
558
597
  inputSourceSpanId: span.id,
559
598
  inputSourceTraceId: span.externalTraceId,
599
+ sourceBitfabTraceId: serverItem.traceId,
560
600
  mockTree,
561
601
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
562
- mockStrategy
602
+ mockStrategy,
603
+ dbBranchLease: lease
563
604
  },
564
605
  () => fn(...inputs)
565
606
  );
566
607
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
567
608
  } catch (e) {
568
609
  error = e instanceof Error ? e.message : String(e);
610
+ } finally {
611
+ if (lease) {
612
+ try {
613
+ await httpClient.releaseDbBranchLease(lease.leaseId);
614
+ } catch (e) {
615
+ try {
616
+ console.warn(
617
+ `Bitfab: failed to release DB branch lease ${lease.leaseId} (TTL janitor will catch it): ${e instanceof Error ? e.message : String(e)}`
618
+ );
619
+ } catch {
620
+ }
621
+ }
622
+ }
569
623
  }
570
624
  return {
571
625
  input: inputs,
@@ -574,7 +628,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
574
628
  error,
575
629
  durationMs: serverItem.durationMs ?? null,
576
630
  tokens: serverItem.tokens ?? null,
577
- model: serverItem.model ?? null
631
+ model: serverItem.model ?? null,
632
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
578
633
  };
579
634
  }
580
635
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -604,12 +659,21 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
604
659
  options?.limit ?? 5,
605
660
  options?.traceIds,
606
661
  options?.codeChangeDescription,
607
- options?.codeChangeFiles
662
+ options?.codeChangeFiles,
663
+ options?.environment !== void 0
664
+ // includeDbBranchLease
608
665
  );
609
666
  const mockStrategy = options?.mock ?? "none";
610
667
  const maxConcurrency = options?.maxConcurrency ?? 10;
611
668
  const tasks = serverItems.map(
612
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
669
+ (serverItem) => () => processItem(
670
+ httpClient,
671
+ serverItem,
672
+ fn,
673
+ testRunId,
674
+ mockStrategy,
675
+ options?.environment
676
+ )
613
677
  );
614
678
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
615
679
  await flushTraces();
@@ -646,6 +710,8 @@ __export(node_exports, {
646
710
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
647
711
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
648
712
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
713
+ ReplayEnvironment: () => ReplayEnvironment,
714
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
649
715
  __version__: () => __version__,
650
716
  flushTraces: () => flushTraces,
651
717
  getCurrentSpan: () => getCurrentSpan,
@@ -1429,6 +1495,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1429
1495
 
1430
1496
  // src/client.ts
1431
1497
  init_constants();
1498
+
1499
+ // src/dbSnapshot.ts
1500
+ init_errors();
1501
+ var SUPPORTED_PROVIDERS = [
1502
+ "neon",
1503
+ "ardent",
1504
+ "dolt",
1505
+ "gfs",
1506
+ "mongodb-atlas",
1507
+ "dynamodb",
1508
+ "snowflake",
1509
+ "bigquery"
1510
+ ];
1511
+ function validateDbSnapshotConfig(config) {
1512
+ if (!SUPPORTED_PROVIDERS.includes(config.provider)) {
1513
+ throw new BitfabError(
1514
+ `dbSnapshot.provider "${config.provider}" is not supported. Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}.`
1515
+ );
1516
+ }
1517
+ }
1518
+ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1519
+ return {
1520
+ provider: config.provider,
1521
+ sdkWallClockBeforeFn
1522
+ };
1523
+ }
1524
+
1525
+ // src/client.ts
1432
1526
  init_http();
1433
1527
 
1434
1528
  // src/langgraph.ts
@@ -1909,6 +2003,84 @@ var BitfabLangGraphCallbackHandler = class {
1909
2003
 
1910
2004
  // src/client.ts
1911
2005
  init_replayContext();
2006
+
2007
+ // src/replayEnvironment.ts
2008
+ init_replayContext();
2009
+ var ReplayEnvironment = class {
2010
+ /**
2011
+ * The per-trace branch URL for the item currently being replayed.
2012
+ * Throws if read outside a replay item.
2013
+ */
2014
+ get databaseUrl() {
2015
+ return this.require().databaseUrl;
2016
+ }
2017
+ /** When the per-trace branch URL stops being valid. ISO-8601. */
2018
+ get expiresAt() {
2019
+ return this.require().expiresAt;
2020
+ }
2021
+ /** Deep link to the branch in the provider console, if available. */
2022
+ get providerConsoleUrl() {
2023
+ return this.require().providerConsoleUrl;
2024
+ }
2025
+ /**
2026
+ * True if the branch is read-only. Customer code can use this to skip
2027
+ * write operations during replay when the provider returned a read-only
2028
+ * lease.
2029
+ */
2030
+ get readOnly() {
2031
+ return this.require().readOnly;
2032
+ }
2033
+ /** The historical trace ID that produced the input for this replay item. */
2034
+ get traceId() {
2035
+ return this.require().traceId;
2036
+ }
2037
+ /**
2038
+ * How the resolver pinned this branch.
2039
+ * - "timestamp": snapshot at SDK wall clock; bounded by replication lag.
2040
+ * - "lsn": customer LSN mapped to a replica snapshot (future).
2041
+ */
2042
+ get precision() {
2043
+ return this.require().precision;
2044
+ }
2045
+ /** True when read inside a replay item that has a resolved branch. */
2046
+ get active() {
2047
+ return this.read() !== null;
2048
+ }
2049
+ /** Non-throwing variant for callers that handle the inactive case. */
2050
+ snapshot() {
2051
+ return this.read();
2052
+ }
2053
+ read() {
2054
+ const ctx = getReplayContext();
2055
+ if (!ctx?.dbBranchLease) {
2056
+ return null;
2057
+ }
2058
+ const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId;
2059
+ if (!traceId) {
2060
+ return null;
2061
+ }
2062
+ const lease = ctx.dbBranchLease;
2063
+ return {
2064
+ databaseUrl: lease.databaseUrl,
2065
+ expiresAt: lease.expiresAt,
2066
+ providerConsoleUrl: lease.providerConsoleUrl,
2067
+ readOnly: lease.readOnly,
2068
+ traceId,
2069
+ precision: lease.precision
2070
+ };
2071
+ }
2072
+ require() {
2073
+ const snapshot = this.read();
2074
+ if (!snapshot) {
2075
+ throw new Error(
2076
+ "ReplayEnvironment accessed outside of a replay item. Pass it to bitfab.replay({ environment }) and only read it inside the replayed function."
2077
+ );
2078
+ }
2079
+ return snapshot;
2080
+ }
2081
+ };
2082
+
2083
+ // src/client.ts
1912
2084
  init_serialize();
1913
2085
 
1914
2086
  // src/tracing.ts
@@ -2404,6 +2576,10 @@ var Bitfab = class {
2404
2576
  this.enabled = enabled;
2405
2577
  }
2406
2578
  this.bamlClient = config.bamlClient ?? null;
2579
+ if (config.dbSnapshot) {
2580
+ validateDbSnapshotConfig(config.dbSnapshot);
2581
+ }
2582
+ this.dbSnapshot = config.dbSnapshot;
2407
2583
  this.httpClient = new HttpClient({
2408
2584
  apiKey: this.apiKey,
2409
2585
  serviceUrl: this.serviceUrl,
@@ -2710,6 +2886,7 @@ var Bitfab = class {
2710
2886
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2711
2887
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2712
2888
  const replayCtxAtRoot = getReplayContext();
2889
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2713
2890
  activeTraceStates.set(traceId, {
2714
2891
  traceId,
2715
2892
  startedAt,
@@ -2719,7 +2896,8 @@ var Bitfab = class {
2719
2896
  },
2720
2897
  ...replayCtxAtRoot?.inputSourceTraceId && {
2721
2898
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2722
- }
2899
+ },
2900
+ ...dbSnapshotRef && { dbSnapshotRef }
2723
2901
  });
2724
2902
  pendingSpanPromises.set(traceId, []);
2725
2903
  }
@@ -2739,6 +2917,7 @@ var Bitfab = class {
2739
2917
  try {
2740
2918
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2741
2919
  const replayCtx = getReplayContext();
2920
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2742
2921
  const spanPromise = self.sendWrapperSpan({
2743
2922
  ...baseSpanParams,
2744
2923
  ...params,
@@ -2748,6 +2927,9 @@ var Bitfab = class {
2748
2927
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2749
2928
  ...replayCtx?.inputSourceSpanId && {
2750
2929
  inputSourceSpanId: replayCtx.inputSourceSpanId
2930
+ },
2931
+ ...dbSnapshotRefForSpan && {
2932
+ dbSnapshotRef: dbSnapshotRefForSpan
2751
2933
  }
2752
2934
  });
2753
2935
  if (isRootSpan) {
@@ -2768,7 +2950,8 @@ var Bitfab = class {
2768
2950
  metadata: traceState?.metadata,
2769
2951
  contexts: traceState?.contexts ?? [],
2770
2952
  testRunId: traceState?.testRunId,
2771
- inputSourceTraceId: traceState?.inputSourceTraceId
2953
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2954
+ dbSnapshotRef: traceState?.dbSnapshotRef
2772
2955
  });
2773
2956
  activeTraceStates.delete(traceId);
2774
2957
  } else {
@@ -2929,6 +3112,9 @@ var Bitfab = class {
2929
3112
  if (params.inputSourceTraceId) {
2930
3113
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2931
3114
  }
3115
+ if (params.dbSnapshotRef) {
3116
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3117
+ }
2932
3118
  this.httpClient.sendExternalTrace({
2933
3119
  type: "sdk-function",
2934
3120
  source: "typescript-sdk-function",
@@ -2971,7 +3157,10 @@ var Bitfab = class {
2971
3157
  ...params.contexts && params.contexts.length > 0 && {
2972
3158
  contexts: params.contexts
2973
3159
  },
2974
- ...params.prompt !== void 0 && { prompt: params.prompt }
3160
+ ...params.prompt !== void 0 && { prompt: params.prompt },
3161
+ ...params.dbSnapshotRef && {
3162
+ db_snapshot_ref: params.dbSnapshotRef
3163
+ }
2975
3164
  }
2976
3165
  };
2977
3166
  if (params.parentSpanId) {
@@ -3014,6 +3203,12 @@ var Bitfab = class {
3014
3203
  );
3015
3204
  }
3016
3205
  };
3206
+ /**
3207
+ * Per-trace environment for `replay({ environment })`. Construct one,
3208
+ * pass it to replay, and read `env.databaseUrl` inside the replayed
3209
+ * function to pick up the per-trace branch URL.
3210
+ */
3211
+ Bitfab.ReplayEnvironment = ReplayEnvironment;
3017
3212
  var BitfabFunction = class {
3018
3213
  constructor(client, traceFunctionKey) {
3019
3214
  this.client = client;
@@ -3079,6 +3274,8 @@ assertAsyncStorageRegistered();
3079
3274
  BitfabLangGraphCallbackHandler,
3080
3275
  BitfabOpenAITracingProcessor,
3081
3276
  DEFAULT_SERVICE_URL,
3277
+ ReplayEnvironment,
3278
+ SUPPORTED_PROVIDERS,
3082
3279
  __version__,
3083
3280
  flushTraces,
3084
3281
  getCurrentSpan,