@bitfab/sdk 0.38.3 → 0.38.5

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.
@@ -1,13 +1,16 @@
1
1
  import {
2
+ __bitfabAutoTraceActive,
2
3
  __setBitfabAutoTraceCapturePolicy,
3
4
  getAutoTraceCapturePolicy,
4
5
  runWithAutoTraceContext,
6
+ runWithAutoTraceNodeConfiguration,
5
7
  runWithAutoTraceRootContext
6
- } from "./chunk-GFBQ2AMO.js";
8
+ } from "./chunk-J47KPS77.js";
7
9
  import {
8
10
  BitfabError,
9
11
  DEFAULT_SERVICE_URL,
10
12
  HttpClient,
13
+ NO_MOCK_OVERRIDE,
11
14
  deserializeValue,
12
15
  getReplayContext,
13
16
  randomUuid,
@@ -16,7 +19,7 @@ import {
16
19
  toJsonSafe,
17
20
  toJsonSafeReport,
18
21
  warnOnce
19
- } from "./chunk-WX6AEFCP.js";
22
+ } from "./chunk-VVI573UU.js";
20
23
  import {
21
24
  __privateAdd,
22
25
  __privateGet,
@@ -2447,6 +2450,102 @@ var Bitfab = class {
2447
2450
  const name = fn.name !== "" ? fn.name : traceFunctionKey;
2448
2451
  return this.createAutoTraceRoot(traceFunctionKey, name, options, fn);
2449
2452
  }
2453
+ /**
2454
+ * Configure a transformed class method when it is discovered beneath a
2455
+ * {@link Bitfab.trace} root.
2456
+ *
2457
+ * The decorator creates no span or trace by itself. Beneath an active trace,
2458
+ * it can rename or retype the discovered call, capture its contents, mark it
2459
+ * for recorded-output replay, finalize its output, or omit it while leaving
2460
+ * captured descendants attached to the nearest captured parent.
2461
+ *
2462
+ * @param options - Trace-owned call configuration.
2463
+ * @experimental Automatic child-call instrumentation is experimental.
2464
+ */
2465
+ node(options = {}) {
2466
+ const configuration = this.resolveNodeConfiguration(options);
2467
+ const decorator = (...args) => {
2468
+ if (args.length === 3) {
2469
+ const descriptor = args[2];
2470
+ if (!descriptor || typeof descriptor.value !== "function") {
2471
+ throw new BitfabError("@bitfab.node can only decorate methods");
2472
+ }
2473
+ if (!this.explicitlyEnabled) {
2474
+ return;
2475
+ }
2476
+ descriptor.value = this.createAutoTraceNode(
2477
+ configuration,
2478
+ descriptor.value,
2479
+ String(args[1])
2480
+ );
2481
+ return;
2482
+ }
2483
+ const method = args[0];
2484
+ const context = args[1];
2485
+ if (typeof method !== "function" || context?.kind !== "method") {
2486
+ throw new BitfabError("@bitfab.node can only decorate methods");
2487
+ }
2488
+ if (!this.explicitlyEnabled) {
2489
+ return method;
2490
+ }
2491
+ return this.createAutoTraceNode(
2492
+ configuration,
2493
+ method,
2494
+ String(context.name)
2495
+ );
2496
+ };
2497
+ return decorator;
2498
+ }
2499
+ withNode(optionsOrFn, maybeFn, internalFunctionName) {
2500
+ const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
2501
+ const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
2502
+ if (!fn) {
2503
+ throw new BitfabError("bitfab.withNode requires a function");
2504
+ }
2505
+ const configuration = this.resolveNodeConfiguration(options);
2506
+ if (!this.explicitlyEnabled) {
2507
+ return fn;
2508
+ }
2509
+ const functionName = internalFunctionName ?? fn.name;
2510
+ if (functionName === "") {
2511
+ throw new BitfabError(
2512
+ "bitfab.withNode requires a named function so the subtree transform can bind its configuration to the correct call."
2513
+ );
2514
+ }
2515
+ return this.createAutoTraceNode(configuration, fn, functionName);
2516
+ }
2517
+ resolveNodeConfiguration(options) {
2518
+ const capture = options.capture ?? true;
2519
+ if (!capture && options.mockOnReplay === true) {
2520
+ throw new BitfabError(
2521
+ "bitfab.node({ capture: false }) cannot use mockOnReplay: true because an uncaptured node has no recorded output."
2522
+ );
2523
+ }
2524
+ return {
2525
+ capture,
2526
+ type: options.type ?? "custom",
2527
+ ...options.name !== void 0 && { name: options.name },
2528
+ ...options.testRunId !== void 0 && {
2529
+ testRunId: options.testRunId
2530
+ },
2531
+ ...options.mockOnReplay !== void 0 && {
2532
+ mockOnReplay: options.mockOnReplay
2533
+ },
2534
+ ...options.finalize !== void 0 && { finalize: options.finalize }
2535
+ };
2536
+ }
2537
+ createAutoTraceNode(configuration, fn, functionName) {
2538
+ const nodeConfiguration = { ...configuration, functionName };
2539
+ return function(...args) {
2540
+ if (!__bitfabAutoTraceActive()) {
2541
+ return fn.apply(this, args);
2542
+ }
2543
+ return runWithAutoTraceNodeConfiguration(
2544
+ nodeConfiguration,
2545
+ () => fn.apply(this, args)
2546
+ );
2547
+ };
2548
+ }
2450
2549
  createAutoTraceRoot(traceFunctionKey, name, options, fn) {
2451
2550
  const self = this;
2452
2551
  const maxDepth = autoTraceLimit(
@@ -2485,29 +2584,51 @@ var Bitfab = class {
2485
2584
  );
2486
2585
  };
2487
2586
  const autoTraceContext = {
2488
- invoke(definition, inputs, invokeFn, depth) {
2587
+ invoke(definition, inputs, invokeFn, depth, nodeConfiguration) {
2489
2588
  const nameParts = definition.name.split(".");
2490
2589
  const simpleName = nameParts[nameParts.length - 1];
2491
- if (excluded.has(definition.name) || simpleName !== void 0 && excluded.has(simpleName) || definition.wrapper === true && !includeWrappers) {
2492
- return invokeFn();
2590
+ const invokeWithoutNode = () => nodeConfiguration === void 0 ? invokeFn() : runWithAutoTraceContext(autoTraceContext, invokeFn, depth);
2591
+ if (excluded.has(definition.name) || simpleName !== void 0 && excluded.has(simpleName) || nodeConfiguration === void 0 && definition.wrapper === true && !includeWrappers) {
2592
+ return invokeWithoutNode();
2593
+ }
2594
+ if (nodeConfiguration?.capture === false) {
2595
+ return runWithAutoTraceContext(autoTraceContext, invokeFn, depth);
2493
2596
  }
2494
2597
  if (depth >= maxDepth || spansUsed >= maxSpans) {
2495
2598
  warnTruncated();
2496
- return invokeFn();
2599
+ return invokeWithoutNode();
2497
2600
  }
2498
2601
  spansUsed += 1;
2499
2602
  const childOptions = {
2500
- name: definition.name,
2501
- type: "function",
2603
+ name: nodeConfiguration?.name ?? definition.name,
2604
+ type: nodeConfiguration?.type ?? "function",
2502
2605
  captureWhen: "nested",
2503
2606
  functionId: definition.id,
2504
- captureContent: capturePolicy.has(definition.id),
2505
- autoTraceDefinition: definition
2607
+ captureContent: nodeConfiguration !== void 0 || capturePolicy.has(definition.id),
2608
+ autoTraceDefinition: definition,
2609
+ ...nodeConfiguration?.testRunId !== void 0 && {
2610
+ testRunId: nodeConfiguration.testRunId
2611
+ },
2612
+ ...nodeConfiguration?.mockOnReplay !== void 0 && {
2613
+ mockOnReplay: nodeConfiguration.mockOnReplay
2614
+ },
2615
+ ...nodeConfiguration?.finalize !== void 0 && {
2616
+ finalize: nodeConfiguration.finalize
2617
+ }
2506
2618
  };
2619
+ const invokeWithAutoTraceContext = () => runWithAutoTraceContext(autoTraceContext, invokeFn, depth + 1);
2620
+ if (definition.async === true) {
2621
+ const tracedAsyncChild = self.withSpan(
2622
+ traceFunctionKey,
2623
+ childOptions,
2624
+ async (..._inputs) => await invokeWithAutoTraceContext()
2625
+ );
2626
+ return tracedAsyncChild(...inputs);
2627
+ }
2507
2628
  const tracedChild = self.withSpan(
2508
2629
  traceFunctionKey,
2509
2630
  childOptions,
2510
- (..._inputs) => runWithAutoTraceContext(autoTraceContext, invokeFn, depth + 1)
2631
+ (..._inputs) => invokeWithAutoTraceContext()
2511
2632
  );
2512
2633
  return tracedChild(...inputs);
2513
2634
  }
@@ -3077,18 +3198,17 @@ var Bitfab = class {
3077
3198
  newStack = [...currentStack, newContext];
3078
3199
  const inputs = args;
3079
3200
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3201
+ const replayCtxAtStart = getReplayContext();
3202
+ const testRunId = replayCtxAtStart?.testRunId ?? options.testRunId;
3080
3203
  if (isRootSpan && !activeTraceStates.has(traceId)) {
3081
- const replayCtxAtRoot = getReplayContext();
3082
3204
  const dbSnapshotRef = buildSnapshotRef(self.dbSnapshot, startedAt);
3083
3205
  activeTraceStates.set(traceId, {
3084
3206
  traceId,
3085
3207
  startedAt,
3086
3208
  contexts: [],
3087
- ...replayCtxAtRoot?.testRunId && {
3088
- testRunId: replayCtxAtRoot.testRunId
3089
- },
3090
- ...replayCtxAtRoot?.inputSourceTraceId && {
3091
- inputSourceTraceId: replayCtxAtRoot.inputSourceTraceId
3209
+ ...testRunId !== void 0 && { testRunId },
3210
+ ...replayCtxAtStart?.inputSourceTraceId && {
3211
+ inputSourceTraceId: replayCtxAtStart.inputSourceTraceId
3092
3212
  },
3093
3213
  dbSnapshotRef
3094
3214
  });
@@ -3121,9 +3241,7 @@ var Bitfab = class {
3121
3241
  contexts: newContext.contexts,
3122
3242
  prompt: newContext.prompt,
3123
3243
  endedAt,
3124
- ...replayCtx?.testRunId && {
3125
- testRunId: replayCtx.testRunId
3126
- },
3244
+ ...testRunId !== void 0 && { testRunId },
3127
3245
  ...replayCtx?.inputSourceSpanId && {
3128
3246
  inputSourceSpanId: replayCtx.inputSourceSpanId
3129
3247
  }
@@ -3163,6 +3281,49 @@ var Bitfab = class {
3163
3281
  } catch {
3164
3282
  }
3165
3283
  };
3284
+ const recordSpan = (result) => {
3285
+ if (options.finalize) {
3286
+ void self.httpClient.trackDeferred(
3287
+ Promise.resolve().then(() => options.finalize(result)).then((output) => sendSpan({ result: output })).catch(
3288
+ (error) => sendSpan({
3289
+ result: void 0,
3290
+ error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3291
+ })
3292
+ )
3293
+ );
3294
+ } else {
3295
+ void sendSpan({ result });
3296
+ }
3297
+ };
3298
+ executeWithContext = () => {
3299
+ let result;
3300
+ try {
3301
+ result = fn.apply(this, args);
3302
+ } catch (error) {
3303
+ void sendSpan({
3304
+ result: void 0,
3305
+ error: error instanceof Error ? error.message : String(error)
3306
+ });
3307
+ throw error;
3308
+ }
3309
+ if (result instanceof Promise) {
3310
+ return result.then((resolvedResult) => {
3311
+ recordSpan(resolvedResult);
3312
+ return resolvedResult;
3313
+ }).catch((error) => {
3314
+ void sendSpan({
3315
+ result: void 0,
3316
+ error: error instanceof Error ? error.message : String(error)
3317
+ });
3318
+ throw error;
3319
+ });
3320
+ }
3321
+ if (isAsyncGenerator(result)) {
3322
+ return wrapAsyncGenerator(result, newStack, sendSpan);
3323
+ }
3324
+ recordSpan(result);
3325
+ return result;
3326
+ };
3166
3327
  const replayCtxForMock = getReplayContext();
3167
3328
  if (replayCtxForMock?.mockTree && !isRootSpan) {
3168
3329
  const counters = replayCtxForMock.callCounters;
@@ -3221,6 +3382,7 @@ var Bitfab = class {
3221
3382
  }
3222
3383
  return output;
3223
3384
  };
3385
+ const shouldMockWithBaseStrategy = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3224
3386
  if (replayCtxForMock.mockOverrides?.length) {
3225
3387
  const nodeMeta = {
3226
3388
  traceFunctionKey,
@@ -3228,28 +3390,75 @@ var Bitfab = class {
3228
3390
  type: options.type ?? "custom",
3229
3391
  originalSpanId: mockSpan?.sourceSpanId
3230
3392
  };
3231
- const override = replayCtxForMock.mockOverrides.find(
3232
- (o) => o.match(nodeMeta)
3233
- );
3234
- if (override) {
3235
- const injected = resolveMockValue(override.value, {
3236
- node: nodeMeta,
3237
- inputs: args,
3238
- getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
3239
- });
3240
- if (injected instanceof Promise) {
3241
- return emitMockAsync(injected, "override");
3393
+ const overrideCtx = {
3394
+ node: nodeMeta,
3395
+ inputs: args,
3396
+ getOriginalOutput: () => Promise.resolve(resolveRecordedOutput())
3397
+ };
3398
+ const resolveOverrideFrom = (startIndex) => {
3399
+ for (let index = startIndex; index < replayCtxForMock.mockOverrides.length; index += 1) {
3400
+ const override = replayCtxForMock.mockOverrides[index];
3401
+ if (!override?.match(nodeMeta)) {
3402
+ continue;
3403
+ }
3404
+ const injected = resolveMockValue(override.value, overrideCtx);
3405
+ if (injected instanceof Promise) {
3406
+ return injected.then(
3407
+ (output) => output === NO_MOCK_OVERRIDE ? resolveOverrideFrom(index + 1) : { matched: true, output }
3408
+ );
3409
+ }
3410
+ if (injected !== NO_MOCK_OVERRIDE) {
3411
+ return { matched: true, output: injected };
3412
+ }
3242
3413
  }
3243
- return emitMock(injected, "override");
3414
+ return { matched: false };
3415
+ };
3416
+ const resolution = resolveOverrideFrom(0);
3417
+ if (resolution instanceof Promise) {
3418
+ if (!fnReturnsPromise) {
3419
+ throw new BitfabError(
3420
+ `Cannot resolve an asynchronous mock override for synchronous span "${traceFunctionKey}". Make the wrapped function async or return NO_MOCK_OVERRIDE synchronously.`
3421
+ );
3422
+ }
3423
+ return runWithSpanStack(newStack, async () => {
3424
+ const resolved = await resolution;
3425
+ if (resolved.matched) {
3426
+ void sendSpan({
3427
+ result: resolved.output,
3428
+ mocked: true,
3429
+ mockTarget: "output",
3430
+ mockSource: "override"
3431
+ });
3432
+ return resolved.output;
3433
+ }
3434
+ if (shouldMockWithBaseStrategy && !mockSpan) {
3435
+ throw new BitfabError(
3436
+ `Replay selected span "${traceFunctionKey}:${baseSpanParams.spanName}" for mocking, but recorded occurrence ${callIndex + 1} is unavailable. The real span was not executed.`
3437
+ );
3438
+ }
3439
+ if (shouldMockWithBaseStrategy) {
3440
+ const output = await resolveRecordedOutput();
3441
+ void sendSpan({
3442
+ result: output,
3443
+ mocked: true,
3444
+ mockTarget: "output",
3445
+ mockSource: "recorded"
3446
+ });
3447
+ return output;
3448
+ }
3449
+ return executeWithContext();
3450
+ });
3451
+ }
3452
+ if (resolution.matched) {
3453
+ return emitMock(resolution.output, "override");
3244
3454
  }
3245
3455
  }
3246
- const shouldMock = replayCtxForMock.mockStrategy === "all" || replayCtxForMock.mockStrategy === "marked" && options.mockOnReplay === true;
3247
- if (shouldMock && !mockSpan) {
3456
+ if (shouldMockWithBaseStrategy && !mockSpan) {
3248
3457
  throw new BitfabError(
3249
3458
  `Replay selected span "${traceFunctionKey}:${baseSpanParams.spanName}" for mocking, but recorded occurrence ${callIndex + 1} is unavailable. The real span was not executed.`
3250
3459
  );
3251
3460
  }
3252
- if (shouldMock) {
3461
+ if (shouldMockWithBaseStrategy) {
3253
3462
  const recorded = resolveRecordedOutput();
3254
3463
  if (recorded instanceof Promise) {
3255
3464
  return emitMockAsync(recorded, "recorded");
@@ -3257,49 +3466,6 @@ var Bitfab = class {
3257
3466
  return emitMock(recorded, "recorded");
3258
3467
  }
3259
3468
  }
3260
- const recordSpan = (result) => {
3261
- if (options.finalize) {
3262
- void self.httpClient.trackDeferred(
3263
- Promise.resolve().then(() => options.finalize(result)).then((output) => sendSpan({ result: output })).catch(
3264
- (error) => sendSpan({
3265
- result: void 0,
3266
- error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
3267
- })
3268
- )
3269
- );
3270
- } else {
3271
- void sendSpan({ result });
3272
- }
3273
- };
3274
- executeWithContext = () => {
3275
- let result;
3276
- try {
3277
- result = fn.apply(this, args);
3278
- } catch (error) {
3279
- void sendSpan({
3280
- result: void 0,
3281
- error: error instanceof Error ? error.message : String(error)
3282
- });
3283
- throw error;
3284
- }
3285
- if (result instanceof Promise) {
3286
- return result.then((resolvedResult) => {
3287
- recordSpan(resolvedResult);
3288
- return resolvedResult;
3289
- }).catch((error) => {
3290
- void sendSpan({
3291
- result: void 0,
3292
- error: error instanceof Error ? error.message : String(error)
3293
- });
3294
- throw error;
3295
- });
3296
- }
3297
- if (isAsyncGenerator(result)) {
3298
- return wrapAsyncGenerator(result, newStack, sendSpan);
3299
- }
3300
- recordSpan(result);
3301
- return result;
3302
- };
3303
3469
  } catch (setupError) {
3304
3470
  if (registeredTraceId) {
3305
3471
  activeTraceStates.delete(registeredTraceId);
@@ -3586,8 +3752,40 @@ var Bitfab = class {
3586
3752
  ...params.mockSource && { mockSource: params.mockSource }
3587
3753
  });
3588
3754
  }
3589
- registerMockOverride(overrideOrMatch, value) {
3590
- const override = typeof overrideOrMatch === "function" ? { match: overrideOrMatch, value } : overrideOrMatch;
3755
+ registerMockOverride(overrideOrResolverOrMatch, ...values) {
3756
+ let override;
3757
+ if (typeof overrideOrResolverOrMatch === "string") {
3758
+ const keyedOverride = values[0];
3759
+ if (values.length !== 1 || keyedOverride === void 0) {
3760
+ throw new BitfabError(
3761
+ "registerMockOverride(traceFunctionKey, override) requires a resolver function or { match, value } override."
3762
+ );
3763
+ }
3764
+ if (typeof keyedOverride === "function") {
3765
+ override = {
3766
+ match: (node) => node.traceFunctionKey === overrideOrResolverOrMatch,
3767
+ value: keyedOverride
3768
+ };
3769
+ } else if (typeof keyedOverride === "object" && keyedOverride !== null && "match" in keyedOverride && "value" in keyedOverride) {
3770
+ override = {
3771
+ match: (node) => node.traceFunctionKey === overrideOrResolverOrMatch && keyedOverride.match(node),
3772
+ value: keyedOverride.value
3773
+ };
3774
+ } else {
3775
+ throw new BitfabError(
3776
+ "registerMockOverride(traceFunctionKey, override) requires a resolver function or { match, value } override."
3777
+ );
3778
+ }
3779
+ } else if (typeof overrideOrResolverOrMatch !== "function") {
3780
+ override = overrideOrResolverOrMatch;
3781
+ } else if (values.length === 0) {
3782
+ override = { match: () => true, value: overrideOrResolverOrMatch };
3783
+ } else {
3784
+ override = {
3785
+ match: overrideOrResolverOrMatch,
3786
+ value: values[0]
3787
+ };
3788
+ }
3591
3789
  this.mockOverrides.push(override);
3592
3790
  }
3593
3791
  /** Remove all overrides registered via {@link registerMockOverride}. */
@@ -3608,7 +3806,7 @@ var Bitfab = class {
3608
3806
  `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.`
3609
3807
  );
3610
3808
  }
3611
- const { replay: doReplay } = await import("./replay-FAO7D7CG.js");
3809
+ const { replay: doReplay } = await import("./replay-Z6DON4SP.js");
3612
3810
  return doReplay(
3613
3811
  this.httpClient,
3614
3812
  this.serviceUrl,
@@ -3852,4 +4050,4 @@ export {
3852
4050
  finalizers,
3853
4051
  defineReplayRegistry
3854
4052
  };
3855
- //# sourceMappingURL=chunk-RJPHMJWO.js.map
4053
+ //# sourceMappingURL=chunk-Z3GIZGS5.js.map