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/node.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;
@@ -81,7 +90,7 @@ var __version__;
81
90
  var init_version_generated = __esm({
82
91
  "src/version.generated.ts"() {
83
92
  "use strict";
84
- __version__ = "0.13.2";
93
+ __version__ = "0.13.4";
85
94
  }
86
95
  });
87
96
 
@@ -95,6 +104,21 @@ var init_constants = __esm({
95
104
  }
96
105
  });
97
106
 
107
+ // src/errors.ts
108
+ var BitfabError;
109
+ var init_errors = __esm({
110
+ "src/errors.ts"() {
111
+ "use strict";
112
+ BitfabError = class extends Error {
113
+ constructor(message, url) {
114
+ super(message);
115
+ this.url = url;
116
+ this.name = "BitfabError";
117
+ }
118
+ };
119
+ }
120
+ });
121
+
98
122
  // src/http.ts
99
123
  function awaitOnExit(promise) {
100
124
  pendingTracePromises.add(promise);
@@ -113,18 +137,12 @@ async function flushTraces(timeoutMs = 5e3) {
113
137
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
114
138
  ]);
115
139
  }
116
- var BitfabError, pendingTracePromises, HttpClient;
140
+ var pendingTracePromises, HttpClient;
117
141
  var init_http = __esm({
118
142
  "src/http.ts"() {
119
143
  "use strict";
120
144
  init_constants();
121
- BitfabError = class extends Error {
122
- constructor(message, url) {
123
- super(message);
124
- this.url = url;
125
- this.name = "BitfabError";
126
- }
127
- };
145
+ init_errors();
128
146
  pendingTracePromises = /* @__PURE__ */ new Set();
129
147
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
130
148
  let isFlushing = false;
@@ -303,7 +321,7 @@ var init_http = __esm({
303
321
  * Start a replay session by fetching historical traces.
304
322
  * Blocking call — creates a test run and returns lightweight item references.
305
323
  */
306
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
324
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
307
325
  const payload = { traceFunctionKey, limit };
308
326
  if (traceIds) {
309
327
  payload.traceIds = traceIds;
@@ -314,8 +332,12 @@ var init_http = __esm({
314
332
  if (codeChangeFiles !== void 0) {
315
333
  payload.codeChangeFiles = codeChangeFiles;
316
334
  }
335
+ if (includeDbBranchLease) {
336
+ payload.includeDbBranchLease = true;
337
+ }
338
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
317
339
  return this.request("/api/sdk/replay/start", payload, {
318
- timeout: 3e4
340
+ timeout
319
341
  });
320
342
  }
321
343
  /**
@@ -401,6 +423,27 @@ var init_http = __esm({
401
423
  { timeout: 3e4 }
402
424
  );
403
425
  }
426
+ /**
427
+ * Ask the server to materialize a per-trace DB branch lease from a
428
+ * captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
429
+ * snapshot + preview branch and polls operations to readiness, which
430
+ * can take seconds.
431
+ */
432
+ async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
433
+ return this.request(
434
+ "/api/sdk/replay/resolveDbBranchLease",
435
+ { testRunId, traceId, dbSnapshotRef },
436
+ { timeout: 9e4 }
437
+ );
438
+ }
439
+ /** Release a previously-resolved DB branch lease. Idempotent server-side. */
440
+ async releaseDbBranchLease(leaseId) {
441
+ await this.request(
442
+ "/api/sdk/replay/releaseDbBranchLease",
443
+ { leaseId },
444
+ { timeout: 3e4 }
445
+ );
446
+ }
404
447
  };
405
448
  }
406
449
  });
@@ -539,33 +582,53 @@ function buildMockTree(rootNode) {
539
582
  }
540
583
  return { spans };
541
584
  }
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
- }
585
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment) {
586
+ const lease = environment ? serverItem.dbBranchLease : void 0;
587
+ let inputs = [];
588
+ let originalOutput;
552
589
  let result;
553
590
  let error = null;
554
591
  try {
592
+ const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
593
+ const spanData = span.rawData?.span_data ?? {};
594
+ inputs = deserializeInputs(spanData);
595
+ originalOutput = deserializeOutput(spanData);
596
+ let mockTree;
597
+ if (mockStrategy === "all" || mockStrategy === "marked") {
598
+ const treeResponse = await httpClient.getSpanTree(
599
+ serverItem.externalSpanId
600
+ );
601
+ mockTree = buildMockTree(treeResponse.root);
602
+ }
555
603
  const maybePromise = runWithReplayContext(
556
604
  {
557
605
  testRunId,
558
606
  inputSourceSpanId: span.id,
559
607
  inputSourceTraceId: span.externalTraceId,
608
+ sourceBitfabTraceId: serverItem.traceId,
560
609
  mockTree,
561
610
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
562
- mockStrategy
611
+ mockStrategy,
612
+ dbBranchLease: lease
563
613
  },
564
614
  () => fn(...inputs)
565
615
  );
566
616
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
567
617
  } catch (e) {
568
618
  error = e instanceof Error ? e.message : String(e);
619
+ } finally {
620
+ if (lease) {
621
+ try {
622
+ await httpClient.releaseDbBranchLease(lease.leaseId);
623
+ } catch (e) {
624
+ try {
625
+ console.warn(
626
+ `Bitfab: failed to release DB branch lease ${lease.leaseId} (TTL janitor will catch it): ${e instanceof Error ? e.message : String(e)}`
627
+ );
628
+ } catch {
629
+ }
630
+ }
631
+ }
569
632
  }
570
633
  return {
571
634
  input: inputs,
@@ -574,7 +637,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
574
637
  error,
575
638
  durationMs: serverItem.durationMs ?? null,
576
639
  tokens: serverItem.tokens ?? null,
577
- model: serverItem.model ?? null
640
+ model: serverItem.model ?? null,
641
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
578
642
  };
579
643
  }
580
644
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -604,12 +668,21 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
604
668
  options?.limit ?? 5,
605
669
  options?.traceIds,
606
670
  options?.codeChangeDescription,
607
- options?.codeChangeFiles
671
+ options?.codeChangeFiles,
672
+ options?.environment !== void 0
673
+ // includeDbBranchLease
608
674
  );
609
675
  const mockStrategy = options?.mock ?? "none";
610
676
  const maxConcurrency = options?.maxConcurrency ?? 10;
611
677
  const tasks = serverItems.map(
612
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
678
+ (serverItem) => () => processItem(
679
+ httpClient,
680
+ serverItem,
681
+ fn,
682
+ testRunId,
683
+ mockStrategy,
684
+ options?.environment
685
+ )
613
686
  );
614
687
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
615
688
  await flushTraces();
@@ -646,6 +719,8 @@ __export(node_exports, {
646
719
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
647
720
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
648
721
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
722
+ ReplayEnvironment: () => ReplayEnvironment,
723
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
649
724
  __version__: () => __version__,
650
725
  flushTraces: () => flushTraces,
651
726
  getCurrentSpan: () => getCurrentSpan,
@@ -1429,6 +1504,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1429
1504
 
1430
1505
  // src/client.ts
1431
1506
  init_constants();
1507
+
1508
+ // src/dbSnapshot.ts
1509
+ init_errors();
1510
+ var SUPPORTED_PROVIDERS = [
1511
+ "neon",
1512
+ "ardent",
1513
+ "dolt",
1514
+ "gfs",
1515
+ "mongodb-atlas",
1516
+ "dynamodb",
1517
+ "snowflake",
1518
+ "bigquery"
1519
+ ];
1520
+ function validateDbSnapshotConfig(config) {
1521
+ if (!SUPPORTED_PROVIDERS.includes(config.provider)) {
1522
+ throw new BitfabError(
1523
+ `dbSnapshot.provider "${config.provider}" is not supported. Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}.`
1524
+ );
1525
+ }
1526
+ }
1527
+ function buildSnapshotRef(config, sdkWallClockBeforeFn) {
1528
+ return {
1529
+ provider: config.provider,
1530
+ sdkWallClockBeforeFn
1531
+ };
1532
+ }
1533
+
1534
+ // src/client.ts
1432
1535
  init_http();
1433
1536
 
1434
1537
  // src/langgraph.ts
@@ -1909,6 +2012,84 @@ var BitfabLangGraphCallbackHandler = class {
1909
2012
 
1910
2013
  // src/client.ts
1911
2014
  init_replayContext();
2015
+
2016
+ // src/replayEnvironment.ts
2017
+ init_replayContext();
2018
+ var ReplayEnvironment = class {
2019
+ /**
2020
+ * The per-trace branch URL for the item currently being replayed.
2021
+ * Throws if read outside a replay item.
2022
+ */
2023
+ get databaseUrl() {
2024
+ return this.require().databaseUrl;
2025
+ }
2026
+ /** When the per-trace branch URL stops being valid. ISO-8601. */
2027
+ get expiresAt() {
2028
+ return this.require().expiresAt;
2029
+ }
2030
+ /** Deep link to the branch in the provider console, if available. */
2031
+ get providerConsoleUrl() {
2032
+ return this.require().providerConsoleUrl;
2033
+ }
2034
+ /**
2035
+ * True if the branch is read-only. Customer code can use this to skip
2036
+ * write operations during replay when the provider returned a read-only
2037
+ * lease.
2038
+ */
2039
+ get readOnly() {
2040
+ return this.require().readOnly;
2041
+ }
2042
+ /** The historical trace ID that produced the input for this replay item. */
2043
+ get traceId() {
2044
+ return this.require().traceId;
2045
+ }
2046
+ /**
2047
+ * How the resolver pinned this branch.
2048
+ * - "timestamp": snapshot at SDK wall clock; bounded by replication lag.
2049
+ * - "lsn": customer LSN mapped to a replica snapshot (future).
2050
+ */
2051
+ get precision() {
2052
+ return this.require().precision;
2053
+ }
2054
+ /** True when read inside a replay item that has a resolved branch. */
2055
+ get active() {
2056
+ return this.read() !== null;
2057
+ }
2058
+ /** Non-throwing variant for callers that handle the inactive case. */
2059
+ snapshot() {
2060
+ return this.read();
2061
+ }
2062
+ read() {
2063
+ const ctx = getReplayContext();
2064
+ if (!ctx?.dbBranchLease) {
2065
+ return null;
2066
+ }
2067
+ const traceId = ctx.sourceBitfabTraceId ?? ctx.inputSourceTraceId;
2068
+ if (!traceId) {
2069
+ return null;
2070
+ }
2071
+ const lease = ctx.dbBranchLease;
2072
+ return {
2073
+ databaseUrl: lease.databaseUrl,
2074
+ expiresAt: lease.expiresAt,
2075
+ providerConsoleUrl: lease.providerConsoleUrl,
2076
+ readOnly: lease.readOnly,
2077
+ traceId,
2078
+ precision: lease.precision
2079
+ };
2080
+ }
2081
+ require() {
2082
+ const snapshot = this.read();
2083
+ if (!snapshot) {
2084
+ throw new Error(
2085
+ "ReplayEnvironment accessed outside of a replay item. Pass it to bitfab.replay({ environment }) and only read it inside the replayed function."
2086
+ );
2087
+ }
2088
+ return snapshot;
2089
+ }
2090
+ };
2091
+
2092
+ // src/client.ts
1912
2093
  init_serialize();
1913
2094
 
1914
2095
  // src/tracing.ts
@@ -2404,6 +2585,10 @@ var Bitfab = class {
2404
2585
  this.enabled = enabled;
2405
2586
  }
2406
2587
  this.bamlClient = config.bamlClient ?? null;
2588
+ if (config.dbSnapshot) {
2589
+ validateDbSnapshotConfig(config.dbSnapshot);
2590
+ }
2591
+ this.dbSnapshot = config.dbSnapshot;
2407
2592
  this.httpClient = new HttpClient({
2408
2593
  apiKey: this.apiKey,
2409
2594
  serviceUrl: this.serviceUrl,
@@ -2710,6 +2895,7 @@ var Bitfab = class {
2710
2895
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2711
2896
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2712
2897
  const replayCtxAtRoot = getReplayContext();
2898
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2713
2899
  activeTraceStates.set(traceId, {
2714
2900
  traceId,
2715
2901
  startedAt,
@@ -2719,7 +2905,8 @@ var Bitfab = class {
2719
2905
  },
2720
2906
  ...replayCtxAtRoot?.inputSourceTraceId && {
2721
2907
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2722
- }
2908
+ },
2909
+ ...dbSnapshotRef && { dbSnapshotRef }
2723
2910
  });
2724
2911
  pendingSpanPromises.set(traceId, []);
2725
2912
  }
@@ -2739,6 +2926,7 @@ var Bitfab = class {
2739
2926
  try {
2740
2927
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2741
2928
  const replayCtx = getReplayContext();
2929
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2742
2930
  const spanPromise = self.sendWrapperSpan({
2743
2931
  ...baseSpanParams,
2744
2932
  ...params,
@@ -2748,6 +2936,9 @@ var Bitfab = class {
2748
2936
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2749
2937
  ...replayCtx?.inputSourceSpanId && {
2750
2938
  inputSourceSpanId: replayCtx.inputSourceSpanId
2939
+ },
2940
+ ...dbSnapshotRefForSpan && {
2941
+ dbSnapshotRef: dbSnapshotRefForSpan
2751
2942
  }
2752
2943
  });
2753
2944
  if (isRootSpan) {
@@ -2768,7 +2959,8 @@ var Bitfab = class {
2768
2959
  metadata: traceState?.metadata,
2769
2960
  contexts: traceState?.contexts ?? [],
2770
2961
  testRunId: traceState?.testRunId,
2771
- inputSourceTraceId: traceState?.inputSourceTraceId
2962
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2963
+ dbSnapshotRef: traceState?.dbSnapshotRef
2772
2964
  });
2773
2965
  activeTraceStates.delete(traceId);
2774
2966
  } else {
@@ -2929,6 +3121,9 @@ var Bitfab = class {
2929
3121
  if (params.inputSourceTraceId) {
2930
3122
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2931
3123
  }
3124
+ if (params.dbSnapshotRef) {
3125
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3126
+ }
2932
3127
  this.httpClient.sendExternalTrace({
2933
3128
  type: "sdk-function",
2934
3129
  source: "typescript-sdk-function",
@@ -2971,7 +3166,10 @@ var Bitfab = class {
2971
3166
  ...params.contexts && params.contexts.length > 0 && {
2972
3167
  contexts: params.contexts
2973
3168
  },
2974
- ...params.prompt !== void 0 && { prompt: params.prompt }
3169
+ ...params.prompt !== void 0 && { prompt: params.prompt },
3170
+ ...params.dbSnapshotRef && {
3171
+ db_snapshot_ref: params.dbSnapshotRef
3172
+ }
2975
3173
  }
2976
3174
  };
2977
3175
  if (params.parentSpanId) {
@@ -3014,6 +3212,12 @@ var Bitfab = class {
3014
3212
  );
3015
3213
  }
3016
3214
  };
3215
+ /**
3216
+ * Per-trace environment for `replay({ environment })`. Construct one,
3217
+ * pass it to replay, and read `env.databaseUrl` inside the replayed
3218
+ * function to pick up the per-trace branch URL.
3219
+ */
3220
+ Bitfab.ReplayEnvironment = ReplayEnvironment;
3017
3221
  var BitfabFunction = class {
3018
3222
  constructor(client, traceFunctionKey) {
3019
3223
  this.client = client;
@@ -3079,6 +3283,8 @@ assertAsyncStorageRegistered();
3079
3283
  BitfabLangGraphCallbackHandler,
3080
3284
  BitfabOpenAITracingProcessor,
3081
3285
  DEFAULT_SERVICE_URL,
3286
+ ReplayEnvironment,
3287
+ SUPPORTED_PROVIDERS,
3082
3288
  __version__,
3083
3289
  flushTraces,
3084
3290
  getCurrentSpan,