@bitfab/sdk 0.28.11 → 0.29.1

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.
@@ -6,14 +6,15 @@ import {
6
6
  getReplayContext,
7
7
  isAsyncStorageInitDone,
8
8
  randomUuid,
9
+ resolveMockValue,
9
10
  serializeValue,
10
11
  toJsonSafe,
11
12
  toJsonSafeReport,
12
13
  warnOnce
13
- } from "./chunk-5E4BUIYA.js";
14
+ } from "./chunk-YZU6WFG2.js";
14
15
 
15
16
  // src/version.generated.ts
16
- var __version__ = "0.28.11";
17
+ var __version__ = "0.29.1";
17
18
 
18
19
  // src/constants.ts
19
20
  var DEFAULT_SERVICE_URL = "https://bitfab.ai";
@@ -438,9 +439,14 @@ var HttpClient = class {
438
439
  /**
439
440
  * Fetch the span tree for a root span.
440
441
  * Blocking GET request.
442
+ *
443
+ * Pass `includeOutputs: false` for a payload-free tree (structure +
444
+ * `externalSpanId` only), so recorded outputs are fetched lazily per mocked
445
+ * span instead of all up front. Omit it (default eager) for `mock: "all"`.
441
446
  */
442
- async getSpanTree(externalSpanId) {
443
- const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`;
447
+ async getSpanTree(externalSpanId, options) {
448
+ const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
449
+ const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
444
450
  const controller = new AbortController();
445
451
  const timeoutId = setTimeout(() => controller.abort(), 3e4);
446
452
  try {
@@ -2821,6 +2827,12 @@ var Bitfab = class {
2821
2827
  constructor(config) {
2822
2828
  /** Gate the empty-key warning to fire at most once. */
2823
2829
  this.apiKeyWarned = false;
2830
+ /**
2831
+ * Mock overrides registered via {@link Bitfab.registerMockOverride}, applied
2832
+ * to every `replay` on this client (after any per-call `mockOverride`). In
2833
+ * registration order; first matcher wins within this list.
2834
+ */
2835
+ this.mockOverrides = [];
2824
2836
  this.apiKeyConfig = config.apiKey;
2825
2837
  this.serviceUrl = config.serviceUrl ?? DEFAULT_SERVICE_URL;
2826
2838
  this.timeout = config.timeout ?? 12e4;
@@ -3405,7 +3417,7 @@ var Bitfab = class {
3405
3417
  dbSnapshotUsage: {
3406
3418
  neonBranchId: replayCtx.dbBranchLease.neonBranchId,
3407
3419
  snapshotTimestamp: replayCtx.dbBranchLease.snapshotTimestamp,
3408
- sourceTraceId: replayCtx.sourceBitfabTraceId,
3420
+ originalTraceId: replayCtx.sourceBitfabTraceId,
3409
3421
  accessed: replayCtx.dbSnapshotAccessed === true
3410
3422
  }
3411
3423
  }
@@ -3433,25 +3445,78 @@ var Bitfab = class {
3433
3445
  const counterKey = `${traceFunctionKey}:${baseSpanParams.spanName}`;
3434
3446
  const callIndex = counters.get(counterKey) ?? 0;
3435
3447
  counters.set(counterKey, callIndex + 1);
3436
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3437
- if (shouldMock) {
3438
- const mockKey = `${counterKey}:${callIndex}`;
3439
- const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3440
- if (mockSpan) {
3441
- let output = mockSpan.output;
3442
- if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3443
- output = deserializeValue({
3444
- json: mockSpan.output,
3445
- meta: mockSpan.outputMeta
3446
- });
3447
- }
3448
+ const mockKey = `${counterKey}:${callIndex}`;
3449
+ const mockSpan = replayCtxForMock.mockTree.spans.get(mockKey);
3450
+ const emitMock = (output) => {
3451
+ void sendSpan({ result: output, mocked: true });
3452
+ if (fnReturnsPromise) {
3453
+ return Promise.resolve(output);
3454
+ }
3455
+ return output;
3456
+ };
3457
+ const emitMockAsync = (pending) => {
3458
+ if (!fnReturnsPromise) {
3459
+ throw new BitfabError(
3460
+ `Cannot mock synchronous span "${traceFunctionKey}" with an asynchronously-resolved value (lazy recorded-output fetch or an async value function). Make the wrapped function async, or use mock: "all" so recorded outputs are fetched eagerly.`
3461
+ );
3462
+ }
3463
+ return (async () => {
3464
+ const output = await pending;
3448
3465
  void sendSpan({ result: output, mocked: true });
3449
- if (fnReturnsPromise) {
3450
- return Promise.resolve(output);
3451
- }
3452
3466
  return output;
3467
+ })();
3468
+ };
3469
+ const resolveRecordedOutput = () => {
3470
+ const hasInlineOutput = mockSpan?.output !== void 0 || mockSpan?.outputMeta !== void 0;
3471
+ if (!hasInlineOutput && replayCtxForMock.fetchSpanOutput && mockSpan?.externalSpanId) {
3472
+ return replayCtxForMock.fetchSpanOutput(mockSpan.externalSpanId);
3473
+ }
3474
+ if (!mockSpan) {
3475
+ return Promise.reject(
3476
+ new BitfabError(
3477
+ `No recorded span to source output for "${traceFunctionKey}".`
3478
+ )
3479
+ );
3480
+ }
3481
+ let output = mockSpan.output;
3482
+ if (mockSpan.outputMeta !== void 0 && mockSpan.outputMeta !== null) {
3483
+ output = deserializeValue({
3484
+ json: mockSpan.output,
3485
+ meta: mockSpan.outputMeta
3486
+ });
3487
+ }
3488
+ return output;
3489
+ };
3490
+ if (replayCtxForMock.mockOverrides?.length) {
3491
+ const nodeMeta = {
3492
+ traceFunctionKey,
3493
+ spanName: baseSpanParams.spanName,
3494
+ type: options.type ?? "custom",
3495
+ originalSpanId: mockSpan?.sourceSpanId
3496
+ };
3497
+ const override = replayCtxForMock.mockOverrides.find(
3498
+ (o) => o.match(nodeMeta)
3499
+ );
3500
+ if (override) {
3501
+ const injected = resolveMockValue(override.value, {
3502
+ node: nodeMeta,
3503
+ inputs: args,
3504
+ getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
3505
+ });
3506
+ if (injected instanceof Promise) {
3507
+ return emitMockAsync(injected);
3508
+ }
3509
+ return emitMock(injected);
3453
3510
  }
3454
3511
  }
3512
+ const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3513
+ if (shouldMock && mockSpan) {
3514
+ const recorded = resolveRecordedOutput();
3515
+ if (recorded instanceof Promise) {
3516
+ return emitMockAsync(recorded);
3517
+ }
3518
+ return emitMock(recorded);
3519
+ }
3455
3520
  }
3456
3521
  const recordSpan = (result) => {
3457
3522
  if (options.finalize) {
@@ -3656,8 +3721,11 @@ var Bitfab = class {
3656
3721
  ...params.dbSnapshotUsage.snapshotTimestamp && {
3657
3722
  snapshot_timestamp: params.dbSnapshotUsage.snapshotTimestamp
3658
3723
  },
3659
- ...params.dbSnapshotUsage.sourceTraceId && {
3660
- source_trace_id: params.dbSnapshotUsage.sourceTraceId
3724
+ ...params.dbSnapshotUsage.originalTraceId && {
3725
+ original_trace_id: params.dbSnapshotUsage.originalTraceId,
3726
+ // Deprecated wire alias, kept so this SDK still reports usage
3727
+ // against servers that predate the rename.
3728
+ source_trace_id: params.dbSnapshotUsage.originalTraceId
3661
3729
  },
3662
3730
  accessed: params.dbSnapshotUsage.accessed
3663
3731
  };
@@ -3730,26 +3798,14 @@ var Bitfab = class {
3730
3798
  ...params.mocked && { mocked: true }
3731
3799
  });
3732
3800
  }
3733
- /**
3734
- * Replay historical traces through a function and create a test run.
3735
- *
3736
- * Fetches the last N traces for the given trace function key, re-runs each
3737
- * through the provided function, and returns comparison data.
3738
- *
3739
- * Accepts either a `withSpan`-wrapped function (under the same key) or any
3740
- * plain callable: plain callables are wrapped internally so each replayed
3741
- * invocation records a trace tied to the test run. The plain-callable form
3742
- * is how handler-instrumented workflows (LangGraph/LangChain, Claude Agent
3743
- * SDK) replay - those record traces under a key with no `withSpan`-wrapped
3744
- * root in the app.
3745
- *
3746
- * @param traceFunctionKey - The trace function key to replay
3747
- * @param fn - The function to run recorded inputs through
3748
- * @param options - Optional replay options. When `traceIds` is passed,
3749
- * `limit` is ignored (with a warning): an explicit ID list already
3750
- * determines how many traces replay.
3751
- * @returns ReplayResult with items, testRunId, and testRunUrl
3752
- */
3801
+ registerMockOverride(overrideOrMatch, value) {
3802
+ const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
3803
+ this.mockOverrides.push(override);
3804
+ }
3805
+ /** Remove all overrides registered via {@link registerMockOverride}. */
3806
+ clearMockOverrides() {
3807
+ this.mockOverrides.length = 0;
3808
+ }
3753
3809
  async replay(traceFunctionKey, fn, options) {
3754
3810
  const wrappedKey = fn._bitfabTraceFunctionKey;
3755
3811
  let replayFn = fn;
@@ -3764,13 +3820,14 @@ var Bitfab = class {
3764
3820
  `Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
3765
3821
  );
3766
3822
  }
3767
- const { replay: doReplay } = await import("./replay-IEE4RY57.js");
3823
+ const { replay: doReplay } = await import("./replay-SKW5A7II.js");
3768
3824
  return doReplay(
3769
3825
  this.httpClient,
3770
3826
  this.serviceUrl,
3771
3827
  traceFunctionKey,
3772
3828
  replayFn,
3773
- options
3829
+ options,
3830
+ this.mockOverrides
3774
3831
  );
3775
3832
  }
3776
3833
  };
@@ -3989,4 +4046,4 @@ export {
3989
4046
  BitfabFunction,
3990
4047
  finalizers
3991
4048
  };
3992
- //# sourceMappingURL=chunk-ZBWTCBVQ.js.map
4049
+ //# sourceMappingURL=chunk-RTPEBWVO.js.map