@reproapp/node-sdk 0.0.33 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -824,6 +824,19 @@ const getCtx = () => als.getStore() || {};
|
|
|
824
824
|
function normalizeNativeMongoSuppressionOp(op) {
|
|
825
825
|
return String(op ?? '').trim();
|
|
826
826
|
}
|
|
827
|
+
const NATIVE_MONGO_WRITE_OPS = new Set([
|
|
828
|
+
'insertOne',
|
|
829
|
+
'insertMany',
|
|
830
|
+
'updateOne',
|
|
831
|
+
'updateMany',
|
|
832
|
+
'replaceOne',
|
|
833
|
+
'deleteOne',
|
|
834
|
+
'deleteMany',
|
|
835
|
+
'bulkWrite',
|
|
836
|
+
'findOneAndUpdate',
|
|
837
|
+
'findOneAndDelete',
|
|
838
|
+
'findOneAndReplace',
|
|
839
|
+
]);
|
|
827
840
|
function pushNativeMongoSuppression(collection, ops) {
|
|
828
841
|
const store = als.getStore();
|
|
829
842
|
if (!store)
|
|
@@ -887,6 +900,8 @@ function isNativeMongoSuppressed(collection, op) {
|
|
|
887
900
|
return false;
|
|
888
901
|
const normalizedCollection = collection == null ? null : String(collection);
|
|
889
902
|
const normalizedOp = normalizeNativeMongoSuppressionOp(op);
|
|
903
|
+
if (NATIVE_MONGO_WRITE_OPS.has(normalizedOp))
|
|
904
|
+
return false;
|
|
890
905
|
const now = Date.now();
|
|
891
906
|
const matchingTokens = [];
|
|
892
907
|
for (let i = suppressions.length - 1; i >= 0; i -= 1) {
|
|
@@ -2988,9 +3003,12 @@ function limitRawInlinePrivacyValue(target, value) {
|
|
|
2988
3003
|
return limited.skipped ? limited : null;
|
|
2989
3004
|
}
|
|
2990
3005
|
function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking, privacy, db) {
|
|
2991
|
-
if (value === undefined
|
|
3006
|
+
if (value === undefined) {
|
|
2992
3007
|
return { value };
|
|
2993
3008
|
}
|
|
3009
|
+
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3010
|
+
return { value: limitMaterializedTraceInlineValue(value) };
|
|
3011
|
+
}
|
|
2994
3012
|
try {
|
|
2995
3013
|
const valueAfterPrivacy = applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy, db);
|
|
2996
3014
|
return { value: valueAfterPrivacy };
|
|
@@ -3007,9 +3025,12 @@ function materializeInlinePrivacyValue(target, value, cfg, req, trace, masking,
|
|
|
3007
3025
|
}
|
|
3008
3026
|
}
|
|
3009
3027
|
async function materializeInlinePrivacyValueAsync(target, value, cfg, req, trace, masking, privacy, db) {
|
|
3010
|
-
if (value === undefined
|
|
3028
|
+
if (value === undefined) {
|
|
3011
3029
|
return { value };
|
|
3012
3030
|
}
|
|
3031
|
+
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3032
|
+
return { value: limitMaterializedTraceInlineValue(value) };
|
|
3033
|
+
}
|
|
3013
3034
|
try {
|
|
3014
3035
|
const valueAfterPrivacy = await applyPrivacyThenMaskAsync(target, value, cfg, req, trace, masking, privacy, db);
|
|
3015
3036
|
return { value: valueAfterPrivacy };
|
|
@@ -3899,7 +3920,7 @@ function estimateTraceBatchSerializedBytes(batch, requestRid, actionId, batchInd
|
|
|
3899
3920
|
function chunkTraceEventsForTransport(events, requestRid, actionId) {
|
|
3900
3921
|
if (!Array.isArray(events) || events.length === 0)
|
|
3901
3922
|
return [];
|
|
3902
|
-
const boundedEvents = events
|
|
3923
|
+
const boundedEvents = boundTraceEventsForTransport(events);
|
|
3903
3924
|
const countBatches = chunkArray(boundedEvents, TRACE_BATCH_SIZE);
|
|
3904
3925
|
const sizedBatches = [];
|
|
3905
3926
|
let current = [];
|
|
@@ -3928,6 +3949,13 @@ function chunkTraceEventsForTransport(events, requestRid, actionId) {
|
|
|
3928
3949
|
}
|
|
3929
3950
|
return sizedBatches;
|
|
3930
3951
|
}
|
|
3952
|
+
function boundTraceEventsForTransport(events) {
|
|
3953
|
+
if (events.length <= TRACE_MAX_EVENTS_PER_REQUEST)
|
|
3954
|
+
return events;
|
|
3955
|
+
const headCount = Math.floor(TRACE_MAX_EVENTS_PER_REQUEST / 2);
|
|
3956
|
+
const tailCount = TRACE_MAX_EVENTS_PER_REQUEST - headCount;
|
|
3957
|
+
return events.slice(0, headCount).concat(events.slice(events.length - tailCount));
|
|
3958
|
+
}
|
|
3931
3959
|
function createCapturedValueEntry(params) {
|
|
3932
3960
|
if (!__FULL_VALUE_CAPTURE_ENABLED)
|
|
3933
3961
|
return undefined;
|
|
@@ -5188,7 +5216,7 @@ function emitDbQuery(cfg, sid, aid, payload) {
|
|
|
5188
5216
|
await als.run(runCtx, async () => {
|
|
5189
5217
|
await emitDbQueryAsync(cfg, sid, aid, payload).catch(() => undefined);
|
|
5190
5218
|
});
|
|
5191
|
-
});
|
|
5219
|
+
}, { priority: 'high' });
|
|
5192
5220
|
}
|
|
5193
5221
|
async function emitDbQueryAsync(cfg, sid, aid, payload) {
|
|
5194
5222
|
if (!sid)
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1227,6 +1227,20 @@ function normalizeNativeMongoSuppressionOp(op: string | null | undefined): strin
|
|
|
1227
1227
|
return String(op ?? '').trim();
|
|
1228
1228
|
}
|
|
1229
1229
|
|
|
1230
|
+
const NATIVE_MONGO_WRITE_OPS = new Set([
|
|
1231
|
+
'insertOne',
|
|
1232
|
+
'insertMany',
|
|
1233
|
+
'updateOne',
|
|
1234
|
+
'updateMany',
|
|
1235
|
+
'replaceOne',
|
|
1236
|
+
'deleteOne',
|
|
1237
|
+
'deleteMany',
|
|
1238
|
+
'bulkWrite',
|
|
1239
|
+
'findOneAndUpdate',
|
|
1240
|
+
'findOneAndDelete',
|
|
1241
|
+
'findOneAndReplace',
|
|
1242
|
+
]);
|
|
1243
|
+
|
|
1230
1244
|
function pushNativeMongoSuppression(collection: any, ops?: string[] | readonly string[]): NativeMongoSuppression | null {
|
|
1231
1245
|
const store = als.getStore();
|
|
1232
1246
|
if (!store) return null;
|
|
@@ -1284,6 +1298,7 @@ function isNativeMongoSuppressed(collection: any, op: string): boolean {
|
|
|
1284
1298
|
if (!Array.isArray(suppressions) || suppressions.length === 0) return false;
|
|
1285
1299
|
const normalizedCollection = collection == null ? null : String(collection);
|
|
1286
1300
|
const normalizedOp = normalizeNativeMongoSuppressionOp(op);
|
|
1301
|
+
if (NATIVE_MONGO_WRITE_OPS.has(normalizedOp)) return false;
|
|
1287
1302
|
const now = Date.now();
|
|
1288
1303
|
const matchingTokens: NativeMongoSuppression[] = [];
|
|
1289
1304
|
for (let i = suppressions.length - 1; i >= 0; i -= 1) {
|
|
@@ -3595,9 +3610,12 @@ function materializeInlinePrivacyValue(
|
|
|
3595
3610
|
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
3596
3611
|
db?: RuntimePrivacyDbContext | null,
|
|
3597
3612
|
): InlinePrivacyMaterializationResult {
|
|
3598
|
-
if (value === undefined
|
|
3613
|
+
if (value === undefined) {
|
|
3599
3614
|
return { value };
|
|
3600
3615
|
}
|
|
3616
|
+
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3617
|
+
return { value: limitMaterializedTraceInlineValue(value) };
|
|
3618
|
+
}
|
|
3601
3619
|
|
|
3602
3620
|
try {
|
|
3603
3621
|
const valueAfterPrivacy = applyPrivacyThenMask(target, value, cfg, req, trace, masking, privacy, db);
|
|
@@ -3624,9 +3642,12 @@ async function materializeInlinePrivacyValueAsync(
|
|
|
3624
3642
|
privacy: NormalizedRuntimePrivacyPolicy | null,
|
|
3625
3643
|
db?: RuntimePrivacyDbContext | null,
|
|
3626
3644
|
): Promise<InlinePrivacyMaterializationResult> {
|
|
3627
|
-
if (value === undefined
|
|
3645
|
+
if (value === undefined) {
|
|
3628
3646
|
return { value };
|
|
3629
3647
|
}
|
|
3648
|
+
if (shouldCapturePrivacyRaw(cfg)) {
|
|
3649
|
+
return { value: limitMaterializedTraceInlineValue(value) };
|
|
3650
|
+
}
|
|
3630
3651
|
|
|
3631
3652
|
try {
|
|
3632
3653
|
const valueAfterPrivacy = await applyPrivacyThenMaskAsync(target, value, cfg, req, trace, masking, privacy, db);
|
|
@@ -4627,7 +4648,7 @@ function chunkTraceEventsForTransport(
|
|
|
4627
4648
|
): TraceEventRecord[][] {
|
|
4628
4649
|
if (!Array.isArray(events) || events.length === 0) return [];
|
|
4629
4650
|
|
|
4630
|
-
const boundedEvents = events
|
|
4651
|
+
const boundedEvents = boundTraceEventsForTransport(events);
|
|
4631
4652
|
const countBatches = chunkArray(boundedEvents, TRACE_BATCH_SIZE);
|
|
4632
4653
|
const sizedBatches: TraceEventRecord[][] = [];
|
|
4633
4654
|
let current: TraceEventRecord[] = [];
|
|
@@ -4671,6 +4692,13 @@ function chunkTraceEventsForTransport(
|
|
|
4671
4692
|
return sizedBatches;
|
|
4672
4693
|
}
|
|
4673
4694
|
|
|
4695
|
+
function boundTraceEventsForTransport(events: TraceEventRecord[]): TraceEventRecord[] {
|
|
4696
|
+
if (events.length <= TRACE_MAX_EVENTS_PER_REQUEST) return events;
|
|
4697
|
+
const headCount = Math.floor(TRACE_MAX_EVENTS_PER_REQUEST / 2);
|
|
4698
|
+
const tailCount = TRACE_MAX_EVENTS_PER_REQUEST - headCount;
|
|
4699
|
+
return events.slice(0, headCount).concat(events.slice(events.length - tailCount));
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4674
4702
|
function createCapturedValueEntry(
|
|
4675
4703
|
params: {
|
|
4676
4704
|
target:
|
|
@@ -6145,7 +6173,7 @@ function emitDbQuery(cfg: any, sid?: string, aid?: string, payload?: any) {
|
|
|
6145
6173
|
await als.run(runCtx, async () => {
|
|
6146
6174
|
await emitDbQueryAsync(cfg, sid, aid, payload).catch(() => undefined);
|
|
6147
6175
|
});
|
|
6148
|
-
});
|
|
6176
|
+
}, { priority: 'high' });
|
|
6149
6177
|
}
|
|
6150
6178
|
|
|
6151
6179
|
async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?: any) {
|
|
@@ -139,6 +139,13 @@ function dbChanges(events) {
|
|
|
139
139
|
.flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function requestCaptureManifests(events) {
|
|
143
|
+
return events
|
|
144
|
+
.filter((event) => event.event_type === 'request_capture_manifest')
|
|
145
|
+
.map((event) => event.payload?.requestCaptureManifest ?? event.payload)
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
|
|
142
149
|
async function testMongooseSuppressionDoesNotHideExplicitNativeInsertOne() {
|
|
143
150
|
await withCapturedFetch(async (capturedBodies) => {
|
|
144
151
|
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
@@ -185,9 +192,11 @@ async function testMongooseSuppressionDoesNotHideExplicitNativeInsertOne() {
|
|
|
185
192
|
});
|
|
186
193
|
|
|
187
194
|
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
|
|
195
|
+
await waitFor(() => requestCaptureManifests(flattenEvents(capturedBodies)).length === 1);
|
|
188
196
|
await drainSdk();
|
|
189
197
|
|
|
190
198
|
const [change] = dbChanges(flattenEvents(capturedBodies));
|
|
199
|
+
const [manifest] = requestCaptureManifests(flattenEvents(capturedBodies));
|
|
191
200
|
assert.strictEqual(change.type, 'mongo');
|
|
192
201
|
assert.strictEqual(change.collection, 'subjects');
|
|
193
202
|
assert.strictEqual(change.op, 'insertOne');
|
|
@@ -198,6 +207,7 @@ async function testMongooseSuppressionDoesNotHideExplicitNativeInsertOne() {
|
|
|
198
207
|
assert.strictEqual(change.query.options.session.__kind, 'mongodb_client_session');
|
|
199
208
|
assert.strictEqual(change.query.options.session.id, 'native-explicit-session-1');
|
|
200
209
|
assert.strictEqual(change.after.subjectNumber, '10013038');
|
|
210
|
+
assert.strictEqual(manifest.expected.dbChange, 1);
|
|
201
211
|
});
|
|
202
212
|
}
|
|
203
213
|
|
|
@@ -379,6 +389,68 @@ async function testNativeInsertOneIsCaptured() {
|
|
|
379
389
|
});
|
|
380
390
|
}
|
|
381
391
|
|
|
392
|
+
async function testCaptureRawNativeInsertOneUsesJsonSafeTransportValues() {
|
|
393
|
+
await withCapturedFetch(async (capturedBodies) => {
|
|
394
|
+
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
395
|
+
const sessionId = 'S_mongo_insert_raw_complex';
|
|
396
|
+
const actionId = 'A_mongo_insert_raw_complex';
|
|
397
|
+
const cfg = {
|
|
398
|
+
...makeConfig(sessionId, actionId),
|
|
399
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
class FakeCollection {
|
|
403
|
+
constructor() {
|
|
404
|
+
this.collectionName = 'subjects';
|
|
405
|
+
this.dbName = 'clinical';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async insertOne(doc, options) {
|
|
409
|
+
assert.strictEqual(options.session.id, 'native-raw-complex-session-1');
|
|
410
|
+
doc._id = doc._id ?? 'subject-raw-complex-1';
|
|
411
|
+
return { acknowledged: true, insertedId: doc._id };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
__reproTestHooks.patchMongoCollectionPrototypeForTest(FakeCollection.prototype, cfg);
|
|
416
|
+
const collection = new FakeCollection();
|
|
417
|
+
const doc = {
|
|
418
|
+
subjectNumber: '10013039',
|
|
419
|
+
status: 'screened',
|
|
420
|
+
counter: BigInt(42),
|
|
421
|
+
};
|
|
422
|
+
doc.self = doc;
|
|
423
|
+
|
|
424
|
+
await runCapturedRequest(cfg, sessionId, actionId, async () => {
|
|
425
|
+
await collection.insertOne(doc, {
|
|
426
|
+
session: {
|
|
427
|
+
id: 'native-raw-complex-session-1',
|
|
428
|
+
hasEnded: false,
|
|
429
|
+
inTransaction: () => true,
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
|
|
435
|
+
await waitFor(() => requestCaptureManifests(flattenEvents(capturedBodies)).length === 1);
|
|
436
|
+
await drainSdk();
|
|
437
|
+
|
|
438
|
+
const [change] = dbChanges(flattenEvents(capturedBodies));
|
|
439
|
+
const [manifest] = requestCaptureManifests(flattenEvents(capturedBodies));
|
|
440
|
+
assert.strictEqual(change.type, 'mongo');
|
|
441
|
+
assert.strictEqual(change.collection, 'subjects');
|
|
442
|
+
assert.strictEqual(change.op, 'insertOne');
|
|
443
|
+
assert.strictEqual(change.query.driver, 'mongodb');
|
|
444
|
+
assert.strictEqual(change.query.doc.subjectNumber, '10013039');
|
|
445
|
+
assert.strictEqual(change.query.doc.counter, '42');
|
|
446
|
+
assert.deepStrictEqual(change.query.doc.self, { __reproCircularRef: '$.doc' });
|
|
447
|
+
assert.strictEqual(change.after.subjectNumber, '10013039');
|
|
448
|
+
assert.strictEqual(change.after.counter, '42');
|
|
449
|
+
assert.deepStrictEqual(change.after.self, { __reproCircularRef: '$' });
|
|
450
|
+
assert.strictEqual(manifest.expected.dbChange, 1);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
382
454
|
async function testNativeUpdateOneUpsertIsCaptured() {
|
|
383
455
|
await withCapturedFetch(async (capturedBodies) => {
|
|
384
456
|
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
@@ -525,6 +597,7 @@ async function testNativeOperationFailureIsCapturedAndRethrown() {
|
|
|
525
597
|
await testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne();
|
|
526
598
|
await testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport();
|
|
527
599
|
await testNativeInsertOneIsCaptured();
|
|
600
|
+
await testCaptureRawNativeInsertOneUsesJsonSafeTransportValues();
|
|
528
601
|
await testNativeUpdateOneUpsertIsCaptured();
|
|
529
602
|
await testNativeFindCursorToArrayIsCaptured();
|
|
530
603
|
await testNativeOperationFailureIsCapturedAndRethrown();
|
|
@@ -34,6 +34,13 @@ function dbChanges(events) {
|
|
|
34
34
|
.flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function requestCaptureManifests(events) {
|
|
38
|
+
return events
|
|
39
|
+
.filter((event) => event.event_type === 'request_capture_manifest')
|
|
40
|
+
.map((event) => event.payload?.requestCaptureManifest ?? event.payload)
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
function makeReqRes(sessionId, actionId) {
|
|
38
45
|
const req = new EventEmitter();
|
|
39
46
|
req.method = 'POST';
|
|
@@ -131,16 +138,29 @@ async function main() {
|
|
|
131
138
|
await Subject.create({ subjectNumber: '10013038', status: 'screened' });
|
|
132
139
|
});
|
|
133
140
|
|
|
134
|
-
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length
|
|
141
|
+
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length >= 2);
|
|
142
|
+
await waitFor(() => requestCaptureManifests(flattenEvents(capturedBodies)).length === 1);
|
|
135
143
|
await __reproTestHooks.drainSdkBackgroundQueueForTest();
|
|
136
144
|
await flushIngestQueue();
|
|
137
145
|
|
|
138
146
|
const changes = dbChanges(flattenEvents(capturedBodies));
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
const manifest = requestCaptureManifests(flattenEvents(capturedBodies))[0];
|
|
148
|
+
const mongooseWrite = changes.find((change) => (
|
|
149
|
+
change.type === 'mongo' &&
|
|
150
|
+
change.collection === 'subjects' &&
|
|
151
|
+
change.op === 'insert' &&
|
|
152
|
+
change.after?.subjectNumber === '10013038'
|
|
153
|
+
));
|
|
154
|
+
const nativeWrite = changes.find((change) => (
|
|
155
|
+
change.type === 'mongo' &&
|
|
156
|
+
change.collection === 'subjects' &&
|
|
157
|
+
change.op === 'insertOne' &&
|
|
158
|
+
change.query?.driver === 'mongodb' &&
|
|
159
|
+
change.after?.subjectNumber === '10013038'
|
|
160
|
+
));
|
|
161
|
+
assert.ok(mongooseWrite, 'expected mongoose-level insert evidence');
|
|
162
|
+
assert.ok(nativeWrite, 'expected native mongodb insertOne evidence');
|
|
163
|
+
assert.ok(manifest.expected.dbChange >= 2);
|
|
144
164
|
|
|
145
165
|
console.log('mongodb mongoose native suppression testcontainers OK');
|
|
146
166
|
} finally {
|
|
@@ -66,6 +66,11 @@ function main() {
|
|
|
66
66
|
cappedEventCount <= 1000,
|
|
67
67
|
`expected trace transport to cap events per request, got ${cappedEventCount}`,
|
|
68
68
|
);
|
|
69
|
+
const cappedEvents = cappedBatches.flat();
|
|
70
|
+
assert.strictEqual(cappedEvents[0].fn, 'fn0');
|
|
71
|
+
assert.strictEqual(cappedEvents[499].fn, 'fn499');
|
|
72
|
+
assert.strictEqual(cappedEvents[500].fn, 'fn1000');
|
|
73
|
+
assert.strictEqual(cappedEvents[cappedEvents.length - 1].fn, 'fn1499');
|
|
69
74
|
|
|
70
75
|
// eslint-disable-next-line no-console
|
|
71
76
|
console.log('trace batch size chunking OK');
|