@reproapp/node-sdk 0.0.25 → 0.0.27

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/src/index.ts CHANGED
@@ -1154,6 +1154,13 @@ type Ctx = {
1154
1154
  maskReq?: MaskRequestContext;
1155
1155
  privacyTasks?: Set<Promise<void>>;
1156
1156
  captureScope?: RequestCaptureScopeState;
1157
+ nativeMongoSuppressions?: NativeMongoSuppression[];
1158
+ };
1159
+
1160
+ type NativeMongoSuppression = {
1161
+ collection?: string | null;
1162
+ ops?: Set<string>;
1163
+ startedAt: number;
1157
1164
  };
1158
1165
 
1159
1166
  type RequestCaptureScopeState = {
@@ -1213,6 +1220,54 @@ function buildRequestCaptureManifestPayload(
1213
1220
  const als = new AsyncLocalStorage<Ctx>();
1214
1221
  const getCtx = () => als.getStore() || {};
1215
1222
 
1223
+ function normalizeNativeMongoSuppressionOp(op: string | null | undefined): string {
1224
+ return String(op ?? '').trim();
1225
+ }
1226
+
1227
+ function pushNativeMongoSuppression(collection: any, ops?: string[] | readonly string[]): NativeMongoSuppression | null {
1228
+ const store = als.getStore();
1229
+ if (!store) return null;
1230
+ const normalizedOps = Array.isArray(ops)
1231
+ ? ops.map(normalizeNativeMongoSuppressionOp).filter(Boolean)
1232
+ : [];
1233
+ const token: NativeMongoSuppression = {
1234
+ collection: collection == null ? null : String(collection),
1235
+ ops: normalizedOps.length ? new Set(normalizedOps) : undefined,
1236
+ startedAt: Date.now(),
1237
+ };
1238
+ if (!Array.isArray(store.nativeMongoSuppressions)) {
1239
+ store.nativeMongoSuppressions = [];
1240
+ }
1241
+ store.nativeMongoSuppressions.push(token);
1242
+ return token;
1243
+ }
1244
+
1245
+ function popNativeMongoSuppression(token: NativeMongoSuppression | null | undefined): void {
1246
+ if (!token) return;
1247
+ const store = als.getStore();
1248
+ const suppressions = store?.nativeMongoSuppressions;
1249
+ if (!Array.isArray(suppressions) || suppressions.length === 0) return;
1250
+ const index = suppressions.indexOf(token);
1251
+ if (index >= 0) {
1252
+ suppressions.splice(index, 1);
1253
+ }
1254
+ }
1255
+
1256
+ function isNativeMongoSuppressed(collection: any, op: string): boolean {
1257
+ const suppressions = (getCtx() as Ctx).nativeMongoSuppressions;
1258
+ if (!Array.isArray(suppressions) || suppressions.length === 0) return false;
1259
+ const normalizedCollection = collection == null ? null : String(collection);
1260
+ const normalizedOp = normalizeNativeMongoSuppressionOp(op);
1261
+ const now = Date.now();
1262
+ for (const token of suppressions) {
1263
+ if (!token || now - token.startedAt > 60000) continue;
1264
+ if (token.collection && normalizedCollection && token.collection !== normalizedCollection) continue;
1265
+ if (token.ops && token.ops.size > 0 && !token.ops.has(normalizedOp)) continue;
1266
+ return true;
1267
+ }
1268
+ return false;
1269
+ }
1270
+
1216
1271
  function currentClockSkewMs(): number {
1217
1272
  const store = als.getStore();
1218
1273
  const skew = store?.clockSkewMs;
@@ -5424,6 +5479,15 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
5424
5479
  // - ONLY schema middleware (pre/post) for specific ops
5425
5480
  // - keeps your existing doc-diff hooks
5426
5481
  // ===================================================================
5482
+ function nativeMongoOpsForMongooseOp(op: string): string[] {
5483
+ if (op === 'findByIdAndUpdate') return ['findOneAndUpdate'];
5484
+ if (op === 'findByIdAndDelete' || op === 'findByIdAndRemove' || op === 'findOneAndRemove') {
5485
+ return ['findOneAndDelete'];
5486
+ }
5487
+ if (op === 'findByIdAndReplace') return ['findOneAndReplace'];
5488
+ return [op];
5489
+ }
5490
+
5427
5491
  export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5428
5492
  return function (schema: Schema) {
5429
5493
  // -------- pre/post save (unchanged) --------
@@ -5441,6 +5505,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5441
5505
  ? (this as any).modifiedPaths()
5442
5506
  : [],
5443
5507
  };
5508
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5509
+ (this as any).__repro_meta.collection,
5510
+ this.isNew ? ['insertOne'] : ['updateOne', 'replaceOne'],
5511
+ );
5444
5512
  next();
5445
5513
  });
5446
5514
 
@@ -5475,6 +5543,12 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5475
5543
  t: alignTimestamp(meta.t0 ?? Date.now()),
5476
5544
  spanContext,
5477
5545
  });
5546
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5547
+ });
5548
+
5549
+ schema.post('save', { document: true }, function (error: any, _doc: any, next: Function) {
5550
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5551
+ next(error);
5478
5552
  });
5479
5553
 
5480
5554
  // -------- findOneAndUpdate capture (unchanged) --------
@@ -5489,6 +5563,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5489
5563
  (this as any).__repro_spanContext = captureSpanContextFromTracer(this);
5490
5564
  (this as any).__repro_filter = filter;
5491
5565
  (this as any).__repro_update = this.getUpdate?.() ?? (this as any)._update ?? undefined;
5566
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5567
+ (this as any).__repro_collection,
5568
+ ['findOneAndUpdate'],
5569
+ );
5492
5570
  } catch {}
5493
5571
  next();
5494
5572
  });
@@ -5518,8 +5596,14 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5518
5596
  t: alignTimestamp((this as any).__repro_t0 ?? Date.now()),
5519
5597
  spanContext,
5520
5598
  });
5599
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5521
5600
  });
5522
5601
 
5602
+ schema.post<Query<any, any>>('findOneAndUpdate', function (this: any, error: any, _res: any, next: Function) {
5603
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5604
+ next(error);
5605
+ } as any);
5606
+
5523
5607
  // -------- deleteOne capture (unchanged) --------
5524
5608
  schema.pre<Query<any, any>>('deleteOne', { document: false, query: true }, function (next) {
5525
5609
  const { sid, aid } = getCtx(); if (!sid || !aid) return next();
@@ -5529,6 +5613,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5529
5613
  (this as any).__repro_collection = resolveCollectionOrWarn(this, 'query');
5530
5614
  (this as any).__repro_filter = filter;
5531
5615
  (this as any).__repro_spanContext = captureSpanContextFromTracer(this);
5616
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5617
+ (this as any).__repro_collection,
5618
+ ['deleteOne'],
5619
+ );
5532
5620
  } catch {}
5533
5621
  next();
5534
5622
  });
@@ -5550,8 +5638,14 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5550
5638
  t: alignTimestamp((this as any).__repro_t0 ?? Date.now()),
5551
5639
  spanContext,
5552
5640
  });
5641
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5553
5642
  });
5554
5643
 
5644
+ schema.post<Query<any, any>>('deleteOne', { document: false, query: true }, function (this: any, error: any, _res: any, next: Function) {
5645
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5646
+ next(error);
5647
+ } as any);
5648
+
5555
5649
  // -------- NON-intrusive generic query telemetry via schema hooks -------
5556
5650
  const READ_OPS = [
5557
5651
  'find',
@@ -5593,6 +5687,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5593
5687
  rawProjection,
5594
5688
  rawOptions,
5595
5689
  };
5690
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5691
+ (this as any).__repro_qmeta.collection,
5692
+ nativeMongoOpsForMongooseOp(op),
5693
+ );
5596
5694
  } catch {
5597
5695
  (this as any).__repro_qmeta = {
5598
5696
  t0: Date.now(),
@@ -5600,6 +5698,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5600
5698
  op,
5601
5699
  spanContext: captureSpanContextFromTracer(this),
5602
5700
  };
5701
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5702
+ 'unknown',
5703
+ nativeMongoOpsForMongooseOp(op),
5704
+ );
5603
5705
  }
5604
5706
  next();
5605
5707
  });
@@ -5625,7 +5727,13 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5625
5727
  t: alignedNow(),
5626
5728
  spanContext,
5627
5729
  });
5730
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5628
5731
  });
5732
+
5733
+ schema.post(op as any, function (this: any, error: any, _res: any, next: Function) {
5734
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5735
+ next(error);
5736
+ } as any);
5629
5737
  }
5630
5738
 
5631
5739
  READ_OPS.forEach(attachQueryHooks);
@@ -5641,12 +5749,17 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5641
5749
  rawDocs,
5642
5750
  spanContext: captureSpanContextFromTracer(this),
5643
5751
  };
5752
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5753
+ (this as any).__repro_insert_meta.collection,
5754
+ ['insertMany'],
5755
+ );
5644
5756
  } catch {
5645
5757
  (this as any).__repro_insert_meta = {
5646
5758
  t0: Date.now(),
5647
5759
  collection: 'unknown',
5648
5760
  spanContext: captureSpanContextFromTracer(this),
5649
5761
  };
5762
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression('unknown', ['insertMany']);
5650
5763
  }
5651
5764
  next();
5652
5765
  } as any);
@@ -5666,6 +5779,12 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5666
5779
  t: alignedNow(),
5667
5780
  spanContext,
5668
5781
  });
5782
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5783
+ } as any);
5784
+
5785
+ schema.post<any>('insertMany' as any, { document: false, query: false } as any, function (this: any, error: any, _res: any, next: Function) {
5786
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5787
+ next(error);
5669
5788
  } as any);
5670
5789
 
5671
5790
  schema.pre<any>('bulkWrite' as any, { document: false, query: false } as any, function (this: any, next: Function, ops?: any[]) {
@@ -5677,12 +5796,17 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5677
5796
  rawOps,
5678
5797
  spanContext: captureSpanContextFromTracer(this),
5679
5798
  };
5799
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5800
+ (this as any).__repro_bulk_meta.collection,
5801
+ ['bulkWrite'],
5802
+ );
5680
5803
  } catch {
5681
5804
  (this as any).__repro_bulk_meta = {
5682
5805
  t0: Date.now(),
5683
5806
  collection: 'unknown',
5684
5807
  spanContext: captureSpanContextFromTracer(this),
5685
5808
  };
5809
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression('unknown', ['bulkWrite']);
5686
5810
  }
5687
5811
  next();
5688
5812
  } as any);
@@ -5702,6 +5826,12 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5702
5826
  t: alignedNow(),
5703
5827
  spanContext,
5704
5828
  });
5829
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5830
+ } as any);
5831
+
5832
+ schema.post<any>('bulkWrite' as any, { document: false, query: false } as any, function (this: any, error: any, _res: any, next: Function) {
5833
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5834
+ next(error);
5705
5835
  } as any);
5706
5836
 
5707
5837
  // Aggregate middleware (non-intrusive)
@@ -5718,6 +5848,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5718
5848
  spanContext: captureSpanContextFromTracer(this),
5719
5849
  rawPipeline,
5720
5850
  };
5851
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5852
+ (this as any).__repro_aggmeta.collection,
5853
+ ['aggregate'],
5854
+ );
5721
5855
  } catch {
5722
5856
  (this as any).__repro_aggmeta = {
5723
5857
  t0: Date.now(),
@@ -5725,6 +5859,7 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5725
5859
  pipeline: undefined,
5726
5860
  spanContext: captureSpanContextFromTracer(this),
5727
5861
  };
5862
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression('unknown', ['aggregate']);
5728
5863
  }
5729
5864
  next();
5730
5865
  });
@@ -5745,7 +5880,13 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5745
5880
  t: alignedNow(),
5746
5881
  spanContext,
5747
5882
  });
5883
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5748
5884
  });
5885
+
5886
+ schema.post('aggregate', function (this: any, error: any, _res: any, next: Function) {
5887
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5888
+ next(error);
5889
+ } as any);
5749
5890
  };
5750
5891
  }
5751
5892
 
@@ -5771,9 +5912,23 @@ function summarizeQueryResult(op: string, res: any) {
5771
5912
  return summary;
5772
5913
  }
5773
5914
 
5915
+ if (op === 'insertOne') {
5916
+ const summary: Record<string, any> = {
5917
+ insertedId: res?.insertedId ?? undefined,
5918
+ acknowledged: res?.acknowledged ?? undefined,
5919
+ };
5920
+ if (typeof resultPreview !== 'undefined') summary.result = resultPreview;
5921
+ return summary;
5922
+ }
5923
+
5774
5924
  if (op === 'insertMany') {
5775
5925
  const summary: Record<string, any> = {};
5776
5926
  if (Array.isArray(res)) summary.inserted = res.length;
5927
+ else {
5928
+ summary.inserted = res?.insertedCount ?? undefined;
5929
+ summary.insertedIds = res?.insertedIds ?? undefined;
5930
+ summary.acknowledged = res?.acknowledged ?? undefined;
5931
+ }
5777
5932
  if (typeof resultPreview !== 'undefined') summary.result = resultPreview;
5778
5933
  return summary;
5779
5934
  }
@@ -7389,6 +7544,7 @@ type AmqpPropagationContext = {
7389
7544
  parentSpanId: string | null;
7390
7545
  requestRid: string | null;
7391
7546
  };
7547
+ const AMQP_INTERNAL_PUBLISH_DEPTH = Symbol.for('repro.amqplib.internalPublishDepth');
7392
7548
 
7393
7549
  export type AmqplibPatchConfig = ReproMiddlewareConfig & {
7394
7550
  resolveContext?: AmqpContextResolver;
@@ -7859,6 +8015,10 @@ function patchAmqplibPublishMethod(
7859
8015
  const method = methodName === 'publish' ? 'AMQP_PUBLISH' : 'AMQP_SEND_TO_QUEUE';
7860
8016
 
7861
8017
  const wrapped = function reproPatchedAmqplibPublish(this: any, ...args: any[]) {
8018
+ if (methodName === 'publish' && Number(this?.[AMQP_INTERNAL_PUBLISH_DEPTH] ?? 0) > 0) {
8019
+ return orig.apply(this, args);
8020
+ }
8021
+
7862
8022
  const ctx = getCtx();
7863
8023
  const fallback = cfg.resolveContext?.() ?? {};
7864
8024
  const sid = ctx.sid ?? fallback.sid ?? null;
@@ -7933,8 +8093,13 @@ function patchAmqplibPublishMethod(
7933
8093
  : (wrappedCallback
7934
8094
  ? setAmqpPublishOptionsArg(args, optionsIndex, nextOptions, callbackIndex, wrappedCallback)
7935
8095
  : args);
8096
+ let bumpedInternalPublishDepth = false;
7936
8097
 
7937
8098
  try {
8099
+ if (methodName === 'sendToQueue' && this && typeof this === 'object') {
8100
+ this[AMQP_INTERNAL_PUBLISH_DEPTH] = Number(this?.[AMQP_INTERNAL_PUBLISH_DEPTH] ?? 0) + 1;
8101
+ bumpedInternalPublishDepth = true;
8102
+ }
7938
8103
  const result = orig.apply(this, nextArgs);
7939
8104
  if (!wrappedCallback) {
7940
8105
  finish(200, undefined, result);
@@ -7943,6 +8108,15 @@ function patchAmqplibPublishMethod(
7943
8108
  } catch (error) {
7944
8109
  finish(500, error);
7945
8110
  throw error;
8111
+ } finally {
8112
+ if (bumpedInternalPublishDepth) {
8113
+ const nextDepth = Number(this?.[AMQP_INTERNAL_PUBLISH_DEPTH] ?? 0) - 1;
8114
+ if (nextDepth > 0) {
8115
+ this[AMQP_INTERNAL_PUBLISH_DEPTH] = nextDepth;
8116
+ } else if (this && typeof this === 'object') {
8117
+ delete this[AMQP_INTERNAL_PUBLISH_DEPTH];
8118
+ }
8119
+ }
7946
8120
  }
7947
8121
  };
7948
8122
 
@@ -8569,6 +8743,502 @@ export function patchRedis(cfg: RedisPatchConfig) {
8569
8743
  }
8570
8744
  }
8571
8745
 
8746
+ // ===================================================================
8747
+ // Native MongoDB driver integration
8748
+ // ===================================================================
8749
+ export type MongoDbPatchConfig = ReproMiddlewareConfig & {
8750
+ resolveContext?: () => { sid?: string; aid?: string } | undefined;
8751
+ };
8752
+
8753
+ const mongoDbPatchConfigs: MongoDbPatchConfig[] = [];
8754
+ let mongoDbRequireHookInstalled = false;
8755
+
8756
+ const MONGODB_PROMISE_OPS = [
8757
+ 'insertOne',
8758
+ 'insertMany',
8759
+ 'updateOne',
8760
+ 'updateMany',
8761
+ 'replaceOne',
8762
+ 'deleteOne',
8763
+ 'deleteMany',
8764
+ 'findOne',
8765
+ 'countDocuments',
8766
+ 'estimatedDocumentCount',
8767
+ 'distinct',
8768
+ 'bulkWrite',
8769
+ 'findOneAndUpdate',
8770
+ 'findOneAndDelete',
8771
+ 'findOneAndReplace',
8772
+ ] as const;
8773
+
8774
+ const MONGODB_CURSOR_OPS = ['find', 'aggregate'] as const;
8775
+ const MONGODB_CURSOR_TERMINALS = ['toArray', 'next', 'forEach'] as const;
8776
+
8777
+ function resolveMongoDbRuntimeContext(cfg: MongoDbPatchConfig) {
8778
+ const ctx = getCtx();
8779
+ const fallback = cfg.resolveContext?.() ?? {};
8780
+ return {
8781
+ ctx,
8782
+ sid: ctx.sid ?? fallback.sid ?? null,
8783
+ aid: ctx.aid ?? fallback.aid ?? null,
8784
+ };
8785
+ }
8786
+
8787
+ function resolveMongoCollectionName(collection: any): string {
8788
+ const candidates = [
8789
+ collection?.collectionName,
8790
+ collection?.namespace?.collection,
8791
+ collection?.s?.namespace?.collection,
8792
+ collection?.s?.namespace?.collectionName,
8793
+ ];
8794
+ for (const candidate of candidates) {
8795
+ if (typeof candidate === 'string' && candidate.trim()) return candidate;
8796
+ }
8797
+
8798
+ const namespace =
8799
+ typeof collection?.namespace === 'string'
8800
+ ? collection.namespace
8801
+ : typeof collection?.s?.namespace === 'string'
8802
+ ? collection.s.namespace
8803
+ : typeof collection?.namespace?.toString === 'function'
8804
+ ? collection.namespace.toString()
8805
+ : typeof collection?.s?.namespace?.toString === 'function'
8806
+ ? collection.s.namespace.toString()
8807
+ : null;
8808
+ if (namespace && namespace.includes('.')) {
8809
+ return namespace.slice(namespace.indexOf('.') + 1) || namespace;
8810
+ }
8811
+ return namespace || 'unknown';
8812
+ }
8813
+
8814
+ function resolveMongoDbName(collection: any): string | undefined {
8815
+ const candidates = [
8816
+ collection?.dbName,
8817
+ collection?.db?.databaseName,
8818
+ collection?.s?.db?.databaseName,
8819
+ collection?.namespace?.db,
8820
+ collection?.s?.namespace?.db,
8821
+ ];
8822
+ for (const candidate of candidates) {
8823
+ if (typeof candidate === 'string' && candidate.trim()) return candidate;
8824
+ }
8825
+ const namespace =
8826
+ typeof collection?.namespace === 'string'
8827
+ ? collection.namespace
8828
+ : typeof collection?.s?.namespace === 'string'
8829
+ ? collection.s.namespace
8830
+ : null;
8831
+ if (namespace && namespace.includes('.')) {
8832
+ return namespace.slice(0, namespace.indexOf('.')) || undefined;
8833
+ }
8834
+ return undefined;
8835
+ }
8836
+
8837
+ function summarizeMongoSession(session: any): any {
8838
+ if (!session || typeof session !== 'object') return session;
8839
+ let inTransaction: any = undefined;
8840
+ try {
8841
+ inTransaction = typeof session.inTransaction === 'function' ? session.inTransaction() : undefined;
8842
+ } catch {}
8843
+ return {
8844
+ __kind: 'mongodb_client_session',
8845
+ id: session.id ?? session.serverSession?.id ?? undefined,
8846
+ hasEnded: session.hasEnded ?? undefined,
8847
+ inTransaction,
8848
+ };
8849
+ }
8850
+
8851
+ function normalizeMongoDriverOptions(options: any): any {
8852
+ if (!options || typeof options !== 'object' || Array.isArray(options)) return options;
8853
+ const normalized: Record<string, any> = {};
8854
+ for (const key of Object.keys(options)) {
8855
+ normalized[key] = key === 'session'
8856
+ ? summarizeMongoSession(options[key])
8857
+ : options[key];
8858
+ }
8859
+ return normalized;
8860
+ }
8861
+
8862
+ function summarizeMongoError(error: any): Record<string, any> {
8863
+ return {
8864
+ name: error?.name ?? undefined,
8865
+ message: error?.message ?? String(error),
8866
+ code: error?.code ?? undefined,
8867
+ codeName: error?.codeName ?? undefined,
8868
+ };
8869
+ }
8870
+
8871
+ function extractMongoPkFromFilter(filter: any): any {
8872
+ if (filter && typeof filter === 'object' && Object.prototype.hasOwnProperty.call(filter, '_id')) {
8873
+ return { _id: filter._id };
8874
+ }
8875
+ return undefined;
8876
+ }
8877
+
8878
+ function extractMongoPkFromDoc(doc: any, result?: any): any {
8879
+ const id = doc && typeof doc === 'object' ? doc._id : undefined;
8880
+ const insertedId = result?.insertedId;
8881
+ if (id !== undefined) return { _id: id };
8882
+ if (insertedId !== undefined) return { _id: insertedId };
8883
+ return undefined;
8884
+ }
8885
+
8886
+ function buildMongoQueryPayload(op: string, args: any[], dbName?: string): any {
8887
+ const withDriver = (payload: Record<string, any>) => ({
8888
+ driver: 'mongodb',
8889
+ db: dbName,
8890
+ ...payload,
8891
+ });
8892
+
8893
+ if (op === 'insertOne') {
8894
+ return withDriver({ doc: args[0], options: normalizeMongoDriverOptions(args[1]) });
8895
+ }
8896
+ if (op === 'insertMany') {
8897
+ return withDriver({ docs: args[0], options: normalizeMongoDriverOptions(args[1]) });
8898
+ }
8899
+ if (op === 'updateOne' || op === 'updateMany') {
8900
+ return withDriver({ filter: args[0], update: args[1], options: normalizeMongoDriverOptions(args[2]) });
8901
+ }
8902
+ if (op === 'replaceOne') {
8903
+ return withDriver({ filter: args[0], replacement: args[1], options: normalizeMongoDriverOptions(args[2]) });
8904
+ }
8905
+ if (op === 'deleteOne' || op === 'deleteMany') {
8906
+ return withDriver({ filter: args[0], options: normalizeMongoDriverOptions(args[1]) });
8907
+ }
8908
+ if (op === 'findOne' || op === 'find' || op === 'countDocuments') {
8909
+ return withDriver({ filter: args[0], options: normalizeMongoDriverOptions(args[1]) });
8910
+ }
8911
+ if (op === 'estimatedDocumentCount') {
8912
+ return withDriver({ options: normalizeMongoDriverOptions(args[0]) });
8913
+ }
8914
+ if (op === 'distinct') {
8915
+ return withDriver({ key: args[0], filter: args[1], options: normalizeMongoDriverOptions(args[2]) });
8916
+ }
8917
+ if (op === 'bulkWrite') {
8918
+ return withDriver({ ops: args[0], options: normalizeMongoDriverOptions(args[1]) });
8919
+ }
8920
+ if (op === 'findOneAndUpdate') {
8921
+ return withDriver({ filter: args[0], update: args[1], options: normalizeMongoDriverOptions(args[2]) });
8922
+ }
8923
+ if (op === 'findOneAndReplace') {
8924
+ return withDriver({ filter: args[0], replacement: args[1], options: normalizeMongoDriverOptions(args[2]) });
8925
+ }
8926
+ if (op === 'findOneAndDelete') {
8927
+ return withDriver({ filter: args[0], options: normalizeMongoDriverOptions(args[1]) });
8928
+ }
8929
+ if (op === 'aggregate') {
8930
+ return withDriver({ pipeline: args[0], options: normalizeMongoDriverOptions(args[1]) });
8931
+ }
8932
+ return withDriver({ args });
8933
+ }
8934
+
8935
+ function buildMongoPk(op: string, args: any[], result?: any): any {
8936
+ if (op === 'insertOne') return extractMongoPkFromDoc(args[0], result);
8937
+ if (
8938
+ op === 'updateOne' ||
8939
+ op === 'replaceOne' ||
8940
+ op === 'deleteOne' ||
8941
+ op === 'findOne' ||
8942
+ op === 'findOneAndUpdate' ||
8943
+ op === 'findOneAndDelete' ||
8944
+ op === 'findOneAndReplace'
8945
+ ) {
8946
+ return extractMongoPkFromFilter(args[0]);
8947
+ }
8948
+ return undefined;
8949
+ }
8950
+
8951
+ function buildMongoAfter(op: string, args: any[], result?: any): any {
8952
+ if (op === 'insertOne') return args[0] ?? null;
8953
+ if (op === 'insertMany') return args[0] ?? null;
8954
+ if (op === 'findOne' || op === 'findOneAndUpdate' || op === 'findOneAndDelete' || op === 'findOneAndReplace') {
8955
+ return result ?? null;
8956
+ }
8957
+ if (op === 'find' || op === 'aggregate') {
8958
+ return Array.isArray(result) ? result : null;
8959
+ }
8960
+ return null;
8961
+ }
8962
+
8963
+ function findMongoCallbackIndex(args: any[]): number {
8964
+ for (let i = args.length - 1; i >= 0; i -= 1) {
8965
+ if (typeof args[i] === 'function') return i;
8966
+ }
8967
+ return -1;
8968
+ }
8969
+
8970
+ function patchMongoCursorInstance(cursor: any, meta: {
8971
+ cfg: MongoDbPatchConfig;
8972
+ sid: string;
8973
+ aid: string | null;
8974
+ collection: string;
8975
+ dbName?: string;
8976
+ op: string;
8977
+ args: any[];
8978
+ spanContext: SpanContext | null;
8979
+ }): any {
8980
+ if (!cursor || typeof cursor !== 'object') return cursor;
8981
+ if (cursor.__repro_mongodb_cursor_patched) return cursor;
8982
+ try {
8983
+ Object.defineProperty(cursor, '__repro_mongodb_cursor_patched', {
8984
+ value: true,
8985
+ configurable: true,
8986
+ });
8987
+ } catch {
8988
+ return cursor;
8989
+ }
8990
+
8991
+ for (const methodName of MONGODB_CURSOR_TERMINALS) {
8992
+ const original = cursor[methodName];
8993
+ if (typeof original !== 'function') continue;
8994
+
8995
+ cursor[methodName] = function reproPatchedMongoCursorTerminal(this: any, ...callArgs: any[]) {
8996
+ const t0 = Date.now();
8997
+ let emitted = false;
8998
+ let forEachDocs: any[] | null = null;
8999
+ const emitSuccess = (value: any) => {
9000
+ if (emitted) return;
9001
+ emitted = true;
9002
+ const terminalQueryArgs = [...meta.args] as any[];
9003
+ const queryRaw = buildMongoQueryPayload(meta.op, terminalQueryArgs, meta.dbName);
9004
+ queryRaw.cursorMethod = methodName;
9005
+ emitDbQuery(meta.cfg, meta.sid, meta.aid ?? undefined, {
9006
+ type: 'mongo',
9007
+ collection: meta.collection,
9008
+ op: meta.op,
9009
+ pk: buildMongoPk(meta.op, meta.args, value),
9010
+ before: null,
9011
+ after: buildMongoAfter(meta.op, meta.args, forEachDocs ?? value),
9012
+ queryRaw,
9013
+ resultMetaRaw: forEachDocs ?? value,
9014
+ durMs: Date.now() - t0,
9015
+ t: alignedNow(),
9016
+ spanContext: meta.spanContext,
9017
+ });
9018
+ };
9019
+ const emitFailure = (error: any) => {
9020
+ if (emitted) return;
9021
+ emitted = true;
9022
+ const terminalQueryArgs = [...meta.args] as any[];
9023
+ const queryRaw = buildMongoQueryPayload(meta.op, terminalQueryArgs, meta.dbName);
9024
+ queryRaw.cursorMethod = methodName;
9025
+ emitDbQuery(meta.cfg, meta.sid, meta.aid ?? undefined, {
9026
+ type: 'mongo',
9027
+ collection: meta.collection,
9028
+ op: meta.op,
9029
+ queryRaw,
9030
+ error: summarizeMongoError(error),
9031
+ durMs: Date.now() - t0,
9032
+ t: alignedNow(),
9033
+ spanContext: meta.spanContext,
9034
+ });
9035
+ };
9036
+
9037
+ const actualArgs = [...callArgs];
9038
+ if (methodName === 'forEach' && typeof actualArgs[0] === 'function') {
9039
+ forEachDocs = [];
9040
+ const originalIterator = actualArgs[0];
9041
+ actualArgs[0] = function reproMongoForEachIterator(this: any, doc: any, ...rest: any[]) {
9042
+ forEachDocs?.push(doc);
9043
+ return originalIterator.call(this, doc, ...rest);
9044
+ };
9045
+ }
9046
+
9047
+ try {
9048
+ const value = original.apply(this, actualArgs);
9049
+ if (value && typeof value.then === 'function') {
9050
+ return value.then(
9051
+ (resolved: any) => {
9052
+ emitSuccess(resolved);
9053
+ return resolved;
9054
+ },
9055
+ (err: any) => {
9056
+ emitFailure(err);
9057
+ throw err;
9058
+ },
9059
+ );
9060
+ }
9061
+ emitSuccess(value);
9062
+ return value;
9063
+ } catch (err) {
9064
+ emitFailure(err);
9065
+ throw err;
9066
+ }
9067
+ };
9068
+ }
9069
+
9070
+ return cursor;
9071
+ }
9072
+
9073
+ function patchMongoCollectionMethod(proto: any, op: string, cursorOp: boolean, cfg: MongoDbPatchConfig): void {
9074
+ if (!proto) return;
9075
+ const marker = `__repro_mongodb_${op}_patched`;
9076
+ if (proto[marker]) return;
9077
+ const original = proto[op];
9078
+ if (typeof original !== 'function') return;
9079
+ proto[marker] = true;
9080
+
9081
+ proto[op] = function reproPatchedMongoCollectionMethod(this: any, ...args: any[]) {
9082
+ const collection = resolveMongoCollectionName(this);
9083
+ if (isNativeMongoSuppressed(collection, op)) {
9084
+ return original.apply(this, args);
9085
+ }
9086
+
9087
+ const { sid, aid } = resolveMongoDbRuntimeContext(cfg);
9088
+ if (!sid) {
9089
+ return original.apply(this, args);
9090
+ }
9091
+
9092
+ const spanContext = captureSpanContextFromTracer(this);
9093
+ const t0 = Date.now();
9094
+ const dbName = resolveMongoDbName(this);
9095
+ const callbackIndex = cursorOp ? -1 : findMongoCallbackIndex(args);
9096
+ const actualArgs = callbackIndex >= 0 ? [...args] : args;
9097
+ let emitted = false;
9098
+
9099
+ const emitSuccess = (result: any) => {
9100
+ if (emitted) return;
9101
+ emitted = true;
9102
+ emitDbQuery(cfg, sid, aid ?? undefined, {
9103
+ type: 'mongo',
9104
+ collection,
9105
+ op,
9106
+ pk: buildMongoPk(op, args, result),
9107
+ before: null,
9108
+ after: buildMongoAfter(op, args, result),
9109
+ queryRaw: buildMongoQueryPayload(op, args, dbName),
9110
+ resultMetaRaw: result,
9111
+ durMs: Date.now() - t0,
9112
+ t: alignedNow(),
9113
+ spanContext,
9114
+ });
9115
+ };
9116
+ const emitFailure = (error: any) => {
9117
+ if (emitted) return;
9118
+ emitted = true;
9119
+ emitDbQuery(cfg, sid, aid ?? undefined, {
9120
+ type: 'mongo',
9121
+ collection,
9122
+ op,
9123
+ queryRaw: buildMongoQueryPayload(op, args, dbName),
9124
+ error: summarizeMongoError(error),
9125
+ durMs: Date.now() - t0,
9126
+ t: alignedNow(),
9127
+ spanContext,
9128
+ });
9129
+ };
9130
+
9131
+ if (callbackIndex >= 0) {
9132
+ const originalCallback = args[callbackIndex];
9133
+ actualArgs[callbackIndex] = function reproMongoCallback(this: any, err: any, result: any, ...rest: any[]) {
9134
+ if (err) emitFailure(err);
9135
+ else emitSuccess(result);
9136
+ return originalCallback.call(this, err, result, ...rest);
9137
+ };
9138
+ }
9139
+
9140
+ try {
9141
+ const result = original.apply(this, actualArgs);
9142
+ if (cursorOp) {
9143
+ return patchMongoCursorInstance(result, {
9144
+ cfg,
9145
+ sid,
9146
+ aid,
9147
+ collection,
9148
+ dbName,
9149
+ op,
9150
+ args,
9151
+ spanContext,
9152
+ });
9153
+ }
9154
+ if (result && typeof result.then === 'function') {
9155
+ return result.then(
9156
+ (value: any) => {
9157
+ emitSuccess(value);
9158
+ return value;
9159
+ },
9160
+ (error: any) => {
9161
+ emitFailure(error);
9162
+ throw error;
9163
+ },
9164
+ );
9165
+ }
9166
+ if (callbackIndex < 0) {
9167
+ emitSuccess(result);
9168
+ }
9169
+ return result;
9170
+ } catch (error) {
9171
+ emitFailure(error);
9172
+ throw error;
9173
+ }
9174
+ };
9175
+ }
9176
+
9177
+ function patchMongoCollectionPrototype(proto: any, cfg: MongoDbPatchConfig): void {
9178
+ if (!proto) return;
9179
+ for (const op of MONGODB_PROMISE_OPS) {
9180
+ patchMongoCollectionMethod(proto, op, false, cfg);
9181
+ }
9182
+ for (const op of MONGODB_CURSOR_OPS) {
9183
+ patchMongoCollectionMethod(proto, op, true, cfg);
9184
+ }
9185
+ }
9186
+
9187
+ function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig): void {
9188
+ if (!mongoModule) return;
9189
+ const constructors = new Set<any>();
9190
+ const addCtor = (candidate: any) => {
9191
+ if (typeof candidate === 'function' && candidate.prototype) {
9192
+ constructors.add(candidate);
9193
+ }
9194
+ };
9195
+
9196
+ addCtor(mongoModule?.Collection);
9197
+ addCtor(mongoModule?.default?.Collection);
9198
+
9199
+ for (const ctor of constructors) {
9200
+ patchMongoCollectionPrototype(ctor.prototype, cfg);
9201
+ }
9202
+ }
9203
+
9204
+ function installMongoDbRequireHook(): void {
9205
+ if (mongoDbRequireHookInstalled) return;
9206
+ mongoDbRequireHookInstalled = true;
9207
+
9208
+ try {
9209
+ const rim = requireOptionalHostModule('require-in-the-middle');
9210
+ const Hook = rim?.Hook ?? rim;
9211
+ if (typeof Hook !== 'function') return;
9212
+
9213
+ // eslint-disable-next-line no-new
9214
+ new Hook(['mongodb'], (exports: any) => {
9215
+ for (const cfg of mongoDbPatchConfigs) {
9216
+ patchMongoDbFromModuleExports(exports, cfg);
9217
+ }
9218
+ return exports;
9219
+ });
9220
+ } catch {}
9221
+ }
9222
+
9223
+ export function patchMongoDb(cfg: MongoDbPatchConfig): void {
9224
+ mongoDbPatchConfigs.push(cfg);
9225
+ installMongoDbRequireHook();
9226
+
9227
+ const mongoModules = new Set<any>();
9228
+ const resolvedMongoModule = requireOptionalHostModule('mongodb');
9229
+ if (resolvedMongoModule) {
9230
+ mongoModules.add(resolvedMongoModule);
9231
+ }
9232
+ for (const loadedMongoModule of getLoadedModuleExportsByPackage('mongodb')) {
9233
+ mongoModules.add(loadedMongoModule);
9234
+ }
9235
+ if (!mongoModules.size) return;
9236
+
9237
+ for (const mongoModule of mongoModules) {
9238
+ patchMongoDbFromModuleExports(mongoModule, cfg);
9239
+ }
9240
+ }
9241
+
8572
9242
  // ===================================================================
8573
9243
  // Auto init helpers
8574
9244
  // ===================================================================
@@ -8996,6 +9666,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
8996
9666
  patchKafkaJs(integrationCfg);
8997
9667
  patchAmqplib(integrationCfg);
8998
9668
  patchRedis(integrationCfg);
9669
+ patchMongoDb(integrationCfg);
8999
9670
  patchSendgridMail(integrationCfg);
9000
9671
  patchMongooseIntegration(reproAutoMiddlewareConfig);
9001
9672
 
@@ -9047,6 +9718,8 @@ export const __reproTestHooks = {
9047
9718
  patchAmqplibConnectionForTest: patchAmqplibConnection,
9048
9719
  patchAmqplibChannelForTest: patchAmqplibChannel,
9049
9720
  wrapAmqplibConsumeHandlerForTest: wrapAmqplibConsumeHandler,
9721
+ patchMongoDbModuleForTest: patchMongoDbFromModuleExports,
9722
+ patchMongoCollectionPrototypeForTest: patchMongoCollectionPrototype,
9050
9723
  chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
9051
9724
  estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
9052
9725
  sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,