@reproapp/node-sdk 0.0.28 → 0.0.29

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
@@ -1028,10 +1028,6 @@ function scheduleTraceMaterializerIdleShutdown() {
1028
1028
  catch { }
1029
1029
  }
1030
1030
  }, TRACE_MATERIALIZE_WORKER_IDLE_SHUTDOWN_MS);
1031
- try {
1032
- traceMaterializerIdleTimer.unref?.();
1033
- }
1034
- catch { }
1035
1031
  }
1036
1032
  function rejectTraceMaterializerPending(error) {
1037
1033
  for (const [taskId, pending] of traceMaterializerPending.entries()) {
@@ -1373,11 +1369,43 @@ function waitForSessionDrain(sessionId) {
1373
1369
  if (remaining <= 0)
1374
1370
  return Promise.resolve();
1375
1371
  return new Promise(resolve => {
1372
+ let settled = false;
1373
+ let timeout = null;
1374
+ const removeWaiter = (waiter) => {
1375
+ const list = sessionWaiters.get(sessionId);
1376
+ if (!list)
1377
+ return;
1378
+ const next = list.filter(fn => fn !== waiter);
1379
+ if (next.length) {
1380
+ sessionWaiters.set(sessionId, next);
1381
+ }
1382
+ else {
1383
+ sessionWaiters.delete(sessionId);
1384
+ }
1385
+ };
1386
+ const done = () => {
1387
+ if (settled)
1388
+ return;
1389
+ settled = true;
1390
+ if (timeout) {
1391
+ try {
1392
+ clearTimeout(timeout);
1393
+ }
1394
+ catch { }
1395
+ timeout = null;
1396
+ }
1397
+ removeWaiter(done);
1398
+ resolve();
1399
+ };
1376
1400
  const list = sessionWaiters.get(sessionId) || [];
1377
- list.push(resolve);
1401
+ list.push(done);
1378
1402
  sessionWaiters.set(sessionId, list);
1379
1403
  if (SESSION_DRAIN_TIMEOUT_MS > 0 && Number.isFinite(SESSION_DRAIN_TIMEOUT_MS)) {
1380
- setTimeout(resolve, SESSION_DRAIN_TIMEOUT_MS);
1404
+ timeout = setTimeout(done, SESSION_DRAIN_TIMEOUT_MS);
1405
+ try {
1406
+ timeout.unref?.();
1407
+ }
1408
+ catch { }
1381
1409
  }
1382
1410
  });
1383
1411
  }
@@ -4546,6 +4574,7 @@ function nativeMongoOpsForMongooseOp(op) {
4546
4574
  return [op];
4547
4575
  }
4548
4576
  function reproMongoosePlugin(cfg) {
4577
+ patchMongoDb(cfg);
4549
4578
  return function (schema) {
4550
4579
  // -------- pre/post save (unchanged) --------
4551
4580
  schema.pre('save', { document: true }, function (next) {
@@ -7942,6 +7971,50 @@ function patchMongoCollectionPrototype(proto, cfg) {
7942
7971
  patchMongoCollectionMethod(proto, op, true, cfg);
7943
7972
  }
7944
7973
  }
7974
+ function patchMongoCollectionInstance(collection, cfg) {
7975
+ if (!collection || typeof collection !== 'object')
7976
+ return collection;
7977
+ patchMongoCollectionPrototype(Object.getPrototypeOf(collection), cfg);
7978
+ patchMongoCollectionPrototype(collection, cfg);
7979
+ return collection;
7980
+ }
7981
+ function patchMongoDbPrototype(proto, cfg) {
7982
+ if (!proto)
7983
+ return;
7984
+ const marker = '__repro_mongodb_db_collection_patched';
7985
+ if (proto[marker])
7986
+ return;
7987
+ const original = proto.collection;
7988
+ if (typeof original !== 'function')
7989
+ return;
7990
+ proto[marker] = true;
7991
+ proto.collection = function reproPatchedMongoDbCollection(...args) {
7992
+ const collection = original.apply(this, args);
7993
+ return patchMongoCollectionInstance(collection, cfg);
7994
+ };
7995
+ }
7996
+ function patchMongoDbInstance(db, cfg) {
7997
+ if (!db || typeof db !== 'object')
7998
+ return db;
7999
+ patchMongoDbPrototype(Object.getPrototypeOf(db), cfg);
8000
+ patchMongoDbPrototype(db, cfg);
8001
+ return db;
8002
+ }
8003
+ function patchMongoClientPrototype(proto, cfg) {
8004
+ if (!proto)
8005
+ return;
8006
+ const marker = '__repro_mongodb_client_db_patched';
8007
+ if (proto[marker])
8008
+ return;
8009
+ const original = proto.db;
8010
+ if (typeof original !== 'function')
8011
+ return;
8012
+ proto[marker] = true;
8013
+ proto.db = function reproPatchedMongoClientDb(...args) {
8014
+ const db = original.apply(this, args);
8015
+ return patchMongoDbInstance(db, cfg);
8016
+ };
8017
+ }
7945
8018
  function patchMongoDbFromModuleExports(mongoModule, cfg) {
7946
8019
  if (!mongoModule)
7947
8020
  return;
@@ -7956,6 +8029,28 @@ function patchMongoDbFromModuleExports(mongoModule, cfg) {
7956
8029
  for (const ctor of constructors) {
7957
8030
  patchMongoCollectionPrototype(ctor.prototype, cfg);
7958
8031
  }
8032
+ const dbConstructors = new Set();
8033
+ const clientConstructors = new Set();
8034
+ const addDbCtor = (candidate) => {
8035
+ if (typeof candidate === 'function' && candidate.prototype) {
8036
+ dbConstructors.add(candidate);
8037
+ }
8038
+ };
8039
+ const addClientCtor = (candidate) => {
8040
+ if (typeof candidate === 'function' && candidate.prototype) {
8041
+ clientConstructors.add(candidate);
8042
+ }
8043
+ };
8044
+ addDbCtor(mongoModule?.Db);
8045
+ addDbCtor(mongoModule?.default?.Db);
8046
+ addClientCtor(mongoModule?.MongoClient);
8047
+ addClientCtor(mongoModule?.default?.MongoClient);
8048
+ for (const ctor of dbConstructors) {
8049
+ patchMongoDbPrototype(ctor.prototype, cfg);
8050
+ }
8051
+ for (const ctor of clientConstructors) {
8052
+ patchMongoClientPrototype(ctor.prototype, cfg);
8053
+ }
7959
8054
  }
7960
8055
  function installMongoDbRequireHook() {
7961
8056
  if (mongoDbRequireHookInstalled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1429,7 +1429,6 @@ function scheduleTraceMaterializerIdleShutdown(): void {
1429
1429
  try { void worker.terminate?.(); } catch {}
1430
1430
  }
1431
1431
  }, TRACE_MATERIALIZE_WORKER_IDLE_SHUTDOWN_MS);
1432
- try { (traceMaterializerIdleTimer as any).unref?.(); } catch {}
1433
1432
  }
1434
1433
 
1435
1434
  function rejectTraceMaterializerPending(error: Error): void {
@@ -1745,11 +1744,34 @@ function waitForSessionDrain(sessionId: string): Promise<void> {
1745
1744
  if (remaining <= 0) return Promise.resolve();
1746
1745
 
1747
1746
  return new Promise(resolve => {
1747
+ let settled = false;
1748
+ let timeout: NodeJS.Timeout | null = null;
1749
+ const removeWaiter = (waiter: () => void) => {
1750
+ const list = sessionWaiters.get(sessionId);
1751
+ if (!list) return;
1752
+ const next = list.filter(fn => fn !== waiter);
1753
+ if (next.length) {
1754
+ sessionWaiters.set(sessionId, next);
1755
+ } else {
1756
+ sessionWaiters.delete(sessionId);
1757
+ }
1758
+ };
1759
+ const done = () => {
1760
+ if (settled) return;
1761
+ settled = true;
1762
+ if (timeout) {
1763
+ try { clearTimeout(timeout); } catch {}
1764
+ timeout = null;
1765
+ }
1766
+ removeWaiter(done);
1767
+ resolve();
1768
+ };
1748
1769
  const list = sessionWaiters.get(sessionId) || [];
1749
- list.push(resolve);
1770
+ list.push(done);
1750
1771
  sessionWaiters.set(sessionId, list);
1751
1772
  if (SESSION_DRAIN_TIMEOUT_MS > 0 && Number.isFinite(SESSION_DRAIN_TIMEOUT_MS)) {
1752
- setTimeout(resolve, SESSION_DRAIN_TIMEOUT_MS);
1773
+ timeout = setTimeout(done, SESSION_DRAIN_TIMEOUT_MS);
1774
+ try { (timeout as any).unref?.(); } catch {}
1753
1775
  }
1754
1776
  });
1755
1777
  }
@@ -5490,6 +5512,7 @@ function nativeMongoOpsForMongooseOp(op: string): string[] {
5490
5512
  }
5491
5513
 
5492
5514
  export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5515
+ patchMongoDb(cfg);
5493
5516
  return function (schema: Schema) {
5494
5517
  // -------- pre/post save (unchanged) --------
5495
5518
  schema.pre('save', { document: true }, function (next) {
@@ -9185,6 +9208,46 @@ function patchMongoCollectionPrototype(proto: any, cfg: MongoDbPatchConfig): voi
9185
9208
  }
9186
9209
  }
9187
9210
 
9211
+ function patchMongoCollectionInstance(collection: any, cfg: MongoDbPatchConfig): any {
9212
+ if (!collection || typeof collection !== 'object') return collection;
9213
+ patchMongoCollectionPrototype(Object.getPrototypeOf(collection), cfg);
9214
+ patchMongoCollectionPrototype(collection, cfg);
9215
+ return collection;
9216
+ }
9217
+
9218
+ function patchMongoDbPrototype(proto: any, cfg: MongoDbPatchConfig): void {
9219
+ if (!proto) return;
9220
+ const marker = '__repro_mongodb_db_collection_patched';
9221
+ if (proto[marker]) return;
9222
+ const original = proto.collection;
9223
+ if (typeof original !== 'function') return;
9224
+ proto[marker] = true;
9225
+ proto.collection = function reproPatchedMongoDbCollection(this: any, ...args: any[]) {
9226
+ const collection = original.apply(this, args);
9227
+ return patchMongoCollectionInstance(collection, cfg);
9228
+ };
9229
+ }
9230
+
9231
+ function patchMongoDbInstance(db: any, cfg: MongoDbPatchConfig): any {
9232
+ if (!db || typeof db !== 'object') return db;
9233
+ patchMongoDbPrototype(Object.getPrototypeOf(db), cfg);
9234
+ patchMongoDbPrototype(db, cfg);
9235
+ return db;
9236
+ }
9237
+
9238
+ function patchMongoClientPrototype(proto: any, cfg: MongoDbPatchConfig): void {
9239
+ if (!proto) return;
9240
+ const marker = '__repro_mongodb_client_db_patched';
9241
+ if (proto[marker]) return;
9242
+ const original = proto.db;
9243
+ if (typeof original !== 'function') return;
9244
+ proto[marker] = true;
9245
+ proto.db = function reproPatchedMongoClientDb(this: any, ...args: any[]) {
9246
+ const db = original.apply(this, args);
9247
+ return patchMongoDbInstance(db, cfg);
9248
+ };
9249
+ }
9250
+
9188
9251
  function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig): void {
9189
9252
  if (!mongoModule) return;
9190
9253
  const constructors = new Set<any>();
@@ -9200,6 +9263,31 @@ function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig
9200
9263
  for (const ctor of constructors) {
9201
9264
  patchMongoCollectionPrototype(ctor.prototype, cfg);
9202
9265
  }
9266
+
9267
+ const dbConstructors = new Set<any>();
9268
+ const clientConstructors = new Set<any>();
9269
+ const addDbCtor = (candidate: any) => {
9270
+ if (typeof candidate === 'function' && candidate.prototype) {
9271
+ dbConstructors.add(candidate);
9272
+ }
9273
+ };
9274
+ const addClientCtor = (candidate: any) => {
9275
+ if (typeof candidate === 'function' && candidate.prototype) {
9276
+ clientConstructors.add(candidate);
9277
+ }
9278
+ };
9279
+
9280
+ addDbCtor(mongoModule?.Db);
9281
+ addDbCtor(mongoModule?.default?.Db);
9282
+ addClientCtor(mongoModule?.MongoClient);
9283
+ addClientCtor(mongoModule?.default?.MongoClient);
9284
+
9285
+ for (const ctor of dbConstructors) {
9286
+ patchMongoDbPrototype(ctor.prototype, cfg);
9287
+ }
9288
+ for (const ctor of clientConstructors) {
9289
+ patchMongoClientPrototype(ctor.prototype, cfg);
9290
+ }
9203
9291
  }
9204
9292
 
9205
9293
  function installMongoDbRequireHook(): void {
@@ -176,6 +176,83 @@ async function testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne() {
176
176
  });
177
177
  }
178
178
 
179
+ async function testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport() {
180
+ await withCapturedFetch(async (capturedBodies) => {
181
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
182
+ const sessionId = 'S_mongo_client_chain';
183
+ const actionId = 'A_mongo_client_chain';
184
+ const cfg = makeConfig(sessionId, actionId);
185
+
186
+ class FakeCollection {
187
+ constructor(dbName, collectionName) {
188
+ this.dbName = dbName;
189
+ this.collectionName = collectionName;
190
+ }
191
+
192
+ async insertOne(doc, options) {
193
+ assert.strictEqual(options.session.id, 'native-chain-session-1');
194
+ doc._id = doc._id ?? 'subject-chain-1';
195
+ return { acknowledged: true, insertedId: doc._id };
196
+ }
197
+ }
198
+
199
+ class FakeDb {
200
+ constructor(dbName) {
201
+ this.databaseName = dbName;
202
+ }
203
+
204
+ collection(collectionName) {
205
+ return new FakeCollection(this.databaseName, collectionName);
206
+ }
207
+ }
208
+
209
+ class FakeMongoClient {
210
+ db(dbName) {
211
+ return new FakeDb(dbName);
212
+ }
213
+ }
214
+
215
+ __reproTestHooks.patchMongoDbModuleForTest({
216
+ MongoClient: FakeMongoClient,
217
+ Db: FakeDb,
218
+ }, cfg);
219
+
220
+ const dbClient = new FakeMongoClient();
221
+ const subjectsCollection = dbClient.db('clinical').collection('subjects');
222
+
223
+ await runCapturedRequest(cfg, sessionId, actionId, async () => {
224
+ const result = await subjectsCollection.insertOne(
225
+ { subjectNumber: '10013038', status: 'screened' },
226
+ {
227
+ session: {
228
+ id: 'native-chain-session-1',
229
+ hasEnded: false,
230
+ inTransaction: () => true,
231
+ },
232
+ },
233
+ );
234
+ assert.deepStrictEqual(result, { acknowledged: true, insertedId: 'subject-chain-1' });
235
+ });
236
+
237
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
238
+ await drainSdk();
239
+
240
+ const [change] = dbChanges(flattenEvents(capturedBodies));
241
+ assert.strictEqual(change.type, 'mongo');
242
+ assert.strictEqual(change.collection, 'subjects');
243
+ assert.strictEqual(change.op, 'insertOne');
244
+ assert.strictEqual(change.pk._id, 'subject-chain-1');
245
+ assert.strictEqual(change.query.driver, 'mongodb');
246
+ assert.strictEqual(change.query.db, 'clinical');
247
+ assert.strictEqual(change.query.doc.subjectNumber, '10013038');
248
+ assert.strictEqual(change.query.options.session.__kind, 'mongodb_client_session');
249
+ assert.strictEqual(change.query.options.session.id, 'native-chain-session-1');
250
+ assert.strictEqual(change.after.subjectNumber, '10013038');
251
+ assert.strictEqual(change.resultMeta.insertedId, 'subject-chain-1');
252
+ assert.strictEqual(change.resultMeta.acknowledged, true);
253
+ });
254
+ }
255
+
179
256
  async function testNativeInsertOneIsCaptured() {
180
257
  await withCapturedFetch(async (capturedBodies) => {
181
258
  __reproTestHooks.resetSdkBackgroundQueuesForTest();
@@ -376,6 +453,7 @@ async function testNativeOperationFailureIsCapturedAndRethrown() {
376
453
 
377
454
  (async () => {
378
455
  await testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne();
456
+ await testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport();
379
457
  await testNativeInsertOneIsCaptured();
380
458
  await testNativeUpdateOneUpsertIsCaptured();
381
459
  await testNativeFindCursorToArrayIsCaptured();