bitfab 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/index.cjs CHANGED
@@ -1,3 +1,12 @@
1
+
2
+ if (typeof globalThis.__bitfab_deprecation_warned === "undefined") {
3
+ globalThis.__bitfab_deprecation_warned = true;
4
+ console.warn(
5
+ '[bitfab] The "bitfab" package has been renamed to "@bitfab/sdk". ' +
6
+ "Please update your dependency: npm install @bitfab/sdk (and remove bitfab). " +
7
+ "The bitfab package will stop receiving updates in a future release."
8
+ );
9
+ }
1
10
  "use strict";
2
11
  var __create = Object.create;
3
12
  var __defProp = Object.defineProperty;
@@ -35,7 +44,7 @@ var __version__;
35
44
  var init_version_generated = __esm({
36
45
  "src/version.generated.ts"() {
37
46
  "use strict";
38
- __version__ = "0.13.2";
47
+ __version__ = "0.13.4";
39
48
  }
40
49
  });
41
50
 
@@ -49,6 +58,21 @@ var init_constants = __esm({
49
58
  }
50
59
  });
51
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
+
52
76
  // src/http.ts
53
77
  function awaitOnExit(promise) {
54
78
  pendingTracePromises.add(promise);
@@ -67,18 +91,12 @@ async function flushTraces(timeoutMs = 5e3) {
67
91
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
68
92
  ]);
69
93
  }
70
- var BitfabError, pendingTracePromises, HttpClient;
94
+ var pendingTracePromises, HttpClient;
71
95
  var init_http = __esm({
72
96
  "src/http.ts"() {
73
97
  "use strict";
74
98
  init_constants();
75
- BitfabError = class extends Error {
76
- constructor(message, url) {
77
- super(message);
78
- this.url = url;
79
- this.name = "BitfabError";
80
- }
81
- };
99
+ init_errors();
82
100
  pendingTracePromises = /* @__PURE__ */ new Set();
83
101
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
84
102
  let isFlushing = false;
@@ -257,7 +275,7 @@ var init_http = __esm({
257
275
  * Start a replay session by fetching historical traces.
258
276
  * Blocking call — creates a test run and returns lightweight item references.
259
277
  */
260
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
278
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
261
279
  const payload = { traceFunctionKey, limit };
262
280
  if (traceIds) {
263
281
  payload.traceIds = traceIds;
@@ -268,8 +286,12 @@ var init_http = __esm({
268
286
  if (codeChangeFiles !== void 0) {
269
287
  payload.codeChangeFiles = codeChangeFiles;
270
288
  }
289
+ if (includeDbBranchLease) {
290
+ payload.includeDbBranchLease = true;
291
+ }
292
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
271
293
  return this.request("/api/sdk/replay/start", payload, {
272
- timeout: 3e4
294
+ timeout
273
295
  });
274
296
  }
275
297
  /**
@@ -355,6 +377,27 @@ var init_http = __esm({
355
377
  { timeout: 3e4 }
356
378
  );
357
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
+ }
358
401
  };
359
402
  }
360
403
  });
@@ -532,33 +575,53 @@ function buildMockTree(rootNode) {
532
575
  }
533
576
  return { spans };
534
577
  }
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
- }
578
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment) {
579
+ const lease = environment ? serverItem.dbBranchLease : void 0;
580
+ let inputs = [];
581
+ let originalOutput;
545
582
  let result;
546
583
  let error = null;
547
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
+ }
548
596
  const maybePromise = runWithReplayContext(
549
597
  {
550
598
  testRunId,
551
599
  inputSourceSpanId: span.id,
552
600
  inputSourceTraceId: span.externalTraceId,
601
+ sourceBitfabTraceId: serverItem.traceId,
553
602
  mockTree,
554
603
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
555
- mockStrategy
604
+ mockStrategy,
605
+ dbBranchLease: lease
556
606
  },
557
607
  () => fn(...inputs)
558
608
  );
559
609
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
560
610
  } catch (e) {
561
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
+ }
562
625
  }
563
626
  return {
564
627
  input: inputs,
@@ -567,7 +630,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
567
630
  error,
568
631
  durationMs: serverItem.durationMs ?? null,
569
632
  tokens: serverItem.tokens ?? null,
570
- model: serverItem.model ?? null
633
+ model: serverItem.model ?? null,
634
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
571
635
  };
572
636
  }
573
637
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -597,12 +661,21 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
597
661
  options?.limit ?? 5,
598
662
  options?.traceIds,
599
663
  options?.codeChangeDescription,
600
- options?.codeChangeFiles
664
+ options?.codeChangeFiles,
665
+ options?.environment !== void 0
666
+ // includeDbBranchLease
601
667
  );
602
668
  const mockStrategy = options?.mock ?? "none";
603
669
  const maxConcurrency = options?.maxConcurrency ?? 10;
604
670
  const tasks = serverItems.map(
605
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
671
+ (serverItem) => () => processItem(
672
+ httpClient,
673
+ serverItem,
674
+ fn,
675
+ testRunId,
676
+ mockStrategy,
677
+ options?.environment
678
+ )
606
679
  );
607
680
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
608
681
  await flushTraces();
@@ -639,6 +712,8 @@ __export(index_exports, {
639
712
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
640
713
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
641
714
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
715
+ ReplayEnvironment: () => ReplayEnvironment,
716
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
642
717
  __version__: () => __version__,
643
718
  flushTraces: () => flushTraces,
644
719
  getCurrentSpan: () => getCurrentSpan,
@@ -1415,6 +1490,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1415
1490
 
1416
1491
  // src/client.ts
1417
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
1418
1521
  init_http();
1419
1522
 
1420
1523
  // src/langgraph.ts
@@ -1895,6 +1998,84 @@ var BitfabLangGraphCallbackHandler = class {
1895
1998
 
1896
1999
  // src/client.ts
1897
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
1898
2079
  init_serialize();
1899
2080
 
1900
2081
  // src/tracing.ts
@@ -2390,6 +2571,10 @@ var Bitfab = class {
2390
2571
  this.enabled = enabled;
2391
2572
  }
2392
2573
  this.bamlClient = config.bamlClient ?? null;
2574
+ if (config.dbSnapshot) {
2575
+ validateDbSnapshotConfig(config.dbSnapshot);
2576
+ }
2577
+ this.dbSnapshot = config.dbSnapshot;
2393
2578
  this.httpClient = new HttpClient({
2394
2579
  apiKey: this.apiKey,
2395
2580
  serviceUrl: this.serviceUrl,
@@ -2696,6 +2881,7 @@ var Bitfab = class {
2696
2881
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2697
2882
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2698
2883
  const replayCtxAtRoot = getReplayContext();
2884
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2699
2885
  activeTraceStates.set(traceId, {
2700
2886
  traceId,
2701
2887
  startedAt,
@@ -2705,7 +2891,8 @@ var Bitfab = class {
2705
2891
  },
2706
2892
  ...replayCtxAtRoot?.inputSourceTraceId && {
2707
2893
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2708
- }
2894
+ },
2895
+ ...dbSnapshotRef && { dbSnapshotRef }
2709
2896
  });
2710
2897
  pendingSpanPromises.set(traceId, []);
2711
2898
  }
@@ -2725,6 +2912,7 @@ var Bitfab = class {
2725
2912
  try {
2726
2913
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2727
2914
  const replayCtx = getReplayContext();
2915
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2728
2916
  const spanPromise = self.sendWrapperSpan({
2729
2917
  ...baseSpanParams,
2730
2918
  ...params,
@@ -2734,6 +2922,9 @@ var Bitfab = class {
2734
2922
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2735
2923
  ...replayCtx?.inputSourceSpanId && {
2736
2924
  inputSourceSpanId: replayCtx.inputSourceSpanId
2925
+ },
2926
+ ...dbSnapshotRefForSpan && {
2927
+ dbSnapshotRef: dbSnapshotRefForSpan
2737
2928
  }
2738
2929
  });
2739
2930
  if (isRootSpan) {
@@ -2754,7 +2945,8 @@ var Bitfab = class {
2754
2945
  metadata: traceState?.metadata,
2755
2946
  contexts: traceState?.contexts ?? [],
2756
2947
  testRunId: traceState?.testRunId,
2757
- inputSourceTraceId: traceState?.inputSourceTraceId
2948
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2949
+ dbSnapshotRef: traceState?.dbSnapshotRef
2758
2950
  });
2759
2951
  activeTraceStates.delete(traceId);
2760
2952
  } else {
@@ -2915,6 +3107,9 @@ var Bitfab = class {
2915
3107
  if (params.inputSourceTraceId) {
2916
3108
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2917
3109
  }
3110
+ if (params.dbSnapshotRef) {
3111
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3112
+ }
2918
3113
  this.httpClient.sendExternalTrace({
2919
3114
  type: "sdk-function",
2920
3115
  source: "typescript-sdk-function",
@@ -2957,7 +3152,10 @@ var Bitfab = class {
2957
3152
  ...params.contexts && params.contexts.length > 0 && {
2958
3153
  contexts: params.contexts
2959
3154
  },
2960
- ...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
+ }
2961
3159
  }
2962
3160
  };
2963
3161
  if (params.parentSpanId) {
@@ -3000,6 +3198,12 @@ var Bitfab = class {
3000
3198
  );
3001
3199
  }
3002
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;
3003
3207
  var BitfabFunction = class {
3004
3208
  constructor(client, traceFunctionKey) {
3005
3209
  this.client = client;
@@ -3061,6 +3265,8 @@ init_http();
3061
3265
  BitfabLangGraphCallbackHandler,
3062
3266
  BitfabOpenAITracingProcessor,
3063
3267
  DEFAULT_SERVICE_URL,
3268
+ ReplayEnvironment,
3269
+ SUPPORTED_PROVIDERS,
3064
3270
  __version__,
3065
3271
  flushTraces,
3066
3272
  getCurrentSpan,