@use-tusk/drift-node-sdk 0.1.7 → 0.1.8

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/index.js CHANGED
@@ -18,11 +18,12 @@ import { IncomingMessage } from "http";
18
18
  import * as crypto from "crypto";
19
19
  import { SpanExportService } from "@use-tusk/drift-schemas/backend/span_export_service";
20
20
  import jp from "jsonpath";
21
+ import net from "net";
22
+ import { Readable } from "stream";
21
23
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
22
24
  import { SpanExportServiceClient } from "@use-tusk/drift-schemas/backend/span_export_service.client";
23
25
  import { TwirpFetchTransport } from "@protobuf-ts/twirp-transport";
24
26
  import { BatchSpanProcessor, NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
25
- import net from "net";
26
27
  import { execSync } from "child_process";
27
28
  import { CLIMessage, ConnectRequest, GetMockRequest, MessageType, SDKMessage, SendInboundSpanForReplayRequest } from "@use-tusk/drift-schemas/core/communication";
28
29
  import { Resource } from "@opentelemetry/resources";
@@ -252,7 +253,7 @@ var TdInstrumentationAbstract = class {
252
253
 
253
254
  //#endregion
254
255
  //#region package.json
255
- var version = "0.1.7";
256
+ var version = "0.1.8";
256
257
 
257
258
  //#endregion
258
259
  //#region src/version.ts
@@ -449,6 +450,20 @@ function loadTuskConfig() {
449
450
  }
450
451
  }
451
452
 
453
+ //#endregion
454
+ //#region src/core/utils/runtimeDetectionUtils.ts
455
+ function isNextJsRuntime() {
456
+ return process.env.NEXT_RUNTIME !== void 0 || typeof global.__NEXT_DATA__ !== "undefined";
457
+ }
458
+ function isEsm(moduleExports) {
459
+ if (!moduleExports || typeof moduleExports !== "object") return false;
460
+ try {
461
+ return moduleExports[Symbol.toStringTag] === "Module";
462
+ } catch (error) {
463
+ return false;
464
+ }
465
+ }
466
+
452
467
  //#endregion
453
468
  //#region src/core/utils/logger.ts
454
469
  var Logger = class {
@@ -728,9 +743,11 @@ var TdInstrumentationNodeModule = class {
728
743
 
729
744
  //#endregion
730
745
  //#region src/core/types.ts
746
+ const TD_INSTRUMENTATION_LIBRARY_NAME = "tusk-drift-sdk";
731
747
  const REPLAY_TRACE_ID_CONTEXT_KEY = createContextKey("td.replayTraceId");
732
748
  const SPAN_KIND_CONTEXT_KEY = createContextKey("td.spanKind");
733
749
  const IS_PRE_APP_START_CONTEXT_KEY = createContextKey("td.isPreAppStart");
750
+ const STOP_RECORDING_CHILD_SPANS_CONTEXT_KEY = createContextKey("td.stopRecordingChildSpans");
734
751
  const CALLING_LIBRARY_CONTEXT_KEY = createContextKey("td.callingLibrary");
735
752
  let TdSpanAttributes = /* @__PURE__ */ function(TdSpanAttributes$1) {
736
753
  /**
@@ -773,6 +790,74 @@ let TdSpanAttributes = /* @__PURE__ */ function(TdSpanAttributes$1) {
773
790
  return TdSpanAttributes$1;
774
791
  }({});
775
792
 
793
+ //#endregion
794
+ //#region src/core/tracing/TraceBlockingManager.ts
795
+ /**
796
+ * Manages blocked trace IDs to prevent creation and export of spans
797
+ * that belong to traces exceeding size limits.
798
+ *
799
+ * This class uses an in-memory Set for O(1) lookup performance and
800
+ * automatically cleans up old entries to prevent memory leaks.
801
+ */
802
+ var TraceBlockingManager = class TraceBlockingManager {
803
+ constructor() {
804
+ this.blockedTraceIds = /* @__PURE__ */ new Set();
805
+ this.traceTimestamps = /* @__PURE__ */ new Map();
806
+ this.cleanupIntervalId = null;
807
+ this.DEFAULT_TTL_MS = 600 * 1e3;
808
+ this.CLEANUP_INTERVAL_MS = 120 * 1e3;
809
+ this.startCleanupInterval();
810
+ }
811
+ /**
812
+ * Get singleton instance
813
+ */
814
+ static getInstance() {
815
+ if (!TraceBlockingManager.instance) TraceBlockingManager.instance = new TraceBlockingManager();
816
+ return TraceBlockingManager.instance;
817
+ }
818
+ /**
819
+ * Check if a trace ID is blocked
820
+ */
821
+ isTraceBlocked(traceId) {
822
+ return this.blockedTraceIds.has(traceId);
823
+ }
824
+ /**
825
+ * Block a trace ID and all future spans for this trace
826
+ */
827
+ blockTrace(traceId) {
828
+ if (!this.blockedTraceIds.has(traceId)) {
829
+ this.blockedTraceIds.add(traceId);
830
+ const originalDate = OriginalGlobalUtils.getOriginalDate();
831
+ this.traceTimestamps.set(traceId, originalDate.getTime());
832
+ logger.debug(`[TraceBlockingManager] Blocked trace: ${traceId}`);
833
+ }
834
+ }
835
+ /**
836
+ * Start periodic cleanup of old blocked trace IDs
837
+ */
838
+ startCleanupInterval() {
839
+ if (this.cleanupIntervalId) return;
840
+ this.cleanupIntervalId = setInterval(() => {
841
+ this.cleanupOldTraces();
842
+ }, this.CLEANUP_INTERVAL_MS);
843
+ if (this.cleanupIntervalId.unref) this.cleanupIntervalId.unref();
844
+ }
845
+ /**
846
+ * Clean up trace IDs older than TTL
847
+ */
848
+ cleanupOldTraces() {
849
+ const now = OriginalGlobalUtils.getOriginalDate().getTime();
850
+ const expiredTraces = [];
851
+ for (const [traceId, timestamp] of this.traceTimestamps.entries()) if (now - timestamp > this.DEFAULT_TTL_MS) expiredTraces.push(traceId);
852
+ for (const traceId of expiredTraces) {
853
+ this.blockedTraceIds.delete(traceId);
854
+ this.traceTimestamps.delete(traceId);
855
+ }
856
+ if (expiredTraces.length > 0) logger.debug(`[TraceBlockingManager] Cleaned up ${expiredTraces.length} expired blocked trace(s)`);
857
+ }
858
+ };
859
+ TraceBlockingManager.instance = null;
860
+
776
861
  //#endregion
777
862
  //#region src/core/tracing/SpanUtils.ts
778
863
  var SpanUtils = class SpanUtils {
@@ -783,6 +868,14 @@ var SpanUtils = class SpanUtils {
783
868
  try {
784
869
  const tracer = TuskDriftCore.getInstance().getTracer();
785
870
  const parentContext = options.parentContext || context.active();
871
+ const activeSpan = trace.getSpan(parentContext);
872
+ if (activeSpan) {
873
+ const parentTraceId = activeSpan.spanContext().traceId;
874
+ if (TraceBlockingManager.getInstance().isTraceBlocked(parentTraceId)) {
875
+ logger.debug(`[SpanUtils] Skipping span creation for '${options.name}' - trace ${parentTraceId} is blocked`);
876
+ return null;
877
+ }
878
+ }
786
879
  const span = tracer.startSpan(options.name, {
787
880
  kind: options.kind || SpanKind.CLIENT,
788
881
  attributes: options.attributes || {}
@@ -822,7 +915,14 @@ var SpanUtils = class SpanUtils {
822
915
  * @returns The result of the function execution
823
916
  */
824
917
  static createAndExecuteSpan(mode, originalFunctionCall, options, fn) {
825
- const { name, kind, packageName, instrumentationName, packageType, submodule, inputValue, inputSchemaMerges, isPreAppStart, metadata } = options;
918
+ const spanContext = trace.getActiveSpan()?.spanContext();
919
+ if (spanContext) {
920
+ if (context.active().getValue(STOP_RECORDING_CHILD_SPANS_CONTEXT_KEY)) {
921
+ logger.debug(`[SpanUtils] Stopping recording of child spans for span ${spanContext.spanId}, packageName: ${options.packageName}, instrumentationName: ${options.instrumentationName}`);
922
+ return originalFunctionCall();
923
+ }
924
+ }
925
+ const { name, kind, packageName, instrumentationName, packageType, submodule, inputValue, inputSchemaMerges, isPreAppStart, metadata, stopRecordingChildSpans } = options;
826
926
  let spanInfo = null;
827
927
  try {
828
928
  spanInfo = SpanUtils.createSpan({
@@ -847,6 +947,7 @@ var SpanUtils = class SpanUtils {
847
947
  }
848
948
  if (!spanInfo) if (mode === TuskDriftMode.REPLAY) throw new Error("Error creating span in replay mode");
849
949
  else return originalFunctionCall();
950
+ if (stopRecordingChildSpans) spanInfo.context = spanInfo.context.setValue(STOP_RECORDING_CHILD_SPANS_CONTEXT_KEY, true);
850
951
  return SpanUtils.withSpan(spanInfo, () => fn(spanInfo));
851
952
  }
852
953
  /**
@@ -1102,6 +1203,22 @@ function getDecodedType(contentType) {
1102
1203
  const mainType = contentTypeString.toLowerCase().split(";")[0].trim();
1103
1204
  return CONTENT_TYPE_MAPPING[mainType];
1104
1205
  }
1206
+ const STATIC_ASSET_TYPES = new Set([
1207
+ DecodedType.HTML,
1208
+ DecodedType.CSS,
1209
+ DecodedType.JAVASCRIPT,
1210
+ DecodedType.JPEG,
1211
+ DecodedType.PNG,
1212
+ DecodedType.GIF,
1213
+ DecodedType.WEBP,
1214
+ DecodedType.SVG,
1215
+ DecodedType.PDF,
1216
+ DecodedType.AUDIO,
1217
+ DecodedType.VIDEO,
1218
+ DecodedType.BINARY,
1219
+ DecodedType.ZIP,
1220
+ DecodedType.GZIP
1221
+ ]);
1105
1222
 
1106
1223
  //#endregion
1107
1224
  //#region src/instrumentation/libraries/http/mocks/TdHttpMockSocket.ts
@@ -1576,6 +1693,28 @@ async function findMockResponseAsync({ mockRequestData, tuskDrift, inputValueSch
1576
1693
  return null;
1577
1694
  }
1578
1695
  }
1696
+ function findMockResponseSync({ mockRequestData, tuskDrift, inputValueSchemaMerges }) {
1697
+ const outboundSpan = convertMockRequestDataToCleanSpanData(mockRequestData, tuskDrift, inputValueSchemaMerges);
1698
+ try {
1699
+ const replayTraceId = SpanUtils.getCurrentReplayTraceId();
1700
+ logger.debug(`Finding ${outboundSpan.traceId} mock for replay trace ID: ${replayTraceId}`);
1701
+ const mockResponse = tuskDrift.requestMockSync({
1702
+ outboundSpan,
1703
+ testId: replayTraceId || ""
1704
+ });
1705
+ if (!mockResponse || !mockResponse.found) {
1706
+ logger.debug(`No matching mock found for ${outboundSpan.traceId} with input value: ${JSON.stringify(outboundSpan.inputValue)}`, replayTraceId);
1707
+ return null;
1708
+ }
1709
+ const responseBody = mockResponse.response?.response?.body;
1710
+ logger.debug(`Found ${outboundSpan.traceId} mock response and timestamp:`, responseBody, { timestamp: mockResponse.response?.timestamp });
1711
+ if (mockResponse.response?.timestamp) DateTracker.updateLatestTimestamp(replayTraceId || "", mockResponse.response.timestamp);
1712
+ return { result: responseBody };
1713
+ } catch (error) {
1714
+ logger.error(`Error finding ${outboundSpan.traceId} mock response:`, error);
1715
+ return null;
1716
+ }
1717
+ }
1579
1718
 
1580
1719
  //#endregion
1581
1720
  //#region src/instrumentation/libraries/http/mocks/TdMockClientRequest.ts
@@ -1988,7 +2127,7 @@ function wrap(target, propertyName, wrapper) {
1988
2127
  * Helper functions for capturing stack traces in replay mode
1989
2128
  *
1990
2129
  * TODO: Consider using a structured format for stack frames:
1991
- *
2130
+ *
1992
2131
  * {
1993
2132
  * "frames": [
1994
2133
  * {
@@ -2004,7 +2143,7 @@ function wrap(target, propertyName, wrapper) {
2004
2143
  * It would also allow for more accurate stack trace reconstruction in replay mode.
2005
2144
  */
2006
2145
  /**
2007
- *
2146
+ *
2008
2147
  * @param excludeClassNames - Class names to exclude from the stack trace
2009
2148
  * @returns The stack trace as a string
2010
2149
  */
@@ -2417,7 +2556,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2417
2556
  logger.debug(`[HttpInstrumentation] ${protocolUpper} module already patched, skipping`);
2418
2557
  return httpModule;
2419
2558
  }
2420
- if (httpModule[Symbol.toStringTag] === "Module") {
2559
+ if (isEsm(httpModule)) {
2421
2560
  if (httpModule.default) {
2422
2561
  this._wrap(httpModule.default, "request", this._getRequestPatchFn(protocol));
2423
2562
  this._wrap(httpModule.default, "get", this._getGetPatchFn(protocol));
@@ -2436,11 +2575,18 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2436
2575
  return httpModule;
2437
2576
  }
2438
2577
  _createServerSpan({ req, res, originalHandler, protocol }) {
2578
+ if (isNextJsRuntime()) {
2579
+ logger.debug(`[HttpInstrumentation] Skipping recording/replaying for nextJs runtime, handled by nextJs instrumentation`);
2580
+ return originalHandler.call(this);
2581
+ }
2439
2582
  const method = req.method || "GET";
2440
2583
  const url = req.url || "/";
2441
2584
  const target = req.url || "/";
2442
2585
  const spanProtocol = this._normalizeProtocol(protocol, "http");
2443
- if (isTuskDriftIngestionUrl(url) || isTuskDriftIngestionUrl(target)) return originalHandler.call(this);
2586
+ if (isTuskDriftIngestionUrl(url) || isTuskDriftIngestionUrl(target)) {
2587
+ logger.debug(`[HttpInstrumentation] Ignoring drift ingestion endpoints`);
2588
+ return originalHandler.call(this);
2589
+ }
2444
2590
  if (this.transformEngine.shouldDropInboundRequest(method, url, req.headers)) {
2445
2591
  logger.debug(`[HttpInstrumentation] Dropping inbound request due to transforms: ${method} ${url}`);
2446
2592
  return originalHandler.call(this);
@@ -2458,7 +2604,10 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2458
2604
  remotePort: req.socket?.remotePort
2459
2605
  };
2460
2606
  const replayTraceId = this.replayHooks.extractTraceIdFromHeaders(req);
2461
- if (!replayTraceId) return originalHandler.call(this);
2607
+ if (!replayTraceId) {
2608
+ logger.debug(`[HttpInstrumentation] No trace id found in headers`, req.headers);
2609
+ return originalHandler.call(this);
2610
+ }
2462
2611
  logger.debug(`[HttpInstrumentation] Setting replay trace id`, replayTraceId);
2463
2612
  const envVars = this.replayHooks.extractEnvVarsFromHeaders(req);
2464
2613
  if (envVars) EnvVarTracker.setEnvVars(replayTraceId, envVars);
@@ -2490,13 +2639,6 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2490
2639
  } });
2491
2640
  else if (this.mode === TuskDriftMode.RECORD) {
2492
2641
  if (method.toUpperCase() === "OPTIONS" || !!req.headers["access-control-request-method"]) return originalHandler.call(this);
2493
- if (!shouldSample({
2494
- samplingRate: this.tuskDrift.getSamplingRate(),
2495
- isAppReady: this.tuskDrift.isAppReady()
2496
- })) {
2497
- logger.debug(`Skipping server span due to sampling rate`, url, this.tuskDrift.getSamplingRate());
2498
- return originalHandler.call(this);
2499
- }
2500
2642
  logger.debug(`[HttpInstrumentation] Creating server span for ${method} ${url}`);
2501
2643
  return handleRecordMode({
2502
2644
  originalFunctionCall: () => originalHandler.call(this),
@@ -2590,7 +2732,6 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2590
2732
  outputValue.bodySize = responseBuffer.length;
2591
2733
  } catch (error) {
2592
2734
  logger.error(`[HttpInstrumentation] Error processing server response body:`, error);
2593
- outputValue.bodyProcessingError = error instanceof Error ? error.message : String(error);
2594
2735
  }
2595
2736
  try {
2596
2737
  const spanData = {
@@ -2608,7 +2749,7 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2608
2749
  outputSchemaMerges: {
2609
2750
  body: {
2610
2751
  encoding: EncodingType.BASE64,
2611
- decodedType: getDecodedType(spanData.outputValue.headers?.["content-type"] || "")
2752
+ decodedType: getDecodedType(spanData.outputValue?.headers?.["content-type"] || "")
2612
2753
  },
2613
2754
  headers: { matchImportance: 0 }
2614
2755
  },
@@ -2621,6 +2762,11 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
2621
2762
  message: `HTTP ${statusCode}`
2622
2763
  } : { code: SpanStatusCode.OK };
2623
2764
  SpanUtils.setStatus(spanInfo.span, status);
2765
+ const decodedType = getDecodedType(outputValue.headers?.["content-type"] || "");
2766
+ if (decodedType && STATIC_ASSET_TYPES.has(decodedType)) {
2767
+ TraceBlockingManager.getInstance().blockTrace(spanInfo.traceId);
2768
+ logger.debug(`[HttpInstrumentation] Blocking trace ${spanInfo.traceId} because it is an static asset response. Decoded type: ${decodedType}`);
2769
+ }
2624
2770
  SpanUtils.endSpan(spanInfo.span);
2625
2771
  } catch (error) {
2626
2772
  logger.error(`[HttpInstrumentation] Error adding response attributes to span:`, error);
@@ -3122,6 +3268,12 @@ var HttpInstrumentation = class extends TdInstrumentationBase {
3122
3268
  return (originalEmit) => {
3123
3269
  return function(eventName, ...args) {
3124
3270
  if (eventName === "request") {
3271
+ if (self.mode === TuskDriftMode.RECORD) {
3272
+ if (!shouldSample({
3273
+ samplingRate: self.tuskDrift.getSamplingRate(),
3274
+ isAppReady: self.tuskDrift.isAppReady()
3275
+ })) return originalEmit.apply(this, [eventName, ...args]);
3276
+ }
3125
3277
  const req = args[0];
3126
3278
  const res = args[1];
3127
3279
  return self._createServerSpan({
@@ -4034,7 +4186,7 @@ var PostgresInstrumentation = class extends TdInstrumentationBase {
4034
4186
  return postgresModule;
4035
4187
  }
4036
4188
  const self = this;
4037
- if (postgresModule[Symbol.toStringTag] === "Module") {
4189
+ if (isEsm(postgresModule)) {
4038
4190
  logger.debug(`[PostgresInstrumentation] Wrapping ESM default export`);
4039
4191
  this._wrap(postgresModule, "default", (originalFunction) => {
4040
4192
  return function(...args) {
@@ -5800,6 +5952,7 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
5800
5952
  super("tcp", config);
5801
5953
  this.loggedSpans = /* @__PURE__ */ new Set();
5802
5954
  this.mode = config.mode || TuskDriftMode.DISABLED;
5955
+ this._patchLoadedModules();
5803
5956
  }
5804
5957
  init() {
5805
5958
  return [new TdInstrumentationNodeModule({
@@ -5808,6 +5961,17 @@ var TcpInstrumentation = class extends TdInstrumentationBase {
5808
5961
  patch: (moduleExports) => this._patchNetModule(moduleExports)
5809
5962
  })];
5810
5963
  }
5964
+ _patchLoadedModules() {
5965
+ if (isNextJsRuntime()) {
5966
+ logger.debug(`[TcpInstrumentation] Next.js environment detected - force-loading net module to ensure patching`);
5967
+ try {
5968
+ __require("net");
5969
+ logger.debug(`[TcpInstrumentation] net module force-loaded`);
5970
+ } catch (err) {
5971
+ logger.error(`[TcpInstrumentation] Error force-loading net module:`, err);
5972
+ }
5973
+ } else logger.debug(`[TcpInstrumentation] Regular Node.js environment - hooks will catch net module on first require`);
5974
+ }
5811
5975
  _patchNetModule(netModule) {
5812
5976
  logger.debug(`[TcpInstrumentation] Patching NET module in ${this.mode} mode`);
5813
5977
  if (this.isModulePatched(netModule)) {
@@ -6960,7 +7124,7 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
6960
7124
  logger.debug(`[IORedisInstrumentation] IORedis module already patched, skipping`);
6961
7125
  return moduleExports;
6962
7126
  }
6963
- const actualExports = moduleExports[Symbol.toStringTag] === "Module" ? moduleExports.default : moduleExports;
7127
+ const actualExports = isEsm(moduleExports) ? moduleExports.default : moduleExports;
6964
7128
  if (!actualExports || !actualExports.prototype) {
6965
7129
  logger.error(`[IORedisInstrumentation] Invalid module exports, cannot patch`);
6966
7130
  return moduleExports;
@@ -6999,7 +7163,7 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
6999
7163
  logger.debug(`[IORedisInstrumentation] Pipeline module already patched, skipping`);
7000
7164
  return moduleExports;
7001
7165
  }
7002
- const actualExports = moduleExports[Symbol.toStringTag] === "Module" ? moduleExports.default : moduleExports;
7166
+ const actualExports = isEsm(moduleExports) ? moduleExports.default : moduleExports;
7003
7167
  if (!actualExports || !actualExports.prototype) {
7004
7168
  logger.debug(`[IORedisInstrumentation] Invalid Pipeline module exports, cannot patch`);
7005
7169
  return moduleExports;
@@ -7592,6 +7756,7 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7592
7756
  return;
7593
7757
  }
7594
7758
  this._wrap(clientPrototype, "makeUnaryRequest", this._getMakeUnaryRequestPatchFn(version$1));
7759
+ this._wrap(clientPrototype, "makeServerStreamRequest", this._getMakeServerStreamRequestPatchFn(version$1));
7595
7760
  this._wrap(clientPrototype, "waitForReady", this._getWaitForReadyPatchFn());
7596
7761
  this._wrap(clientPrototype, "close", this._getClosePatchFn());
7597
7762
  this._wrap(clientPrototype, "getChannel", this._getGetChannelPatchFn());
@@ -7641,6 +7806,7 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7641
7806
  logger.debug(`[GrpcInstrumentation] _getMakeUnaryRequestPatchFn called for version: ${version$1}`);
7642
7807
  return (original) => {
7643
7808
  return function makeUnaryRequest(...args) {
7809
+ logger.debug(`[GrpcInstrumentation] makeUnaryRequest called! args length: ${args.length}`);
7644
7810
  const MetadataConstructor = GrpcInstrumentation.metadataStore.get(version$1) || self.Metadata;
7645
7811
  if (!MetadataConstructor) {
7646
7812
  logger.warn(`[GrpcInstrumentation] Metadata constructor not found for version ${version$1}`);
@@ -7716,6 +7882,115 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7716
7882
  };
7717
7883
  };
7718
7884
  }
7885
+ _getMakeServerStreamRequestPatchFn(version$1) {
7886
+ const self = this;
7887
+ logger.debug(`[GrpcInstrumentation] _getMakeServerStreamRequestPatchFn called for version: ${version$1}`);
7888
+ return (original) => {
7889
+ return function makeServerStreamRequest(...args) {
7890
+ logger.debug(`[GrpcInstrumentation] makeServerStreamRequest called! args length: ${args.length}`);
7891
+ const MetadataConstructor = GrpcInstrumentation.metadataStore.get(version$1) || self.Metadata;
7892
+ if (!MetadataConstructor) {
7893
+ logger.warn(`[GrpcInstrumentation] Metadata constructor not found for version ${version$1}`);
7894
+ return original.apply(this, args);
7895
+ }
7896
+ let parsedParams;
7897
+ try {
7898
+ parsedParams = self.extractServerStreamRequestParameters(args, MetadataConstructor);
7899
+ } catch (error) {
7900
+ logger.error(`[GrpcInstrumentation] Error parsing makeServerStreamRequest arguments:`, error);
7901
+ return original.apply(this, args);
7902
+ }
7903
+ const { method: path$2, argument, metadata, options } = parsedParams;
7904
+ let method;
7905
+ let service;
7906
+ let readableBody;
7907
+ let bufferMap;
7908
+ let jsonableStringMap;
7909
+ let readableMetadata;
7910
+ try {
7911
+ ({method, service} = parseGrpcPath(path$2));
7912
+ ({readableBody, bufferMap, jsonableStringMap} = serializeGrpcPayload(argument));
7913
+ readableMetadata = serializeGrpcMetadata(metadata);
7914
+ } catch (error) {
7915
+ logger.error(`[GrpcInstrumentation] Error parsing makeServerStreamRequest arguments:`, error);
7916
+ return original.apply(this, args);
7917
+ }
7918
+ const inputValue = {
7919
+ method,
7920
+ service,
7921
+ body: readableBody,
7922
+ metadata: readableMetadata,
7923
+ inputMeta: {
7924
+ bufferMap,
7925
+ jsonableStringMap
7926
+ }
7927
+ };
7928
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
7929
+ return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
7930
+ name: "grpc.client.server_stream",
7931
+ kind: SpanKind.CLIENT,
7932
+ submodule: "client",
7933
+ packageType: PackageType.GRPC,
7934
+ packageName: GRPC_MODULE_NAME,
7935
+ instrumentationName: self.INSTRUMENTATION_NAME,
7936
+ inputValue,
7937
+ isPreAppStart: false
7938
+ }, (spanInfo) => {
7939
+ return self._handleReplayServerStreamRequest(spanInfo, inputValue, MetadataConstructor);
7940
+ });
7941
+ } });
7942
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
7943
+ originalFunctionCall: () => original.apply(this, args),
7944
+ recordModeHandler: ({ isPreAppStart }) => {
7945
+ return SpanUtils.createAndExecuteSpan(self.mode, () => original.apply(this, args), {
7946
+ name: "grpc.client.server_stream",
7947
+ kind: SpanKind.CLIENT,
7948
+ submodule: "client",
7949
+ packageType: PackageType.GRPC,
7950
+ packageName: GRPC_MODULE_NAME,
7951
+ instrumentationName: self.INSTRUMENTATION_NAME,
7952
+ inputValue,
7953
+ isPreAppStart
7954
+ }, (spanInfo) => {
7955
+ return self._handleRecordServerStreamRequest(spanInfo, original, this, parsedParams);
7956
+ });
7957
+ },
7958
+ spanKind: SpanKind.CLIENT
7959
+ });
7960
+ else return original.apply(this, args);
7961
+ };
7962
+ };
7963
+ }
7964
+ extractServerStreamRequestParameters(args, MetadataConstructor) {
7965
+ const method = args[0];
7966
+ const serialize = args[1];
7967
+ const deserialize = args[2];
7968
+ const argument = args[3];
7969
+ let metadata;
7970
+ let options;
7971
+ if (args.length === 6) {
7972
+ metadata = args[4];
7973
+ options = args[5];
7974
+ } else if (args.length === 5) if (args[4] instanceof MetadataConstructor) {
7975
+ metadata = args[4];
7976
+ options = {};
7977
+ } else {
7978
+ metadata = new MetadataConstructor();
7979
+ options = args[4] || {};
7980
+ }
7981
+ else {
7982
+ metadata = new MetadataConstructor();
7983
+ options = {};
7984
+ }
7985
+ return {
7986
+ method,
7987
+ serialize,
7988
+ deserialize,
7989
+ argument,
7990
+ metadata,
7991
+ options
7992
+ };
7993
+ }
7719
7994
  _handleRecordUnaryRequest(spanInfo, original, context$1, parsedParams, callback) {
7720
7995
  let isResponseReceived = false;
7721
7996
  let isStatusEmitted = false;
@@ -7826,105 +8101,1160 @@ var GrpcInstrumentation = class GrpcInstrumentation extends TdInstrumentationBas
7826
8101
  isGrpcErrorOutput(result) {
7827
8102
  return "error" in result;
7828
8103
  }
7829
- async _handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor, stackTrace) {
8104
+ _handleReplayUnaryRequest(spanInfo, inputValue, callback, MetadataConstructor, stackTrace) {
7830
8105
  logger.debug(`[GrpcInstrumentation] Replaying gRPC unary request`);
7831
- const mockData = await findMockResponseAsync({
8106
+ const emitter = Object.assign(new EventEmitter(), {
8107
+ cancel() {},
8108
+ getPeer: () => "0.0.0.0:0000",
8109
+ call: void 0
8110
+ });
8111
+ findMockResponseAsync({
7832
8112
  mockRequestData: {
7833
8113
  traceId: spanInfo.traceId,
7834
8114
  spanId: spanInfo.spanId,
7835
8115
  name: "grpc.client.unary",
7836
8116
  inputValue,
7837
8117
  packageName: GRPC_MODULE_NAME,
8118
+ packageType: PackageType.GRPC,
7838
8119
  instrumentationName: this.INSTRUMENTATION_NAME,
7839
8120
  submoduleName: "client",
7840
8121
  kind: SpanKind.CLIENT,
7841
8122
  stackTrace
7842
8123
  },
7843
8124
  tuskDrift: this.tuskDrift
7844
- });
7845
- if (!mockData) {
7846
- logger.warn(`[GrpcInstrumentation] No mock data found for gRPC request: ${inputValue.service}/${inputValue.method}`);
7847
- callback(/* @__PURE__ */ new Error("No mock data found"));
7848
- SpanUtils.endSpan(spanInfo.span, {
7849
- code: SpanStatusCode.ERROR,
7850
- message: "No mock data found"
8125
+ }).then((mockData) => {
8126
+ if (!mockData) {
8127
+ logger.warn(`[GrpcInstrumentation] No mock data found for gRPC request: ${inputValue.service}/${inputValue.method}`, inputValue);
8128
+ callback(/* @__PURE__ */ new Error("No mock data found"));
8129
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.ERROR });
8130
+ return;
8131
+ }
8132
+ const mockResult = mockData.result;
8133
+ let status;
8134
+ if (this.isGrpcErrorOutput(mockResult)) {
8135
+ const { error, status: errorStatus } = mockResult;
8136
+ status = {
8137
+ code: errorStatus.code,
8138
+ details: errorStatus.details,
8139
+ metadata: deserializeGrpcMetadata(MetadataConstructor, errorStatus.metadata)
8140
+ };
8141
+ const errorObj = Object.assign(new Error(error.message), {
8142
+ name: error.name,
8143
+ stack: error.stack,
8144
+ ...status
8145
+ });
8146
+ callback(errorObj);
8147
+ } else {
8148
+ const { body, status: successStatus, bufferMap, jsonableStringMap } = mockResult;
8149
+ const bufferMapToUse = bufferMap || {};
8150
+ const jsonableStringMapToUse = jsonableStringMap || {};
8151
+ status = {
8152
+ code: successStatus.code,
8153
+ details: successStatus.details,
8154
+ metadata: deserializeGrpcMetadata(MetadataConstructor, successStatus.metadata)
8155
+ };
8156
+ const realResponse = deserializeGrpcPayload(body, bufferMapToUse, jsonableStringMapToUse);
8157
+ callback(null, realResponse);
8158
+ }
8159
+ process.nextTick(() => {
8160
+ if (mockResult.metadata) emitter.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
8161
+ emitter.emit("status", status);
7851
8162
  });
7852
- return;
7853
- }
7854
- const mockResult = mockData.result;
8163
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
8164
+ SpanUtils.endSpan(spanInfo.span, { code: mockResult.error ? SpanStatusCode.ERROR : SpanStatusCode.OK });
8165
+ }).catch((error) => {
8166
+ logger.error(`[GrpcInstrumentation] Error fetching mock data:`, error);
8167
+ callback(error);
8168
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.ERROR });
8169
+ });
8170
+ return emitter;
8171
+ }
8172
+ _handleRecordServerStreamRequest(spanInfo, original, context$1, parsedParams) {
8173
+ let hasErrorOccurred = false;
8174
+ let isSpanCompleted = false;
8175
+ let streamResponses = [];
7855
8176
  let status;
7856
- if (this.isGrpcErrorOutput(mockResult)) {
7857
- const { error, status: errorStatus } = mockResult;
7858
- status = {
7859
- code: errorStatus.code,
7860
- details: errorStatus.details,
7861
- metadata: deserializeGrpcMetadata(MetadataConstructor, errorStatus.metadata)
7862
- };
7863
- const errorObj = Object.assign(new Error(error.message), {
7864
- name: error.name,
7865
- stack: error.stack,
7866
- ...status
7867
- });
7868
- callback(errorObj);
7869
- } else {
7870
- const { body, status: successStatus, bufferMap, jsonableStringMap } = mockResult;
7871
- const bufferMapToUse = bufferMap || {};
7872
- const jsonableStringMapToUse = jsonableStringMap || {};
8177
+ let responseMetadataInitial = {};
8178
+ let serviceError;
8179
+ /**
8180
+ * Completes the span exactly once
8181
+ */
8182
+ const completeSpan = (output, statusCode, errorMessage) => {
8183
+ if (isSpanCompleted) return;
8184
+ isSpanCompleted = true;
8185
+ try {
8186
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: output });
8187
+ SpanUtils.endSpan(spanInfo.span, { code: statusCode });
8188
+ } catch (e) {
8189
+ logger.error(`[GrpcInstrumentation] Error completing span:`, e);
8190
+ }
8191
+ };
8192
+ const inputArgs = [
8193
+ parsedParams.method,
8194
+ parsedParams.serialize,
8195
+ parsedParams.deserialize,
8196
+ parsedParams.argument,
8197
+ parsedParams.metadata,
8198
+ parsedParams.options
8199
+ ];
8200
+ const stream = original.apply(context$1, inputArgs);
8201
+ stream.on("data", (data) => {
8202
+ try {
8203
+ const { readableBody, bufferMap, jsonableStringMap } = serializeGrpcPayload(data);
8204
+ streamResponses.push({
8205
+ body: readableBody,
8206
+ bufferMap,
8207
+ jsonableStringMap
8208
+ });
8209
+ } catch (e) {
8210
+ logger.error(`[GrpcInstrumentation] Error serializing stream data:`, e);
8211
+ }
8212
+ });
8213
+ stream.on("metadata", (initialMetadata) => {
8214
+ responseMetadataInitial = serializeGrpcMetadata(initialMetadata);
8215
+ });
8216
+ stream.on("error", (err) => {
8217
+ serviceError = err;
8218
+ hasErrorOccurred = true;
8219
+ });
8220
+ stream.on("status", (responseStatus) => {
7873
8221
  status = {
7874
- code: successStatus.code,
7875
- details: successStatus.details,
7876
- metadata: deserializeGrpcMetadata(MetadataConstructor, successStatus.metadata)
8222
+ code: responseStatus.code,
8223
+ details: responseStatus.details,
8224
+ metadata: serializeGrpcMetadata(responseStatus.metadata)
7877
8225
  };
7878
- const realResponse = deserializeGrpcPayload(body, bufferMapToUse, jsonableStringMapToUse);
7879
- callback(null, realResponse);
7880
- }
7881
- const emitter = Object.assign(new EventEmitter(), {
8226
+ if (!hasErrorOccurred && streamResponses.length > 0) completeSpan({
8227
+ body: streamResponses,
8228
+ metadata: responseMetadataInitial,
8229
+ status,
8230
+ bufferMap: {},
8231
+ jsonableStringMap: {}
8232
+ }, SpanStatusCode.OK);
8233
+ else if (!hasErrorOccurred && streamResponses.length === 0) completeSpan({
8234
+ body: [],
8235
+ metadata: responseMetadataInitial,
8236
+ status,
8237
+ bufferMap: {},
8238
+ jsonableStringMap: {}
8239
+ }, SpanStatusCode.OK);
8240
+ else if (hasErrorOccurred) {
8241
+ const errorOutput = {
8242
+ error: {
8243
+ message: serviceError.message,
8244
+ name: serviceError.name,
8245
+ stack: serviceError.stack
8246
+ },
8247
+ status,
8248
+ metadata: responseMetadataInitial
8249
+ };
8250
+ completeSpan(errorOutput, SpanStatusCode.ERROR, serviceError.message);
8251
+ }
8252
+ });
8253
+ return stream;
8254
+ }
8255
+ _handleReplayServerStreamRequest(spanInfo, inputValue, MetadataConstructor) {
8256
+ logger.debug(`[GrpcInstrumentation] Replaying gRPC server stream request`);
8257
+ const stream = new Readable({
8258
+ objectMode: true,
8259
+ read() {}
8260
+ });
8261
+ Object.assign(stream, {
7882
8262
  cancel() {},
7883
8263
  getPeer: () => "0.0.0.0:0000",
7884
8264
  call: void 0
7885
8265
  });
7886
- process.nextTick(() => {
7887
- if (mockResult.metadata) emitter.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
7888
- emitter.emit("status", status);
7889
- });
7890
- SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
7891
- SpanUtils.endSpan(spanInfo.span, { code: mockResult.error ? SpanStatusCode.ERROR : SpanStatusCode.OK });
7892
- return emitter;
7893
- }
7894
- _getWaitForReadyPatchFn() {
7895
- const self = this;
7896
- return (original) => {
7897
- return function waitForReady(deadline, callback) {
7898
- if (self.mode === TuskDriftMode.REPLAY) {
7899
- process.nextTick(() => callback());
7900
- return;
7901
- } else return original.apply(this, [deadline, callback]);
8266
+ findMockResponseAsync({
8267
+ mockRequestData: {
8268
+ traceId: spanInfo.traceId,
8269
+ spanId: spanInfo.spanId,
8270
+ name: "grpc.client.server_stream",
8271
+ inputValue,
8272
+ packageName: GRPC_MODULE_NAME,
8273
+ packageType: PackageType.GRPC,
8274
+ instrumentationName: this.INSTRUMENTATION_NAME,
8275
+ submoduleName: "client",
8276
+ kind: SpanKind.CLIENT
8277
+ },
8278
+ tuskDrift: this.tuskDrift
8279
+ }).then((mockData) => {
8280
+ if (!mockData) {
8281
+ logger.warn(`[GrpcInstrumentation] No mock data found for gRPC server stream request: ${inputValue.service}/${inputValue.method}`, inputValue);
8282
+ const error = /* @__PURE__ */ new Error("No mock data found");
8283
+ process.nextTick(() => {
8284
+ stream.emit("error", error);
8285
+ stream.emit("status", {
8286
+ code: 2,
8287
+ details: "No mock data found",
8288
+ metadata: new MetadataConstructor()
8289
+ });
8290
+ stream.push(null);
8291
+ });
8292
+ SpanUtils.endSpan(spanInfo.span, {
8293
+ code: SpanStatusCode.ERROR,
8294
+ message: "No mock data found"
8295
+ });
8296
+ return;
8297
+ }
8298
+ const mockResult = mockData.result;
8299
+ process.nextTick(() => {
8300
+ if (this.isGrpcErrorOutput(mockResult)) {
8301
+ const { error, status: errorStatus } = mockResult;
8302
+ const status = {
8303
+ code: errorStatus.code,
8304
+ details: errorStatus.details,
8305
+ metadata: deserializeGrpcMetadata(MetadataConstructor, errorStatus.metadata)
8306
+ };
8307
+ if (mockResult.metadata) stream.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
8308
+ const errorObj = Object.assign(new Error(error.message), {
8309
+ name: error.name,
8310
+ stack: error.stack,
8311
+ ...status
8312
+ });
8313
+ stream.emit("error", errorObj);
8314
+ stream.emit("status", status);
8315
+ stream.push(null);
8316
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
8317
+ SpanUtils.endSpan(spanInfo.span, {
8318
+ code: SpanStatusCode.ERROR,
8319
+ message: error.message
8320
+ });
8321
+ } else {
8322
+ const { body, status: successStatus } = mockResult;
8323
+ const status = {
8324
+ code: successStatus.code,
8325
+ details: successStatus.details,
8326
+ metadata: deserializeGrpcMetadata(MetadataConstructor, successStatus.metadata)
8327
+ };
8328
+ if (mockResult.metadata) stream.emit("metadata", deserializeGrpcMetadata(MetadataConstructor, mockResult.metadata));
8329
+ if (Array.isArray(body)) body.forEach((item) => {
8330
+ const bufferMapToUse = item.bufferMap || {};
8331
+ const jsonableStringMapToUse = item.jsonableStringMap || {};
8332
+ const realResponse = deserializeGrpcPayload(item.body, bufferMapToUse, jsonableStringMapToUse);
8333
+ stream.push(realResponse);
8334
+ });
8335
+ stream.push(null);
8336
+ stream.emit("status", status);
8337
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: mockResult });
8338
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
8339
+ }
8340
+ });
8341
+ }).catch((error) => {
8342
+ logger.error(`[GrpcInstrumentation] Error fetching mock data for server stream:`, error);
8343
+ process.nextTick(() => {
8344
+ stream.emit("error", error);
8345
+ stream.emit("status", {
8346
+ code: 2,
8347
+ details: error.message,
8348
+ metadata: new MetadataConstructor()
8349
+ });
8350
+ stream.push(null);
8351
+ });
8352
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.ERROR });
8353
+ });
8354
+ return stream;
8355
+ }
8356
+ _getWaitForReadyPatchFn() {
8357
+ const self = this;
8358
+ return (original) => {
8359
+ return function waitForReady(deadline, callback) {
8360
+ if (self.mode === TuskDriftMode.REPLAY) {
8361
+ process.nextTick(() => callback());
8362
+ return;
8363
+ } else return original.apply(this, [deadline, callback]);
8364
+ };
8365
+ };
8366
+ }
8367
+ _getClosePatchFn() {
8368
+ const self = this;
8369
+ return (original) => {
8370
+ return function close() {
8371
+ if (self.mode === TuskDriftMode.REPLAY) return;
8372
+ else return original.apply(this, arguments);
8373
+ };
8374
+ };
8375
+ }
8376
+ _getGetChannelPatchFn() {
8377
+ const self = this;
8378
+ return (original) => {
8379
+ return function getChannel() {
8380
+ if (self.mode === TuskDriftMode.REPLAY) return {};
8381
+ else return original.apply(this, arguments);
8382
+ };
8383
+ };
8384
+ }
8385
+ _wrap(target, propertyName, wrapper) {
8386
+ wrap(target, propertyName, wrapper);
8387
+ }
8388
+ };
8389
+ GrpcInstrumentation.metadataStore = /* @__PURE__ */ new Map();
8390
+
8391
+ //#endregion
8392
+ //#region src/instrumentation/libraries/firestore/mocks/TdFirestoreDocumentMock.ts
8393
+ /**
8394
+ * Mock Firestore DocumentSnapshot for replay mode
8395
+ * Mimics the Firestore DocumentSnapshot API
8396
+ */
8397
+ var TdFirestoreDocumentMock = class {
8398
+ constructor(documentData) {
8399
+ this.documentData = documentData;
8400
+ }
8401
+ /**
8402
+ * The document's identifier within its collection
8403
+ */
8404
+ get id() {
8405
+ return this.documentData.id;
8406
+ }
8407
+ /**
8408
+ * Whether the document exists
8409
+ */
8410
+ get exists() {
8411
+ return this.documentData.exists;
8412
+ }
8413
+ /**
8414
+ * A DocumentReference to the document location
8415
+ */
8416
+ get ref() {
8417
+ return {
8418
+ id: this.documentData.id,
8419
+ path: this.documentData.path
8420
+ };
8421
+ }
8422
+ /**
8423
+ * The time the document was created (if available)
8424
+ */
8425
+ get createTime() {
8426
+ return this.documentData.createTime ? {
8427
+ seconds: this.documentData.createTime.seconds,
8428
+ nanoseconds: this.documentData.createTime.nanoseconds,
8429
+ toDate: () => /* @__PURE__ */ new Date(this.documentData.createTime.seconds * 1e3 + this.documentData.createTime.nanoseconds / 1e6)
8430
+ } : null;
8431
+ }
8432
+ /**
8433
+ * The time the document was last updated (if available)
8434
+ */
8435
+ get updateTime() {
8436
+ return this.documentData.updateTime ? {
8437
+ seconds: this.documentData.updateTime.seconds,
8438
+ nanoseconds: this.documentData.updateTime.nanoseconds,
8439
+ toDate: () => /* @__PURE__ */ new Date(this.documentData.updateTime.seconds * 1e3 + this.documentData.updateTime.nanoseconds / 1e6)
8440
+ } : null;
8441
+ }
8442
+ /**
8443
+ * The time the document was read (if available)
8444
+ */
8445
+ get readTime() {
8446
+ return this.documentData.readTime ? {
8447
+ seconds: this.documentData.readTime.seconds,
8448
+ nanoseconds: this.documentData.readTime.nanoseconds,
8449
+ toDate: () => /* @__PURE__ */ new Date(this.documentData.readTime.seconds * 1e3 + this.documentData.readTime.nanoseconds / 1e6)
8450
+ } : null;
8451
+ }
8452
+ /**
8453
+ * Retrieves all fields in the document as an object
8454
+ */
8455
+ data() {
8456
+ return this.documentData.data;
8457
+ }
8458
+ /**
8459
+ * Retrieves the field specified by fieldPath
8460
+ */
8461
+ get(fieldPath) {
8462
+ if (!this.documentData.data) return;
8463
+ return this.documentData.data[fieldPath];
8464
+ }
8465
+ /**
8466
+ * Returns true if the document's data and path are equal to the provided value
8467
+ */
8468
+ isEqual(other) {
8469
+ return this.documentData.path === other.documentData.path;
8470
+ }
8471
+ };
8472
+
8473
+ //#endregion
8474
+ //#region src/instrumentation/libraries/firestore/mocks/TdFirestoreQueryMock.ts
8475
+ /**
8476
+ * Mock Firestore QuerySnapshot for replay mode
8477
+ * Mimics the Firestore QuerySnapshot API
8478
+ */
8479
+ var TdFirestoreQueryMock = class {
8480
+ constructor(queryResult) {
8481
+ this.queryResult = queryResult;
8482
+ this._docs = queryResult.docs.map((doc) => new TdFirestoreDocumentMock(doc));
8483
+ }
8484
+ /**
8485
+ * An array of all the documents in the QuerySnapshot
8486
+ */
8487
+ get docs() {
8488
+ return this._docs;
8489
+ }
8490
+ /**
8491
+ * The number of documents in the QuerySnapshot
8492
+ */
8493
+ get size() {
8494
+ return this.queryResult.size;
8495
+ }
8496
+ /**
8497
+ * True if there are no documents in the QuerySnapshot
8498
+ */
8499
+ get empty() {
8500
+ return this.queryResult.empty;
8501
+ }
8502
+ /**
8503
+ * The time the query snapshot was read
8504
+ */
8505
+ get readTime() {
8506
+ return this.queryResult.readTime ? {
8507
+ seconds: this.queryResult.readTime.seconds,
8508
+ nanoseconds: this.queryResult.readTime.nanoseconds,
8509
+ toDate: () => /* @__PURE__ */ new Date(this.queryResult.readTime.seconds * 1e3 + this.queryResult.readTime.nanoseconds / 1e6)
8510
+ } : null;
8511
+ }
8512
+ /**
8513
+ * The query on which you called get or onSnapshot
8514
+ */
8515
+ get query() {
8516
+ return {};
8517
+ }
8518
+ /**
8519
+ * Enumerates all of the documents in the QuerySnapshot
8520
+ */
8521
+ forEach(callback, thisArg) {
8522
+ this._docs.forEach(callback, thisArg);
8523
+ }
8524
+ /**
8525
+ * Returns an array of the documents changes since the last snapshot
8526
+ */
8527
+ docChanges() {
8528
+ return [];
8529
+ }
8530
+ /**
8531
+ * Returns true if the document data and path are equal to this QuerySnapshot
8532
+ */
8533
+ isEqual(other) {
8534
+ if (this.size !== other.size) return false;
8535
+ return this.size === other.size && this.empty === other.empty;
8536
+ }
8537
+ };
8538
+
8539
+ //#endregion
8540
+ //#region src/instrumentation/libraries/firestore/mocks/TdFirestoreWriteResultMock.ts
8541
+ /**
8542
+ * Mock Firestore WriteResult for replay mode
8543
+ * Mimics the Firestore WriteResult API
8544
+ */
8545
+ var TdFirestoreWriteResultMock = class {
8546
+ constructor(result) {
8547
+ this.result = result;
8548
+ }
8549
+ /**
8550
+ * The write time as reported by the server
8551
+ */
8552
+ get writeTime() {
8553
+ return this.result.writeTime ? {
8554
+ seconds: this.result.writeTime.seconds,
8555
+ nanoseconds: this.result.writeTime.nanoseconds,
8556
+ toDate: () => /* @__PURE__ */ new Date(this.result.writeTime.seconds * 1e3 + this.result.writeTime.nanoseconds / 1e6)
8557
+ } : null;
8558
+ }
8559
+ /**
8560
+ * Returns true if this WriteResult is equal to the provided one
8561
+ */
8562
+ isEqual(other) {
8563
+ if (!this.writeTime || !other.writeTime) return this.writeTime === other.writeTime;
8564
+ return this.writeTime.seconds === other.writeTime.seconds && this.writeTime.nanoseconds === other.writeTime.nanoseconds;
8565
+ }
8566
+ };
8567
+
8568
+ //#endregion
8569
+ //#region src/instrumentation/libraries/firestore/Instrumentation.ts
8570
+ const FIRESTORE_VERSION = "7.*";
8571
+ const PACKAGE_NAME = "@google-cloud/firestore";
8572
+ var FirestoreInstrumentation = class extends TdInstrumentationBase {
8573
+ constructor(config = {}) {
8574
+ super(PACKAGE_NAME, config);
8575
+ this.INSTRUMENTATION_NAME = "FirestoreInstrumentation";
8576
+ this.originalCollectionDocFn = null;
8577
+ this.mode = config.mode || TuskDriftMode.DISABLED;
8578
+ this.tuskDrift = TuskDriftCore.getInstance();
8579
+ }
8580
+ init() {
8581
+ return [new TdInstrumentationNodeModule({
8582
+ name: PACKAGE_NAME,
8583
+ supportedVersions: [FIRESTORE_VERSION],
8584
+ files: [
8585
+ new TdInstrumentationNodeModuleFile({
8586
+ name: "@google-cloud/firestore/build/src/reference/document-reference.js",
8587
+ supportedVersions: [FIRESTORE_VERSION],
8588
+ patch: (moduleExports) => this._patchDocumentReference(moduleExports)
8589
+ }),
8590
+ new TdInstrumentationNodeModuleFile({
8591
+ name: "@google-cloud/firestore/build/src/reference/collection-reference.js",
8592
+ supportedVersions: [FIRESTORE_VERSION],
8593
+ patch: (moduleExports) => this._patchCollectionReference(moduleExports)
8594
+ }),
8595
+ new TdInstrumentationNodeModuleFile({
8596
+ name: "@google-cloud/firestore/build/src/reference/query.js",
8597
+ supportedVersions: [FIRESTORE_VERSION],
8598
+ patch: (moduleExports) => this._patchQuery(moduleExports)
8599
+ })
8600
+ ]
8601
+ })];
8602
+ }
8603
+ _patchDocumentReference(moduleExports) {
8604
+ logger.debug(`[FirestoreInstrumentation] Patching DocumentReference in ${this.mode} mode`);
8605
+ if (this.isModulePatched(moduleExports)) {
8606
+ logger.debug(`[FirestoreInstrumentation] DocumentReference already patched, skipping`);
8607
+ return moduleExports;
8608
+ }
8609
+ const DocumentReference = moduleExports.DocumentReference;
8610
+ if (!DocumentReference || !DocumentReference.prototype) {
8611
+ logger.warn(`[FirestoreInstrumentation] DocumentReference.prototype not found`);
8612
+ return moduleExports;
8613
+ }
8614
+ this._wrap(DocumentReference.prototype, "get", this._getDocumentGetPatchFn());
8615
+ this._wrap(DocumentReference.prototype, "create", this._getDocumentCreatePatchFn());
8616
+ this._wrap(DocumentReference.prototype, "set", this._getDocumentSetPatchFn());
8617
+ this._wrap(DocumentReference.prototype, "update", this._getDocumentUpdatePatchFn());
8618
+ this._wrap(DocumentReference.prototype, "delete", this._getDocumentDeletePatchFn());
8619
+ this.markModuleAsPatched(moduleExports);
8620
+ logger.debug(`[FirestoreInstrumentation] DocumentReference patching complete`);
8621
+ return moduleExports;
8622
+ }
8623
+ _patchCollectionReference(moduleExports) {
8624
+ logger.debug(`[FirestoreInstrumentation] Patching CollectionReference in ${this.mode} mode`);
8625
+ if (this.isModulePatched(moduleExports)) {
8626
+ logger.debug(`[FirestoreInstrumentation] CollectionReference already patched, skipping`);
8627
+ return moduleExports;
8628
+ }
8629
+ const CollectionReference = moduleExports.CollectionReference;
8630
+ if (!CollectionReference || !CollectionReference.prototype) {
8631
+ logger.warn(`[FirestoreInstrumentation] CollectionReference.prototype not found`);
8632
+ return moduleExports;
8633
+ }
8634
+ this.originalCollectionDocFn = CollectionReference.prototype.doc;
8635
+ this._wrap(CollectionReference.prototype, "add", this._getCollectionAddPatchFn());
8636
+ this._wrap(CollectionReference.prototype, "doc", this._getCollectionDocPatchFn());
8637
+ this.markModuleAsPatched(moduleExports);
8638
+ logger.debug(`[FirestoreInstrumentation] CollectionReference patching complete`);
8639
+ return moduleExports;
8640
+ }
8641
+ _patchQuery(moduleExports) {
8642
+ logger.debug(`[FirestoreInstrumentation] Patching Query in ${this.mode} mode`);
8643
+ if (this.isModulePatched(moduleExports)) {
8644
+ logger.debug(`[FirestoreInstrumentation] Query already patched, skipping`);
8645
+ return moduleExports;
8646
+ }
8647
+ const Query = moduleExports.Query;
8648
+ if (!Query || !Query.prototype) {
8649
+ logger.warn(`[FirestoreInstrumentation] Query.prototype not found`);
8650
+ return moduleExports;
8651
+ }
8652
+ this._wrap(Query.prototype, "get", this._getQueryGetPatchFn());
8653
+ this.markModuleAsPatched(moduleExports);
8654
+ logger.debug(`[FirestoreInstrumentation] Query patching complete`);
8655
+ return moduleExports;
8656
+ }
8657
+ _getDocumentGetPatchFn() {
8658
+ const self = this;
8659
+ return (originalGet) => {
8660
+ return function() {
8661
+ const inputValue = {
8662
+ operation: "document.get",
8663
+ path: this.path
8664
+ };
8665
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
8666
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
8667
+ name: "firestore.document.get",
8668
+ kind: SpanKind.CLIENT,
8669
+ submodule: "document",
8670
+ packageType: PackageType.FIRESTORE,
8671
+ packageName: PACKAGE_NAME,
8672
+ instrumentationName: self.INSTRUMENTATION_NAME,
8673
+ inputValue,
8674
+ isPreAppStart: false,
8675
+ stopRecordingChildSpans: true
8676
+ }, (spanInfo) => {
8677
+ return self._handleReplayDocumentGet(spanInfo, inputValue);
8678
+ });
8679
+ } });
8680
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
8681
+ originalFunctionCall: () => originalGet.call(this),
8682
+ recordModeHandler: ({ isPreAppStart }) => {
8683
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
8684
+ name: "firestore.document.get",
8685
+ kind: SpanKind.CLIENT,
8686
+ submodule: "document",
8687
+ packageType: PackageType.FIRESTORE,
8688
+ packageName: PACKAGE_NAME,
8689
+ instrumentationName: self.INSTRUMENTATION_NAME,
8690
+ inputValue,
8691
+ isPreAppStart,
8692
+ stopRecordingChildSpans: true
8693
+ }, (spanInfo) => {
8694
+ return self._handleRecordDocumentGet(spanInfo, originalGet, this);
8695
+ });
8696
+ },
8697
+ spanKind: SpanKind.CLIENT
8698
+ });
8699
+ else return originalGet.call(this);
8700
+ };
8701
+ };
8702
+ }
8703
+ async _handleRecordDocumentGet(spanInfo, originalGet, context$1) {
8704
+ const snapshot = await originalGet.call(context$1);
8705
+ const documentResult = {
8706
+ id: snapshot.id,
8707
+ path: snapshot.ref.path,
8708
+ exists: snapshot.exists,
8709
+ data: snapshot.exists ? snapshot.data() : void 0,
8710
+ createTime: snapshot.createTime ? {
8711
+ seconds: snapshot.createTime.seconds,
8712
+ nanoseconds: snapshot.createTime.nanoseconds
8713
+ } : void 0,
8714
+ updateTime: snapshot.updateTime ? {
8715
+ seconds: snapshot.updateTime.seconds,
8716
+ nanoseconds: snapshot.updateTime.nanoseconds
8717
+ } : void 0,
8718
+ readTime: snapshot.readTime ? {
8719
+ seconds: snapshot.readTime.seconds,
8720
+ nanoseconds: snapshot.readTime.nanoseconds
8721
+ } : void 0
8722
+ };
8723
+ try {
8724
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: documentResult });
8725
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
8726
+ } catch {
8727
+ logger.error(`[FirestoreInstrumentation] Error updating span attributes for document.get`);
8728
+ }
8729
+ return snapshot;
8730
+ }
8731
+ async _handleReplayDocumentGet(spanInfo, inputValue) {
8732
+ logger.debug(`[FirestoreInstrumentation] Replaying document.get`);
8733
+ const mockData = await findMockResponseAsync({
8734
+ mockRequestData: {
8735
+ traceId: spanInfo.traceId,
8736
+ spanId: spanInfo.spanId,
8737
+ name: "firestore.document.get",
8738
+ inputValue: createMockInputValue(inputValue),
8739
+ packageName: PACKAGE_NAME,
8740
+ instrumentationName: this.INSTRUMENTATION_NAME,
8741
+ submoduleName: "document",
8742
+ kind: SpanKind.CLIENT
8743
+ },
8744
+ tuskDrift: this.tuskDrift
8745
+ });
8746
+ if (!mockData) {
8747
+ logger.warn(`[FirestoreInstrumentation] No mock data found for document.get: ${inputValue.path}`);
8748
+ return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
8749
+ }
8750
+ const documentResult = mockData.result;
8751
+ return new TdFirestoreDocumentMock(documentResult);
8752
+ }
8753
+ _getDocumentCreatePatchFn() {
8754
+ const self = this;
8755
+ return (originalCreate) => {
8756
+ return function(data) {
8757
+ const inputValue = {
8758
+ operation: "document.create",
8759
+ path: this.path,
8760
+ data
8761
+ };
8762
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
8763
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalCreate.call(this, data), {
8764
+ name: "firestore.document.create",
8765
+ kind: SpanKind.CLIENT,
8766
+ submodule: "document",
8767
+ packageType: PackageType.FIRESTORE,
8768
+ packageName: PACKAGE_NAME,
8769
+ instrumentationName: self.INSTRUMENTATION_NAME,
8770
+ inputValue,
8771
+ isPreAppStart: false,
8772
+ stopRecordingChildSpans: true
8773
+ }, (spanInfo) => {
8774
+ return self._handleReplayDocumentWrite(spanInfo, inputValue);
8775
+ });
8776
+ } });
8777
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
8778
+ originalFunctionCall: () => originalCreate.call(this, data),
8779
+ recordModeHandler: ({ isPreAppStart }) => {
8780
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalCreate.call(this, data), {
8781
+ name: "firestore.document.create",
8782
+ kind: SpanKind.CLIENT,
8783
+ submodule: "document",
8784
+ packageType: PackageType.FIRESTORE,
8785
+ packageName: PACKAGE_NAME,
8786
+ instrumentationName: self.INSTRUMENTATION_NAME,
8787
+ inputValue,
8788
+ isPreAppStart,
8789
+ stopRecordingChildSpans: true
8790
+ }, (spanInfo) => {
8791
+ return self._handleRecordDocumentWrite(spanInfo, originalCreate, this, data);
8792
+ });
8793
+ },
8794
+ spanKind: SpanKind.CLIENT
8795
+ });
8796
+ else return originalCreate.call(this, data);
8797
+ };
8798
+ };
8799
+ }
8800
+ _getDocumentSetPatchFn() {
8801
+ const self = this;
8802
+ return (originalSet) => {
8803
+ return function(data, options) {
8804
+ const inputValue = {
8805
+ operation: "document.set",
8806
+ path: this.path,
8807
+ data,
8808
+ options
8809
+ };
8810
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
8811
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalSet.call(this, data, options), {
8812
+ name: "firestore.document.set",
8813
+ kind: SpanKind.CLIENT,
8814
+ submodule: "document",
8815
+ packageType: PackageType.FIRESTORE,
8816
+ packageName: PACKAGE_NAME,
8817
+ instrumentationName: self.INSTRUMENTATION_NAME,
8818
+ inputValue,
8819
+ isPreAppStart: false,
8820
+ stopRecordingChildSpans: true
8821
+ }, (spanInfo) => {
8822
+ return self._handleReplayDocumentWrite(spanInfo, inputValue);
8823
+ });
8824
+ } });
8825
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
8826
+ originalFunctionCall: () => originalSet.call(this, data, options),
8827
+ recordModeHandler: ({ isPreAppStart }) => {
8828
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalSet.call(this, data, options), {
8829
+ name: "firestore.document.set",
8830
+ kind: SpanKind.CLIENT,
8831
+ submodule: "document",
8832
+ packageType: PackageType.FIRESTORE,
8833
+ packageName: PACKAGE_NAME,
8834
+ instrumentationName: self.INSTRUMENTATION_NAME,
8835
+ inputValue,
8836
+ isPreAppStart,
8837
+ stopRecordingChildSpans: true
8838
+ }, (spanInfo) => {
8839
+ return self._handleRecordDocumentWrite(spanInfo, originalSet, this, data, options);
8840
+ });
8841
+ },
8842
+ spanKind: SpanKind.CLIENT
8843
+ });
8844
+ else return originalSet.call(this, data, options);
8845
+ };
8846
+ };
8847
+ }
8848
+ _getDocumentUpdatePatchFn() {
8849
+ const self = this;
8850
+ return (originalUpdate) => {
8851
+ return function(...args) {
8852
+ const inputValue = {
8853
+ operation: "document.update",
8854
+ path: this.path,
8855
+ data: args.length === 1 ? args[0] : args
8856
+ };
8857
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
8858
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalUpdate.apply(this, args), {
8859
+ name: "firestore.document.update",
8860
+ kind: SpanKind.CLIENT,
8861
+ submodule: "document",
8862
+ packageType: PackageType.FIRESTORE,
8863
+ packageName: PACKAGE_NAME,
8864
+ instrumentationName: self.INSTRUMENTATION_NAME,
8865
+ inputValue,
8866
+ isPreAppStart: false,
8867
+ stopRecordingChildSpans: true
8868
+ }, (spanInfo) => {
8869
+ return self._handleReplayDocumentWrite(spanInfo, inputValue);
8870
+ });
8871
+ } });
8872
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
8873
+ originalFunctionCall: () => originalUpdate.apply(this, args),
8874
+ recordModeHandler: ({ isPreAppStart }) => {
8875
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalUpdate.apply(this, args), {
8876
+ name: "firestore.document.update",
8877
+ kind: SpanKind.CLIENT,
8878
+ submodule: "document",
8879
+ packageType: PackageType.FIRESTORE,
8880
+ packageName: PACKAGE_NAME,
8881
+ instrumentationName: self.INSTRUMENTATION_NAME,
8882
+ inputValue,
8883
+ isPreAppStart,
8884
+ stopRecordingChildSpans: true
8885
+ }, (spanInfo) => {
8886
+ return self._handleRecordDocumentWrite(spanInfo, originalUpdate, this, ...args);
8887
+ });
8888
+ },
8889
+ spanKind: SpanKind.CLIENT
8890
+ });
8891
+ else return originalUpdate.apply(this, args);
7902
8892
  };
7903
8893
  };
7904
8894
  }
7905
- _getClosePatchFn() {
8895
+ _getDocumentDeletePatchFn() {
7906
8896
  const self = this;
7907
- return (original) => {
7908
- return function close() {
7909
- if (self.mode === TuskDriftMode.REPLAY) return;
7910
- else return original.apply(this, arguments);
8897
+ return (originalDelete) => {
8898
+ return function(precondition) {
8899
+ const inputValue = {
8900
+ operation: "document.delete",
8901
+ path: this.path,
8902
+ options: precondition
8903
+ };
8904
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
8905
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalDelete.call(this, precondition), {
8906
+ name: "firestore.document.delete",
8907
+ kind: SpanKind.CLIENT,
8908
+ submodule: "document",
8909
+ packageType: PackageType.FIRESTORE,
8910
+ packageName: PACKAGE_NAME,
8911
+ instrumentationName: self.INSTRUMENTATION_NAME,
8912
+ inputValue,
8913
+ isPreAppStart: false,
8914
+ stopRecordingChildSpans: true
8915
+ }, (spanInfo) => {
8916
+ return self._handleReplayDocumentWrite(spanInfo, inputValue);
8917
+ });
8918
+ } });
8919
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
8920
+ originalFunctionCall: () => originalDelete.call(this, precondition),
8921
+ recordModeHandler: ({ isPreAppStart }) => {
8922
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalDelete.call(this, precondition), {
8923
+ name: "firestore.document.delete",
8924
+ kind: SpanKind.CLIENT,
8925
+ submodule: "document",
8926
+ packageType: PackageType.FIRESTORE,
8927
+ packageName: PACKAGE_NAME,
8928
+ instrumentationName: self.INSTRUMENTATION_NAME,
8929
+ inputValue,
8930
+ isPreAppStart,
8931
+ stopRecordingChildSpans: true
8932
+ }, (spanInfo) => {
8933
+ return self._handleRecordDocumentWrite(spanInfo, originalDelete, this, precondition);
8934
+ });
8935
+ },
8936
+ spanKind: SpanKind.CLIENT
8937
+ });
8938
+ else return originalDelete.call(this, precondition);
7911
8939
  };
7912
8940
  };
7913
8941
  }
7914
- _getGetChannelPatchFn() {
8942
+ async _handleRecordDocumentWrite(spanInfo, originalWrite, context$1, ...args) {
8943
+ const writeResult = await originalWrite.apply(context$1, args);
8944
+ const result = { writeTime: writeResult.writeTime ? {
8945
+ seconds: writeResult.writeTime.seconds,
8946
+ nanoseconds: writeResult.writeTime.nanoseconds
8947
+ } : void 0 };
8948
+ try {
8949
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: result });
8950
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
8951
+ } catch {
8952
+ logger.error(`[FirestoreInstrumentation] Error updating span attributes for document.write`);
8953
+ }
8954
+ return writeResult;
8955
+ }
8956
+ async _handleReplayDocumentWrite(spanInfo, inputValue) {
8957
+ logger.debug(`[FirestoreInstrumentation] Replaying document write: ${inputValue.operation}`);
8958
+ const mockData = await findMockResponseAsync({
8959
+ mockRequestData: {
8960
+ traceId: spanInfo.traceId,
8961
+ spanId: spanInfo.spanId,
8962
+ name: `firestore.${inputValue.operation}`,
8963
+ inputValue: createMockInputValue(inputValue),
8964
+ packageName: PACKAGE_NAME,
8965
+ instrumentationName: this.INSTRUMENTATION_NAME,
8966
+ submoduleName: "document",
8967
+ kind: SpanKind.CLIENT
8968
+ },
8969
+ tuskDrift: this.tuskDrift
8970
+ });
8971
+ if (!mockData) {
8972
+ logger.warn(`[FirestoreInstrumentation] No mock data found for ${inputValue.operation}: ${inputValue.path}`);
8973
+ return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
8974
+ }
8975
+ const writeResult = mockData.result;
8976
+ return new TdFirestoreWriteResultMock(writeResult);
8977
+ }
8978
+ _getCollectionAddPatchFn() {
7915
8979
  const self = this;
7916
- return (original) => {
7917
- return function getChannel() {
7918
- if (self.mode === TuskDriftMode.REPLAY) return {};
7919
- else return original.apply(this, arguments);
8980
+ return (originalAdd) => {
8981
+ return function(data) {
8982
+ const inputValue = {
8983
+ operation: "collection.add",
8984
+ path: this.path,
8985
+ data
8986
+ };
8987
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
8988
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalAdd.call(this, data), {
8989
+ name: "firestore.collection.add",
8990
+ kind: SpanKind.CLIENT,
8991
+ submodule: "collection",
8992
+ packageType: PackageType.FIRESTORE,
8993
+ packageName: PACKAGE_NAME,
8994
+ instrumentationName: self.INSTRUMENTATION_NAME,
8995
+ inputValue,
8996
+ isPreAppStart: false,
8997
+ stopRecordingChildSpans: true
8998
+ }, (spanInfo) => {
8999
+ return self._handleReplayCollectionAdd(spanInfo, inputValue, this);
9000
+ });
9001
+ } });
9002
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
9003
+ originalFunctionCall: () => originalAdd.call(this, data),
9004
+ recordModeHandler: ({ isPreAppStart }) => {
9005
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalAdd.call(this, data), {
9006
+ name: "firestore.collection.add",
9007
+ kind: SpanKind.CLIENT,
9008
+ submodule: "collection",
9009
+ packageType: PackageType.FIRESTORE,
9010
+ packageName: PACKAGE_NAME,
9011
+ instrumentationName: self.INSTRUMENTATION_NAME,
9012
+ inputValue,
9013
+ isPreAppStart,
9014
+ stopRecordingChildSpans: true
9015
+ }, (spanInfo) => {
9016
+ return self._handleRecordCollectionAdd(spanInfo, originalAdd, this, data);
9017
+ });
9018
+ },
9019
+ spanKind: SpanKind.CLIENT
9020
+ });
9021
+ else return originalAdd.call(this, data);
9022
+ };
9023
+ };
9024
+ }
9025
+ async _handleRecordCollectionAdd(spanInfo, originalAdd, context$1, data) {
9026
+ const docRef = await originalAdd.call(context$1, data);
9027
+ const result = {
9028
+ id: docRef.id,
9029
+ path: docRef.path
9030
+ };
9031
+ try {
9032
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: result });
9033
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
9034
+ } catch {
9035
+ logger.error(`[FirestoreInstrumentation] Error updating span attributes for collection.add`);
9036
+ }
9037
+ return docRef;
9038
+ }
9039
+ async _handleReplayCollectionAdd(spanInfo, inputValue, collectionRef) {
9040
+ logger.debug(`[FirestoreInstrumentation] Replaying collection.add`);
9041
+ const mockData = await findMockResponseAsync({
9042
+ mockRequestData: {
9043
+ traceId: spanInfo.traceId,
9044
+ spanId: spanInfo.spanId,
9045
+ name: "firestore.collection.add",
9046
+ inputValue: createMockInputValue(inputValue),
9047
+ packageName: PACKAGE_NAME,
9048
+ instrumentationName: this.INSTRUMENTATION_NAME,
9049
+ submoduleName: "collection",
9050
+ kind: SpanKind.CLIENT
9051
+ },
9052
+ tuskDrift: this.tuskDrift
9053
+ });
9054
+ if (!mockData) {
9055
+ logger.warn(`[FirestoreInstrumentation] No mock data found for collection.add: ${inputValue.path}`);
9056
+ return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
9057
+ }
9058
+ const recordedId = mockData.result.id;
9059
+ if (!this.originalCollectionDocFn) {
9060
+ logger.error(`[FirestoreInstrumentation] Original doc function not available`);
9061
+ return Promise.reject(/* @__PURE__ */ new Error("Original doc function not available"));
9062
+ }
9063
+ return this.originalCollectionDocFn.call(collectionRef, recordedId);
9064
+ }
9065
+ _getCollectionDocPatchFn() {
9066
+ const self = this;
9067
+ return (originalDoc) => {
9068
+ return function(documentPath) {
9069
+ const collectionPath = this.path;
9070
+ const inputValue = {
9071
+ operation: "collection.doc",
9072
+ path: collectionPath,
9073
+ documentId: documentPath
9074
+ };
9075
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
9076
+ return SpanUtils.createAndExecuteSpan(self.mode, () => documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this), {
9077
+ name: "firestore.collection.doc",
9078
+ kind: SpanKind.CLIENT,
9079
+ submodule: "collection",
9080
+ packageType: PackageType.FIRESTORE,
9081
+ packageName: PACKAGE_NAME,
9082
+ instrumentationName: self.INSTRUMENTATION_NAME,
9083
+ inputValue,
9084
+ isPreAppStart: false,
9085
+ stopRecordingChildSpans: true
9086
+ }, (spanInfo) => {
9087
+ const mockData = findMockResponseSync({
9088
+ mockRequestData: {
9089
+ traceId: spanInfo.traceId,
9090
+ spanId: spanInfo.spanId,
9091
+ name: "firestore.collection.doc",
9092
+ inputValue: createMockInputValue(inputValue),
9093
+ packageName: PACKAGE_NAME,
9094
+ instrumentationName: self.INSTRUMENTATION_NAME,
9095
+ submoduleName: "collection",
9096
+ kind: SpanKind.CLIENT
9097
+ },
9098
+ tuskDrift: self.tuskDrift
9099
+ });
9100
+ if (!mockData) {
9101
+ logger.warn(`[FirestoreInstrumentation] No mock data found for collection.doc: ${collectionPath}`);
9102
+ throw new Error("No mock data found for collection.doc");
9103
+ }
9104
+ const recordedId = mockData.result.id;
9105
+ logger.debug(`[FirestoreInstrumentation] replaying doc call with recorded id: ${recordedId}`);
9106
+ const docRef = originalDoc.call(this, recordedId);
9107
+ logger.debug(`[FirestoreInstrumentation] doc ref, id`, docRef, recordedId);
9108
+ return docRef;
9109
+ });
9110
+ } });
9111
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
9112
+ originalFunctionCall: () => documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this),
9113
+ recordModeHandler: ({ isPreAppStart }) => {
9114
+ return SpanUtils.createAndExecuteSpan(self.mode, () => documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this), {
9115
+ name: "firestore.collection.doc",
9116
+ kind: SpanKind.CLIENT,
9117
+ submodule: "collection",
9118
+ packageType: PackageType.FIRESTORE,
9119
+ packageName: PACKAGE_NAME,
9120
+ instrumentationName: self.INSTRUMENTATION_NAME,
9121
+ inputValue,
9122
+ isPreAppStart,
9123
+ stopRecordingChildSpans: true
9124
+ }, (spanInfo) => {
9125
+ const docRef = documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this);
9126
+ const result = {
9127
+ id: docRef.id,
9128
+ path: docRef.path
9129
+ };
9130
+ try {
9131
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: result });
9132
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
9133
+ } catch {
9134
+ logger.error(`[FirestoreInstrumentation] Error updating span attributes for collection.doc`);
9135
+ }
9136
+ return docRef;
9137
+ });
9138
+ },
9139
+ spanKind: SpanKind.CLIENT
9140
+ });
9141
+ else return documentPath ? originalDoc.call(this, documentPath) : originalDoc.call(this);
9142
+ };
9143
+ };
9144
+ }
9145
+ _getQueryGetPatchFn() {
9146
+ const self = this;
9147
+ return (originalGet) => {
9148
+ return function() {
9149
+ const inputValue = {
9150
+ operation: "query.get",
9151
+ path: this._queryOptions?.parentPath?.formattedName || "unknown"
9152
+ };
9153
+ if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({ replayModeHandler: () => {
9154
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
9155
+ name: "firestore.query.get",
9156
+ kind: SpanKind.CLIENT,
9157
+ submodule: "query",
9158
+ packageType: PackageType.FIRESTORE,
9159
+ packageName: PACKAGE_NAME,
9160
+ instrumentationName: self.INSTRUMENTATION_NAME,
9161
+ inputValue,
9162
+ isPreAppStart: false,
9163
+ stopRecordingChildSpans: true
9164
+ }, (spanInfo) => {
9165
+ return self._handleReplayQueryGet(spanInfo, inputValue);
9166
+ });
9167
+ } });
9168
+ else if (self.mode === TuskDriftMode.RECORD) return handleRecordMode({
9169
+ originalFunctionCall: () => originalGet.call(this),
9170
+ recordModeHandler: ({ isPreAppStart }) => {
9171
+ return SpanUtils.createAndExecuteSpan(self.mode, () => originalGet.call(this), {
9172
+ name: "firestore.query.get",
9173
+ kind: SpanKind.CLIENT,
9174
+ submodule: "query",
9175
+ packageType: PackageType.FIRESTORE,
9176
+ packageName: PACKAGE_NAME,
9177
+ instrumentationName: self.INSTRUMENTATION_NAME,
9178
+ inputValue,
9179
+ isPreAppStart,
9180
+ stopRecordingChildSpans: true
9181
+ }, (spanInfo) => {
9182
+ return self._handleRecordQueryGet(spanInfo, originalGet, this);
9183
+ });
9184
+ },
9185
+ spanKind: SpanKind.CLIENT
9186
+ });
9187
+ else return originalGet.call(this);
7920
9188
  };
7921
9189
  };
7922
9190
  }
9191
+ async _handleRecordQueryGet(spanInfo, originalGet, context$1) {
9192
+ const querySnapshot = await originalGet.call(context$1);
9193
+ const queryResult = {
9194
+ docs: querySnapshot.docs.map((doc) => ({
9195
+ id: doc.id,
9196
+ path: doc.ref.path,
9197
+ exists: doc.exists,
9198
+ data: doc.exists ? doc.data() : void 0,
9199
+ createTime: doc.createTime ? {
9200
+ seconds: doc.createTime.seconds,
9201
+ nanoseconds: doc.createTime.nanoseconds
9202
+ } : void 0,
9203
+ updateTime: doc.updateTime ? {
9204
+ seconds: doc.updateTime.seconds,
9205
+ nanoseconds: doc.updateTime.nanoseconds
9206
+ } : void 0,
9207
+ readTime: doc.readTime ? {
9208
+ seconds: doc.readTime.seconds,
9209
+ nanoseconds: doc.readTime.nanoseconds
9210
+ } : void 0
9211
+ })),
9212
+ size: querySnapshot.size,
9213
+ empty: querySnapshot.empty,
9214
+ readTime: querySnapshot.readTime ? {
9215
+ seconds: querySnapshot.readTime.seconds,
9216
+ nanoseconds: querySnapshot.readTime.nanoseconds
9217
+ } : void 0
9218
+ };
9219
+ try {
9220
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: queryResult });
9221
+ SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
9222
+ } catch {
9223
+ logger.error(`[FirestoreInstrumentation] Error updating span attributes for query.get`);
9224
+ }
9225
+ return querySnapshot;
9226
+ }
9227
+ async _handleReplayQueryGet(spanInfo, inputValue) {
9228
+ logger.debug(`[FirestoreInstrumentation] Replaying query.get`);
9229
+ const mockData = await findMockResponseAsync({
9230
+ mockRequestData: {
9231
+ traceId: spanInfo.traceId,
9232
+ spanId: spanInfo.spanId,
9233
+ name: "firestore.query.get",
9234
+ inputValue: createMockInputValue(inputValue),
9235
+ packageName: PACKAGE_NAME,
9236
+ instrumentationName: this.INSTRUMENTATION_NAME,
9237
+ submoduleName: "query",
9238
+ kind: SpanKind.CLIENT
9239
+ },
9240
+ tuskDrift: this.tuskDrift
9241
+ });
9242
+ if (!mockData) {
9243
+ logger.warn(`[FirestoreInstrumentation] No mock data found for query.get: ${inputValue.path}`);
9244
+ return Promise.reject(/* @__PURE__ */ new Error("No mock data found"));
9245
+ }
9246
+ const queryResult = mockData.result;
9247
+ return new TdFirestoreQueryMock(queryResult);
9248
+ }
7923
9249
  _wrap(target, propertyName, wrapper) {
7924
- wrap(target, propertyName, wrapper);
9250
+ if (!target || typeof target[propertyName] !== "function") {
9251
+ logger.warn(`[FirestoreInstrumentation] Cannot wrap ${propertyName}: not a function or target is undefined`);
9252
+ return;
9253
+ }
9254
+ const original = target[propertyName];
9255
+ target[propertyName] = wrapper(original);
7925
9256
  }
7926
9257
  };
7927
- GrpcInstrumentation.metadataStore = /* @__PURE__ */ new Map();
7928
9258
 
7929
9259
  //#endregion
7930
9260
  //#region src/instrumentation/libraries/nextjs/Instrumentation.ts
@@ -7984,6 +9314,12 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
7984
9314
  const self = this;
7985
9315
  return (originalHandleRequest) => {
7986
9316
  return async function(req, res, parsedUrl) {
9317
+ if (self.mode === TuskDriftMode.RECORD) {
9318
+ if (!shouldSample({
9319
+ samplingRate: self.tuskDrift.getSamplingRate(),
9320
+ isAppReady: self.tuskDrift.isAppReady()
9321
+ })) return originalHandleRequest.call(this, req, res, parsedUrl);
9322
+ }
7987
9323
  const method = req.method || "GET";
7988
9324
  const url = req.url || "/";
7989
9325
  logger.debug(`[NextjsInstrumentation] Intercepted Next.js request: ${method} ${url}`);
@@ -7999,6 +9335,7 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
7999
9335
  logger.debug(`[NextjsInstrumentation] No trace ID found, calling original handler`);
8000
9336
  return originalHandleRequest.call(this, req, res, parsedUrl);
8001
9337
  }
9338
+ logger.debug(`[NextjsInstrumentation] Setting replay trace id`, replayTraceId);
8002
9339
  const envVars = self.replayHooks.extractEnvVarsFromHeaders(req);
8003
9340
  if (envVars) EnvVarTracker.setEnvVars(replayTraceId, envVars);
8004
9341
  const ctxWithReplayTraceId = SpanUtils.setCurrentReplayTraceId(replayTraceId);
@@ -8029,13 +9366,6 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
8029
9366
  } });
8030
9367
  else if (self.mode === TuskDriftMode.RECORD) {
8031
9368
  if (method.toUpperCase() === "OPTIONS" || !!req.headers["access-control-request-method"]) return originalHandleRequest.call(this, req, res, parsedUrl);
8032
- if (!shouldSample({
8033
- samplingRate: self.tuskDrift.getSamplingRate(),
8034
- isAppReady: self.tuskDrift.isAppReady()
8035
- })) {
8036
- logger.debug(`[NextjsInstrumentation] Skipping server span due to sampling rate: ${url}`);
8037
- return originalHandleRequest.call(this, req, res, parsedUrl);
8038
- }
8039
9369
  logger.debug(`[NextjsInstrumentation] Creating server span for ${method} ${url}`);
8040
9370
  return handleRecordMode({
8041
9371
  originalFunctionCall: () => originalHandleRequest.call(this, req, res, parsedUrl),
@@ -8114,6 +9444,19 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
8114
9444
  };
8115
9445
  try {
8116
9446
  await originalHandleRequest.call(thisContext, req, res, parsedUrl);
9447
+ } catch (error) {
9448
+ logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message : "Unknown error"}`);
9449
+ try {
9450
+ SpanUtils.endSpan(spanInfo.span, {
9451
+ code: SpanStatusCode.ERROR,
9452
+ message: error instanceof Error ? error.message : "Unknown error"
9453
+ });
9454
+ } catch (e) {
9455
+ logger.error(`[NextjsInstrumentation] Error ending span:`, e);
9456
+ }
9457
+ throw error;
9458
+ }
9459
+ try {
8117
9460
  if (!capturedStatusCode) {
8118
9461
  capturedStatusCode = res.statusCode;
8119
9462
  capturedStatusMessage = res.statusMessage;
@@ -8138,7 +9481,6 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
8138
9481
  outputValue.bodySize = responseBuffer.length;
8139
9482
  } catch (error) {
8140
9483
  logger.error(`[NextjsInstrumentation] Error processing response body:`, error);
8141
- outputValue.bodyProcessingError = error instanceof Error ? error.message : String(error);
8142
9484
  }
8143
9485
  SpanUtils.addSpanAttributes(spanInfo.span, {
8144
9486
  inputValue: completeInputValue,
@@ -8158,6 +9500,11 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
8158
9500
  message: `HTTP ${capturedStatusCode}`
8159
9501
  } : { code: SpanStatusCode.OK };
8160
9502
  SpanUtils.setStatus(spanInfo.span, status);
9503
+ const decodedType = getDecodedType(outputValue.headers?.["content-type"] || "");
9504
+ if (decodedType && STATIC_ASSET_TYPES.has(decodedType)) {
9505
+ TraceBlockingManager.getInstance().blockTrace(spanInfo.traceId);
9506
+ logger.debug(`[NextjsInstrumentation] Blocking trace ${spanInfo.traceId} because it is an static asset response. Decoded type: ${decodedType}`);
9507
+ }
8161
9508
  SpanUtils.endSpan(spanInfo.span);
8162
9509
  if (self.mode === TuskDriftMode.REPLAY) try {
8163
9510
  const now = OriginalGlobalUtils.getOriginalDate();
@@ -8215,12 +9562,15 @@ var NextjsInstrumentation = class extends TdInstrumentationBase {
8215
9562
  logger.error("[NextjsInstrumentation] Failed to build/send inbound replay span:", e);
8216
9563
  }
8217
9564
  } catch (error) {
8218
- logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message : String(error)}`);
8219
- SpanUtils.endSpan(spanInfo.span, {
8220
- code: SpanStatusCode.ERROR,
8221
- message: error instanceof Error ? error.message : String(error)
8222
- });
8223
- throw error;
9565
+ logger.error(`[NextjsInstrumentation] Error in Next.js request: ${error instanceof Error ? error.message : "Unknown error"}`);
9566
+ try {
9567
+ SpanUtils.endSpan(spanInfo.span, {
9568
+ code: SpanStatusCode.ERROR,
9569
+ message: error instanceof Error ? error.message : "Unknown error"
9570
+ });
9571
+ } catch (e) {
9572
+ logger.error(`[NextjsInstrumentation] Error ending span:`, e);
9573
+ }
8224
9574
  }
8225
9575
  }
8226
9576
  /**
@@ -10447,20 +11797,21 @@ var SpanTransformer = class SpanTransformer {
10447
11797
  const inputData = JSON.parse(inputValueString);
10448
11798
  const inputSchemaMergesString = attributes[TdSpanAttributes.INPUT_SCHEMA_MERGES];
10449
11799
  const inputSchemaMerges = inputSchemaMergesString ? JSON.parse(inputSchemaMergesString) : void 0;
10450
- const { schema: inputSchema, decodedValueHash: inputValueHash } = JsonSchemaHelper.generateSchemaAndHash(inputData, inputSchemaMerges);
11800
+ const { schema: inputSchema, decodedValueHash: inputValueHash, decodedSchemaHash: inputSchemaHash } = JsonSchemaHelper.generateSchemaAndHash(inputData, inputSchemaMerges);
10451
11801
  let outputData = {};
10452
11802
  let outputSchema = {
10453
11803
  type: JsonSchemaType.OBJECT,
10454
11804
  properties: {}
10455
11805
  };
10456
11806
  let outputValueHash = "";
11807
+ let outputSchemaHash = "";
10457
11808
  if (attributes[TdSpanAttributes.OUTPUT_VALUE]) {
10458
11809
  const outputValueString = attributes[TdSpanAttributes.OUTPUT_VALUE];
10459
11810
  outputData = JSON.parse(outputValueString);
10460
11811
  const outputSchemaMergesString = attributes[TdSpanAttributes.OUTPUT_SCHEMA_MERGES];
10461
11812
  const outputSchemaMerges = outputSchemaMergesString ? JSON.parse(outputSchemaMergesString) : void 0;
10462
- ({schema: outputSchema, decodedValueHash: outputValueHash} = JsonSchemaHelper.generateSchemaAndHash(outputData, outputSchemaMerges));
10463
- } else ({schema: outputSchema, decodedSchemaHash: outputValueHash} = JsonSchemaHelper.generateSchemaAndHash(outputData));
11813
+ ({schema: outputSchema, decodedValueHash: outputValueHash, decodedSchemaHash: outputSchemaHash} = JsonSchemaHelper.generateSchemaAndHash(outputData, outputSchemaMerges));
11814
+ } else ({schema: outputSchema, decodedValueHash: outputValueHash, decodedSchemaHash: outputSchemaHash} = JsonSchemaHelper.generateSchemaAndHash(outputData));
10464
11815
  let metadata = void 0;
10465
11816
  if (attributes[TdSpanAttributes.METADATA]) metadata = JSON.parse(attributes[TdSpanAttributes.METADATA]);
10466
11817
  let transformMetadata;
@@ -10484,8 +11835,8 @@ var SpanTransformer = class SpanTransformer {
10484
11835
  outputValue: outputData,
10485
11836
  inputSchema,
10486
11837
  outputSchema,
10487
- inputSchemaHash: JsonSchemaHelper.generateDeterministicHash(inputSchema),
10488
- outputSchemaHash: JsonSchemaHelper.generateDeterministicHash(outputSchema),
11838
+ inputSchemaHash,
11839
+ outputSchemaHash,
10489
11840
  inputValueHash,
10490
11841
  outputValueHash,
10491
11842
  kind: span.kind,
@@ -10728,18 +12079,35 @@ var TdSpanExporter = class {
10728
12079
  */
10729
12080
  export(spans, resultCallback) {
10730
12081
  logger.debug(`TdSpanExporter.export() called with ${spans.length} span(s)`);
10731
- const filteredSpans = spans.filter((span) => {
10732
- if (span.instrumentationLibrary?.name === "next.js") return false;
12082
+ const traceBlockingManager = TraceBlockingManager.getInstance();
12083
+ const filteredSpansBasedOnLibraryName = spans.filter((span) => {
12084
+ if (span.instrumentationLibrary.name === TD_INSTRUMENTATION_LIBRARY_NAME) return true;
12085
+ return false;
12086
+ });
12087
+ logger.debug(`After filtering based on library name: ${filteredSpansBasedOnLibraryName.length} span(s) remaining`);
12088
+ const MAX_SPAN_SIZE_MB = 1;
12089
+ const MAX_SPAN_SIZE_BYTES = MAX_SPAN_SIZE_MB * 1024 * 1024;
12090
+ const filteredSpansBasedOnSize = filteredSpansBasedOnLibraryName.filter((span) => {
12091
+ const traceId = span.spanContext().traceId;
12092
+ if (traceBlockingManager.isTraceBlocked(traceId)) {
12093
+ logger.debug(`Skipping span '${span.name}' (${span.spanContext().spanId}) - trace ${traceId} is blocked`);
12094
+ return false;
12095
+ }
12096
+ const inputValueString = span.attributes[TdSpanAttributes.INPUT_VALUE] || "";
12097
+ const outputValueString = span.attributes[TdSpanAttributes.OUTPUT_VALUE] || "";
12098
+ const inputSize = Buffer.byteLength(inputValueString, "utf8");
12099
+ const outputSize = Buffer.byteLength(outputValueString, "utf8");
12100
+ const estimatedTotalSize = inputSize + outputSize + 5e4;
12101
+ const estimatedSizeMB = estimatedTotalSize / (1024 * 1024);
12102
+ if (estimatedTotalSize > MAX_SPAN_SIZE_BYTES) {
12103
+ traceBlockingManager.blockTrace(traceId);
12104
+ logger.warn(`Blocking trace ${traceId} - span '${span.name}' (${span.spanContext().spanId}) has estimated size ${estimatedSizeMB.toFixed(2)} MB exceeding limit of ${MAX_SPAN_SIZE_MB} MB. Future spans for this trace will be prevented.`);
12105
+ return false;
12106
+ }
10733
12107
  return true;
10734
12108
  });
10735
- logger.debug(`After filtering: ${filteredSpans.length} span(s) remaining`);
10736
- let cleanSpans;
10737
- try {
10738
- cleanSpans = filteredSpans.map((span) => SpanTransformer.transformSpanToCleanJSON(span));
10739
- } catch (error) {
10740
- logger.error("Error transforming spans to CleanSpanData", error);
10741
- throw error;
10742
- }
12109
+ logger.debug(`Filtered ${filteredSpansBasedOnLibraryName.length - filteredSpansBasedOnSize.length} oversized span(s), ${filteredSpansBasedOnSize.length} remaining`);
12110
+ const cleanSpans = filteredSpansBasedOnSize.map((span) => SpanTransformer.transformSpanToCleanJSON(span));
10743
12111
  if (this.adapters.length === 0) {
10744
12112
  logger.debug("No adapters configured");
10745
12113
  resultCallback({ code: import_src.ExportResultCode.SUCCESS });
@@ -10874,15 +12242,12 @@ var ProtobufCommunicator = class {
10874
12242
  });
10875
12243
  }
10876
12244
  /**
10877
- * This function uses a Node.js script to communicate with the CLI over a socket.
10878
- * The script is called using execSync, which will block the main thread until the child process exits. This makes requesting mocks from the CLI synchronous.
10879
- *
10880
- * Since this function blocks the main thread, there is a perfomance impact for using this. We should use requestMockAsync whenever possilbe and only use this function
10881
- * for instrumentations that request fetching mocks synchronously.
12245
+ * This function uses a separate Node.js child process to communicate with the CLI over a socket.
12246
+ * The child process creates its own connection and event loop, allowing proper async socket handling.
12247
+ * The parent process blocks synchronously waiting for the child to complete.
10882
12248
  *
10883
- * (10/9/2025) Currently not using this function since we are not actually fetching mocks for the only sync instrumentation (Date)
10884
- * NOTE: This function probably doesn't work. plus, nc might not be installed on all machines (especially windows)
10885
- * Better approach is replacing nc command with pure Node.js implementation
12249
+ * Since this function blocks the main thread, there is a performance impact. We should use requestMockAsync whenever possible and only use this function
12250
+ * for instrumentations that require fetching mocks synchronously.
10886
12251
  */
10887
12252
  requestMockSync(mockRequest) {
10888
12253
  const requestId = this.generateRequestId();
@@ -10905,10 +12270,15 @@ var ProtobufCommunicator = class {
10905
12270
  getMockRequest: protoMockRequest
10906
12271
  }
10907
12272
  });
12273
+ logger.debug("Sending protobuf request to CLI (sync)", {
12274
+ outboundSpan: mockRequest.outboundSpan,
12275
+ testId: mockRequest.testId
12276
+ });
10908
12277
  const messageBytes = SDKMessage.toBinary(sdkMessage);
10909
12278
  const tempDir = os.tmpdir();
10910
- const requestFile = path.join(tempDir, `tusk-request-${requestId}.bin`);
10911
- const responseFile = path.join(tempDir, `tusk-response-${requestId}.bin`);
12279
+ const requestFile = path.join(tempDir, `tusk-sync-request-${requestId}.bin`);
12280
+ const responseFile = path.join(tempDir, `tusk-sync-response-${requestId}.bin`);
12281
+ const scriptFile = path.join(tempDir, `tusk-sync-script-${requestId}.js`);
10912
12282
  try {
10913
12283
  const lengthBuffer = Buffer.allocUnsafe(4);
10914
12284
  lengthBuffer.writeUInt32BE(messageBytes.length, 0);
@@ -10917,19 +12287,112 @@ var ProtobufCommunicator = class {
10917
12287
  const mockSocket = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_MOCK_SOCKET");
10918
12288
  const mockHost = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_MOCK_HOST");
10919
12289
  const mockPort = OriginalGlobalUtils.getOriginalProcessEnvVar("TUSK_MOCK_PORT");
10920
- let command;
10921
- if (mockSocket) {
10922
- if (!fs.existsSync(mockSocket)) throw new Error(`Socket file does not exist: ${mockSocket}`);
10923
- command = `nc -U -w 10 "${mockSocket}" < "${requestFile}" > "${responseFile}"`;
10924
- } else if (mockHost && mockPort) command = `nc -w 10 "${mockHost}" ${mockPort} < "${requestFile}" > "${responseFile}"`;
10925
- else {
10926
- const socketPath = path.join(os.tmpdir(), "tusk-connect.sock");
10927
- if (!fs.existsSync(socketPath)) throw new Error(`Socket file does not exist: ${socketPath}`);
10928
- command = `nc -U -w 10 "${socketPath}" < "${requestFile}" > "${responseFile}"`;
10929
- }
12290
+ let connectionConfig;
12291
+ if (mockSocket) connectionConfig = {
12292
+ type: "unix",
12293
+ path: mockSocket
12294
+ };
12295
+ else if (mockHost && mockPort) connectionConfig = {
12296
+ type: "tcp",
12297
+ host: mockHost,
12298
+ port: parseInt(mockPort, 10)
12299
+ };
12300
+ else connectionConfig = {
12301
+ type: "unix",
12302
+ path: path.join(os.tmpdir(), "tusk-connect.sock")
12303
+ };
12304
+ fs.writeFileSync(scriptFile, `
12305
+ const net = require('net');
12306
+ const fs = require('fs');
12307
+
12308
+ const requestFile = process.argv[2];
12309
+ const responseFile = process.argv[3];
12310
+ const config = JSON.parse(process.argv[4]);
12311
+
12312
+ let responseReceived = false;
12313
+ let timeoutId;
12314
+
12315
+ function cleanup(exitCode) {
12316
+ if (timeoutId) clearTimeout(timeoutId);
12317
+ process.exit(exitCode);
12318
+ }
12319
+
12320
+ try {
12321
+ // Read the request data
12322
+ const requestData = fs.readFileSync(requestFile);
12323
+
12324
+ // Create connection based on config
12325
+ const client = config.type === 'unix'
12326
+ ? net.createConnection({ path: config.path })
12327
+ : net.createConnection({ host: config.host, port: config.port });
12328
+
12329
+ const incomingChunks = [];
12330
+ let incomingBuffer = Buffer.alloc(0);
12331
+
12332
+ // Set timeout
12333
+ timeoutId = setTimeout(() => {
12334
+ if (!responseReceived) {
12335
+ console.error('Timeout waiting for response');
12336
+ client.destroy();
12337
+ cleanup(1);
12338
+ }
12339
+ }, 10000);
12340
+
12341
+ client.on('connect', () => {
12342
+ // Send the request
12343
+ client.write(requestData);
12344
+ });
12345
+
12346
+ client.on('data', (data) => {
12347
+ incomingBuffer = Buffer.concat([incomingBuffer, data]);
12348
+
12349
+ // Try to parse complete message (4 byte length prefix + message)
12350
+ while (incomingBuffer.length >= 4) {
12351
+ const messageLength = incomingBuffer.readUInt32BE(0);
12352
+
12353
+ if (incomingBuffer.length < 4 + messageLength) {
12354
+ // Incomplete message, wait for more data
12355
+ break;
12356
+ }
12357
+
12358
+ // We have a complete message
12359
+ const messageData = incomingBuffer.slice(4, 4 + messageLength);
12360
+ incomingBuffer = incomingBuffer.slice(4 + messageLength);
12361
+
12362
+ // Write the complete response (including length prefix)
12363
+ const lengthPrefix = Buffer.allocUnsafe(4);
12364
+ lengthPrefix.writeUInt32BE(messageLength, 0);
12365
+ fs.writeFileSync(responseFile, Buffer.concat([lengthPrefix, messageData]));
12366
+
12367
+ responseReceived = true;
12368
+ client.destroy();
12369
+ cleanup(0);
12370
+ break;
12371
+ }
12372
+ });
12373
+
12374
+ client.on('error', (err) => {
12375
+ if (!responseReceived) {
12376
+ console.error('Connection error:', err.message);
12377
+ cleanup(1);
12378
+ }
12379
+ });
12380
+
12381
+ client.on('close', () => {
12382
+ if (!responseReceived) {
12383
+ console.error('Connection closed without response');
12384
+ cleanup(1);
12385
+ }
12386
+ });
12387
+
12388
+ } catch (err) {
12389
+ console.error('Script error:', err.message);
12390
+ cleanup(1);
12391
+ }
12392
+ `);
10930
12393
  try {
10931
- execSync(command, {
10932
- timeout: 1e4,
12394
+ execSync(`node "${scriptFile}" "${requestFile}" "${responseFile}" '${JSON.stringify(connectionConfig)}'`, {
12395
+ timeout: 12e3,
10933
12396
  stdio: "pipe"
10934
12397
  });
10935
12398
  const responseBuffer = fs.readFileSync(responseFile);
@@ -10954,22 +12417,27 @@ var ProtobufCommunicator = class {
10954
12417
  error: mockResponse.error || "Mock not found"
10955
12418
  };
10956
12419
  } catch (error) {
10957
- logger.error("[ProtobufCommunicator] error sending request to CLI:", error);
12420
+ logger.error("[ProtobufCommunicator] error in sync request child process:", error);
10958
12421
  throw error;
10959
12422
  }
10960
12423
  } catch (error) {
10961
12424
  throw new Error(`Sync request failed: ${error.message}`);
10962
12425
  } finally {
10963
12426
  try {
10964
- fs.unlinkSync(requestFile);
12427
+ if (fs.existsSync(requestFile)) fs.unlinkSync(requestFile);
10965
12428
  } catch (e) {
10966
12429
  logger.error("[ProtobufCommunicator] error cleaning up request file:", e);
10967
12430
  }
10968
12431
  try {
10969
- fs.unlinkSync(responseFile);
12432
+ if (fs.existsSync(responseFile)) fs.unlinkSync(responseFile);
10970
12433
  } catch (e) {
10971
12434
  logger.error("[ProtobufCommunicator] error cleaning up response file:", e);
10972
12435
  }
12436
+ try {
12437
+ if (fs.existsSync(scriptFile)) fs.unlinkSync(scriptFile);
12438
+ } catch (e) {
12439
+ logger.error("[ProtobufCommunicator] error cleaning up script file:", e);
12440
+ }
10973
12441
  }
10974
12442
  }
10975
12443
  async sendProtobufMessage(message) {
@@ -11137,7 +12605,8 @@ const TuskDriftInstrumentationModuleNames = [
11137
12605
  "jwks-rsa",
11138
12606
  "mysql2",
11139
12607
  "ioredis",
11140
- "@grpc/grpc-js"
12608
+ "@grpc/grpc-js",
12609
+ "@google-cloud/firestore"
11141
12610
  ];
11142
12611
 
11143
12612
  //#endregion
@@ -11259,6 +12728,10 @@ var TuskDriftCore = class TuskDriftCore {
11259
12728
  enabled: true,
11260
12729
  mode: this.mode
11261
12730
  });
12731
+ new FirestoreInstrumentation({
12732
+ enabled: true,
12733
+ mode: this.mode
12734
+ });
11262
12735
  new NextjsInstrumentation({
11263
12736
  enabled: true,
11264
12737
  mode: this.mode
@@ -11431,6 +12904,10 @@ var TuskDriftCore = class TuskDriftCore {
11431
12904
  }
11432
12905
  }
11433
12906
  requestMockSync(mockRequest) {
12907
+ if (!this.isConnectedWithCLI) {
12908
+ logger.error("Requesting sync mock but CLI is not ready yet");
12909
+ throw new Error("Requesting sync mock but CLI is not ready yet");
12910
+ }
11434
12911
  const mockRequestCore = this.createMockRequestCore(mockRequest);
11435
12912
  if (mockRequestCore) return mockRequestCore;
11436
12913
  return this.requestMockFromCLISync(mockRequest);
@@ -11478,7 +12955,7 @@ var TuskDriftCore = class TuskDriftCore {
11478
12955
  return this.initParams;
11479
12956
  }
11480
12957
  getTracer() {
11481
- return trace.getTracer("tusk-drift-sdk", "1.0.0");
12958
+ return trace.getTracer(TD_INSTRUMENTATION_LIBRARY_NAME);
11482
12959
  }
11483
12960
  };
11484
12961
  var TuskDriftSDK = class {