@reproapp/node-sdk 0.0.36 → 0.0.38

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
@@ -330,7 +330,7 @@ let disabledFunctionTraceRules = computeDisabledFunctionTraceRules();
330
330
  let disabledFunctionTypePatterns = [];
331
331
  let disabledTraceFilePatterns = [];
332
332
  let __TRACE_LOG_PREF = null;
333
- let __FULL_VALUE_CAPTURE_ENABLED = false;
333
+ let __FULL_VALUE_CAPTURE_ENABLED = true;
334
334
  function setInterceptorTracingEnabled(enabled) {
335
335
  const next = !!enabled;
336
336
  if (interceptorTracingEnabled === next)
@@ -581,9 +581,9 @@ function defaultTracerInitOpts() {
581
581
  /** Call this from the client app to enable tracing. Safe to call multiple times. */
582
582
  function initReproTracing(opts) {
583
583
  const options = opts ?? {};
584
- if (hasOwn(options, 'fullValueCapture')) {
585
- __FULL_VALUE_CAPTURE_ENABLED = options.fullValueCapture === true;
586
- }
584
+ __FULL_VALUE_CAPTURE_ENABLED = hasOwn(options, 'fullValueCapture')
585
+ ? options.fullValueCapture === true
586
+ : true;
587
587
  if (hasOwn(options, 'traceInterceptors')) {
588
588
  setInterceptorTracingEnabled(!!options.traceInterceptors);
589
589
  }
@@ -4092,6 +4092,45 @@ async function maybeCaptureDbValueAsync(params, sink) {
4092
4092
  }
4093
4093
  return result.captureRef;
4094
4094
  }
4095
+ function pushUniqueCaptureCandidate(candidates, value) {
4096
+ if (value === undefined)
4097
+ return;
4098
+ if (candidates.some((candidate) => candidate === value))
4099
+ return;
4100
+ candidates.push(value);
4101
+ }
4102
+ function buildResultMetaFullValueCandidates(resultMetaRaw, resultMetaSource) {
4103
+ const candidates = [];
4104
+ pushUniqueCaptureCandidate(candidates, resultMetaRaw);
4105
+ if (resultMetaSource &&
4106
+ typeof resultMetaSource === 'object' &&
4107
+ Object.prototype.hasOwnProperty.call(resultMetaSource, 'result')) {
4108
+ pushUniqueCaptureCandidate(candidates, resultMetaSource.result);
4109
+ }
4110
+ pushUniqueCaptureCandidate(candidates, resultMetaSource);
4111
+ return candidates;
4112
+ }
4113
+ async function maybeCaptureDbValueFromCandidatesAsync(params, sink) {
4114
+ let skippedRef;
4115
+ for (const rawValue of params.rawValues) {
4116
+ const result = await createCapturedValueEntryAsync({
4117
+ target: params.target,
4118
+ rawValue,
4119
+ previewValue: params.previewValue,
4120
+ capture: params.capture,
4121
+ });
4122
+ if (!result)
4123
+ continue;
4124
+ if (result.entry) {
4125
+ sink.push(result.entry);
4126
+ return result.captureRef;
4127
+ }
4128
+ if (!skippedRef) {
4129
+ skippedRef = result.captureRef;
4130
+ }
4131
+ }
4132
+ return skippedRef;
4133
+ }
4095
4134
  function reproMiddleware(cfg) {
4096
4135
  const masking = normalizeMaskingConfig(cfg.masking);
4097
4136
  patchMongoDb(cfg);
@@ -5285,9 +5324,9 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
5285
5324
  previewValue: queryPreview,
5286
5325
  capture,
5287
5326
  }, dbValues);
5288
- const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
5327
+ const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
5289
5328
  target: 'db.resultMeta',
5290
- rawValue: payload.resultMetaRaw ?? resultMetaSource,
5329
+ rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
5291
5330
  previewValue: resultMetaPreview,
5292
5331
  capture,
5293
5332
  }, dbValues);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -604,7 +604,7 @@ let disabledFunctionTraceRules: DisableFunctionTraceConfig[] = computeDisabledFu
604
604
  let disabledFunctionTypePatterns: Array<string | number | RegExp> = [];
605
605
  let disabledTraceFilePatterns: Array<string | number | RegExp> = [];
606
606
  let __TRACE_LOG_PREF: boolean | null = null;
607
- let __FULL_VALUE_CAPTURE_ENABLED = false;
607
+ let __FULL_VALUE_CAPTURE_ENABLED = true;
608
608
 
609
609
  function setInterceptorTracingEnabled(enabled: boolean) {
610
610
  const next = !!enabled;
@@ -929,9 +929,9 @@ function defaultTracerInitOpts(): TracerInitOpts {
929
929
  export function initReproTracing(opts?: ReproTracingInitOptions) {
930
930
  const options = opts ?? {};
931
931
 
932
- if (hasOwn(options, 'fullValueCapture')) {
933
- __FULL_VALUE_CAPTURE_ENABLED = options.fullValueCapture === true;
934
- }
932
+ __FULL_VALUE_CAPTURE_ENABLED = hasOwn(options, 'fullValueCapture')
933
+ ? options.fullValueCapture === true
934
+ : true;
935
935
 
936
936
  if (hasOwn(options, 'traceInterceptors')) {
937
937
  setInterceptorTracingEnabled(!!options.traceInterceptors);
@@ -4955,6 +4955,55 @@ async function maybeCaptureDbValueAsync(
4955
4955
  return result.captureRef;
4956
4956
  }
4957
4957
 
4958
+ function pushUniqueCaptureCandidate(candidates: any[], value: any): void {
4959
+ if (value === undefined) return;
4960
+ if (candidates.some((candidate) => candidate === value)) return;
4961
+ candidates.push(value);
4962
+ }
4963
+
4964
+ function buildResultMetaFullValueCandidates(resultMetaRaw: any, resultMetaSource: any): any[] {
4965
+ const candidates: any[] = [];
4966
+ pushUniqueCaptureCandidate(candidates, resultMetaRaw);
4967
+ if (
4968
+ resultMetaSource &&
4969
+ typeof resultMetaSource === 'object' &&
4970
+ Object.prototype.hasOwnProperty.call(resultMetaSource, 'result')
4971
+ ) {
4972
+ pushUniqueCaptureCandidate(candidates, resultMetaSource.result);
4973
+ }
4974
+ pushUniqueCaptureCandidate(candidates, resultMetaSource);
4975
+ return candidates;
4976
+ }
4977
+
4978
+ async function maybeCaptureDbValueFromCandidatesAsync(
4979
+ params: {
4980
+ target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error';
4981
+ rawValues: any[];
4982
+ previewValue: any;
4983
+ capture: FullValueCaptureContext;
4984
+ },
4985
+ sink: TraceValueBatchEntry[],
4986
+ ): Promise<TraceValueCaptureRef | undefined> {
4987
+ let skippedRef: TraceValueCaptureRef | undefined;
4988
+ for (const rawValue of params.rawValues) {
4989
+ const result = await createCapturedValueEntryAsync({
4990
+ target: params.target,
4991
+ rawValue,
4992
+ previewValue: params.previewValue,
4993
+ capture: params.capture,
4994
+ });
4995
+ if (!result) continue;
4996
+ if (result.entry) {
4997
+ sink.push(result.entry);
4998
+ return result.captureRef;
4999
+ }
5000
+ if (!skippedRef) {
5001
+ skippedRef = result.captureRef;
5002
+ }
5003
+ }
5004
+ return skippedRef;
5005
+ }
5006
+
4958
5007
  // ===================================================================
4959
5008
  // reproMiddleware — unchanged behavior + passive per-request trace
4960
5009
  // ===================================================================
@@ -6241,9 +6290,9 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
6241
6290
  previewValue: queryPreview,
6242
6291
  capture,
6243
6292
  }, dbValues);
6244
- const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
6293
+ const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
6245
6294
  target: 'db.resultMeta',
6246
- rawValue: payload.resultMetaRaw ?? resultMetaSource,
6295
+ rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
6247
6296
  previewValue: resultMetaPreview,
6248
6297
  capture,
6249
6298
  }, dbValues);
@@ -170,10 +170,28 @@ async function main() {
170
170
  event?.returnValue,
171
171
  );
172
172
  assert(largeReturnExit, JSON.stringify(traceEvents));
173
- assert.deepEqual(
174
- largeReturnExit.returnValue,
175
- { __kind: 'omitted', reason: 'inline-size-limit' },
173
+ assert(
174
+ Buffer.byteLength(JSON.stringify(largeReturnExit.returnValue), 'utf8') <= 2048,
175
+ JSON.stringify(largeReturnExit.returnValue),
176
176
  );
177
+ assert.equal(
178
+ largeReturnExit.returnValueCapture?.status,
179
+ 'stored',
180
+ JSON.stringify(largeReturnExit),
181
+ );
182
+ assert(
183
+ typeof largeReturnExit.returnValueCapture?.valueId === 'string' &&
184
+ largeReturnExit.returnValueCapture.valueId.length > 0,
185
+ JSON.stringify(largeReturnExit),
186
+ );
187
+ const traceValues = eventsOf(capturedBodies, 'trace_batch')
188
+ .flatMap((event) => Array.isArray(event?.payload?.traceValues) ? event.payload.traceValues : []);
189
+ const capturedReturnValue = traceValues.find((entry) =>
190
+ entry?.id === largeReturnExit.returnValueCapture.valueId &&
191
+ entry?.target === 'trace.returnValue'
192
+ );
193
+ assert(capturedReturnValue, JSON.stringify(traceValues));
194
+ assert.equal(capturedReturnValue.value?.rows?.length, 400, JSON.stringify(capturedReturnValue));
177
195
 
178
196
  console.log('client performance regression guards OK');
179
197
  } finally {
@@ -133,12 +133,24 @@ function execPre(hooks, name, context) {
133
133
  });
134
134
  }
135
135
 
136
+ function execPost(hooks, name, context, args = []) {
137
+ return new Promise((resolve, reject) => {
138
+ hooks.execPost(name, context, args, {}, (error) => (error ? reject(error) : resolve()));
139
+ });
140
+ }
141
+
136
142
  function dbChanges(events) {
137
143
  return events
138
144
  .filter((event) => event.event_type === 'db_change')
139
145
  .flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
140
146
  }
141
147
 
148
+ function dbValueEntries(events) {
149
+ return events
150
+ .filter((event) => event.event_type === 'db_change')
151
+ .flatMap((event) => Array.isArray(event.payload?.dbValues) ? event.payload.dbValues : []);
152
+ }
153
+
142
154
  function requestCaptureManifests(events) {
143
155
  return events
144
156
  .filter((event) => event.event_type === 'request_capture_manifest')
@@ -146,6 +158,81 @@ function requestCaptureManifests(events) {
146
158
  .filter(Boolean);
147
159
  }
148
160
 
161
+ function makeNotificationModuleDocument() {
162
+ const notificationPreferences = Array.from({ length: 24 }, (_, index) => ({
163
+ trigger: 'SubjectScreened',
164
+ enabled: true,
165
+ channels: [
166
+ {
167
+ type: 'email',
168
+ template: {
169
+ subject: `Participant Screening ${index}`,
170
+ html: `<p>Screening notification ${index}</p>`,
171
+ },
172
+ recipients: {
173
+ to: ['noreply@korioclinical.com'],
174
+ cc: [
175
+ `pobrien+dev-conf-csm${index}@korioclinical.com`,
176
+ `mnebot+site-monitor${index}@korioclinical.com`,
177
+ ],
178
+ bcc: [`audit-${index}@korioclinical.com`],
179
+ },
180
+ },
181
+ ],
182
+ }));
183
+ const notificationTrigger = Array.from({ length: 24 }, (_, index) => ({
184
+ active: true,
185
+ event: 'SubjectScreened',
186
+ visitType: 'Screening',
187
+ subjectType: 'Participant',
188
+ rules: [
189
+ {
190
+ condition: {
191
+ path: 'subject.subjectStatusOption',
192
+ operator: 'equals',
193
+ value: 'Screened',
194
+ },
195
+ action: {
196
+ module: 'NotificationGeneratorService',
197
+ method: 'SubjectScreened',
198
+ order: index,
199
+ },
200
+ },
201
+ ],
202
+ }));
203
+
204
+ return {
205
+ __t: 'NotificationModule',
206
+ _id: '69e77c77ab541cfac917b37a',
207
+ active: true,
208
+ environment: 'dev-configure-pulmovant',
209
+ notificationPreferences,
210
+ notification_trigger: notificationTrigger,
211
+ protocol: {
212
+ id: '69e77c77ab541cfac917b36b',
213
+ name: 'RVT-2301-301',
214
+ },
215
+ schema_version: 1,
216
+ sponsor_name: 'Pulmovant',
217
+ study_name: 'RVT-2301-301',
218
+ };
219
+ }
220
+
221
+ class NotificationModuleHostDocument {
222
+ constructor(value) {
223
+ this.$__ = {};
224
+ this.value = value;
225
+ }
226
+
227
+ toObject() {
228
+ return JSON.parse(JSON.stringify(this.value));
229
+ }
230
+
231
+ toJSON() {
232
+ return this.toObject();
233
+ }
234
+ }
235
+
149
236
  async function testMongooseSuppressionDoesNotHideExplicitNativeInsertOne() {
150
237
  await withCapturedFetch(async (capturedBodies) => {
151
238
  __reproTestHooks.resetSdkBackgroundQueuesForTest();
@@ -477,6 +564,82 @@ async function testClientScreenNativeMongoInsertOneShapeIsCaptured() {
477
564
  });
478
565
  }
479
566
 
567
+ async function testLargeMongooseFindOneResultStoresFullResultMetaSidecar() {
568
+ await withCapturedFetch(async (capturedBodies) => {
569
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
570
+ const sessionId = 'S_mongoose_large_result_meta';
571
+ const actionId = 'A_mongoose_large_result_meta';
572
+ const cfg = {
573
+ ...makeConfig(sessionId, actionId),
574
+ privacy: { environment: 'dev', captureRaw: true },
575
+ };
576
+
577
+ const schema = new mongoose.Schema({}, { strict: false });
578
+ reproMongoosePlugin(cfg)(schema);
579
+
580
+ const notificationModule = makeNotificationModuleDocument();
581
+ const rawResult = new NotificationModuleHostDocument(notificationModule);
582
+ const queryContext = {
583
+ model: {
584
+ collection: {
585
+ name: 'configStudy',
586
+ },
587
+ },
588
+ getFilter: () => ({
589
+ __t: 'NotificationModule',
590
+ active: true,
591
+ protocol: {
592
+ id: '69e77c77ab541cfac917b36b',
593
+ },
594
+ }),
595
+ getUpdate: () => undefined,
596
+ projection: () => undefined,
597
+ getOptions: () => ({ lean: false }),
598
+ };
599
+
600
+ await runCapturedRequest(cfg, sessionId, actionId, async () => {
601
+ await execPre(schema.s.hooks, 'findOne', queryContext);
602
+ await execPost(schema.s.hooks, 'findOne', queryContext, [rawResult]);
603
+ });
604
+
605
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
606
+ await waitFor(() => dbValueEntries(flattenEvents(capturedBodies)).some((entry) => entry.target === 'db.resultMeta'));
607
+ await drainSdk();
608
+
609
+ const [change] = dbChanges(flattenEvents(capturedBodies));
610
+ const values = dbValueEntries(flattenEvents(capturedBodies));
611
+ const resultMetaValue = values.find((entry) => entry.id === change.resultMetaValueCapture?.valueId);
612
+
613
+ assert.strictEqual(change.type, 'mongo');
614
+ assert.strictEqual(change.collection, 'configStudy');
615
+ assert.strictEqual(change.op, 'findOne');
616
+ assert.strictEqual(change.query.filter.__t, 'NotificationModule');
617
+ assert.strictEqual(change.resultMeta.docsCount, 1);
618
+ assert.deepStrictEqual(change.resultMeta.result.notificationPreferences[0], {
619
+ __kind: 'omitted',
620
+ reason: 'depth>3',
621
+ });
622
+ assert.deepStrictEqual(change.resultMeta.result.notification_trigger[0], {
623
+ __kind: 'omitted',
624
+ reason: 'depth>3',
625
+ });
626
+ assert.strictEqual(change.resultMetaValueCapture.status, 'stored');
627
+ assert.ok(change.resultMetaValueCapture.valueId, 'expected resultMeta value id');
628
+ assert.ok(resultMetaValue, 'expected matching db.resultMeta sidecar');
629
+ assert.strictEqual(resultMetaValue.target, 'db.resultMeta');
630
+ assert.strictEqual(resultMetaValue.value.__t, 'NotificationModule');
631
+ assert.strictEqual(resultMetaValue.value.notificationPreferences.length, 24);
632
+ assert.strictEqual(
633
+ resultMetaValue.value.notificationPreferences[0].channels[0].recipients.cc[0],
634
+ 'pobrien+dev-conf-csm0@korioclinical.com',
635
+ );
636
+ assert.strictEqual(
637
+ resultMetaValue.value.notification_trigger[0].rules[0].action.method,
638
+ 'SubjectScreened',
639
+ );
640
+ });
641
+ }
642
+
480
643
  async function testNativeInsertOneIsCaptured() {
481
644
  await withCapturedFetch(async (capturedBodies) => {
482
645
  __reproTestHooks.resetSdkBackgroundQueuesForTest();
@@ -742,6 +905,7 @@ async function testNativeOperationFailureIsCapturedAndRethrown() {
742
905
  await testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne();
743
906
  await testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport();
744
907
  await testClientScreenNativeMongoInsertOneShapeIsCaptured();
908
+ await testLargeMongooseFindOneResultStoresFullResultMetaSidecar();
745
909
  await testNativeInsertOneIsCaptured();
746
910
  await testCaptureRawNativeInsertOneUsesJsonSafeTransportValues();
747
911
  await testNativeUpdateOneUpsertIsCaptured();