@bitfab/sdk 0.28.11 → 0.29.0

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-V3XORTWI.js";
14
15
 
15
16
  // src/version.generated.ts
16
- var __version__ = "0.28.11";
17
+ var __version__ = "0.29.0";
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;
@@ -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) {
@@ -3730,26 +3795,14 @@ var Bitfab = class {
3730
3795
  ...params.mocked && { mocked: true }
3731
3796
  });
3732
3797
  }
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
- */
3798
+ registerMockOverride(overrideOrMatch, value) {
3799
+ const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
3800
+ this.mockOverrides.push(override);
3801
+ }
3802
+ /** Remove all overrides registered via {@link registerMockOverride}. */
3803
+ clearMockOverrides() {
3804
+ this.mockOverrides.length = 0;
3805
+ }
3753
3806
  async replay(traceFunctionKey, fn, options) {
3754
3807
  const wrappedKey = fn._bitfabTraceFunctionKey;
3755
3808
  let replayFn = fn;
@@ -3764,13 +3817,14 @@ var Bitfab = class {
3764
3817
  `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
3818
  );
3766
3819
  }
3767
- const { replay: doReplay } = await import("./replay-IEE4RY57.js");
3820
+ const { replay: doReplay } = await import("./replay-HEGU3YU2.js");
3768
3821
  return doReplay(
3769
3822
  this.httpClient,
3770
3823
  this.serviceUrl,
3771
3824
  traceFunctionKey,
3772
3825
  replayFn,
3773
- options
3826
+ options,
3827
+ this.mockOverrides
3774
3828
  );
3775
3829
  }
3776
3830
  };
@@ -3989,4 +4043,4 @@ export {
3989
4043
  BitfabFunction,
3990
4044
  finalizers
3991
4045
  };
3992
- //# sourceMappingURL=chunk-ZBWTCBVQ.js.map
4046
+ //# sourceMappingURL=chunk-2M5AWVVQ.js.map