@reproapp/node-sdk 0.0.26 → 0.0.28

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;
@@ -4837,6 +4892,7 @@ export type ReproInitConfig = ReproMiddlewareConfig & {
4837
4892
 
4838
4893
  export function reproMiddleware(cfg: ReproMiddlewareConfig) {
4839
4894
  const masking = normalizeMaskingConfig(cfg.masking);
4895
+ patchMongoDb(cfg);
4840
4896
  return async function (req: Request, res: Response, next: NextFunction) {
4841
4897
  const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
4842
4898
  const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
@@ -5424,6 +5480,15 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
5424
5480
  // - ONLY schema middleware (pre/post) for specific ops
5425
5481
  // - keeps your existing doc-diff hooks
5426
5482
  // ===================================================================
5483
+ function nativeMongoOpsForMongooseOp(op: string): string[] {
5484
+ if (op === 'findByIdAndUpdate') return ['findOneAndUpdate'];
5485
+ if (op === 'findByIdAndDelete' || op === 'findByIdAndRemove' || op === 'findOneAndRemove') {
5486
+ return ['findOneAndDelete'];
5487
+ }
5488
+ if (op === 'findByIdAndReplace') return ['findOneAndReplace'];
5489
+ return [op];
5490
+ }
5491
+
5427
5492
  export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5428
5493
  return function (schema: Schema) {
5429
5494
  // -------- pre/post save (unchanged) --------
@@ -5441,6 +5506,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5441
5506
  ? (this as any).modifiedPaths()
5442
5507
  : [],
5443
5508
  };
5509
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5510
+ (this as any).__repro_meta.collection,
5511
+ this.isNew ? ['insertOne'] : ['updateOne', 'replaceOne'],
5512
+ );
5444
5513
  next();
5445
5514
  });
5446
5515
 
@@ -5475,6 +5544,12 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5475
5544
  t: alignTimestamp(meta.t0 ?? Date.now()),
5476
5545
  spanContext,
5477
5546
  });
5547
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5548
+ });
5549
+
5550
+ schema.post('save', { document: true }, function (error: any, _doc: any, next: Function) {
5551
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5552
+ next(error);
5478
5553
  });
5479
5554
 
5480
5555
  // -------- findOneAndUpdate capture (unchanged) --------
@@ -5489,6 +5564,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5489
5564
  (this as any).__repro_spanContext = captureSpanContextFromTracer(this);
5490
5565
  (this as any).__repro_filter = filter;
5491
5566
  (this as any).__repro_update = this.getUpdate?.() ?? (this as any)._update ?? undefined;
5567
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5568
+ (this as any).__repro_collection,
5569
+ ['findOneAndUpdate'],
5570
+ );
5492
5571
  } catch {}
5493
5572
  next();
5494
5573
  });
@@ -5518,8 +5597,14 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5518
5597
  t: alignTimestamp((this as any).__repro_t0 ?? Date.now()),
5519
5598
  spanContext,
5520
5599
  });
5600
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5521
5601
  });
5522
5602
 
5603
+ schema.post<Query<any, any>>('findOneAndUpdate', function (this: any, error: any, _res: any, next: Function) {
5604
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5605
+ next(error);
5606
+ } as any);
5607
+
5523
5608
  // -------- deleteOne capture (unchanged) --------
5524
5609
  schema.pre<Query<any, any>>('deleteOne', { document: false, query: true }, function (next) {
5525
5610
  const { sid, aid } = getCtx(); if (!sid || !aid) return next();
@@ -5529,6 +5614,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5529
5614
  (this as any).__repro_collection = resolveCollectionOrWarn(this, 'query');
5530
5615
  (this as any).__repro_filter = filter;
5531
5616
  (this as any).__repro_spanContext = captureSpanContextFromTracer(this);
5617
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5618
+ (this as any).__repro_collection,
5619
+ ['deleteOne'],
5620
+ );
5532
5621
  } catch {}
5533
5622
  next();
5534
5623
  });
@@ -5550,8 +5639,14 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5550
5639
  t: alignTimestamp((this as any).__repro_t0 ?? Date.now()),
5551
5640
  spanContext,
5552
5641
  });
5642
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5553
5643
  });
5554
5644
 
5645
+ schema.post<Query<any, any>>('deleteOne', { document: false, query: true }, function (this: any, error: any, _res: any, next: Function) {
5646
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5647
+ next(error);
5648
+ } as any);
5649
+
5555
5650
  // -------- NON-intrusive generic query telemetry via schema hooks -------
5556
5651
  const READ_OPS = [
5557
5652
  'find',
@@ -5593,6 +5688,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5593
5688
  rawProjection,
5594
5689
  rawOptions,
5595
5690
  };
5691
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5692
+ (this as any).__repro_qmeta.collection,
5693
+ nativeMongoOpsForMongooseOp(op),
5694
+ );
5596
5695
  } catch {
5597
5696
  (this as any).__repro_qmeta = {
5598
5697
  t0: Date.now(),
@@ -5600,6 +5699,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5600
5699
  op,
5601
5700
  spanContext: captureSpanContextFromTracer(this),
5602
5701
  };
5702
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5703
+ 'unknown',
5704
+ nativeMongoOpsForMongooseOp(op),
5705
+ );
5603
5706
  }
5604
5707
  next();
5605
5708
  });
@@ -5625,7 +5728,13 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5625
5728
  t: alignedNow(),
5626
5729
  spanContext,
5627
5730
  });
5731
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5628
5732
  });
5733
+
5734
+ schema.post(op as any, function (this: any, error: any, _res: any, next: Function) {
5735
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5736
+ next(error);
5737
+ } as any);
5629
5738
  }
5630
5739
 
5631
5740
  READ_OPS.forEach(attachQueryHooks);
@@ -5641,12 +5750,17 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5641
5750
  rawDocs,
5642
5751
  spanContext: captureSpanContextFromTracer(this),
5643
5752
  };
5753
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5754
+ (this as any).__repro_insert_meta.collection,
5755
+ ['insertMany'],
5756
+ );
5644
5757
  } catch {
5645
5758
  (this as any).__repro_insert_meta = {
5646
5759
  t0: Date.now(),
5647
5760
  collection: 'unknown',
5648
5761
  spanContext: captureSpanContextFromTracer(this),
5649
5762
  };
5763
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression('unknown', ['insertMany']);
5650
5764
  }
5651
5765
  next();
5652
5766
  } as any);
@@ -5666,6 +5780,12 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5666
5780
  t: alignedNow(),
5667
5781
  spanContext,
5668
5782
  });
5783
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5784
+ } as any);
5785
+
5786
+ schema.post<any>('insertMany' as any, { document: false, query: false } as any, function (this: any, error: any, _res: any, next: Function) {
5787
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5788
+ next(error);
5669
5789
  } as any);
5670
5790
 
5671
5791
  schema.pre<any>('bulkWrite' as any, { document: false, query: false } as any, function (this: any, next: Function, ops?: any[]) {
@@ -5677,12 +5797,17 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5677
5797
  rawOps,
5678
5798
  spanContext: captureSpanContextFromTracer(this),
5679
5799
  };
5800
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5801
+ (this as any).__repro_bulk_meta.collection,
5802
+ ['bulkWrite'],
5803
+ );
5680
5804
  } catch {
5681
5805
  (this as any).__repro_bulk_meta = {
5682
5806
  t0: Date.now(),
5683
5807
  collection: 'unknown',
5684
5808
  spanContext: captureSpanContextFromTracer(this),
5685
5809
  };
5810
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression('unknown', ['bulkWrite']);
5686
5811
  }
5687
5812
  next();
5688
5813
  } as any);
@@ -5702,6 +5827,12 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5702
5827
  t: alignedNow(),
5703
5828
  spanContext,
5704
5829
  });
5830
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5831
+ } as any);
5832
+
5833
+ schema.post<any>('bulkWrite' as any, { document: false, query: false } as any, function (this: any, error: any, _res: any, next: Function) {
5834
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5835
+ next(error);
5705
5836
  } as any);
5706
5837
 
5707
5838
  // Aggregate middleware (non-intrusive)
@@ -5718,6 +5849,10 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5718
5849
  spanContext: captureSpanContextFromTracer(this),
5719
5850
  rawPipeline,
5720
5851
  };
5852
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression(
5853
+ (this as any).__repro_aggmeta.collection,
5854
+ ['aggregate'],
5855
+ );
5721
5856
  } catch {
5722
5857
  (this as any).__repro_aggmeta = {
5723
5858
  t0: Date.now(),
@@ -5725,6 +5860,7 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5725
5860
  pipeline: undefined,
5726
5861
  spanContext: captureSpanContextFromTracer(this),
5727
5862
  };
5863
+ (this as any).__repro_native_mongo_suppression = pushNativeMongoSuppression('unknown', ['aggregate']);
5728
5864
  }
5729
5865
  next();
5730
5866
  });
@@ -5745,7 +5881,13 @@ export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5745
5881
  t: alignedNow(),
5746
5882
  spanContext,
5747
5883
  });
5884
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5748
5885
  });
5886
+
5887
+ schema.post('aggregate', function (this: any, error: any, _res: any, next: Function) {
5888
+ popNativeMongoSuppression((this as any).__repro_native_mongo_suppression);
5889
+ next(error);
5890
+ } as any);
5749
5891
  };
5750
5892
  }
5751
5893
 
@@ -5771,9 +5913,23 @@ function summarizeQueryResult(op: string, res: any) {
5771
5913
  return summary;
5772
5914
  }
5773
5915
 
5916
+ if (op === 'insertOne') {
5917
+ const summary: Record<string, any> = {
5918
+ insertedId: res?.insertedId ?? undefined,
5919
+ acknowledged: res?.acknowledged ?? undefined,
5920
+ };
5921
+ if (typeof resultPreview !== 'undefined') summary.result = resultPreview;
5922
+ return summary;
5923
+ }
5924
+
5774
5925
  if (op === 'insertMany') {
5775
5926
  const summary: Record<string, any> = {};
5776
5927
  if (Array.isArray(res)) summary.inserted = res.length;
5928
+ else {
5929
+ summary.inserted = res?.insertedCount ?? undefined;
5930
+ summary.insertedIds = res?.insertedIds ?? undefined;
5931
+ summary.acknowledged = res?.acknowledged ?? undefined;
5932
+ }
5777
5933
  if (typeof resultPreview !== 'undefined') summary.result = resultPreview;
5778
5934
  return summary;
5779
5935
  }
@@ -8588,6 +8744,498 @@ export function patchRedis(cfg: RedisPatchConfig) {
8588
8744
  }
8589
8745
  }
8590
8746
 
8747
+ // ===================================================================
8748
+ // Native MongoDB driver integration
8749
+ // ===================================================================
8750
+ export type MongoDbPatchConfig = ReproMiddlewareConfig & {
8751
+ resolveContext?: () => { sid?: string; aid?: string } | undefined;
8752
+ };
8753
+
8754
+ const mongoDbPatchConfigs: MongoDbPatchConfig[] = [];
8755
+ let mongoDbRequireHookInstalled = false;
8756
+
8757
+ const MONGODB_PROMISE_OPS = [
8758
+ 'insertOne',
8759
+ 'insertMany',
8760
+ 'updateOne',
8761
+ 'updateMany',
8762
+ 'replaceOne',
8763
+ 'deleteOne',
8764
+ 'deleteMany',
8765
+ 'findOne',
8766
+ 'countDocuments',
8767
+ 'estimatedDocumentCount',
8768
+ 'distinct',
8769
+ 'bulkWrite',
8770
+ 'findOneAndUpdate',
8771
+ 'findOneAndDelete',
8772
+ 'findOneAndReplace',
8773
+ ] as const;
8774
+
8775
+ const MONGODB_CURSOR_OPS = ['find', 'aggregate'] as const;
8776
+ const MONGODB_CURSOR_TERMINALS = ['toArray', 'next', 'forEach'] as const;
8777
+
8778
+ function resolveMongoDbRuntimeContext(cfg: MongoDbPatchConfig) {
8779
+ const ctx = getCtx();
8780
+ const fallback = cfg.resolveContext?.() ?? {};
8781
+ return {
8782
+ ctx,
8783
+ sid: ctx.sid ?? fallback.sid ?? null,
8784
+ aid: ctx.aid ?? fallback.aid ?? null,
8785
+ };
8786
+ }
8787
+
8788
+ function resolveMongoCollectionName(collection: any): string {
8789
+ const candidates = [
8790
+ collection?.collectionName,
8791
+ collection?.namespace?.collection,
8792
+ collection?.s?.namespace?.collection,
8793
+ collection?.s?.namespace?.collectionName,
8794
+ ];
8795
+ for (const candidate of candidates) {
8796
+ if (typeof candidate === 'string' && candidate.trim()) return candidate;
8797
+ }
8798
+
8799
+ const namespace =
8800
+ typeof collection?.namespace === 'string'
8801
+ ? collection.namespace
8802
+ : typeof collection?.s?.namespace === 'string'
8803
+ ? collection.s.namespace
8804
+ : typeof collection?.namespace?.toString === 'function'
8805
+ ? collection.namespace.toString()
8806
+ : typeof collection?.s?.namespace?.toString === 'function'
8807
+ ? collection.s.namespace.toString()
8808
+ : null;
8809
+ if (namespace && namespace.includes('.')) {
8810
+ return namespace.slice(namespace.indexOf('.') + 1) || namespace;
8811
+ }
8812
+ return namespace || 'unknown';
8813
+ }
8814
+
8815
+ function resolveMongoDbName(collection: any): string | undefined {
8816
+ const candidates = [
8817
+ collection?.dbName,
8818
+ collection?.db?.databaseName,
8819
+ collection?.s?.db?.databaseName,
8820
+ collection?.namespace?.db,
8821
+ collection?.s?.namespace?.db,
8822
+ ];
8823
+ for (const candidate of candidates) {
8824
+ if (typeof candidate === 'string' && candidate.trim()) return candidate;
8825
+ }
8826
+ const namespace =
8827
+ typeof collection?.namespace === 'string'
8828
+ ? collection.namespace
8829
+ : typeof collection?.s?.namespace === 'string'
8830
+ ? collection.s.namespace
8831
+ : null;
8832
+ if (namespace && namespace.includes('.')) {
8833
+ return namespace.slice(0, namespace.indexOf('.')) || undefined;
8834
+ }
8835
+ return undefined;
8836
+ }
8837
+
8838
+ function summarizeMongoSession(session: any): any {
8839
+ if (!session || typeof session !== 'object') return session;
8840
+ let inTransaction: any = undefined;
8841
+ try {
8842
+ inTransaction = typeof session.inTransaction === 'function' ? session.inTransaction() : undefined;
8843
+ } catch {}
8844
+ return {
8845
+ __kind: 'mongodb_client_session',
8846
+ id: session.id ?? session.serverSession?.id ?? undefined,
8847
+ hasEnded: session.hasEnded ?? undefined,
8848
+ inTransaction,
8849
+ };
8850
+ }
8851
+
8852
+ function normalizeMongoDriverOptions(options: any): any {
8853
+ if (!options || typeof options !== 'object' || Array.isArray(options)) return options;
8854
+ const normalized: Record<string, any> = {};
8855
+ for (const key of Object.keys(options)) {
8856
+ normalized[key] = key === 'session'
8857
+ ? summarizeMongoSession(options[key])
8858
+ : options[key];
8859
+ }
8860
+ return normalized;
8861
+ }
8862
+
8863
+ function summarizeMongoError(error: any): Record<string, any> {
8864
+ return {
8865
+ name: error?.name ?? undefined,
8866
+ message: error?.message ?? String(error),
8867
+ code: error?.code ?? undefined,
8868
+ codeName: error?.codeName ?? undefined,
8869
+ };
8870
+ }
8871
+
8872
+ function extractMongoPkFromFilter(filter: any): any {
8873
+ if (filter && typeof filter === 'object' && Object.prototype.hasOwnProperty.call(filter, '_id')) {
8874
+ return { _id: filter._id };
8875
+ }
8876
+ return undefined;
8877
+ }
8878
+
8879
+ function extractMongoPkFromDoc(doc: any, result?: any): any {
8880
+ const id = doc && typeof doc === 'object' ? doc._id : undefined;
8881
+ const insertedId = result?.insertedId;
8882
+ if (id !== undefined) return { _id: id };
8883
+ if (insertedId !== undefined) return { _id: insertedId };
8884
+ return undefined;
8885
+ }
8886
+
8887
+ function buildMongoQueryPayload(op: string, args: any[], dbName?: string): any {
8888
+ const withDriver = (payload: Record<string, any>) => ({
8889
+ driver: 'mongodb',
8890
+ db: dbName,
8891
+ ...payload,
8892
+ });
8893
+
8894
+ if (op === 'insertOne') {
8895
+ return withDriver({ doc: args[0], options: normalizeMongoDriverOptions(args[1]) });
8896
+ }
8897
+ if (op === 'insertMany') {
8898
+ return withDriver({ docs: args[0], options: normalizeMongoDriverOptions(args[1]) });
8899
+ }
8900
+ if (op === 'updateOne' || op === 'updateMany') {
8901
+ return withDriver({ filter: args[0], update: args[1], options: normalizeMongoDriverOptions(args[2]) });
8902
+ }
8903
+ if (op === 'replaceOne') {
8904
+ return withDriver({ filter: args[0], replacement: args[1], options: normalizeMongoDriverOptions(args[2]) });
8905
+ }
8906
+ if (op === 'deleteOne' || op === 'deleteMany') {
8907
+ return withDriver({ filter: args[0], options: normalizeMongoDriverOptions(args[1]) });
8908
+ }
8909
+ if (op === 'findOne' || op === 'find' || op === 'countDocuments') {
8910
+ return withDriver({ filter: args[0], options: normalizeMongoDriverOptions(args[1]) });
8911
+ }
8912
+ if (op === 'estimatedDocumentCount') {
8913
+ return withDriver({ options: normalizeMongoDriverOptions(args[0]) });
8914
+ }
8915
+ if (op === 'distinct') {
8916
+ return withDriver({ key: args[0], filter: args[1], options: normalizeMongoDriverOptions(args[2]) });
8917
+ }
8918
+ if (op === 'bulkWrite') {
8919
+ return withDriver({ ops: args[0], options: normalizeMongoDriverOptions(args[1]) });
8920
+ }
8921
+ if (op === 'findOneAndUpdate') {
8922
+ return withDriver({ filter: args[0], update: args[1], options: normalizeMongoDriverOptions(args[2]) });
8923
+ }
8924
+ if (op === 'findOneAndReplace') {
8925
+ return withDriver({ filter: args[0], replacement: args[1], options: normalizeMongoDriverOptions(args[2]) });
8926
+ }
8927
+ if (op === 'findOneAndDelete') {
8928
+ return withDriver({ filter: args[0], options: normalizeMongoDriverOptions(args[1]) });
8929
+ }
8930
+ if (op === 'aggregate') {
8931
+ return withDriver({ pipeline: args[0], options: normalizeMongoDriverOptions(args[1]) });
8932
+ }
8933
+ return withDriver({ args });
8934
+ }
8935
+
8936
+ function buildMongoPk(op: string, args: any[], result?: any): any {
8937
+ if (op === 'insertOne') return extractMongoPkFromDoc(args[0], result);
8938
+ if (
8939
+ op === 'updateOne' ||
8940
+ op === 'replaceOne' ||
8941
+ op === 'deleteOne' ||
8942
+ op === 'findOne' ||
8943
+ op === 'findOneAndUpdate' ||
8944
+ op === 'findOneAndDelete' ||
8945
+ op === 'findOneAndReplace'
8946
+ ) {
8947
+ return extractMongoPkFromFilter(args[0]);
8948
+ }
8949
+ return undefined;
8950
+ }
8951
+
8952
+ function buildMongoAfter(op: string, args: any[], result?: any): any {
8953
+ if (op === 'insertOne') return args[0] ?? null;
8954
+ if (op === 'insertMany') return args[0] ?? null;
8955
+ if (op === 'findOne' || op === 'findOneAndUpdate' || op === 'findOneAndDelete' || op === 'findOneAndReplace') {
8956
+ return result ?? null;
8957
+ }
8958
+ if (op === 'find' || op === 'aggregate') {
8959
+ return Array.isArray(result) ? result : null;
8960
+ }
8961
+ return null;
8962
+ }
8963
+
8964
+ function findMongoCallbackIndex(args: any[]): number {
8965
+ for (let i = args.length - 1; i >= 0; i -= 1) {
8966
+ if (typeof args[i] === 'function') return i;
8967
+ }
8968
+ return -1;
8969
+ }
8970
+
8971
+ function patchMongoCursorInstance(cursor: any, meta: {
8972
+ cfg: MongoDbPatchConfig;
8973
+ sid: string;
8974
+ aid: string | null;
8975
+ collection: string;
8976
+ dbName?: string;
8977
+ op: string;
8978
+ args: any[];
8979
+ spanContext: SpanContext | null;
8980
+ }): any {
8981
+ if (!cursor || typeof cursor !== 'object') return cursor;
8982
+ if (cursor.__repro_mongodb_cursor_patched) return cursor;
8983
+ try {
8984
+ Object.defineProperty(cursor, '__repro_mongodb_cursor_patched', {
8985
+ value: true,
8986
+ configurable: true,
8987
+ });
8988
+ } catch {
8989
+ return cursor;
8990
+ }
8991
+
8992
+ for (const methodName of MONGODB_CURSOR_TERMINALS) {
8993
+ const original = cursor[methodName];
8994
+ if (typeof original !== 'function') continue;
8995
+
8996
+ cursor[methodName] = function reproPatchedMongoCursorTerminal(this: any, ...callArgs: any[]) {
8997
+ const t0 = Date.now();
8998
+ let emitted = false;
8999
+ let forEachDocs: any[] | null = null;
9000
+ const emitSuccess = (value: any) => {
9001
+ if (emitted) return;
9002
+ emitted = true;
9003
+ const terminalQueryArgs = [...meta.args] as any[];
9004
+ const queryRaw = buildMongoQueryPayload(meta.op, terminalQueryArgs, meta.dbName);
9005
+ queryRaw.cursorMethod = methodName;
9006
+ emitDbQuery(meta.cfg, meta.sid, meta.aid ?? undefined, {
9007
+ type: 'mongo',
9008
+ collection: meta.collection,
9009
+ op: meta.op,
9010
+ pk: buildMongoPk(meta.op, meta.args, value),
9011
+ before: null,
9012
+ after: buildMongoAfter(meta.op, meta.args, forEachDocs ?? value),
9013
+ queryRaw,
9014
+ resultMetaRaw: forEachDocs ?? value,
9015
+ durMs: Date.now() - t0,
9016
+ t: alignedNow(),
9017
+ spanContext: meta.spanContext,
9018
+ });
9019
+ };
9020
+ const emitFailure = (error: any) => {
9021
+ if (emitted) return;
9022
+ emitted = true;
9023
+ const terminalQueryArgs = [...meta.args] as any[];
9024
+ const queryRaw = buildMongoQueryPayload(meta.op, terminalQueryArgs, meta.dbName);
9025
+ queryRaw.cursorMethod = methodName;
9026
+ emitDbQuery(meta.cfg, meta.sid, meta.aid ?? undefined, {
9027
+ type: 'mongo',
9028
+ collection: meta.collection,
9029
+ op: meta.op,
9030
+ queryRaw,
9031
+ error: summarizeMongoError(error),
9032
+ durMs: Date.now() - t0,
9033
+ t: alignedNow(),
9034
+ spanContext: meta.spanContext,
9035
+ });
9036
+ };
9037
+
9038
+ const actualArgs = [...callArgs];
9039
+ if (methodName === 'forEach' && typeof actualArgs[0] === 'function') {
9040
+ forEachDocs = [];
9041
+ const originalIterator = actualArgs[0];
9042
+ actualArgs[0] = function reproMongoForEachIterator(this: any, doc: any, ...rest: any[]) {
9043
+ forEachDocs?.push(doc);
9044
+ return originalIterator.call(this, doc, ...rest);
9045
+ };
9046
+ }
9047
+
9048
+ try {
9049
+ const value = original.apply(this, actualArgs);
9050
+ if (value && typeof value.then === 'function') {
9051
+ return value.then(
9052
+ (resolved: any) => {
9053
+ emitSuccess(resolved);
9054
+ return resolved;
9055
+ },
9056
+ (err: any) => {
9057
+ emitFailure(err);
9058
+ throw err;
9059
+ },
9060
+ );
9061
+ }
9062
+ emitSuccess(value);
9063
+ return value;
9064
+ } catch (err) {
9065
+ emitFailure(err);
9066
+ throw err;
9067
+ }
9068
+ };
9069
+ }
9070
+
9071
+ return cursor;
9072
+ }
9073
+
9074
+ function patchMongoCollectionMethod(proto: any, op: string, cursorOp: boolean, cfg: MongoDbPatchConfig): void {
9075
+ if (!proto) return;
9076
+ const marker = `__repro_mongodb_${op}_patched`;
9077
+ if (proto[marker]) return;
9078
+ const original = proto[op];
9079
+ if (typeof original !== 'function') return;
9080
+ proto[marker] = true;
9081
+
9082
+ proto[op] = function reproPatchedMongoCollectionMethod(this: any, ...args: any[]) {
9083
+ const collection = resolveMongoCollectionName(this);
9084
+ if (isNativeMongoSuppressed(collection, op)) {
9085
+ return original.apply(this, args);
9086
+ }
9087
+
9088
+ const { sid, aid } = resolveMongoDbRuntimeContext(cfg);
9089
+ if (!sid) {
9090
+ return original.apply(this, args);
9091
+ }
9092
+
9093
+ const spanContext = captureSpanContextFromTracer(this);
9094
+ const t0 = Date.now();
9095
+ const dbName = resolveMongoDbName(this);
9096
+ const callbackIndex = cursorOp ? -1 : findMongoCallbackIndex(args);
9097
+ const actualArgs = callbackIndex >= 0 ? [...args] : args;
9098
+ let emitted = false;
9099
+
9100
+ const emitSuccess = (result: any) => {
9101
+ if (emitted) return;
9102
+ emitted = true;
9103
+ emitDbQuery(cfg, sid, aid ?? undefined, {
9104
+ type: 'mongo',
9105
+ collection,
9106
+ op,
9107
+ pk: buildMongoPk(op, args, result),
9108
+ before: null,
9109
+ after: buildMongoAfter(op, args, result),
9110
+ queryRaw: buildMongoQueryPayload(op, args, dbName),
9111
+ resultMetaRaw: result,
9112
+ durMs: Date.now() - t0,
9113
+ t: alignedNow(),
9114
+ spanContext,
9115
+ });
9116
+ };
9117
+ const emitFailure = (error: any) => {
9118
+ if (emitted) return;
9119
+ emitted = true;
9120
+ emitDbQuery(cfg, sid, aid ?? undefined, {
9121
+ type: 'mongo',
9122
+ collection,
9123
+ op,
9124
+ queryRaw: buildMongoQueryPayload(op, args, dbName),
9125
+ error: summarizeMongoError(error),
9126
+ durMs: Date.now() - t0,
9127
+ t: alignedNow(),
9128
+ spanContext,
9129
+ });
9130
+ };
9131
+
9132
+ if (callbackIndex >= 0) {
9133
+ const originalCallback = args[callbackIndex];
9134
+ actualArgs[callbackIndex] = function reproMongoCallback(this: any, err: any, result: any, ...rest: any[]) {
9135
+ if (err) emitFailure(err);
9136
+ else emitSuccess(result);
9137
+ return originalCallback.call(this, err, result, ...rest);
9138
+ };
9139
+ }
9140
+
9141
+ try {
9142
+ const result = original.apply(this, actualArgs);
9143
+ if (cursorOp) {
9144
+ return patchMongoCursorInstance(result, {
9145
+ cfg,
9146
+ sid,
9147
+ aid,
9148
+ collection,
9149
+ dbName,
9150
+ op,
9151
+ args,
9152
+ spanContext,
9153
+ });
9154
+ }
9155
+ if (result && typeof result.then === 'function') {
9156
+ return result.then(
9157
+ (value: any) => {
9158
+ emitSuccess(value);
9159
+ return value;
9160
+ },
9161
+ (error: any) => {
9162
+ emitFailure(error);
9163
+ throw error;
9164
+ },
9165
+ );
9166
+ }
9167
+ if (callbackIndex < 0) {
9168
+ emitSuccess(result);
9169
+ }
9170
+ return result;
9171
+ } catch (error) {
9172
+ emitFailure(error);
9173
+ throw error;
9174
+ }
9175
+ };
9176
+ }
9177
+
9178
+ function patchMongoCollectionPrototype(proto: any, cfg: MongoDbPatchConfig): void {
9179
+ if (!proto) return;
9180
+ for (const op of MONGODB_PROMISE_OPS) {
9181
+ patchMongoCollectionMethod(proto, op, false, cfg);
9182
+ }
9183
+ for (const op of MONGODB_CURSOR_OPS) {
9184
+ patchMongoCollectionMethod(proto, op, true, cfg);
9185
+ }
9186
+ }
9187
+
9188
+ function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig): void {
9189
+ if (!mongoModule) return;
9190
+ const constructors = new Set<any>();
9191
+ const addCtor = (candidate: any) => {
9192
+ if (typeof candidate === 'function' && candidate.prototype) {
9193
+ constructors.add(candidate);
9194
+ }
9195
+ };
9196
+
9197
+ addCtor(mongoModule?.Collection);
9198
+ addCtor(mongoModule?.default?.Collection);
9199
+
9200
+ for (const ctor of constructors) {
9201
+ patchMongoCollectionPrototype(ctor.prototype, cfg);
9202
+ }
9203
+ }
9204
+
9205
+ function installMongoDbRequireHook(): void {
9206
+ if (mongoDbRequireHookInstalled) return;
9207
+ mongoDbRequireHookInstalled = true;
9208
+
9209
+ try {
9210
+ const rim = requireOptionalHostModule('require-in-the-middle');
9211
+ const Hook = rim?.Hook ?? rim;
9212
+ if (typeof Hook !== 'function') return;
9213
+
9214
+ // eslint-disable-next-line no-new
9215
+ new Hook(['mongodb'], (exports: any) => {
9216
+ for (const cfg of mongoDbPatchConfigs) {
9217
+ patchMongoDbFromModuleExports(exports, cfg);
9218
+ }
9219
+ return exports;
9220
+ });
9221
+ } catch {}
9222
+ }
9223
+
9224
+ export function patchMongoDb(cfg: MongoDbPatchConfig): void {
9225
+ mongoDbPatchConfigs.push(cfg);
9226
+ installMongoDbRequireHook();
9227
+
9228
+ const mongoModules = new Set<any>();
9229
+ for (const loadedMongoModule of getLoadedModuleExportsByPackage('mongodb')) {
9230
+ mongoModules.add(loadedMongoModule);
9231
+ }
9232
+ if (!mongoModules.size) return;
9233
+
9234
+ for (const mongoModule of mongoModules) {
9235
+ patchMongoDbFromModuleExports(mongoModule, cfg);
9236
+ }
9237
+ }
9238
+
8591
9239
  // ===================================================================
8592
9240
  // Auto init helpers
8593
9241
  // ===================================================================
@@ -9015,6 +9663,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
9015
9663
  patchKafkaJs(integrationCfg);
9016
9664
  patchAmqplib(integrationCfg);
9017
9665
  patchRedis(integrationCfg);
9666
+ patchMongoDb(integrationCfg);
9018
9667
  patchSendgridMail(integrationCfg);
9019
9668
  patchMongooseIntegration(reproAutoMiddlewareConfig);
9020
9669
 
@@ -9066,6 +9715,8 @@ export const __reproTestHooks = {
9066
9715
  patchAmqplibConnectionForTest: patchAmqplibConnection,
9067
9716
  patchAmqplibChannelForTest: patchAmqplibChannel,
9068
9717
  wrapAmqplibConsumeHandlerForTest: wrapAmqplibConsumeHandler,
9718
+ patchMongoDbModuleForTest: patchMongoDbFromModuleExports,
9719
+ patchMongoCollectionPrototypeForTest: patchMongoCollectionPrototype,
9069
9720
  chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
9070
9721
  estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
9071
9722
  sanitizeTraceValueForPrivacyForTest: sanitizeTraceValueForPrivacy,