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/node.cjs CHANGED
@@ -90,7 +90,7 @@ var __version__;
90
90
  var init_version_generated = __esm({
91
91
  "src/version.generated.ts"() {
92
92
  "use strict";
93
- __version__ = "0.13.3";
93
+ __version__ = "0.13.4";
94
94
  }
95
95
  });
96
96
 
@@ -104,6 +104,21 @@ var init_constants = __esm({
104
104
  }
105
105
  });
106
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
+
107
122
  // src/http.ts
108
123
  function awaitOnExit(promise) {
109
124
  pendingTracePromises.add(promise);
@@ -122,18 +137,12 @@ async function flushTraces(timeoutMs = 5e3) {
122
137
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
123
138
  ]);
124
139
  }
125
- var BitfabError, pendingTracePromises, HttpClient;
140
+ var pendingTracePromises, HttpClient;
126
141
  var init_http = __esm({
127
142
  "src/http.ts"() {
128
143
  "use strict";
129
144
  init_constants();
130
- BitfabError = class extends Error {
131
- constructor(message, url) {
132
- super(message);
133
- this.url = url;
134
- this.name = "BitfabError";
135
- }
136
- };
145
+ init_errors();
137
146
  pendingTracePromises = /* @__PURE__ */ new Set();
138
147
  if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
139
148
  let isFlushing = false;
@@ -312,7 +321,7 @@ var init_http = __esm({
312
321
  * Start a replay session by fetching historical traces.
313
322
  * Blocking call — creates a test run and returns lightweight item references.
314
323
  */
315
- async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles) {
324
+ async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease) {
316
325
  const payload = { traceFunctionKey, limit };
317
326
  if (traceIds) {
318
327
  payload.traceIds = traceIds;
@@ -323,8 +332,12 @@ var init_http = __esm({
323
332
  if (codeChangeFiles !== void 0) {
324
333
  payload.codeChangeFiles = codeChangeFiles;
325
334
  }
335
+ if (includeDbBranchLease) {
336
+ payload.includeDbBranchLease = true;
337
+ }
338
+ const timeout = includeDbBranchLease ? 18e4 : 3e4;
326
339
  return this.request("/api/sdk/replay/start", payload, {
327
- timeout: 3e4
340
+ timeout
328
341
  });
329
342
  }
330
343
  /**
@@ -410,6 +423,27 @@ var init_http = __esm({
410
423
  { timeout: 3e4 }
411
424
  );
412
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
+ }
413
447
  };
414
448
  }
415
449
  });
@@ -548,33 +582,53 @@ function buildMockTree(rootNode) {
548
582
  }
549
583
  return { spans };
550
584
  }
551
- async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy) {
552
- const span = await httpClient.getExternalSpan(serverItem.externalSpanId);
553
- const spanData = span.rawData?.span_data ?? {};
554
- const inputs = deserializeInputs(spanData);
555
- const originalOutput = deserializeOutput(spanData);
556
- let mockTree;
557
- if (mockStrategy === "all" || mockStrategy === "marked") {
558
- const treeResponse = await httpClient.getSpanTree(serverItem.externalSpanId);
559
- mockTree = buildMockTree(treeResponse.root);
560
- }
585
+ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy, environment) {
586
+ const lease = environment ? serverItem.dbBranchLease : void 0;
587
+ let inputs = [];
588
+ let originalOutput;
561
589
  let result;
562
590
  let error = null;
563
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
+ }
564
603
  const maybePromise = runWithReplayContext(
565
604
  {
566
605
  testRunId,
567
606
  inputSourceSpanId: span.id,
568
607
  inputSourceTraceId: span.externalTraceId,
608
+ sourceBitfabTraceId: serverItem.traceId,
569
609
  mockTree,
570
610
  callCounters: mockTree ? /* @__PURE__ */ new Map() : void 0,
571
- mockStrategy
611
+ mockStrategy,
612
+ dbBranchLease: lease
572
613
  },
573
614
  () => fn(...inputs)
574
615
  );
575
616
  result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
576
617
  } catch (e) {
577
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
+ }
578
632
  }
579
633
  return {
580
634
  input: inputs,
@@ -583,7 +637,8 @@ async function processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
583
637
  error,
584
638
  durationMs: serverItem.durationMs ?? null,
585
639
  tokens: serverItem.tokens ?? null,
586
- model: serverItem.model ?? null
640
+ model: serverItem.model ?? null,
641
+ dbSnapshotRef: serverItem.dbSnapshotRef ?? null
587
642
  };
588
643
  }
589
644
  async function mapWithConcurrency(tasks, maxConcurrency) {
@@ -613,12 +668,21 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options) {
613
668
  options?.limit ?? 5,
614
669
  options?.traceIds,
615
670
  options?.codeChangeDescription,
616
- options?.codeChangeFiles
671
+ options?.codeChangeFiles,
672
+ options?.environment !== void 0
673
+ // includeDbBranchLease
617
674
  );
618
675
  const mockStrategy = options?.mock ?? "none";
619
676
  const maxConcurrency = options?.maxConcurrency ?? 10;
620
677
  const tasks = serverItems.map(
621
- (serverItem) => () => processItem(httpClient, serverItem, fn, testRunId, mockStrategy)
678
+ (serverItem) => () => processItem(
679
+ httpClient,
680
+ serverItem,
681
+ fn,
682
+ testRunId,
683
+ mockStrategy,
684
+ options?.environment
685
+ )
622
686
  );
623
687
  const resultItems = await mapWithConcurrency(tasks, maxConcurrency);
624
688
  await flushTraces();
@@ -655,6 +719,8 @@ __export(node_exports, {
655
719
  BitfabLangGraphCallbackHandler: () => BitfabLangGraphCallbackHandler,
656
720
  BitfabOpenAITracingProcessor: () => BitfabOpenAITracingProcessor,
657
721
  DEFAULT_SERVICE_URL: () => DEFAULT_SERVICE_URL,
722
+ ReplayEnvironment: () => ReplayEnvironment,
723
+ SUPPORTED_PROVIDERS: () => SUPPORTED_PROVIDERS,
658
724
  __version__: () => __version__,
659
725
  flushTraces: () => flushTraces,
660
726
  getCurrentSpan: () => getCurrentSpan,
@@ -1438,6 +1504,34 @@ async function runFunctionWithBaml(bamlSource, inputs, providers, envVars) {
1438
1504
 
1439
1505
  // src/client.ts
1440
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
1441
1535
  init_http();
1442
1536
 
1443
1537
  // src/langgraph.ts
@@ -1918,6 +2012,84 @@ var BitfabLangGraphCallbackHandler = class {
1918
2012
 
1919
2013
  // src/client.ts
1920
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
1921
2093
  init_serialize();
1922
2094
 
1923
2095
  // src/tracing.ts
@@ -2413,6 +2585,10 @@ var Bitfab = class {
2413
2585
  this.enabled = enabled;
2414
2586
  }
2415
2587
  this.bamlClient = config.bamlClient ?? null;
2588
+ if (config.dbSnapshot) {
2589
+ validateDbSnapshotConfig(config.dbSnapshot);
2590
+ }
2591
+ this.dbSnapshot = config.dbSnapshot;
2416
2592
  this.httpClient = new HttpClient({
2417
2593
  apiKey: this.apiKey,
2418
2594
  serviceUrl: this.serviceUrl,
@@ -2719,6 +2895,7 @@ var Bitfab = class {
2719
2895
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2720
2896
  if (isRootSpan && !activeTraceStates.has(traceId)) {
2721
2897
  const replayCtxAtRoot = getReplayContext();
2898
+ const dbSnapshotRef = self.dbSnapshot ? buildSnapshotRef(self.dbSnapshot, startedAt) : void 0;
2722
2899
  activeTraceStates.set(traceId, {
2723
2900
  traceId,
2724
2901
  startedAt,
@@ -2728,7 +2905,8 @@ var Bitfab = class {
2728
2905
  },
2729
2906
  ...replayCtxAtRoot?.inputSourceTraceId && {
2730
2907
  inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
2731
- }
2908
+ },
2909
+ ...dbSnapshotRef && { dbSnapshotRef }
2732
2910
  });
2733
2911
  pendingSpanPromises.set(traceId, []);
2734
2912
  }
@@ -2748,6 +2926,7 @@ var Bitfab = class {
2748
2926
  try {
2749
2927
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2750
2928
  const replayCtx = getReplayContext();
2929
+ const dbSnapshotRefForSpan = isRootSpan ? activeTraceStates.get(traceId)?.dbSnapshotRef : void 0;
2751
2930
  const spanPromise = self.sendWrapperSpan({
2752
2931
  ...baseSpanParams,
2753
2932
  ...params,
@@ -2757,6 +2936,9 @@ var Bitfab = class {
2757
2936
  ...replayCtx?.testRunId && { testRunId: replayCtx.testRunId },
2758
2937
  ...replayCtx?.inputSourceSpanId && {
2759
2938
  inputSourceSpanId: replayCtx.inputSourceSpanId
2939
+ },
2940
+ ...dbSnapshotRefForSpan && {
2941
+ dbSnapshotRef: dbSnapshotRefForSpan
2760
2942
  }
2761
2943
  });
2762
2944
  if (isRootSpan) {
@@ -2777,7 +2959,8 @@ var Bitfab = class {
2777
2959
  metadata: traceState?.metadata,
2778
2960
  contexts: traceState?.contexts ?? [],
2779
2961
  testRunId: traceState?.testRunId,
2780
- inputSourceTraceId: traceState?.inputSourceTraceId
2962
+ inputSourceTraceId: traceState?.inputSourceTraceId,
2963
+ dbSnapshotRef: traceState?.dbSnapshotRef
2781
2964
  });
2782
2965
  activeTraceStates.delete(traceId);
2783
2966
  } else {
@@ -2938,6 +3121,9 @@ var Bitfab = class {
2938
3121
  if (params.inputSourceTraceId) {
2939
3122
  rawTrace.input_source_trace_id = params.inputSourceTraceId;
2940
3123
  }
3124
+ if (params.dbSnapshotRef) {
3125
+ rawTrace.db_snapshot_ref = params.dbSnapshotRef;
3126
+ }
2941
3127
  this.httpClient.sendExternalTrace({
2942
3128
  type: "sdk-function",
2943
3129
  source: "typescript-sdk-function",
@@ -2980,7 +3166,10 @@ var Bitfab = class {
2980
3166
  ...params.contexts && params.contexts.length > 0 && {
2981
3167
  contexts: params.contexts
2982
3168
  },
2983
- ...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
+ }
2984
3173
  }
2985
3174
  };
2986
3175
  if (params.parentSpanId) {
@@ -3023,6 +3212,12 @@ var Bitfab = class {
3023
3212
  );
3024
3213
  }
3025
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;
3026
3221
  var BitfabFunction = class {
3027
3222
  constructor(client, traceFunctionKey) {
3028
3223
  this.client = client;
@@ -3088,6 +3283,8 @@ assertAsyncStorageRegistered();
3088
3283
  BitfabLangGraphCallbackHandler,
3089
3284
  BitfabOpenAITracingProcessor,
3090
3285
  DEFAULT_SERVICE_URL,
3286
+ ReplayEnvironment,
3287
+ SUPPORTED_PROVIDERS,
3091
3288
  __version__,
3092
3289
  flushTraces,
3093
3290
  getCurrentSpan,