@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 +101 -6
- package/package.json +1 -1
- package/src/index.ts +91 -3
- package/test/mongodb-integration.test.js +78 -0
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(
|
|
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(
|
|
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
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(
|
|
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(
|
|
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();
|