@use-tusk/drift-node-sdk 0.1.34 → 0.1.35

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.cjs CHANGED
@@ -304,6 +304,7 @@ var TdInstrumentationAbstract = class {
304
304
  function safeJsonStringify(obj) {
305
305
  const seen = /* @__PURE__ */ new WeakSet();
306
306
  return JSON.stringify(obj, (key, value) => {
307
+ if (typeof value === "bigint") return value.toString();
307
308
  if (typeof value === "object" && value !== null) {
308
309
  if (seen.has(value)) return "[Circular]";
309
310
  seen.add(value);
@@ -7926,7 +7927,7 @@ var SpanUtils = class SpanUtils {
7926
7927
  ...addSpanAttributesOptions.submodule && { [TdSpanAttributes.SUBMODULE_NAME]: addSpanAttributesOptions.submodule },
7927
7928
  ...addSpanAttributesOptions.isPreAppStart && { [TdSpanAttributes.IS_PRE_APP_START]: addSpanAttributesOptions.isPreAppStart },
7928
7929
  ...addSpanAttributesOptions.inputValue && { [TdSpanAttributes.INPUT_VALUE]: createSpanInputValue(addSpanAttributesOptions.inputValue) },
7929
- ...addSpanAttributesOptions.outputValue && { [TdSpanAttributes.OUTPUT_VALUE]: JSON.stringify(addSpanAttributesOptions.outputValue) },
7930
+ ...addSpanAttributesOptions.outputValue && { [TdSpanAttributes.OUTPUT_VALUE]: safeJsonStringify(addSpanAttributesOptions.outputValue) },
7930
7931
  ...addSpanAttributesOptions.inputSchemaMerges && { [TdSpanAttributes.INPUT_SCHEMA_MERGES]: JSON.stringify(addSpanAttributesOptions.inputSchemaMerges) },
7931
7932
  ...addSpanAttributesOptions.outputSchemaMerges && { [TdSpanAttributes.OUTPUT_SCHEMA_MERGES]: JSON.stringify(addSpanAttributesOptions.outputSchemaMerges) },
7932
7933
  ...addSpanAttributesOptions.metadata && { [TdSpanAttributes.METADATA]: JSON.stringify(addSpanAttributesOptions.metadata) },
@@ -18773,6 +18774,11 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
18773
18774
  if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({
18774
18775
  noOpRequestHandler: () => {
18775
18776
  process.nextTick(() => {
18777
+ this.status = "connecting";
18778
+ this.emit("connecting");
18779
+ this.status = "connect";
18780
+ this.emit("connect");
18781
+ this.status = "ready";
18776
18782
  this.emit("ready");
18777
18783
  });
18778
18784
  return Promise.resolve();
@@ -18979,6 +18985,11 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
18979
18985
  async _handleReplayConnect(thisContext) {
18980
18986
  logger.debug(`[IORedisInstrumentation] Replaying IORedis connect`);
18981
18987
  process.nextTick(() => {
18988
+ thisContext.status = "connecting";
18989
+ thisContext.emit("connecting");
18990
+ thisContext.status = "connect";
18991
+ thisContext.emit("connect");
18992
+ thisContext.status = "ready";
18982
18993
  thisContext.emit("ready");
18983
18994
  });
18984
18995
  return Promise.resolve();
@@ -22279,11 +22290,34 @@ let PrismaErrorClassName = /* @__PURE__ */ function(PrismaErrorClassName$1) {
22279
22290
  //#endregion
22280
22291
  //#region src/instrumentation/libraries/prisma/Instrumentation.ts
22281
22292
  var import_src$14 = /* @__PURE__ */ __toESM(require_src$7(), 1);
22282
- var PrismaInstrumentation = class extends TdInstrumentationBase {
22293
+ /**
22294
+ * Prisma instrumentation for recording and replaying database operations.
22295
+ *
22296
+ * ## Type deserialization
22297
+ *
22298
+ * Prisma returns special JS types (Date, BigInt, Decimal, Buffer) for certain
22299
+ * column types. JSON round-trip during record/replay loses this type information.
22300
+ * We reconstruct types during replay using two strategies:
22301
+ *
22302
+ * 1. **Model-based operations** (findFirst, create, update, etc.): The middleware
22303
+ * provides the model name, so we look up field types from `_runtimeDataModel`
22304
+ * at replay time. No extra metadata needed during recording.
22305
+ *
22306
+ * 2. **Raw queries** ($queryRaw): No model name is available. During recording,
22307
+ * we sniff JS types from the result and store a per-column `_tdTypeMap`
22308
+ * (e.g., `{bigNum: "bigint", data: "bytes"}`). During replay, we use this
22309
+ * map to reconstruct the values.
22310
+ *
22311
+ * Both strategies share `_reconstructSingleValue` for the actual conversion.
22312
+ * See docs/prisma-type-deserialization-bug.md for the full design doc.
22313
+ */
22314
+ var PrismaInstrumentation = class PrismaInstrumentation extends TdInstrumentationBase {
22283
22315
  constructor(config = {}) {
22284
22316
  super("@prisma/client", config);
22285
22317
  this.INSTRUMENTATION_NAME = "PrismaInstrumentation";
22286
22318
  this.prismaErrorClasses = [];
22319
+ this.prismaClient = null;
22320
+ this.prismaNamespace = null;
22287
22321
  this.mode = config.mode || TuskDriftMode.DISABLED;
22288
22322
  this.tuskDrift = TuskDriftCore.getInstance();
22289
22323
  }
@@ -22300,6 +22334,7 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22300
22334
  return prismaModule;
22301
22335
  }
22302
22336
  this._storePrismaErrorClasses(prismaModule);
22337
+ this.prismaNamespace = prismaModule.Prisma || prismaModule;
22303
22338
  logger.debug(`[PrismaInstrumentation] Wrapping PrismaClient constructor`);
22304
22339
  this._wrap(prismaModule, "PrismaClient", (OriginalPrismaClient) => {
22305
22340
  const self = this;
@@ -22307,7 +22342,9 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22307
22342
  return class TdPrismaClient {
22308
22343
  constructor(...args) {
22309
22344
  logger.debug(`[PrismaInstrumentation] Creating patched PrismaClient instance`);
22310
- return new OriginalPrismaClient(...args).$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
22345
+ const prismaClient = new OriginalPrismaClient(...args);
22346
+ self.prismaClient = prismaClient;
22347
+ const extendedClient = prismaClient.$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
22311
22348
  logger.debug(`[PrismaInstrumentation] $allOperations intercepted: ${model}.${operation}`);
22312
22349
  return self._handlePrismaOperation({
22313
22350
  model,
@@ -22316,6 +22353,22 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22316
22353
  query
22317
22354
  });
22318
22355
  } } });
22356
+ if (self.mode === TuskDriftMode.REPLAY) {
22357
+ const originalTransaction = extendedClient.$transaction.bind(extendedClient);
22358
+ extendedClient.$transaction = async function(...txArgs) {
22359
+ const firstArg = txArgs[0];
22360
+ if (typeof firstArg === "function") {
22361
+ logger.debug(`[PrismaInstrumentation] Replay: bypassing interactive $transaction DB connection`);
22362
+ return firstArg(extendedClient);
22363
+ }
22364
+ if (Array.isArray(firstArg)) {
22365
+ logger.debug(`[PrismaInstrumentation] Replay: bypassing sequential $transaction DB connection`);
22366
+ return Promise.all(firstArg);
22367
+ }
22368
+ return originalTransaction(...txArgs);
22369
+ };
22370
+ }
22371
+ return extendedClient;
22319
22372
  }
22320
22373
  };
22321
22374
  });
@@ -22372,7 +22425,7 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22372
22425
  inputValue,
22373
22426
  isPreAppStart
22374
22427
  }, (spanInfo) => {
22375
- return this._handleRecordPrismaOperation(spanInfo, query, args);
22428
+ return this._handleRecordPrismaOperation(spanInfo, query, args, model);
22376
22429
  });
22377
22430
  },
22378
22431
  spanKind: import_src$14.SpanKind.CLIENT
@@ -22399,13 +22452,15 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22399
22452
  });
22400
22453
  } else return query(args);
22401
22454
  }
22402
- async _handleRecordPrismaOperation(spanInfo, query, args) {
22455
+ async _handleRecordPrismaOperation(spanInfo, query, args, model) {
22403
22456
  try {
22404
22457
  logger.debug(`[PrismaInstrumentation] Recording Prisma operation`);
22405
22458
  const result = await query(args);
22459
+ const typeMap = model ? null : this._buildTypeMap(result);
22406
22460
  const outputValue = {
22407
22461
  prismaResult: result,
22408
- _tdOriginalFormat: "result"
22462
+ _tdOriginalFormat: "result",
22463
+ ...typeMap && { _tdTypeMap: typeMap }
22409
22464
  };
22410
22465
  try {
22411
22466
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
@@ -22468,8 +22523,158 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22468
22523
  });
22469
22524
  throw errorObj;
22470
22525
  }
22526
+ let result = outputValue.prismaResult;
22527
+ try {
22528
+ if (inputValue.model) result = this._reconstructPrismaTypes(result, inputValue.model);
22529
+ else if (outputValue._tdTypeMap) result = this._reconstructFromTypeMap(result, outputValue._tdTypeMap);
22530
+ } catch (reconstructError) {
22531
+ logger.debug(`[PrismaInstrumentation] Failed to reconstruct types: ${reconstructError}`);
22532
+ }
22471
22533
  SpanUtils.endSpan(spanInfo.span, { code: import_src$14.SpanStatusCode.OK });
22472
- return outputValue.prismaResult;
22534
+ return result;
22535
+ }
22536
+ /**
22537
+ * Sniff the JS type of a value returned by Prisma.
22538
+ *
22539
+ * Returns type names that mirror Prisma's internal `QueryIntrospectionBuiltinType`
22540
+ * enum from `@prisma/client/runtime` (used in `deserializeRawResults.ts`):
22541
+ * "bigint" | "bytes" | "decimal" | "datetime"
22542
+ *
22543
+ * We don't import these types directly — we use our own string literals that
22544
+ * match Prisma's naming convention so the type map is self-documenting and
22545
+ * consistent with what Prisma would produce internally.
22546
+ */
22547
+ _sniffType(value) {
22548
+ if (typeof value === "bigint") return "bigint";
22549
+ if (value instanceof Date) return "datetime";
22550
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return "bytes";
22551
+ if (typeof value === "object" && value !== null && typeof value.toFixed === "function" && typeof value.toExponential === "function" && typeof value.toSignificantDigits === "function") return "decimal";
22552
+ return null;
22553
+ }
22554
+ /**
22555
+ * Build a per-column type map by sniffing values in the result.
22556
+ * For arrays of objects (e.g., $queryRaw results), scans all rows to handle
22557
+ * cases where the first row has null values but later rows don't.
22558
+ * Returns null if no special types are detected.
22559
+ */
22560
+ _buildTypeMap(result) {
22561
+ if (result === null || result === void 0) return null;
22562
+ const rows = Array.isArray(result) ? result : typeof result === "object" ? [result] : null;
22563
+ if (!rows || rows.length === 0) return null;
22564
+ const typeMap = {};
22565
+ let hasTypes = false;
22566
+ for (const row of rows) {
22567
+ if (!row || typeof row !== "object") continue;
22568
+ for (const [key, value] of Object.entries(row)) {
22569
+ if (typeMap[key] || value === null || value === void 0) continue;
22570
+ if (Array.isArray(value) && value.length > 0) {
22571
+ const elemType = this._sniffType(value[0]);
22572
+ if (elemType) {
22573
+ typeMap[key] = `${elemType}-array`;
22574
+ hasTypes = true;
22575
+ continue;
22576
+ }
22577
+ }
22578
+ const type = this._sniffType(value);
22579
+ if (type) {
22580
+ typeMap[key] = type;
22581
+ hasTypes = true;
22582
+ }
22583
+ }
22584
+ if (hasTypes && Object.keys(typeMap).length >= Object.keys(row).length) break;
22585
+ }
22586
+ return hasTypes ? typeMap : null;
22587
+ }
22588
+ /**
22589
+ * Reconstruct types from a _tdTypeMap (used for $queryRaw and other model-less operations).
22590
+ */
22591
+ _reconstructFromTypeMap(result, typeMap) {
22592
+ if (result === null || result === void 0) return result;
22593
+ const reconstructRow = (row) => {
22594
+ if (typeof row !== "object" || row === null) return row;
22595
+ for (const [key, type] of Object.entries(typeMap)) {
22596
+ const value = row[key];
22597
+ if (value === null || value === void 0) continue;
22598
+ if (typeof type === "string" && type.endsWith("-array") && Array.isArray(value)) {
22599
+ const baseType = type.replace("-array", "");
22600
+ row[key] = value.map((v) => this._reconstructSingleValue(v, baseType));
22601
+ continue;
22602
+ }
22603
+ row[key] = this._reconstructSingleValue(value, type);
22604
+ }
22605
+ return row;
22606
+ };
22607
+ if (Array.isArray(result)) return result.map(reconstructRow);
22608
+ return reconstructRow(result);
22609
+ }
22610
+ /**
22611
+ * Reconstruct a single value from its JSON-deserialized form back to the
22612
+ * original JS type that Prisma would have returned.
22613
+ *
22614
+ * The `type` parameter uses the same names as Prisma's query engine types
22615
+ * (see _sniffType and PRISMA_SCHEMA_TO_ENGINE_TYPE). Both the model-based
22616
+ * and raw-query replay paths converge here.
22617
+ */
22618
+ _reconstructSingleValue(value, type) {
22619
+ if (value === null || value === void 0) return value;
22620
+ switch (type) {
22621
+ case "bigint":
22622
+ if (typeof value === "string" || typeof value === "number") return BigInt(value);
22623
+ return value;
22624
+ case "datetime":
22625
+ if (typeof value === "string") return new Date(value);
22626
+ return value;
22627
+ case "decimal":
22628
+ if (typeof value === "string" || typeof value === "number") {
22629
+ if (this.prismaNamespace?.Decimal) return new this.prismaNamespace.Decimal(value);
22630
+ const decimalValue = String(value);
22631
+ return {
22632
+ toString: () => decimalValue,
22633
+ toFixed: (dp) => Number(decimalValue).toFixed(dp),
22634
+ valueOf: () => Number(decimalValue),
22635
+ [Symbol.toPrimitive]: (hint) => hint === "string" ? decimalValue : Number(decimalValue)
22636
+ };
22637
+ }
22638
+ return value;
22639
+ case "bytes":
22640
+ if (typeof value === "string") return Buffer.from(value, "base64");
22641
+ if (typeof value === "object" && !Buffer.isBuffer(value)) {
22642
+ const bufferData = value.data || Object.values(value);
22643
+ return Buffer.from(bufferData);
22644
+ }
22645
+ return value;
22646
+ default: return value;
22647
+ }
22648
+ }
22649
+ /**
22650
+ * Reconstruct Prisma types for model-based operations (findFirst, create, update, etc.).
22651
+ *
22652
+ * Uses `prismaClient._runtimeDataModel` — Prisma's internal schema representation
22653
+ * available at runtime — to determine field types. This avoids needing to store
22654
+ * type metadata during recording; the schema itself is the source of truth.
22655
+ *
22656
+ * Handles scalar fields, array fields (e.g., BigInt[]), and nested relations recursively.
22657
+ */
22658
+ _reconstructPrismaTypes(result, modelName) {
22659
+ if (result === null || result === void 0) return result;
22660
+ const runtimeDataModel = this.prismaClient?._runtimeDataModel;
22661
+ if (!runtimeDataModel) return result;
22662
+ if (Array.isArray(result)) return result.map((item) => this._reconstructPrismaTypes(item, modelName));
22663
+ const model = runtimeDataModel.models[modelName];
22664
+ if (!model) return result;
22665
+ if (typeof result !== "object") return result;
22666
+ const fieldTypeMap = new Map(model.fields.map((f) => [f.name, f]));
22667
+ for (const [key, value] of Object.entries(result)) {
22668
+ const field = fieldTypeMap.get(key);
22669
+ if (!field || value === null || value === void 0) continue;
22670
+ if (field.kind === "scalar") {
22671
+ const engineType = PrismaInstrumentation.PRISMA_SCHEMA_TO_ENGINE_TYPE[field.type];
22672
+ if (engineType) if (Array.isArray(value)) result[key] = value.map((v) => this._reconstructSingleValue(v, engineType));
22673
+ else result[key] = this._reconstructSingleValue(value, engineType);
22674
+ }
22675
+ if (field.kind === "object" && field.type && typeof value === "object") result[key] = this._reconstructPrismaTypes(value, field.type);
22676
+ }
22677
+ return result;
22473
22678
  }
22474
22679
  _getPrismaErrorClassName(error) {
22475
22680
  for (const errorInfo of this.prismaErrorClasses) if (error instanceof errorInfo.errorClass) return errorInfo.name;
@@ -22494,6 +22699,70 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22494
22699
  wrap(target, propertyName, wrapper);
22495
22700
  }
22496
22701
  };
22702
+ PrismaInstrumentation.PRISMA_SCHEMA_TO_ENGINE_TYPE = {
22703
+ DateTime: "datetime",
22704
+ BigInt: "bigint",
22705
+ Decimal: "decimal",
22706
+ Bytes: "bytes"
22707
+ };
22708
+
22709
+ //#endregion
22710
+ //#region src/instrumentation/libraries/mongodb/mocks/FakeTopology.ts
22711
+ /**
22712
+ * Fake MongoDB Topology for replay mode.
22713
+ *
22714
+ * When BulkOperationBase is constructed, it calls getTopology(collection)
22715
+ * which requires a connected topology. In replay mode, no real connection
22716
+ * exists, so we inject this fake topology to satisfy the constructor's
22717
+ * requirements without hitting a real server.
22718
+ *
22719
+ * The constructor reads:
22720
+ * - topology.lastHello() -> returns {} so all size limits use defaults
22721
+ * - topology.lastIsMaster() -> returns {} (legacy compat)
22722
+ * - topology.s.options -> returns {} so autoEncryption check is false
22723
+ *
22724
+ * The topology is also injected onto the shared MongoClient (via
22725
+ * _injectFakeTopology) and persists across requests. Other driver code
22726
+ * (e.g. ClientSession.loadBalanced getter) accesses
22727
+ * topology.description.type, so we provide a minimal description to
22728
+ * prevent TypeError when the property is read.
22729
+ */
22730
+ var TdFakeTopology = class extends events.EventEmitter {
22731
+ constructor() {
22732
+ super();
22733
+ this.fakeServer = { description: { address: "fake:27017" } };
22734
+ this.s = { options: {} };
22735
+ this.description = {
22736
+ type: "Unknown",
22737
+ servers: /* @__PURE__ */ new Map(),
22738
+ hasServer: () => false
22739
+ };
22740
+ }
22741
+ lastHello() {
22742
+ return {};
22743
+ }
22744
+ lastIsMaster() {
22745
+ return {};
22746
+ }
22747
+ shouldCheckForSessionSupport() {
22748
+ return false;
22749
+ }
22750
+ hasSessionSupport() {
22751
+ return true;
22752
+ }
22753
+ isConnected() {
22754
+ return true;
22755
+ }
22756
+ isDestroyed() {
22757
+ return false;
22758
+ }
22759
+ selectServerAsync() {
22760
+ return Promise.resolve(this.fakeServer);
22761
+ }
22762
+ selectServer(_selector, _options, callback) {
22763
+ if (typeof callback === "function") callback(null, this.fakeServer);
22764
+ }
22765
+ };
22497
22766
 
22498
22767
  //#endregion
22499
22768
  //#region src/instrumentation/libraries/mongodb/handlers/ConnectionHandler.ts
@@ -22528,7 +22797,7 @@ var ConnectionHandler = class {
22528
22797
  inputValue,
22529
22798
  isPreAppStart: !this.isAppReady()
22530
22799
  }, (spanInfo) => {
22531
- return this.handleReplayConnect(spanInfo, thisArg);
22800
+ return this.handleReplayConnect(spanInfo, thisArg, args);
22532
22801
  });
22533
22802
  }
22534
22803
  });
@@ -22559,17 +22828,21 @@ var ConnectionHandler = class {
22559
22828
  * - DISABLED: Passthrough.
22560
22829
  */
22561
22830
  handleClose(original, thisArg, args) {
22562
- if (this.mode === TuskDriftMode.REPLAY) return handleReplayMode({
22563
- noOpRequestHandler: () => {
22564
- return Promise.resolve();
22565
- },
22566
- isServerRequest: false,
22567
- replayModeHandler: () => {
22568
- logger.debug(`[${this.instrumentationName}] Replaying MongoDB close (no-op)`);
22569
- return Promise.resolve();
22570
- }
22571
- });
22572
- else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
22831
+ if (this.mode === TuskDriftMode.REPLAY) {
22832
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
22833
+ return handleReplayMode({
22834
+ noOpRequestHandler: () => {
22835
+ if (callback) callback();
22836
+ return Promise.resolve();
22837
+ },
22838
+ isServerRequest: false,
22839
+ replayModeHandler: () => {
22840
+ logger.debug(`[${this.instrumentationName}] Replaying MongoDB close (no-op)`);
22841
+ if (callback) callback();
22842
+ return Promise.resolve();
22843
+ }
22844
+ });
22845
+ } else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
22573
22846
  originalFunctionCall: () => original.apply(thisArg, args),
22574
22847
  recordModeHandler: ({ isPreAppStart }) => {
22575
22848
  return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
@@ -22599,7 +22872,18 @@ var ConnectionHandler = class {
22599
22872
  return original.apply(thisArg, args);
22600
22873
  }
22601
22874
  handleRecordConnect(spanInfo, original, thisArg, args) {
22602
- return original.apply(thisArg, args).then((result) => {
22875
+ const connectResult = original.apply(thisArg, args);
22876
+ if (!(connectResult && typeof connectResult.then === "function")) {
22877
+ try {
22878
+ logger.debug(`[${this.instrumentationName}] MongoDB connect returned a non-promise value; recording best-effort span`);
22879
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { connected: true } });
22880
+ SpanUtils.endSpan(spanInfo.span, { code: import_src$13.SpanStatusCode.OK });
22881
+ } catch (error) {
22882
+ logger.error(`[${this.instrumentationName}] Error ending non-promise connect span:`, error);
22883
+ }
22884
+ return Promise.resolve(connectResult);
22885
+ }
22886
+ return connectResult.then((result) => {
22603
22887
  try {
22604
22888
  logger.debug(`[${this.instrumentationName}] MongoDB connection created successfully (${SpanUtils.getTraceInfo()})`);
22605
22889
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { connected: true } });
@@ -22622,8 +22906,9 @@ var ConnectionHandler = class {
22622
22906
  throw error;
22623
22907
  });
22624
22908
  }
22625
- handleReplayConnect(spanInfo, thisArg) {
22909
+ handleReplayConnect(spanInfo, thisArg, args) {
22626
22910
  logger.debug(`[${this.instrumentationName}] Replaying MongoDB connection (skipping TCP connect)`);
22911
+ this.injectFakeTopology(thisArg);
22627
22912
  try {
22628
22913
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: {
22629
22914
  connected: true,
@@ -22633,10 +22918,35 @@ var ConnectionHandler = class {
22633
22918
  } catch (error) {
22634
22919
  logger.error(`[${this.instrumentationName}] Error ending replay connect span:`, error);
22635
22920
  }
22921
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
22922
+ if (callback) {
22923
+ callback(null, thisArg);
22924
+ return Promise.resolve(thisArg);
22925
+ }
22636
22926
  return Promise.resolve(thisArg);
22637
22927
  }
22928
+ injectFakeTopology(client) {
22929
+ const fakeTopology = new TdFakeTopology();
22930
+ if (!client) return;
22931
+ if (!client.topology) client.topology = fakeTopology;
22932
+ if (client.s && !client.s.topology) client.s.topology = fakeTopology;
22933
+ if (client.s) {
22934
+ if (client.s.hasBeenClosed === true) client.s.hasBeenClosed = false;
22935
+ if (client.s.isMongoClient === false) client.s.isMongoClient = true;
22936
+ }
22937
+ }
22638
22938
  handleRecordClose(spanInfo, original, thisArg, args) {
22639
- return original.apply(thisArg, args).then(() => {
22939
+ const closeResult = original.apply(thisArg, args);
22940
+ if (!(closeResult && typeof closeResult.then === "function")) {
22941
+ try {
22942
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { closed: true } });
22943
+ SpanUtils.endSpan(spanInfo.span, { code: import_src$13.SpanStatusCode.OK });
22944
+ } catch (error) {
22945
+ logger.error(`[${this.instrumentationName}] Error ending non-promise close span:`, error);
22946
+ }
22947
+ return Promise.resolve();
22948
+ }
22949
+ return closeResult.then(() => {
22640
22950
  try {
22641
22951
  logger.debug(`[${this.instrumentationName}] MongoDB connection closed successfully (${SpanUtils.getTraceInfo()})`);
22642
22952
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { closed: true } });
@@ -22717,21 +23027,37 @@ var TdFakeFindCursor = class TdFakeFindCursor {
22717
23027
  });
22718
23028
  if (this._mockLoadPromise) await this._mockLoadPromise;
22719
23029
  }
22720
- async toArray() {
22721
- await this._ensureMockLoaded();
22722
- return [...this.documents];
23030
+ toArray(callback) {
23031
+ const promise = this._ensureMockLoaded().then(() => [...this.documents]);
23032
+ if (typeof callback === "function") {
23033
+ promise.then((docs) => callback(null, docs), (error) => callback(error));
23034
+ return;
23035
+ }
23036
+ return promise;
22723
23037
  }
22724
- async next() {
22725
- await this._ensureMockLoaded();
22726
- return this.documents[this.index++] ?? null;
23038
+ next(callback) {
23039
+ const promise = this._ensureMockLoaded().then(() => this.documents[this.index++] ?? null);
23040
+ if (typeof callback === "function") {
23041
+ promise.then((doc) => callback(null, doc), (error) => callback(error));
23042
+ return;
23043
+ }
23044
+ return promise;
22727
23045
  }
22728
- async tryNext() {
22729
- await this._ensureMockLoaded();
22730
- return this.documents[this.index++] ?? null;
23046
+ tryNext(callback) {
23047
+ const promise = this._ensureMockLoaded().then(() => this.documents[this.index++] ?? null);
23048
+ if (typeof callback === "function") {
23049
+ promise.then((doc) => callback(null, doc), (error) => callback(error));
23050
+ return;
23051
+ }
23052
+ return promise;
22731
23053
  }
22732
- async hasNext() {
22733
- await this._ensureMockLoaded();
22734
- return this.index < this.documents.length;
23054
+ hasNext(callback) {
23055
+ const promise = this._ensureMockLoaded().then(() => this.index < this.documents.length);
23056
+ if (typeof callback === "function") {
23057
+ promise.then((hasNext) => callback(null, hasNext), (error) => callback(error));
23058
+ return;
23059
+ }
23060
+ return promise;
22735
23061
  }
22736
23062
  async forEach(fn) {
22737
23063
  await this._ensureMockLoaded();
@@ -22804,7 +23130,14 @@ var TdFakeFindCursor = class TdFakeFindCursor {
22804
23130
  map() {
22805
23131
  return this;
22806
23132
  }
22807
- async close() {}
23133
+ close(callback) {
23134
+ const promise = Promise.resolve();
23135
+ if (typeof callback === "function") {
23136
+ promise.then(() => callback(), (error) => callback(error));
23137
+ return;
23138
+ }
23139
+ return promise;
23140
+ }
22808
23141
  rewind() {
22809
23142
  this.index = 0;
22810
23143
  }
@@ -22922,45 +23255,6 @@ var TdFakeChangeStream = class {
22922
23255
  }
22923
23256
  };
22924
23257
 
22925
- //#endregion
22926
- //#region src/instrumentation/libraries/mongodb/mocks/FakeTopology.ts
22927
- /**
22928
- * Fake MongoDB Topology for replay mode.
22929
- *
22930
- * When BulkOperationBase is constructed, it calls getTopology(collection)
22931
- * which requires a connected topology. In replay mode, no real connection
22932
- * exists, so we inject this fake topology to satisfy the constructor's
22933
- * requirements without hitting a real server.
22934
- *
22935
- * The constructor reads:
22936
- * - topology.lastHello() -> returns {} so all size limits use defaults
22937
- * - topology.lastIsMaster() -> returns {} (legacy compat)
22938
- * - topology.s.options -> returns {} so autoEncryption check is false
22939
- *
22940
- * The topology is also injected onto the shared MongoClient (via
22941
- * _injectFakeTopology) and persists across requests. Other driver code
22942
- * (e.g. ClientSession.loadBalanced getter) accesses
22943
- * topology.description.type, so we provide a minimal description to
22944
- * prevent TypeError when the property is read.
22945
- */
22946
- var TdFakeTopology = class extends events.EventEmitter {
22947
- constructor() {
22948
- super();
22949
- this.s = { options: {} };
22950
- this.description = {
22951
- type: "Unknown",
22952
- servers: /* @__PURE__ */ new Map(),
22953
- hasServer: () => false
22954
- };
22955
- }
22956
- lastHello() {
22957
- return {};
22958
- }
22959
- lastIsMaster() {
22960
- return {};
22961
- }
22962
- };
22963
-
22964
23258
  //#endregion
22965
23259
  //#region node_modules/bson/lib/bson.cjs
22966
23260
  var require_bson = /* @__PURE__ */ __commonJS({ "node_modules/bson/lib/bson.cjs": ((exports) => {
@@ -27010,7 +27304,8 @@ const COLLECTION_METHODS_TO_WRAP = [
27010
27304
  "createIndexes",
27011
27305
  "dropIndex",
27012
27306
  "dropIndexes",
27013
- "indexes"
27307
+ "indexes",
27308
+ "drop"
27014
27309
  ];
27015
27310
  /**
27016
27311
  * Cursor-returning methods on Collection.prototype.
@@ -27035,6 +27330,12 @@ const DB_METHODS_TO_WRAP = [
27035
27330
  * Db.prototype methods that return cursors.
27036
27331
  */
27037
27332
  const DB_CURSOR_METHODS_TO_WRAP = ["listCollections", "aggregate"];
27333
+ const SUPPORTED_MONGODB_VERSIONS = [
27334
+ "4.*",
27335
+ "5.*",
27336
+ "6.*",
27337
+ "7.*"
27338
+ ];
27038
27339
  var MongodbInstrumentation = class extends TdInstrumentationBase {
27039
27340
  constructor(config = {}) {
27040
27341
  super("mongodb", config);
@@ -27046,38 +27347,37 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27046
27347
  init() {
27047
27348
  return [new TdInstrumentationNodeModule({
27048
27349
  name: "mongodb",
27049
- supportedVersions: [
27050
- "5.*",
27051
- "6.*",
27052
- "7.*"
27053
- ],
27054
- patch: (moduleExports) => this._patchMongodbModule(moduleExports),
27350
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27351
+ patch: (moduleExports, moduleVersion) => this._patchMongodbModule(moduleExports, moduleVersion),
27055
27352
  files: [
27056
27353
  new TdInstrumentationNodeModuleFile({
27057
27354
  name: "mongodb/lib/sessions.js",
27058
- supportedVersions: [
27059
- "5.*",
27060
- "6.*",
27061
- "7.*"
27062
- ],
27355
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27356
+ patch: (moduleExports) => this._patchSessionModule(moduleExports)
27357
+ }),
27358
+ new TdInstrumentationNodeModuleFile({
27359
+ name: "mongodb/lib/sessions",
27360
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27063
27361
  patch: (moduleExports) => this._patchSessionModule(moduleExports)
27064
27362
  }),
27065
27363
  new TdInstrumentationNodeModuleFile({
27066
27364
  name: "mongodb/lib/bulk/ordered.js",
27067
- supportedVersions: [
27068
- "5.*",
27069
- "6.*",
27070
- "7.*"
27071
- ],
27365
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27366
+ patch: (moduleExports) => this._patchOrderedBulkModule(moduleExports)
27367
+ }),
27368
+ new TdInstrumentationNodeModuleFile({
27369
+ name: "mongodb/lib/bulk/ordered",
27370
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27072
27371
  patch: (moduleExports) => this._patchOrderedBulkModule(moduleExports)
27073
27372
  }),
27074
27373
  new TdInstrumentationNodeModuleFile({
27075
27374
  name: "mongodb/lib/bulk/unordered.js",
27076
- supportedVersions: [
27077
- "5.*",
27078
- "6.*",
27079
- "7.*"
27080
- ],
27375
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27376
+ patch: (moduleExports) => this._patchUnorderedBulkModule(moduleExports)
27377
+ }),
27378
+ new TdInstrumentationNodeModuleFile({
27379
+ name: "mongodb/lib/bulk/unordered",
27380
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27081
27381
  patch: (moduleExports) => this._patchUnorderedBulkModule(moduleExports)
27082
27382
  })
27083
27383
  ]
@@ -27088,8 +27388,9 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27088
27388
  * Wraps MongoClient, Collection, Db, and cursor prototypes to intercept
27089
27389
  * all database operations for record/replay.
27090
27390
  */
27091
- _patchMongodbModule(mongodbModule) {
27092
- logger.debug(`[${this.INSTRUMENTATION_NAME}] Patching MongoDB module in ${this.mode} mode`);
27391
+ _patchMongodbModule(mongodbModule, moduleVersion) {
27392
+ this.mongodbMajorVersion = this._getMajorVersion(moduleVersion);
27393
+ logger.debug(`[${this.INSTRUMENTATION_NAME}] Patching MongoDB module v${moduleVersion || "unknown"} in ${this.mode} mode`);
27093
27394
  if (this.isModulePatched(mongodbModule)) {
27094
27395
  logger.debug(`[${this.INSTRUMENTATION_NAME}] MongoDB module already patched, skipping`);
27095
27396
  return mongodbModule;
@@ -27156,6 +27457,11 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27156
27457
  logger.debug(`[${this.INSTRUMENTATION_NAME}] MongoDB module patching complete`);
27157
27458
  return mongodbModule;
27158
27459
  }
27460
+ _getMajorVersion(version$1) {
27461
+ if (!version$1) return void 0;
27462
+ const major$1 = Number(version$1.split(".")[0]);
27463
+ return Number.isFinite(major$1) ? major$1 : void 0;
27464
+ }
27159
27465
  /**
27160
27466
  * Patch all Collection.prototype CRUD methods that return Promises.
27161
27467
  * Guards each method with typeof check for version compatibility.
@@ -27181,13 +27487,24 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27181
27487
  const submodule = `collection-${methodName}`;
27182
27488
  return (original) => {
27183
27489
  return function(...args) {
27490
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
27491
+ const effectiveArgs = callback ? args.slice(0, -1) : args;
27184
27492
  const collectionName = this?.s?.namespace?.collection;
27185
27493
  const databaseName = this?.s?.namespace?.db;
27186
- const inputValue = self._extractCollectionInput(methodName, collectionName, databaseName, args);
27494
+ const inputValue = self._extractCollectionInput(methodName, collectionName, databaseName, effectiveArgs);
27187
27495
  if (self.mode === TuskDriftMode.DISABLED) return original.apply(this, args);
27188
- if (self.mode === TuskDriftMode.RECORD) return self._handleRecordCollectionMethod(original, this, args, inputValue, spanName, submodule);
27189
- if (self.mode === TuskDriftMode.REPLAY) return self._handleReplayCollectionMethod(original, this, args, inputValue, spanName, submodule, methodName);
27190
- return original.apply(this, args);
27496
+ const invokeHandler = () => {
27497
+ if (self.mode === TuskDriftMode.RECORD) return self._handleRecordCollectionMethod(original, this, effectiveArgs, inputValue, spanName, submodule);
27498
+ if (self.mode === TuskDriftMode.REPLAY) return self._handleReplayCollectionMethod(original, this, effectiveArgs, inputValue, spanName, submodule, methodName);
27499
+ return original.apply(this, effectiveArgs);
27500
+ };
27501
+ if (callback) {
27502
+ Promise.resolve(invokeHandler()).then((result) => callback(null, result)).catch((error) => callback(error));
27503
+ return;
27504
+ }
27505
+ if (self.mode === TuskDriftMode.RECORD) return invokeHandler();
27506
+ if (self.mode === TuskDriftMode.REPLAY) return invokeHandler();
27507
+ return original.apply(this, effectiveArgs);
27191
27508
  };
27192
27509
  };
27193
27510
  }
@@ -27210,7 +27527,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27210
27527
  isPreAppStart,
27211
27528
  stopRecordingChildSpans: true
27212
27529
  }, (spanInfo) => {
27213
- return original.apply(thisArg, args).then((result) => {
27530
+ const resultPromise = original.apply(thisArg, args);
27531
+ return Promise.resolve(resultPromise).then((result) => {
27214
27532
  try {
27215
27533
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
27216
27534
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -27247,7 +27565,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27247
27565
  },
27248
27566
  isServerRequest: false,
27249
27567
  replayModeHandler: () => {
27250
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
27568
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(this._getNoOpResult(methodName)), {
27251
27569
  name: spanName,
27252
27570
  kind: import_src$12.SpanKind.CLIENT,
27253
27571
  submodule,
@@ -27402,10 +27720,16 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27402
27720
  };
27403
27721
  if (typeof cursor.toArray === "function") {
27404
27722
  const originalToArray = cursor.toArray.bind(cursor);
27405
- cursor.toArray = () => {
27406
- if (cursorState.recorded) return originalToArray();
27723
+ cursor.toArray = (callback) => {
27724
+ if (cursorState.recorded) {
27725
+ if (typeof callback === "function") {
27726
+ originalToArray().then((result) => callback(null, result)).catch((error) => callback(error));
27727
+ return;
27728
+ }
27729
+ return originalToArray();
27730
+ }
27407
27731
  cursorState.recorded = true;
27408
- return import_src$12.context.with(creationContext, () => {
27732
+ const resultPromise = import_src$12.context.with(creationContext, () => {
27409
27733
  return handleRecordMode({
27410
27734
  originalFunctionCall: () => originalToArray(),
27411
27735
  recordModeHandler: ({ isPreAppStart }) => {
@@ -27445,37 +27769,125 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27445
27769
  spanKind: import_src$12.SpanKind.CLIENT
27446
27770
  });
27447
27771
  });
27772
+ if (typeof callback === "function") {
27773
+ resultPromise.then((result) => callback(null, result)).catch((error) => callback(error));
27774
+ return;
27775
+ }
27776
+ return resultPromise;
27448
27777
  };
27449
27778
  }
27450
27779
  if (typeof cursor.next === "function") {
27451
27780
  const originalNext = cursor.next.bind(cursor);
27452
- cursor.next = async () => {
27453
- if (cursorState.recorded) return originalNext();
27454
- if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return originalNext();
27455
- try {
27456
- const doc = await (cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => originalNext()) : originalNext());
27781
+ cursor.next = (callback) => {
27782
+ if (typeof callback === "function") {
27783
+ if (cursorState.recorded) {
27784
+ let settled$1 = false;
27785
+ const finish$1 = (error, doc) => {
27786
+ if (settled$1) return;
27787
+ settled$1 = true;
27788
+ callback(error, doc);
27789
+ };
27790
+ const maybeResult$1 = originalNext((error, doc) => finish$1(error, doc));
27791
+ Promise.resolve(maybeResult$1).then((doc) => finish$1(null, doc)).catch((error) => finish$1(error, null));
27792
+ return;
27793
+ }
27794
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) {
27795
+ let settled$1 = false;
27796
+ const finish$1 = (error, doc) => {
27797
+ if (settled$1) return;
27798
+ settled$1 = true;
27799
+ callback(error, doc);
27800
+ };
27801
+ const maybeResult$1 = originalNext((error, doc) => finish$1(error, doc));
27802
+ Promise.resolve(maybeResult$1).then((doc) => finish$1(null, doc)).catch((error) => finish$1(error, null));
27803
+ return;
27804
+ }
27805
+ let settled = false;
27806
+ const finish = (error, doc) => {
27807
+ if (settled) return;
27808
+ settled = true;
27809
+ if (error) {
27810
+ handleCursorError(error);
27811
+ callback(error);
27812
+ return;
27813
+ }
27814
+ if (doc !== null) cursorState.collectedDocuments.push(doc);
27815
+ else finalizeCursorSpan();
27816
+ callback(null, doc);
27817
+ };
27818
+ const runOriginal = () => originalNext((error, doc) => {
27819
+ finish(error, doc);
27820
+ });
27821
+ const maybeResult = cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => runOriginal()) : runOriginal();
27822
+ Promise.resolve(maybeResult).then((doc) => finish(null, doc)).catch((error) => finish(error, null));
27823
+ return;
27824
+ }
27825
+ if (cursorState.recorded) return callback === void 0 ? originalNext() : originalNext(callback);
27826
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return callback === void 0 ? originalNext() : originalNext(callback);
27827
+ return Promise.resolve(cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => callback === void 0 ? originalNext() : originalNext(callback)) : callback === void 0 ? originalNext() : originalNext(callback)).then((doc) => {
27457
27828
  if (doc !== null) cursorState.collectedDocuments.push(doc);
27458
27829
  else finalizeCursorSpan();
27459
27830
  return doc;
27460
- } catch (error) {
27831
+ }).catch((error) => {
27461
27832
  handleCursorError(error);
27462
27833
  throw error;
27463
- }
27834
+ });
27464
27835
  };
27465
27836
  }
27466
27837
  if (typeof cursor.hasNext === "function") {
27467
27838
  const originalHasNext = cursor.hasNext.bind(cursor);
27468
- cursor.hasNext = async () => {
27469
- if (cursorState.recorded) return originalHasNext();
27470
- if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return originalHasNext();
27471
- try {
27472
- const result = await (cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => originalHasNext()) : originalHasNext());
27839
+ cursor.hasNext = (callback) => {
27840
+ if (typeof callback === "function") {
27841
+ if (cursorState.recorded) {
27842
+ let settled$1 = false;
27843
+ const finish$1 = (error, result) => {
27844
+ if (settled$1) return;
27845
+ settled$1 = true;
27846
+ callback(error, result);
27847
+ };
27848
+ const maybeResult$1 = originalHasNext((error, result) => finish$1(error, result));
27849
+ Promise.resolve(maybeResult$1).then((result) => finish$1(null, result)).catch((error) => finish$1(error, false));
27850
+ return;
27851
+ }
27852
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) {
27853
+ let settled$1 = false;
27854
+ const finish$1 = (error, result) => {
27855
+ if (settled$1) return;
27856
+ settled$1 = true;
27857
+ callback(error, result);
27858
+ };
27859
+ const maybeResult$1 = originalHasNext((error, result) => finish$1(error, result));
27860
+ Promise.resolve(maybeResult$1).then((result) => finish$1(null, result)).catch((error) => finish$1(error, false));
27861
+ return;
27862
+ }
27863
+ let settled = false;
27864
+ const finish = (error, result) => {
27865
+ if (settled) return;
27866
+ settled = true;
27867
+ if (error) {
27868
+ handleCursorError(error);
27869
+ callback(error);
27870
+ return;
27871
+ }
27872
+ if (!result) finalizeCursorSpan();
27873
+ callback(null, result);
27874
+ };
27875
+ const runOriginal = () => originalHasNext((error, result) => {
27876
+ finish(error, result);
27877
+ });
27878
+ const maybeResult = cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => runOriginal()) : runOriginal();
27879
+ Promise.resolve(maybeResult).then((result) => finish(null, result)).catch((error) => finish(error, false));
27880
+ return;
27881
+ }
27882
+ if (cursorState.recorded) return callback === void 0 ? originalHasNext() : originalHasNext(callback);
27883
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return callback === void 0 ? originalHasNext() : originalHasNext(callback);
27884
+ return Promise.resolve(cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => callback === void 0 ? originalHasNext() : originalHasNext(callback)) : callback === void 0 ? originalHasNext() : originalHasNext(callback)).then((result) => {
27473
27885
  if (!result) finalizeCursorSpan();
27474
27886
  return result;
27475
- } catch (error) {
27887
+ }).catch((error) => {
27476
27888
  handleCursorError(error);
27477
27889
  throw error;
27478
- }
27890
+ });
27479
27891
  };
27480
27892
  }
27481
27893
  if (typeof cursor.forEach === "function") {
@@ -27531,14 +27943,30 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27531
27943
  }
27532
27944
  if (typeof cursor.close === "function") {
27533
27945
  const originalClose = cursor.close.bind(cursor);
27534
- cursor.close = async () => {
27535
- try {
27536
- await originalClose();
27946
+ cursor.close = (callback) => {
27947
+ if (typeof callback === "function") {
27948
+ let settled = false;
27949
+ const finish = (error) => {
27950
+ if (settled) return;
27951
+ settled = true;
27952
+ if (error) {
27953
+ handleCursorError(error);
27954
+ callback(error);
27955
+ return;
27956
+ }
27957
+ finalizeCursorSpan();
27958
+ callback();
27959
+ };
27960
+ const maybeResult = originalClose((error) => finish(error));
27961
+ Promise.resolve(maybeResult).then(() => finish()).catch((error) => finish(error));
27962
+ return;
27963
+ }
27964
+ return Promise.resolve(callback === void 0 ? originalClose() : originalClose(callback)).then(() => {
27537
27965
  finalizeCursorSpan();
27538
- } catch (error) {
27966
+ }).catch((error) => {
27539
27967
  handleCursorError(error);
27540
27968
  throw error;
27541
- }
27969
+ });
27542
27970
  };
27543
27971
  }
27544
27972
  if (typeof cursor[Symbol.asyncIterator] === "function") {
@@ -27814,6 +28242,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27814
28242
  case "dropIndex": return {};
27815
28243
  case "dropIndexes": return { ok: 1 };
27816
28244
  case "indexes": return [];
28245
+ case "drop": return true;
27817
28246
  default: return null;
27818
28247
  }
27819
28248
  }
@@ -27842,18 +28271,29 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27842
28271
  const submodule = `db-${methodName}`;
27843
28272
  return (original) => {
27844
28273
  return function(...args) {
28274
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
28275
+ const effectiveArgs = callback ? args.slice(0, -1) : args;
27845
28276
  const databaseName = this?.databaseName || this?.s?.namespace?.db;
27846
- const inputValue = self._extractDbInput(methodName, databaseName, args);
28277
+ const inputValue = self._extractDbInput(methodName, databaseName, effectiveArgs);
27847
28278
  if (self.mode === TuskDriftMode.DISABLED) return original.apply(this, args);
27848
- if (self.mode === TuskDriftMode.RECORD) {
27849
- if (methodName === "createCollection") return self._handleRecordCreateCollection(original, this, args, inputValue, spanName, submodule);
27850
- return self._handleRecordDbMethod(original, this, args, inputValue, spanName, submodule);
27851
- }
27852
- if (self.mode === TuskDriftMode.REPLAY) {
27853
- if (methodName === "createCollection") return self._handleReplayCreateCollection(this, args, inputValue, spanName, submodule);
27854
- return self._handleReplayDbMethod(original, this, args, inputValue, spanName, submodule, methodName);
28279
+ const invokeHandler = () => {
28280
+ if (self.mode === TuskDriftMode.RECORD) {
28281
+ if (methodName === "createCollection") return self._handleRecordCreateCollection(original, this, effectiveArgs, inputValue, spanName, submodule);
28282
+ return self._handleRecordDbMethod(original, this, effectiveArgs, inputValue, spanName, submodule);
28283
+ }
28284
+ if (self.mode === TuskDriftMode.REPLAY) {
28285
+ if (methodName === "createCollection") return self._handleReplayCreateCollection(this, effectiveArgs, inputValue, spanName, submodule);
28286
+ return self._handleReplayDbMethod(original, this, effectiveArgs, inputValue, spanName, submodule, methodName);
28287
+ }
28288
+ return original.apply(this, effectiveArgs);
28289
+ };
28290
+ if (callback) {
28291
+ Promise.resolve(invokeHandler()).then((result) => callback(null, result)).catch((error) => callback(error));
28292
+ return;
27855
28293
  }
27856
- return original.apply(this, args);
28294
+ if (self.mode === TuskDriftMode.RECORD) return invokeHandler();
28295
+ if (self.mode === TuskDriftMode.REPLAY) return invokeHandler();
28296
+ return original.apply(this, effectiveArgs);
27857
28297
  };
27858
28298
  };
27859
28299
  }
@@ -27876,7 +28316,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27876
28316
  isPreAppStart,
27877
28317
  stopRecordingChildSpans: true
27878
28318
  }, (spanInfo) => {
27879
- return original.apply(thisArg, args).then((result) => {
28319
+ const resultPromise = original.apply(thisArg, args);
28320
+ return Promise.resolve(resultPromise).then((result) => {
27880
28321
  try {
27881
28322
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
27882
28323
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -27913,7 +28354,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27913
28354
  },
27914
28355
  isServerRequest: false,
27915
28356
  replayModeHandler: () => {
27916
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28357
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(this._getDbNoOpResult(methodName)), {
27917
28358
  name: spanName,
27918
28359
  kind: import_src$12.SpanKind.CLIENT,
27919
28360
  submodule,
@@ -27971,7 +28412,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27971
28412
  isPreAppStart,
27972
28413
  stopRecordingChildSpans: true
27973
28414
  }, (spanInfo) => {
27974
- return original.apply(thisArg, args).then((result) => {
28415
+ const resultPromise = original.apply(thisArg, args);
28416
+ return Promise.resolve(resultPromise).then((result) => {
27975
28417
  try {
27976
28418
  const collectionName = args[0];
27977
28419
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: sanitizeBsonValue({ collectionName }) });
@@ -28235,13 +28677,26 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28235
28677
  */
28236
28678
  _getStartSessionWrapper() {
28237
28679
  const self = this;
28680
+ const REPLAY_WITH_TXN_PATCHED = Symbol("tdReplayWithTransactionPatched");
28238
28681
  return (original) => {
28239
28682
  return function(...args) {
28240
- if (self.mode === TuskDriftMode.REPLAY) {
28241
- logger.debug(`[${self.INSTRUMENTATION_NAME}] startSession called in REPLAY mode`);
28242
- return original.apply(this, args);
28683
+ const session = original.apply(this, args);
28684
+ if (self.mode === TuskDriftMode.REPLAY && self.mongodbMajorVersion === 4 && session && typeof session.withTransaction === "function" && !session[REPLAY_WITH_TXN_PATCHED]) {
28685
+ const originalWithTransaction = session.withTransaction.bind(session);
28686
+ session.withTransaction = async (fn, _options) => {
28687
+ try {
28688
+ await Promise.resolve(fn(session));
28689
+ if (typeof session.commitTransaction === "function") return await session.commitTransaction();
28690
+ return;
28691
+ } catch (error) {
28692
+ if (typeof session.abortTransaction === "function") await session.abortTransaction();
28693
+ throw error;
28694
+ }
28695
+ };
28696
+ session.__tdOriginalWithTransaction = originalWithTransaction;
28697
+ session[REPLAY_WITH_TXN_PATCHED] = true;
28243
28698
  }
28244
- return original.apply(this, args);
28699
+ return session;
28245
28700
  };
28246
28701
  };
28247
28702
  }
@@ -28317,7 +28772,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28317
28772
  isPreAppStart,
28318
28773
  stopRecordingChildSpans: true
28319
28774
  }, (spanInfo) => {
28320
- return original.apply(thisArg, args).then((result) => {
28775
+ const resultPromise = original.apply(thisArg, args);
28776
+ return Promise.resolve(resultPromise).then((result) => {
28321
28777
  try {
28322
28778
  addOutputAttributesToSpan(spanInfo, result ?? { success: true });
28323
28779
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -28352,7 +28808,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28352
28808
  noOpRequestHandler: () => Promise.resolve(void 0),
28353
28809
  isServerRequest: false,
28354
28810
  replayModeHandler: () => {
28355
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28811
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(void 0), {
28356
28812
  name: spanName,
28357
28813
  kind: import_src$12.SpanKind.CLIENT,
28358
28814
  submodule,
@@ -28626,7 +29082,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28626
29082
  isPreAppStart,
28627
29083
  stopRecordingChildSpans: true
28628
29084
  }, (spanInfo) => {
28629
- return original.apply(thisArg, args).then((result) => {
29085
+ const resultPromise = original.apply(thisArg, args);
29086
+ return Promise.resolve(resultPromise).then((result) => {
28630
29087
  try {
28631
29088
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
28632
29089
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -28672,7 +29129,16 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28672
29129
  },
28673
29130
  isServerRequest: false,
28674
29131
  replayModeHandler: () => {
28675
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
29132
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve({
29133
+ acknowledged: false,
29134
+ insertedCount: 0,
29135
+ matchedCount: 0,
29136
+ modifiedCount: 0,
29137
+ deletedCount: 0,
29138
+ upsertedCount: 0,
29139
+ insertedIds: {},
29140
+ upsertedIds: {}
29141
+ }), {
28676
29142
  name: spanName,
28677
29143
  kind: import_src$12.SpanKind.CLIENT,
28678
29144
  submodule,
@@ -40907,7 +41373,7 @@ var require_src = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/sdk-
40907
41373
  //#endregion
40908
41374
  //#region package.json
40909
41375
  var import_src$1 = /* @__PURE__ */ __toESM(require_src(), 1);
40910
- var version = "0.1.34";
41376
+ var version = "0.1.35";
40911
41377
 
40912
41378
  //#endregion
40913
41379
  //#region src/version.ts
@@ -42808,7 +43274,14 @@ var TuskDriftCore = class TuskDriftCore {
42808
43274
  logger.info(`Rust core path enabled at startup (env=${envDisplay}, reason=${status.reason}).`);
42809
43275
  return;
42810
43276
  }
42811
- logger.warn(`Rust core path requested but binding unavailable; falling back to JavaScript path (env=${envDisplay}, reason=${status.reason}, error=${status.bindingError}).`);
43277
+ logger.info(`Rust core path unavailable at startup; using JavaScript path instead (env=${envDisplay}, reason=${status.reason}, error=${status.bindingError}).`);
43278
+ }
43279
+ logStartupSummary() {
43280
+ const serviceName = this.config.service?.name || "unknown";
43281
+ const serviceId = this.config.service?.id || "<unset>";
43282
+ const environment = this.initParams.env || "<unset>";
43283
+ const exportSpans = this.config.recording?.export_spans || false;
43284
+ logger.info(`SDK initialized successfully (version=${SDK_VERSION}, mode=${this.mode}, env=${environment}, service=${serviceName}, serviceId=${serviceId}, exportSpans=${exportSpans}, samplingRate=${this.samplingRate}, logLevel=${logger.getLogLevel()}, runtime=node ${process.version}, platform=${process.platform}/${process.arch}).`);
42812
43285
  }
42813
43286
  validateSamplingRate(value, source) {
42814
43287
  if (typeof value !== "number" || isNaN(value)) {
@@ -43055,7 +43528,7 @@ var TuskDriftCore = class TuskDriftCore {
43055
43528
  this.initializeTracing({ baseDirectory });
43056
43529
  this.createEnvVarsSnapshot();
43057
43530
  this.initialized = true;
43058
- logger.info("SDK initialized successfully");
43531
+ this.logStartupSummary();
43059
43532
  }
43060
43533
  markAppAsReady() {
43061
43534
  if (!this.initialized) {