@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.js CHANGED
@@ -293,6 +293,7 @@ var TdInstrumentationAbstract = class {
293
293
  function safeJsonStringify(obj) {
294
294
  const seen = /* @__PURE__ */ new WeakSet();
295
295
  return JSON.stringify(obj, (key, value) => {
296
+ if (typeof value === "bigint") return value.toString();
296
297
  if (typeof value === "object" && value !== null) {
297
298
  if (seen.has(value)) return "[Circular]";
298
299
  seen.add(value);
@@ -7915,7 +7916,7 @@ var SpanUtils = class SpanUtils {
7915
7916
  ...addSpanAttributesOptions.submodule && { [TdSpanAttributes.SUBMODULE_NAME]: addSpanAttributesOptions.submodule },
7916
7917
  ...addSpanAttributesOptions.isPreAppStart && { [TdSpanAttributes.IS_PRE_APP_START]: addSpanAttributesOptions.isPreAppStart },
7917
7918
  ...addSpanAttributesOptions.inputValue && { [TdSpanAttributes.INPUT_VALUE]: createSpanInputValue(addSpanAttributesOptions.inputValue) },
7918
- ...addSpanAttributesOptions.outputValue && { [TdSpanAttributes.OUTPUT_VALUE]: JSON.stringify(addSpanAttributesOptions.outputValue) },
7919
+ ...addSpanAttributesOptions.outputValue && { [TdSpanAttributes.OUTPUT_VALUE]: safeJsonStringify(addSpanAttributesOptions.outputValue) },
7919
7920
  ...addSpanAttributesOptions.inputSchemaMerges && { [TdSpanAttributes.INPUT_SCHEMA_MERGES]: JSON.stringify(addSpanAttributesOptions.inputSchemaMerges) },
7920
7921
  ...addSpanAttributesOptions.outputSchemaMerges && { [TdSpanAttributes.OUTPUT_SCHEMA_MERGES]: JSON.stringify(addSpanAttributesOptions.outputSchemaMerges) },
7921
7922
  ...addSpanAttributesOptions.metadata && { [TdSpanAttributes.METADATA]: JSON.stringify(addSpanAttributesOptions.metadata) },
@@ -18762,6 +18763,11 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
18762
18763
  if (self.mode === TuskDriftMode.REPLAY) return handleReplayMode({
18763
18764
  noOpRequestHandler: () => {
18764
18765
  process.nextTick(() => {
18766
+ this.status = "connecting";
18767
+ this.emit("connecting");
18768
+ this.status = "connect";
18769
+ this.emit("connect");
18770
+ this.status = "ready";
18765
18771
  this.emit("ready");
18766
18772
  });
18767
18773
  return Promise.resolve();
@@ -18968,6 +18974,11 @@ var IORedisInstrumentation = class extends TdInstrumentationBase {
18968
18974
  async _handleReplayConnect(thisContext) {
18969
18975
  logger.debug(`[IORedisInstrumentation] Replaying IORedis connect`);
18970
18976
  process.nextTick(() => {
18977
+ thisContext.status = "connecting";
18978
+ thisContext.emit("connecting");
18979
+ thisContext.status = "connect";
18980
+ thisContext.emit("connect");
18981
+ thisContext.status = "ready";
18971
18982
  thisContext.emit("ready");
18972
18983
  });
18973
18984
  return Promise.resolve();
@@ -22268,11 +22279,34 @@ let PrismaErrorClassName = /* @__PURE__ */ function(PrismaErrorClassName$1) {
22268
22279
  //#endregion
22269
22280
  //#region src/instrumentation/libraries/prisma/Instrumentation.ts
22270
22281
  var import_src$14 = /* @__PURE__ */ __toESM(require_src$7(), 1);
22271
- var PrismaInstrumentation = class extends TdInstrumentationBase {
22282
+ /**
22283
+ * Prisma instrumentation for recording and replaying database operations.
22284
+ *
22285
+ * ## Type deserialization
22286
+ *
22287
+ * Prisma returns special JS types (Date, BigInt, Decimal, Buffer) for certain
22288
+ * column types. JSON round-trip during record/replay loses this type information.
22289
+ * We reconstruct types during replay using two strategies:
22290
+ *
22291
+ * 1. **Model-based operations** (findFirst, create, update, etc.): The middleware
22292
+ * provides the model name, so we look up field types from `_runtimeDataModel`
22293
+ * at replay time. No extra metadata needed during recording.
22294
+ *
22295
+ * 2. **Raw queries** ($queryRaw): No model name is available. During recording,
22296
+ * we sniff JS types from the result and store a per-column `_tdTypeMap`
22297
+ * (e.g., `{bigNum: "bigint", data: "bytes"}`). During replay, we use this
22298
+ * map to reconstruct the values.
22299
+ *
22300
+ * Both strategies share `_reconstructSingleValue` for the actual conversion.
22301
+ * See docs/prisma-type-deserialization-bug.md for the full design doc.
22302
+ */
22303
+ var PrismaInstrumentation = class PrismaInstrumentation extends TdInstrumentationBase {
22272
22304
  constructor(config = {}) {
22273
22305
  super("@prisma/client", config);
22274
22306
  this.INSTRUMENTATION_NAME = "PrismaInstrumentation";
22275
22307
  this.prismaErrorClasses = [];
22308
+ this.prismaClient = null;
22309
+ this.prismaNamespace = null;
22276
22310
  this.mode = config.mode || TuskDriftMode.DISABLED;
22277
22311
  this.tuskDrift = TuskDriftCore.getInstance();
22278
22312
  }
@@ -22289,6 +22323,7 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22289
22323
  return prismaModule;
22290
22324
  }
22291
22325
  this._storePrismaErrorClasses(prismaModule);
22326
+ this.prismaNamespace = prismaModule.Prisma || prismaModule;
22292
22327
  logger.debug(`[PrismaInstrumentation] Wrapping PrismaClient constructor`);
22293
22328
  this._wrap(prismaModule, "PrismaClient", (OriginalPrismaClient) => {
22294
22329
  const self = this;
@@ -22296,7 +22331,9 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22296
22331
  return class TdPrismaClient {
22297
22332
  constructor(...args) {
22298
22333
  logger.debug(`[PrismaInstrumentation] Creating patched PrismaClient instance`);
22299
- return new OriginalPrismaClient(...args).$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
22334
+ const prismaClient = new OriginalPrismaClient(...args);
22335
+ self.prismaClient = prismaClient;
22336
+ const extendedClient = prismaClient.$extends({ query: { async $allOperations({ model, operation, args: operationArgs, query }) {
22300
22337
  logger.debug(`[PrismaInstrumentation] $allOperations intercepted: ${model}.${operation}`);
22301
22338
  return self._handlePrismaOperation({
22302
22339
  model,
@@ -22305,6 +22342,22 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22305
22342
  query
22306
22343
  });
22307
22344
  } } });
22345
+ if (self.mode === TuskDriftMode.REPLAY) {
22346
+ const originalTransaction = extendedClient.$transaction.bind(extendedClient);
22347
+ extendedClient.$transaction = async function(...txArgs) {
22348
+ const firstArg = txArgs[0];
22349
+ if (typeof firstArg === "function") {
22350
+ logger.debug(`[PrismaInstrumentation] Replay: bypassing interactive $transaction DB connection`);
22351
+ return firstArg(extendedClient);
22352
+ }
22353
+ if (Array.isArray(firstArg)) {
22354
+ logger.debug(`[PrismaInstrumentation] Replay: bypassing sequential $transaction DB connection`);
22355
+ return Promise.all(firstArg);
22356
+ }
22357
+ return originalTransaction(...txArgs);
22358
+ };
22359
+ }
22360
+ return extendedClient;
22308
22361
  }
22309
22362
  };
22310
22363
  });
@@ -22361,7 +22414,7 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22361
22414
  inputValue,
22362
22415
  isPreAppStart
22363
22416
  }, (spanInfo) => {
22364
- return this._handleRecordPrismaOperation(spanInfo, query, args);
22417
+ return this._handleRecordPrismaOperation(spanInfo, query, args, model);
22365
22418
  });
22366
22419
  },
22367
22420
  spanKind: import_src$14.SpanKind.CLIENT
@@ -22388,13 +22441,15 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22388
22441
  });
22389
22442
  } else return query(args);
22390
22443
  }
22391
- async _handleRecordPrismaOperation(spanInfo, query, args) {
22444
+ async _handleRecordPrismaOperation(spanInfo, query, args, model) {
22392
22445
  try {
22393
22446
  logger.debug(`[PrismaInstrumentation] Recording Prisma operation`);
22394
22447
  const result = await query(args);
22448
+ const typeMap = model ? null : this._buildTypeMap(result);
22395
22449
  const outputValue = {
22396
22450
  prismaResult: result,
22397
- _tdOriginalFormat: "result"
22451
+ _tdOriginalFormat: "result",
22452
+ ...typeMap && { _tdTypeMap: typeMap }
22398
22453
  };
22399
22454
  try {
22400
22455
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue });
@@ -22457,8 +22512,158 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22457
22512
  });
22458
22513
  throw errorObj;
22459
22514
  }
22515
+ let result = outputValue.prismaResult;
22516
+ try {
22517
+ if (inputValue.model) result = this._reconstructPrismaTypes(result, inputValue.model);
22518
+ else if (outputValue._tdTypeMap) result = this._reconstructFromTypeMap(result, outputValue._tdTypeMap);
22519
+ } catch (reconstructError) {
22520
+ logger.debug(`[PrismaInstrumentation] Failed to reconstruct types: ${reconstructError}`);
22521
+ }
22460
22522
  SpanUtils.endSpan(spanInfo.span, { code: import_src$14.SpanStatusCode.OK });
22461
- return outputValue.prismaResult;
22523
+ return result;
22524
+ }
22525
+ /**
22526
+ * Sniff the JS type of a value returned by Prisma.
22527
+ *
22528
+ * Returns type names that mirror Prisma's internal `QueryIntrospectionBuiltinType`
22529
+ * enum from `@prisma/client/runtime` (used in `deserializeRawResults.ts`):
22530
+ * "bigint" | "bytes" | "decimal" | "datetime"
22531
+ *
22532
+ * We don't import these types directly — we use our own string literals that
22533
+ * match Prisma's naming convention so the type map is self-documenting and
22534
+ * consistent with what Prisma would produce internally.
22535
+ */
22536
+ _sniffType(value) {
22537
+ if (typeof value === "bigint") return "bigint";
22538
+ if (value instanceof Date) return "datetime";
22539
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return "bytes";
22540
+ if (typeof value === "object" && value !== null && typeof value.toFixed === "function" && typeof value.toExponential === "function" && typeof value.toSignificantDigits === "function") return "decimal";
22541
+ return null;
22542
+ }
22543
+ /**
22544
+ * Build a per-column type map by sniffing values in the result.
22545
+ * For arrays of objects (e.g., $queryRaw results), scans all rows to handle
22546
+ * cases where the first row has null values but later rows don't.
22547
+ * Returns null if no special types are detected.
22548
+ */
22549
+ _buildTypeMap(result) {
22550
+ if (result === null || result === void 0) return null;
22551
+ const rows = Array.isArray(result) ? result : typeof result === "object" ? [result] : null;
22552
+ if (!rows || rows.length === 0) return null;
22553
+ const typeMap = {};
22554
+ let hasTypes = false;
22555
+ for (const row of rows) {
22556
+ if (!row || typeof row !== "object") continue;
22557
+ for (const [key, value] of Object.entries(row)) {
22558
+ if (typeMap[key] || value === null || value === void 0) continue;
22559
+ if (Array.isArray(value) && value.length > 0) {
22560
+ const elemType = this._sniffType(value[0]);
22561
+ if (elemType) {
22562
+ typeMap[key] = `${elemType}-array`;
22563
+ hasTypes = true;
22564
+ continue;
22565
+ }
22566
+ }
22567
+ const type = this._sniffType(value);
22568
+ if (type) {
22569
+ typeMap[key] = type;
22570
+ hasTypes = true;
22571
+ }
22572
+ }
22573
+ if (hasTypes && Object.keys(typeMap).length >= Object.keys(row).length) break;
22574
+ }
22575
+ return hasTypes ? typeMap : null;
22576
+ }
22577
+ /**
22578
+ * Reconstruct types from a _tdTypeMap (used for $queryRaw and other model-less operations).
22579
+ */
22580
+ _reconstructFromTypeMap(result, typeMap) {
22581
+ if (result === null || result === void 0) return result;
22582
+ const reconstructRow = (row) => {
22583
+ if (typeof row !== "object" || row === null) return row;
22584
+ for (const [key, type] of Object.entries(typeMap)) {
22585
+ const value = row[key];
22586
+ if (value === null || value === void 0) continue;
22587
+ if (typeof type === "string" && type.endsWith("-array") && Array.isArray(value)) {
22588
+ const baseType = type.replace("-array", "");
22589
+ row[key] = value.map((v) => this._reconstructSingleValue(v, baseType));
22590
+ continue;
22591
+ }
22592
+ row[key] = this._reconstructSingleValue(value, type);
22593
+ }
22594
+ return row;
22595
+ };
22596
+ if (Array.isArray(result)) return result.map(reconstructRow);
22597
+ return reconstructRow(result);
22598
+ }
22599
+ /**
22600
+ * Reconstruct a single value from its JSON-deserialized form back to the
22601
+ * original JS type that Prisma would have returned.
22602
+ *
22603
+ * The `type` parameter uses the same names as Prisma's query engine types
22604
+ * (see _sniffType and PRISMA_SCHEMA_TO_ENGINE_TYPE). Both the model-based
22605
+ * and raw-query replay paths converge here.
22606
+ */
22607
+ _reconstructSingleValue(value, type) {
22608
+ if (value === null || value === void 0) return value;
22609
+ switch (type) {
22610
+ case "bigint":
22611
+ if (typeof value === "string" || typeof value === "number") return BigInt(value);
22612
+ return value;
22613
+ case "datetime":
22614
+ if (typeof value === "string") return new Date(value);
22615
+ return value;
22616
+ case "decimal":
22617
+ if (typeof value === "string" || typeof value === "number") {
22618
+ if (this.prismaNamespace?.Decimal) return new this.prismaNamespace.Decimal(value);
22619
+ const decimalValue = String(value);
22620
+ return {
22621
+ toString: () => decimalValue,
22622
+ toFixed: (dp) => Number(decimalValue).toFixed(dp),
22623
+ valueOf: () => Number(decimalValue),
22624
+ [Symbol.toPrimitive]: (hint) => hint === "string" ? decimalValue : Number(decimalValue)
22625
+ };
22626
+ }
22627
+ return value;
22628
+ case "bytes":
22629
+ if (typeof value === "string") return Buffer.from(value, "base64");
22630
+ if (typeof value === "object" && !Buffer.isBuffer(value)) {
22631
+ const bufferData = value.data || Object.values(value);
22632
+ return Buffer.from(bufferData);
22633
+ }
22634
+ return value;
22635
+ default: return value;
22636
+ }
22637
+ }
22638
+ /**
22639
+ * Reconstruct Prisma types for model-based operations (findFirst, create, update, etc.).
22640
+ *
22641
+ * Uses `prismaClient._runtimeDataModel` — Prisma's internal schema representation
22642
+ * available at runtime — to determine field types. This avoids needing to store
22643
+ * type metadata during recording; the schema itself is the source of truth.
22644
+ *
22645
+ * Handles scalar fields, array fields (e.g., BigInt[]), and nested relations recursively.
22646
+ */
22647
+ _reconstructPrismaTypes(result, modelName) {
22648
+ if (result === null || result === void 0) return result;
22649
+ const runtimeDataModel = this.prismaClient?._runtimeDataModel;
22650
+ if (!runtimeDataModel) return result;
22651
+ if (Array.isArray(result)) return result.map((item) => this._reconstructPrismaTypes(item, modelName));
22652
+ const model = runtimeDataModel.models[modelName];
22653
+ if (!model) return result;
22654
+ if (typeof result !== "object") return result;
22655
+ const fieldTypeMap = new Map(model.fields.map((f) => [f.name, f]));
22656
+ for (const [key, value] of Object.entries(result)) {
22657
+ const field = fieldTypeMap.get(key);
22658
+ if (!field || value === null || value === void 0) continue;
22659
+ if (field.kind === "scalar") {
22660
+ const engineType = PrismaInstrumentation.PRISMA_SCHEMA_TO_ENGINE_TYPE[field.type];
22661
+ if (engineType) if (Array.isArray(value)) result[key] = value.map((v) => this._reconstructSingleValue(v, engineType));
22662
+ else result[key] = this._reconstructSingleValue(value, engineType);
22663
+ }
22664
+ if (field.kind === "object" && field.type && typeof value === "object") result[key] = this._reconstructPrismaTypes(value, field.type);
22665
+ }
22666
+ return result;
22462
22667
  }
22463
22668
  _getPrismaErrorClassName(error) {
22464
22669
  for (const errorInfo of this.prismaErrorClasses) if (error instanceof errorInfo.errorClass) return errorInfo.name;
@@ -22483,6 +22688,70 @@ var PrismaInstrumentation = class extends TdInstrumentationBase {
22483
22688
  wrap(target, propertyName, wrapper);
22484
22689
  }
22485
22690
  };
22691
+ PrismaInstrumentation.PRISMA_SCHEMA_TO_ENGINE_TYPE = {
22692
+ DateTime: "datetime",
22693
+ BigInt: "bigint",
22694
+ Decimal: "decimal",
22695
+ Bytes: "bytes"
22696
+ };
22697
+
22698
+ //#endregion
22699
+ //#region src/instrumentation/libraries/mongodb/mocks/FakeTopology.ts
22700
+ /**
22701
+ * Fake MongoDB Topology for replay mode.
22702
+ *
22703
+ * When BulkOperationBase is constructed, it calls getTopology(collection)
22704
+ * which requires a connected topology. In replay mode, no real connection
22705
+ * exists, so we inject this fake topology to satisfy the constructor's
22706
+ * requirements without hitting a real server.
22707
+ *
22708
+ * The constructor reads:
22709
+ * - topology.lastHello() -> returns {} so all size limits use defaults
22710
+ * - topology.lastIsMaster() -> returns {} (legacy compat)
22711
+ * - topology.s.options -> returns {} so autoEncryption check is false
22712
+ *
22713
+ * The topology is also injected onto the shared MongoClient (via
22714
+ * _injectFakeTopology) and persists across requests. Other driver code
22715
+ * (e.g. ClientSession.loadBalanced getter) accesses
22716
+ * topology.description.type, so we provide a minimal description to
22717
+ * prevent TypeError when the property is read.
22718
+ */
22719
+ var TdFakeTopology = class extends EventEmitter {
22720
+ constructor() {
22721
+ super();
22722
+ this.fakeServer = { description: { address: "fake:27017" } };
22723
+ this.s = { options: {} };
22724
+ this.description = {
22725
+ type: "Unknown",
22726
+ servers: /* @__PURE__ */ new Map(),
22727
+ hasServer: () => false
22728
+ };
22729
+ }
22730
+ lastHello() {
22731
+ return {};
22732
+ }
22733
+ lastIsMaster() {
22734
+ return {};
22735
+ }
22736
+ shouldCheckForSessionSupport() {
22737
+ return false;
22738
+ }
22739
+ hasSessionSupport() {
22740
+ return true;
22741
+ }
22742
+ isConnected() {
22743
+ return true;
22744
+ }
22745
+ isDestroyed() {
22746
+ return false;
22747
+ }
22748
+ selectServerAsync() {
22749
+ return Promise.resolve(this.fakeServer);
22750
+ }
22751
+ selectServer(_selector, _options, callback) {
22752
+ if (typeof callback === "function") callback(null, this.fakeServer);
22753
+ }
22754
+ };
22486
22755
 
22487
22756
  //#endregion
22488
22757
  //#region src/instrumentation/libraries/mongodb/handlers/ConnectionHandler.ts
@@ -22517,7 +22786,7 @@ var ConnectionHandler = class {
22517
22786
  inputValue,
22518
22787
  isPreAppStart: !this.isAppReady()
22519
22788
  }, (spanInfo) => {
22520
- return this.handleReplayConnect(spanInfo, thisArg);
22789
+ return this.handleReplayConnect(spanInfo, thisArg, args);
22521
22790
  });
22522
22791
  }
22523
22792
  });
@@ -22548,17 +22817,21 @@ var ConnectionHandler = class {
22548
22817
  * - DISABLED: Passthrough.
22549
22818
  */
22550
22819
  handleClose(original, thisArg, args) {
22551
- if (this.mode === TuskDriftMode.REPLAY) return handleReplayMode({
22552
- noOpRequestHandler: () => {
22553
- return Promise.resolve();
22554
- },
22555
- isServerRequest: false,
22556
- replayModeHandler: () => {
22557
- logger.debug(`[${this.instrumentationName}] Replaying MongoDB close (no-op)`);
22558
- return Promise.resolve();
22559
- }
22560
- });
22561
- else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
22820
+ if (this.mode === TuskDriftMode.REPLAY) {
22821
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
22822
+ return handleReplayMode({
22823
+ noOpRequestHandler: () => {
22824
+ if (callback) callback();
22825
+ return Promise.resolve();
22826
+ },
22827
+ isServerRequest: false,
22828
+ replayModeHandler: () => {
22829
+ logger.debug(`[${this.instrumentationName}] Replaying MongoDB close (no-op)`);
22830
+ if (callback) callback();
22831
+ return Promise.resolve();
22832
+ }
22833
+ });
22834
+ } else if (this.mode === TuskDriftMode.RECORD) return handleRecordMode({
22562
22835
  originalFunctionCall: () => original.apply(thisArg, args),
22563
22836
  recordModeHandler: ({ isPreAppStart }) => {
22564
22837
  return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
@@ -22588,7 +22861,18 @@ var ConnectionHandler = class {
22588
22861
  return original.apply(thisArg, args);
22589
22862
  }
22590
22863
  handleRecordConnect(spanInfo, original, thisArg, args) {
22591
- return original.apply(thisArg, args).then((result) => {
22864
+ const connectResult = original.apply(thisArg, args);
22865
+ if (!(connectResult && typeof connectResult.then === "function")) {
22866
+ try {
22867
+ logger.debug(`[${this.instrumentationName}] MongoDB connect returned a non-promise value; recording best-effort span`);
22868
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { connected: true } });
22869
+ SpanUtils.endSpan(spanInfo.span, { code: import_src$13.SpanStatusCode.OK });
22870
+ } catch (error) {
22871
+ logger.error(`[${this.instrumentationName}] Error ending non-promise connect span:`, error);
22872
+ }
22873
+ return Promise.resolve(connectResult);
22874
+ }
22875
+ return connectResult.then((result) => {
22592
22876
  try {
22593
22877
  logger.debug(`[${this.instrumentationName}] MongoDB connection created successfully (${SpanUtils.getTraceInfo()})`);
22594
22878
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { connected: true } });
@@ -22611,8 +22895,9 @@ var ConnectionHandler = class {
22611
22895
  throw error;
22612
22896
  });
22613
22897
  }
22614
- handleReplayConnect(spanInfo, thisArg) {
22898
+ handleReplayConnect(spanInfo, thisArg, args) {
22615
22899
  logger.debug(`[${this.instrumentationName}] Replaying MongoDB connection (skipping TCP connect)`);
22900
+ this.injectFakeTopology(thisArg);
22616
22901
  try {
22617
22902
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: {
22618
22903
  connected: true,
@@ -22622,10 +22907,35 @@ var ConnectionHandler = class {
22622
22907
  } catch (error) {
22623
22908
  logger.error(`[${this.instrumentationName}] Error ending replay connect span:`, error);
22624
22909
  }
22910
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
22911
+ if (callback) {
22912
+ callback(null, thisArg);
22913
+ return Promise.resolve(thisArg);
22914
+ }
22625
22915
  return Promise.resolve(thisArg);
22626
22916
  }
22917
+ injectFakeTopology(client) {
22918
+ const fakeTopology = new TdFakeTopology();
22919
+ if (!client) return;
22920
+ if (!client.topology) client.topology = fakeTopology;
22921
+ if (client.s && !client.s.topology) client.s.topology = fakeTopology;
22922
+ if (client.s) {
22923
+ if (client.s.hasBeenClosed === true) client.s.hasBeenClosed = false;
22924
+ if (client.s.isMongoClient === false) client.s.isMongoClient = true;
22925
+ }
22926
+ }
22627
22927
  handleRecordClose(spanInfo, original, thisArg, args) {
22628
- return original.apply(thisArg, args).then(() => {
22928
+ const closeResult = original.apply(thisArg, args);
22929
+ if (!(closeResult && typeof closeResult.then === "function")) {
22930
+ try {
22931
+ SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { closed: true } });
22932
+ SpanUtils.endSpan(spanInfo.span, { code: import_src$13.SpanStatusCode.OK });
22933
+ } catch (error) {
22934
+ logger.error(`[${this.instrumentationName}] Error ending non-promise close span:`, error);
22935
+ }
22936
+ return Promise.resolve();
22937
+ }
22938
+ return closeResult.then(() => {
22629
22939
  try {
22630
22940
  logger.debug(`[${this.instrumentationName}] MongoDB connection closed successfully (${SpanUtils.getTraceInfo()})`);
22631
22941
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: { closed: true } });
@@ -22706,21 +23016,37 @@ var TdFakeFindCursor = class TdFakeFindCursor {
22706
23016
  });
22707
23017
  if (this._mockLoadPromise) await this._mockLoadPromise;
22708
23018
  }
22709
- async toArray() {
22710
- await this._ensureMockLoaded();
22711
- return [...this.documents];
23019
+ toArray(callback) {
23020
+ const promise = this._ensureMockLoaded().then(() => [...this.documents]);
23021
+ if (typeof callback === "function") {
23022
+ promise.then((docs) => callback(null, docs), (error) => callback(error));
23023
+ return;
23024
+ }
23025
+ return promise;
22712
23026
  }
22713
- async next() {
22714
- await this._ensureMockLoaded();
22715
- return this.documents[this.index++] ?? null;
23027
+ next(callback) {
23028
+ const promise = this._ensureMockLoaded().then(() => this.documents[this.index++] ?? null);
23029
+ if (typeof callback === "function") {
23030
+ promise.then((doc) => callback(null, doc), (error) => callback(error));
23031
+ return;
23032
+ }
23033
+ return promise;
22716
23034
  }
22717
- async tryNext() {
22718
- await this._ensureMockLoaded();
22719
- return this.documents[this.index++] ?? null;
23035
+ tryNext(callback) {
23036
+ const promise = this._ensureMockLoaded().then(() => this.documents[this.index++] ?? null);
23037
+ if (typeof callback === "function") {
23038
+ promise.then((doc) => callback(null, doc), (error) => callback(error));
23039
+ return;
23040
+ }
23041
+ return promise;
22720
23042
  }
22721
- async hasNext() {
22722
- await this._ensureMockLoaded();
22723
- return this.index < this.documents.length;
23043
+ hasNext(callback) {
23044
+ const promise = this._ensureMockLoaded().then(() => this.index < this.documents.length);
23045
+ if (typeof callback === "function") {
23046
+ promise.then((hasNext) => callback(null, hasNext), (error) => callback(error));
23047
+ return;
23048
+ }
23049
+ return promise;
22724
23050
  }
22725
23051
  async forEach(fn) {
22726
23052
  await this._ensureMockLoaded();
@@ -22793,7 +23119,14 @@ var TdFakeFindCursor = class TdFakeFindCursor {
22793
23119
  map() {
22794
23120
  return this;
22795
23121
  }
22796
- async close() {}
23122
+ close(callback) {
23123
+ const promise = Promise.resolve();
23124
+ if (typeof callback === "function") {
23125
+ promise.then(() => callback(), (error) => callback(error));
23126
+ return;
23127
+ }
23128
+ return promise;
23129
+ }
22797
23130
  rewind() {
22798
23131
  this.index = 0;
22799
23132
  }
@@ -22911,45 +23244,6 @@ var TdFakeChangeStream = class {
22911
23244
  }
22912
23245
  };
22913
23246
 
22914
- //#endregion
22915
- //#region src/instrumentation/libraries/mongodb/mocks/FakeTopology.ts
22916
- /**
22917
- * Fake MongoDB Topology for replay mode.
22918
- *
22919
- * When BulkOperationBase is constructed, it calls getTopology(collection)
22920
- * which requires a connected topology. In replay mode, no real connection
22921
- * exists, so we inject this fake topology to satisfy the constructor's
22922
- * requirements without hitting a real server.
22923
- *
22924
- * The constructor reads:
22925
- * - topology.lastHello() -> returns {} so all size limits use defaults
22926
- * - topology.lastIsMaster() -> returns {} (legacy compat)
22927
- * - topology.s.options -> returns {} so autoEncryption check is false
22928
- *
22929
- * The topology is also injected onto the shared MongoClient (via
22930
- * _injectFakeTopology) and persists across requests. Other driver code
22931
- * (e.g. ClientSession.loadBalanced getter) accesses
22932
- * topology.description.type, so we provide a minimal description to
22933
- * prevent TypeError when the property is read.
22934
- */
22935
- var TdFakeTopology = class extends EventEmitter {
22936
- constructor() {
22937
- super();
22938
- this.s = { options: {} };
22939
- this.description = {
22940
- type: "Unknown",
22941
- servers: /* @__PURE__ */ new Map(),
22942
- hasServer: () => false
22943
- };
22944
- }
22945
- lastHello() {
22946
- return {};
22947
- }
22948
- lastIsMaster() {
22949
- return {};
22950
- }
22951
- };
22952
-
22953
23247
  //#endregion
22954
23248
  //#region node_modules/bson/lib/bson.cjs
22955
23249
  var require_bson = /* @__PURE__ */ __commonJS({ "node_modules/bson/lib/bson.cjs": ((exports) => {
@@ -26999,7 +27293,8 @@ const COLLECTION_METHODS_TO_WRAP = [
26999
27293
  "createIndexes",
27000
27294
  "dropIndex",
27001
27295
  "dropIndexes",
27002
- "indexes"
27296
+ "indexes",
27297
+ "drop"
27003
27298
  ];
27004
27299
  /**
27005
27300
  * Cursor-returning methods on Collection.prototype.
@@ -27024,6 +27319,12 @@ const DB_METHODS_TO_WRAP = [
27024
27319
  * Db.prototype methods that return cursors.
27025
27320
  */
27026
27321
  const DB_CURSOR_METHODS_TO_WRAP = ["listCollections", "aggregate"];
27322
+ const SUPPORTED_MONGODB_VERSIONS = [
27323
+ "4.*",
27324
+ "5.*",
27325
+ "6.*",
27326
+ "7.*"
27327
+ ];
27027
27328
  var MongodbInstrumentation = class extends TdInstrumentationBase {
27028
27329
  constructor(config = {}) {
27029
27330
  super("mongodb", config);
@@ -27035,38 +27336,37 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27035
27336
  init() {
27036
27337
  return [new TdInstrumentationNodeModule({
27037
27338
  name: "mongodb",
27038
- supportedVersions: [
27039
- "5.*",
27040
- "6.*",
27041
- "7.*"
27042
- ],
27043
- patch: (moduleExports) => this._patchMongodbModule(moduleExports),
27339
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27340
+ patch: (moduleExports, moduleVersion) => this._patchMongodbModule(moduleExports, moduleVersion),
27044
27341
  files: [
27045
27342
  new TdInstrumentationNodeModuleFile({
27046
27343
  name: "mongodb/lib/sessions.js",
27047
- supportedVersions: [
27048
- "5.*",
27049
- "6.*",
27050
- "7.*"
27051
- ],
27344
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27345
+ patch: (moduleExports) => this._patchSessionModule(moduleExports)
27346
+ }),
27347
+ new TdInstrumentationNodeModuleFile({
27348
+ name: "mongodb/lib/sessions",
27349
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27052
27350
  patch: (moduleExports) => this._patchSessionModule(moduleExports)
27053
27351
  }),
27054
27352
  new TdInstrumentationNodeModuleFile({
27055
27353
  name: "mongodb/lib/bulk/ordered.js",
27056
- supportedVersions: [
27057
- "5.*",
27058
- "6.*",
27059
- "7.*"
27060
- ],
27354
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27355
+ patch: (moduleExports) => this._patchOrderedBulkModule(moduleExports)
27356
+ }),
27357
+ new TdInstrumentationNodeModuleFile({
27358
+ name: "mongodb/lib/bulk/ordered",
27359
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27061
27360
  patch: (moduleExports) => this._patchOrderedBulkModule(moduleExports)
27062
27361
  }),
27063
27362
  new TdInstrumentationNodeModuleFile({
27064
27363
  name: "mongodb/lib/bulk/unordered.js",
27065
- supportedVersions: [
27066
- "5.*",
27067
- "6.*",
27068
- "7.*"
27069
- ],
27364
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27365
+ patch: (moduleExports) => this._patchUnorderedBulkModule(moduleExports)
27366
+ }),
27367
+ new TdInstrumentationNodeModuleFile({
27368
+ name: "mongodb/lib/bulk/unordered",
27369
+ supportedVersions: [...SUPPORTED_MONGODB_VERSIONS],
27070
27370
  patch: (moduleExports) => this._patchUnorderedBulkModule(moduleExports)
27071
27371
  })
27072
27372
  ]
@@ -27077,8 +27377,9 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27077
27377
  * Wraps MongoClient, Collection, Db, and cursor prototypes to intercept
27078
27378
  * all database operations for record/replay.
27079
27379
  */
27080
- _patchMongodbModule(mongodbModule) {
27081
- logger.debug(`[${this.INSTRUMENTATION_NAME}] Patching MongoDB module in ${this.mode} mode`);
27380
+ _patchMongodbModule(mongodbModule, moduleVersion) {
27381
+ this.mongodbMajorVersion = this._getMajorVersion(moduleVersion);
27382
+ logger.debug(`[${this.INSTRUMENTATION_NAME}] Patching MongoDB module v${moduleVersion || "unknown"} in ${this.mode} mode`);
27082
27383
  if (this.isModulePatched(mongodbModule)) {
27083
27384
  logger.debug(`[${this.INSTRUMENTATION_NAME}] MongoDB module already patched, skipping`);
27084
27385
  return mongodbModule;
@@ -27145,6 +27446,11 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27145
27446
  logger.debug(`[${this.INSTRUMENTATION_NAME}] MongoDB module patching complete`);
27146
27447
  return mongodbModule;
27147
27448
  }
27449
+ _getMajorVersion(version$1) {
27450
+ if (!version$1) return void 0;
27451
+ const major$1 = Number(version$1.split(".")[0]);
27452
+ return Number.isFinite(major$1) ? major$1 : void 0;
27453
+ }
27148
27454
  /**
27149
27455
  * Patch all Collection.prototype CRUD methods that return Promises.
27150
27456
  * Guards each method with typeof check for version compatibility.
@@ -27170,13 +27476,24 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27170
27476
  const submodule = `collection-${methodName}`;
27171
27477
  return (original) => {
27172
27478
  return function(...args) {
27479
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
27480
+ const effectiveArgs = callback ? args.slice(0, -1) : args;
27173
27481
  const collectionName = this?.s?.namespace?.collection;
27174
27482
  const databaseName = this?.s?.namespace?.db;
27175
- const inputValue = self._extractCollectionInput(methodName, collectionName, databaseName, args);
27483
+ const inputValue = self._extractCollectionInput(methodName, collectionName, databaseName, effectiveArgs);
27176
27484
  if (self.mode === TuskDriftMode.DISABLED) return original.apply(this, args);
27177
- if (self.mode === TuskDriftMode.RECORD) return self._handleRecordCollectionMethod(original, this, args, inputValue, spanName, submodule);
27178
- if (self.mode === TuskDriftMode.REPLAY) return self._handleReplayCollectionMethod(original, this, args, inputValue, spanName, submodule, methodName);
27179
- return original.apply(this, args);
27485
+ const invokeHandler = () => {
27486
+ if (self.mode === TuskDriftMode.RECORD) return self._handleRecordCollectionMethod(original, this, effectiveArgs, inputValue, spanName, submodule);
27487
+ if (self.mode === TuskDriftMode.REPLAY) return self._handleReplayCollectionMethod(original, this, effectiveArgs, inputValue, spanName, submodule, methodName);
27488
+ return original.apply(this, effectiveArgs);
27489
+ };
27490
+ if (callback) {
27491
+ Promise.resolve(invokeHandler()).then((result) => callback(null, result)).catch((error) => callback(error));
27492
+ return;
27493
+ }
27494
+ if (self.mode === TuskDriftMode.RECORD) return invokeHandler();
27495
+ if (self.mode === TuskDriftMode.REPLAY) return invokeHandler();
27496
+ return original.apply(this, effectiveArgs);
27180
27497
  };
27181
27498
  };
27182
27499
  }
@@ -27199,7 +27516,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27199
27516
  isPreAppStart,
27200
27517
  stopRecordingChildSpans: true
27201
27518
  }, (spanInfo) => {
27202
- return original.apply(thisArg, args).then((result) => {
27519
+ const resultPromise = original.apply(thisArg, args);
27520
+ return Promise.resolve(resultPromise).then((result) => {
27203
27521
  try {
27204
27522
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
27205
27523
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -27236,7 +27554,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27236
27554
  },
27237
27555
  isServerRequest: false,
27238
27556
  replayModeHandler: () => {
27239
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
27557
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(this._getNoOpResult(methodName)), {
27240
27558
  name: spanName,
27241
27559
  kind: import_src$12.SpanKind.CLIENT,
27242
27560
  submodule,
@@ -27391,10 +27709,16 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27391
27709
  };
27392
27710
  if (typeof cursor.toArray === "function") {
27393
27711
  const originalToArray = cursor.toArray.bind(cursor);
27394
- cursor.toArray = () => {
27395
- if (cursorState.recorded) return originalToArray();
27712
+ cursor.toArray = (callback) => {
27713
+ if (cursorState.recorded) {
27714
+ if (typeof callback === "function") {
27715
+ originalToArray().then((result) => callback(null, result)).catch((error) => callback(error));
27716
+ return;
27717
+ }
27718
+ return originalToArray();
27719
+ }
27396
27720
  cursorState.recorded = true;
27397
- return import_src$12.context.with(creationContext, () => {
27721
+ const resultPromise = import_src$12.context.with(creationContext, () => {
27398
27722
  return handleRecordMode({
27399
27723
  originalFunctionCall: () => originalToArray(),
27400
27724
  recordModeHandler: ({ isPreAppStart }) => {
@@ -27434,37 +27758,125 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27434
27758
  spanKind: import_src$12.SpanKind.CLIENT
27435
27759
  });
27436
27760
  });
27761
+ if (typeof callback === "function") {
27762
+ resultPromise.then((result) => callback(null, result)).catch((error) => callback(error));
27763
+ return;
27764
+ }
27765
+ return resultPromise;
27437
27766
  };
27438
27767
  }
27439
27768
  if (typeof cursor.next === "function") {
27440
27769
  const originalNext = cursor.next.bind(cursor);
27441
- cursor.next = async () => {
27442
- if (cursorState.recorded) return originalNext();
27443
- if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return originalNext();
27444
- try {
27445
- const doc = await (cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => originalNext()) : originalNext());
27770
+ cursor.next = (callback) => {
27771
+ if (typeof callback === "function") {
27772
+ if (cursorState.recorded) {
27773
+ let settled$1 = false;
27774
+ const finish$1 = (error, doc) => {
27775
+ if (settled$1) return;
27776
+ settled$1 = true;
27777
+ callback(error, doc);
27778
+ };
27779
+ const maybeResult$1 = originalNext((error, doc) => finish$1(error, doc));
27780
+ Promise.resolve(maybeResult$1).then((doc) => finish$1(null, doc)).catch((error) => finish$1(error, null));
27781
+ return;
27782
+ }
27783
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) {
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
+ let settled = false;
27795
+ const finish = (error, doc) => {
27796
+ if (settled) return;
27797
+ settled = true;
27798
+ if (error) {
27799
+ handleCursorError(error);
27800
+ callback(error);
27801
+ return;
27802
+ }
27803
+ if (doc !== null) cursorState.collectedDocuments.push(doc);
27804
+ else finalizeCursorSpan();
27805
+ callback(null, doc);
27806
+ };
27807
+ const runOriginal = () => originalNext((error, doc) => {
27808
+ finish(error, doc);
27809
+ });
27810
+ const maybeResult = cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => runOriginal()) : runOriginal();
27811
+ Promise.resolve(maybeResult).then((doc) => finish(null, doc)).catch((error) => finish(error, null));
27812
+ return;
27813
+ }
27814
+ if (cursorState.recorded) return callback === void 0 ? originalNext() : originalNext(callback);
27815
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return callback === void 0 ? originalNext() : originalNext(callback);
27816
+ return Promise.resolve(cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => callback === void 0 ? originalNext() : originalNext(callback)) : callback === void 0 ? originalNext() : originalNext(callback)).then((doc) => {
27446
27817
  if (doc !== null) cursorState.collectedDocuments.push(doc);
27447
27818
  else finalizeCursorSpan();
27448
27819
  return doc;
27449
- } catch (error) {
27820
+ }).catch((error) => {
27450
27821
  handleCursorError(error);
27451
27822
  throw error;
27452
- }
27823
+ });
27453
27824
  };
27454
27825
  }
27455
27826
  if (typeof cursor.hasNext === "function") {
27456
27827
  const originalHasNext = cursor.hasNext.bind(cursor);
27457
- cursor.hasNext = async () => {
27458
- if (cursorState.recorded) return originalHasNext();
27459
- if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return originalHasNext();
27460
- try {
27461
- const result = await (cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => originalHasNext()) : originalHasNext());
27828
+ cursor.hasNext = (callback) => {
27829
+ if (typeof callback === "function") {
27830
+ if (cursorState.recorded) {
27831
+ let settled$1 = false;
27832
+ const finish$1 = (error, result) => {
27833
+ if (settled$1) return;
27834
+ settled$1 = true;
27835
+ callback(error, result);
27836
+ };
27837
+ const maybeResult$1 = originalHasNext((error, result) => finish$1(error, result));
27838
+ Promise.resolve(maybeResult$1).then((result) => finish$1(null, result)).catch((error) => finish$1(error, false));
27839
+ return;
27840
+ }
27841
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) {
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
+ let settled = false;
27853
+ const finish = (error, result) => {
27854
+ if (settled) return;
27855
+ settled = true;
27856
+ if (error) {
27857
+ handleCursorError(error);
27858
+ callback(error);
27859
+ return;
27860
+ }
27861
+ if (!result) finalizeCursorSpan();
27862
+ callback(null, result);
27863
+ };
27864
+ const runOriginal = () => originalHasNext((error, result) => {
27865
+ finish(error, result);
27866
+ });
27867
+ const maybeResult = cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => runOriginal()) : runOriginal();
27868
+ Promise.resolve(maybeResult).then((result) => finish(null, result)).catch((error) => finish(error, false));
27869
+ return;
27870
+ }
27871
+ if (cursorState.recorded) return callback === void 0 ? originalHasNext() : originalHasNext(callback);
27872
+ if (!import_src$12.context.with(creationContext, () => ensureSpanCreated())) return callback === void 0 ? originalHasNext() : originalHasNext(callback);
27873
+ return Promise.resolve(cursorState.spanInfo ? SpanUtils.withSpan(cursorState.spanInfo, () => callback === void 0 ? originalHasNext() : originalHasNext(callback)) : callback === void 0 ? originalHasNext() : originalHasNext(callback)).then((result) => {
27462
27874
  if (!result) finalizeCursorSpan();
27463
27875
  return result;
27464
- } catch (error) {
27876
+ }).catch((error) => {
27465
27877
  handleCursorError(error);
27466
27878
  throw error;
27467
- }
27879
+ });
27468
27880
  };
27469
27881
  }
27470
27882
  if (typeof cursor.forEach === "function") {
@@ -27520,14 +27932,30 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27520
27932
  }
27521
27933
  if (typeof cursor.close === "function") {
27522
27934
  const originalClose = cursor.close.bind(cursor);
27523
- cursor.close = async () => {
27524
- try {
27525
- await originalClose();
27935
+ cursor.close = (callback) => {
27936
+ if (typeof callback === "function") {
27937
+ let settled = false;
27938
+ const finish = (error) => {
27939
+ if (settled) return;
27940
+ settled = true;
27941
+ if (error) {
27942
+ handleCursorError(error);
27943
+ callback(error);
27944
+ return;
27945
+ }
27946
+ finalizeCursorSpan();
27947
+ callback();
27948
+ };
27949
+ const maybeResult = originalClose((error) => finish(error));
27950
+ Promise.resolve(maybeResult).then(() => finish()).catch((error) => finish(error));
27951
+ return;
27952
+ }
27953
+ return Promise.resolve(callback === void 0 ? originalClose() : originalClose(callback)).then(() => {
27526
27954
  finalizeCursorSpan();
27527
- } catch (error) {
27955
+ }).catch((error) => {
27528
27956
  handleCursorError(error);
27529
27957
  throw error;
27530
- }
27958
+ });
27531
27959
  };
27532
27960
  }
27533
27961
  if (typeof cursor[Symbol.asyncIterator] === "function") {
@@ -27803,6 +28231,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27803
28231
  case "dropIndex": return {};
27804
28232
  case "dropIndexes": return { ok: 1 };
27805
28233
  case "indexes": return [];
28234
+ case "drop": return true;
27806
28235
  default: return null;
27807
28236
  }
27808
28237
  }
@@ -27831,18 +28260,29 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27831
28260
  const submodule = `db-${methodName}`;
27832
28261
  return (original) => {
27833
28262
  return function(...args) {
28263
+ const callback = args.length > 0 && typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
28264
+ const effectiveArgs = callback ? args.slice(0, -1) : args;
27834
28265
  const databaseName = this?.databaseName || this?.s?.namespace?.db;
27835
- const inputValue = self._extractDbInput(methodName, databaseName, args);
28266
+ const inputValue = self._extractDbInput(methodName, databaseName, effectiveArgs);
27836
28267
  if (self.mode === TuskDriftMode.DISABLED) return original.apply(this, args);
27837
- if (self.mode === TuskDriftMode.RECORD) {
27838
- if (methodName === "createCollection") return self._handleRecordCreateCollection(original, this, args, inputValue, spanName, submodule);
27839
- return self._handleRecordDbMethod(original, this, args, inputValue, spanName, submodule);
27840
- }
27841
- if (self.mode === TuskDriftMode.REPLAY) {
27842
- if (methodName === "createCollection") return self._handleReplayCreateCollection(this, args, inputValue, spanName, submodule);
27843
- return self._handleReplayDbMethod(original, this, args, inputValue, spanName, submodule, methodName);
28268
+ const invokeHandler = () => {
28269
+ if (self.mode === TuskDriftMode.RECORD) {
28270
+ if (methodName === "createCollection") return self._handleRecordCreateCollection(original, this, effectiveArgs, inputValue, spanName, submodule);
28271
+ return self._handleRecordDbMethod(original, this, effectiveArgs, inputValue, spanName, submodule);
28272
+ }
28273
+ if (self.mode === TuskDriftMode.REPLAY) {
28274
+ if (methodName === "createCollection") return self._handleReplayCreateCollection(this, effectiveArgs, inputValue, spanName, submodule);
28275
+ return self._handleReplayDbMethod(original, this, effectiveArgs, inputValue, spanName, submodule, methodName);
28276
+ }
28277
+ return original.apply(this, effectiveArgs);
28278
+ };
28279
+ if (callback) {
28280
+ Promise.resolve(invokeHandler()).then((result) => callback(null, result)).catch((error) => callback(error));
28281
+ return;
27844
28282
  }
27845
- return original.apply(this, args);
28283
+ if (self.mode === TuskDriftMode.RECORD) return invokeHandler();
28284
+ if (self.mode === TuskDriftMode.REPLAY) return invokeHandler();
28285
+ return original.apply(this, effectiveArgs);
27846
28286
  };
27847
28287
  };
27848
28288
  }
@@ -27865,7 +28305,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27865
28305
  isPreAppStart,
27866
28306
  stopRecordingChildSpans: true
27867
28307
  }, (spanInfo) => {
27868
- return original.apply(thisArg, args).then((result) => {
28308
+ const resultPromise = original.apply(thisArg, args);
28309
+ return Promise.resolve(resultPromise).then((result) => {
27869
28310
  try {
27870
28311
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
27871
28312
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -27902,7 +28343,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27902
28343
  },
27903
28344
  isServerRequest: false,
27904
28345
  replayModeHandler: () => {
27905
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28346
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(this._getDbNoOpResult(methodName)), {
27906
28347
  name: spanName,
27907
28348
  kind: import_src$12.SpanKind.CLIENT,
27908
28349
  submodule,
@@ -27960,7 +28401,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
27960
28401
  isPreAppStart,
27961
28402
  stopRecordingChildSpans: true
27962
28403
  }, (spanInfo) => {
27963
- return original.apply(thisArg, args).then((result) => {
28404
+ const resultPromise = original.apply(thisArg, args);
28405
+ return Promise.resolve(resultPromise).then((result) => {
27964
28406
  try {
27965
28407
  const collectionName = args[0];
27966
28408
  SpanUtils.addSpanAttributes(spanInfo.span, { outputValue: sanitizeBsonValue({ collectionName }) });
@@ -28224,13 +28666,26 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28224
28666
  */
28225
28667
  _getStartSessionWrapper() {
28226
28668
  const self = this;
28669
+ const REPLAY_WITH_TXN_PATCHED = Symbol("tdReplayWithTransactionPatched");
28227
28670
  return (original) => {
28228
28671
  return function(...args) {
28229
- if (self.mode === TuskDriftMode.REPLAY) {
28230
- logger.debug(`[${self.INSTRUMENTATION_NAME}] startSession called in REPLAY mode`);
28231
- return original.apply(this, args);
28672
+ const session = original.apply(this, args);
28673
+ if (self.mode === TuskDriftMode.REPLAY && self.mongodbMajorVersion === 4 && session && typeof session.withTransaction === "function" && !session[REPLAY_WITH_TXN_PATCHED]) {
28674
+ const originalWithTransaction = session.withTransaction.bind(session);
28675
+ session.withTransaction = async (fn, _options) => {
28676
+ try {
28677
+ await Promise.resolve(fn(session));
28678
+ if (typeof session.commitTransaction === "function") return await session.commitTransaction();
28679
+ return;
28680
+ } catch (error) {
28681
+ if (typeof session.abortTransaction === "function") await session.abortTransaction();
28682
+ throw error;
28683
+ }
28684
+ };
28685
+ session.__tdOriginalWithTransaction = originalWithTransaction;
28686
+ session[REPLAY_WITH_TXN_PATCHED] = true;
28232
28687
  }
28233
- return original.apply(this, args);
28688
+ return session;
28234
28689
  };
28235
28690
  };
28236
28691
  }
@@ -28306,7 +28761,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28306
28761
  isPreAppStart,
28307
28762
  stopRecordingChildSpans: true
28308
28763
  }, (spanInfo) => {
28309
- return original.apply(thisArg, args).then((result) => {
28764
+ const resultPromise = original.apply(thisArg, args);
28765
+ return Promise.resolve(resultPromise).then((result) => {
28310
28766
  try {
28311
28767
  addOutputAttributesToSpan(spanInfo, result ?? { success: true });
28312
28768
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -28341,7 +28797,7 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28341
28797
  noOpRequestHandler: () => Promise.resolve(void 0),
28342
28798
  isServerRequest: false,
28343
28799
  replayModeHandler: () => {
28344
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
28800
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve(void 0), {
28345
28801
  name: spanName,
28346
28802
  kind: import_src$12.SpanKind.CLIENT,
28347
28803
  submodule,
@@ -28615,7 +29071,8 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28615
29071
  isPreAppStart,
28616
29072
  stopRecordingChildSpans: true
28617
29073
  }, (spanInfo) => {
28618
- return original.apply(thisArg, args).then((result) => {
29074
+ const resultPromise = original.apply(thisArg, args);
29075
+ return Promise.resolve(resultPromise).then((result) => {
28619
29076
  try {
28620
29077
  addOutputAttributesToSpan(spanInfo, wrapDirectOutput(result));
28621
29078
  SpanUtils.endSpan(spanInfo.span, { code: import_src$12.SpanStatusCode.OK });
@@ -28661,7 +29118,16 @@ var MongodbInstrumentation = class extends TdInstrumentationBase {
28661
29118
  },
28662
29119
  isServerRequest: false,
28663
29120
  replayModeHandler: () => {
28664
- return SpanUtils.createAndExecuteSpan(this.mode, () => original.apply(thisArg, args), {
29121
+ return SpanUtils.createAndExecuteSpan(this.mode, () => Promise.resolve({
29122
+ acknowledged: false,
29123
+ insertedCount: 0,
29124
+ matchedCount: 0,
29125
+ modifiedCount: 0,
29126
+ deletedCount: 0,
29127
+ upsertedCount: 0,
29128
+ insertedIds: {},
29129
+ upsertedIds: {}
29130
+ }), {
28665
29131
  name: spanName,
28666
29132
  kind: import_src$12.SpanKind.CLIENT,
28667
29133
  submodule,
@@ -40896,7 +41362,7 @@ var require_src = /* @__PURE__ */ __commonJS({ "node_modules/@opentelemetry/sdk-
40896
41362
  //#endregion
40897
41363
  //#region package.json
40898
41364
  var import_src$1 = /* @__PURE__ */ __toESM(require_src(), 1);
40899
- var version = "0.1.34";
41365
+ var version = "0.1.35";
40900
41366
 
40901
41367
  //#endregion
40902
41368
  //#region src/version.ts
@@ -42797,7 +43263,14 @@ var TuskDriftCore = class TuskDriftCore {
42797
43263
  logger.info(`Rust core path enabled at startup (env=${envDisplay}, reason=${status.reason}).`);
42798
43264
  return;
42799
43265
  }
42800
- logger.warn(`Rust core path requested but binding unavailable; falling back to JavaScript path (env=${envDisplay}, reason=${status.reason}, error=${status.bindingError}).`);
43266
+ logger.info(`Rust core path unavailable at startup; using JavaScript path instead (env=${envDisplay}, reason=${status.reason}, error=${status.bindingError}).`);
43267
+ }
43268
+ logStartupSummary() {
43269
+ const serviceName = this.config.service?.name || "unknown";
43270
+ const serviceId = this.config.service?.id || "<unset>";
43271
+ const environment = this.initParams.env || "<unset>";
43272
+ const exportSpans = this.config.recording?.export_spans || false;
43273
+ 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}).`);
42801
43274
  }
42802
43275
  validateSamplingRate(value, source) {
42803
43276
  if (typeof value !== "number" || isNaN(value)) {
@@ -43044,7 +43517,7 @@ var TuskDriftCore = class TuskDriftCore {
43044
43517
  this.initializeTracing({ baseDirectory });
43045
43518
  this.createEnvVarsSnapshot();
43046
43519
  this.initialized = true;
43047
- logger.info("SDK initialized successfully");
43520
+ this.logStartupSummary();
43048
43521
  }
43049
43522
  markAppAsReady() {
43050
43523
  if (!this.initialized) {