@reproapp/node-sdk 0.0.21 → 0.0.22

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
@@ -843,7 +843,7 @@ const SDK_BACKGROUND_IDLE_DELAY_MS = (() => {
843
843
  const env = Number(process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS);
844
844
  if (Number.isFinite(env) && env >= 0)
845
845
  return env;
846
- return 25;
846
+ return 5000;
847
847
  })();
848
848
  const SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = (() => {
849
849
  const env = Number(process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS);
@@ -852,10 +852,17 @@ const SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = (() => {
852
852
  return 1000;
853
853
  })();
854
854
  const SDK_BACKGROUND_MAX_DEFER_MS = (() => {
855
- const env = Number(process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS);
855
+ const raw = String(process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS ?? '').trim().toLowerCase();
856
+ if (!raw) {
857
+ return Number.POSITIVE_INFINITY;
858
+ }
859
+ if (raw === 'off' || raw === 'never' || raw === 'infinite' || raw === 'infinity' || raw === '-1') {
860
+ return Number.POSITIVE_INFINITY;
861
+ }
862
+ const env = Number(raw);
856
863
  if (Number.isFinite(env) && env >= 0)
857
864
  return Math.trunc(env);
858
- return 2000;
865
+ return Number.POSITIVE_INFINITY;
859
866
  })();
860
867
  const SDK_BACKGROUND_WORK_TIMEOUT_MS = (() => {
861
868
  const env = Number(process.env.REPRO_SDK_BACKGROUND_WORK_TIMEOUT_MS);
@@ -915,7 +922,9 @@ function noteClientActivity(now = Date.now()) {
915
922
  }
916
923
  function getSdkBackgroundDelayMs(now = Date.now()) {
917
924
  if (hasActiveClientRequests()) {
918
- if (oldestActiveClientRequestAt !== null && SDK_BACKGROUND_MAX_DEFER_MS >= 0) {
925
+ if (oldestActiveClientRequestAt !== null &&
926
+ Number.isFinite(SDK_BACKGROUND_MAX_DEFER_MS) &&
927
+ SDK_BACKGROUND_MAX_DEFER_MS >= 0) {
919
928
  const activeForMs = now - oldestActiveClientRequestAt;
920
929
  if (activeForMs >= SDK_BACKGROUND_MAX_DEFER_MS)
921
930
  return 0;
@@ -2259,54 +2268,69 @@ async function materializePendingTraceEvents(events, params) {
2259
2268
  parentSpanId: evt.parentSpanId ?? null,
2260
2269
  };
2261
2270
  if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
2262
- const argsMaterialized = await materializeTracePrivacyValueAsync('trace.args', sanitizeTraceArgsForPrivacy(pending.argsRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2263
- evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
2264
- const argsValueCapture = await maybeCaptureTraceValueAsync({
2265
- target: 'trace.args',
2266
- rawValue: pending.argsRaw,
2267
- previewValue: evt.args,
2268
- capture: captureContext,
2269
- metadata: traceValueMetadata,
2270
- });
2271
- if (argsValueCapture?.entry) {
2272
- attachTraceValueEntry(evt, argsValueCapture.entry);
2271
+ if (!__FULL_VALUE_CAPTURE_ENABLED && traceInlineValueExceedsLimit(pending.argsRaw)) {
2272
+ evt.args = makeOmittedValue('inline-size-limit');
2273
2273
  }
2274
- if (argsValueCapture?.captureRef) {
2275
- evt.argsValueCapture = argsValueCapture.captureRef;
2274
+ else {
2275
+ const argsMaterialized = await materializeTracePrivacyValueAsync('trace.args', sanitizeTraceArgsForPrivacy(pending.argsRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2276
+ evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
2277
+ const argsValueCapture = await maybeCaptureTraceValueAsync({
2278
+ target: 'trace.args',
2279
+ rawValue: pending.argsRaw,
2280
+ previewValue: evt.args,
2281
+ capture: captureContext,
2282
+ metadata: traceValueMetadata,
2283
+ });
2284
+ if (argsValueCapture?.entry) {
2285
+ attachTraceValueEntry(evt, argsValueCapture.entry);
2286
+ }
2287
+ if (argsValueCapture?.captureRef) {
2288
+ evt.argsValueCapture = argsValueCapture.captureRef;
2289
+ }
2276
2290
  }
2277
2291
  }
2278
2292
  if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
2279
- const returnValueMaterialized = await materializeTracePrivacyValueAsync('trace.returnValue', sanitizeTraceValueForPrivacy(pending.returnValueRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2280
- evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
2281
- const returnValueCapture = await maybeCaptureTraceValueAsync({
2282
- target: 'trace.returnValue',
2283
- rawValue: pending.returnValueRaw,
2284
- previewValue: evt.returnValue,
2285
- capture: captureContext,
2286
- metadata: traceValueMetadata,
2287
- });
2288
- if (returnValueCapture?.entry) {
2289
- attachTraceValueEntry(evt, returnValueCapture.entry);
2293
+ if (!__FULL_VALUE_CAPTURE_ENABLED && traceInlineValueExceedsLimit(pending.returnValueRaw)) {
2294
+ evt.returnValue = makeOmittedValue('inline-size-limit');
2290
2295
  }
2291
- if (returnValueCapture?.captureRef) {
2292
- evt.returnValueCapture = returnValueCapture.captureRef;
2296
+ else {
2297
+ const returnValueMaterialized = await materializeTracePrivacyValueAsync('trace.returnValue', sanitizeTraceValueForPrivacy(pending.returnValueRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2298
+ evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
2299
+ const returnValueCapture = await maybeCaptureTraceValueAsync({
2300
+ target: 'trace.returnValue',
2301
+ rawValue: pending.returnValueRaw,
2302
+ previewValue: evt.returnValue,
2303
+ capture: captureContext,
2304
+ metadata: traceValueMetadata,
2305
+ });
2306
+ if (returnValueCapture?.entry) {
2307
+ attachTraceValueEntry(evt, returnValueCapture.entry);
2308
+ }
2309
+ if (returnValueCapture?.captureRef) {
2310
+ evt.returnValueCapture = returnValueCapture.captureRef;
2311
+ }
2293
2312
  }
2294
2313
  }
2295
2314
  if (pending.errorRaw !== undefined) {
2296
- const errorMaterialized = await materializeTracePrivacyValueAsync('trace.error', sanitizeTraceValueForPrivacy(pending.errorRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2297
- evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
2298
- const errorValueCapture = await maybeCaptureTraceValueAsync({
2299
- target: 'trace.error',
2300
- rawValue: pending.errorRaw,
2301
- previewValue: evt.error,
2302
- capture: captureContext,
2303
- metadata: traceValueMetadata,
2304
- });
2305
- if (errorValueCapture?.entry) {
2306
- attachTraceValueEntry(evt, errorValueCapture.entry);
2315
+ if (!__FULL_VALUE_CAPTURE_ENABLED && traceInlineValueExceedsLimit(pending.errorRaw)) {
2316
+ evt.error = makeOmittedValue('inline-size-limit');
2307
2317
  }
2308
- if (errorValueCapture?.captureRef) {
2309
- evt.errorValueCapture = errorValueCapture.captureRef;
2318
+ else {
2319
+ const errorMaterialized = await materializeTracePrivacyValueAsync('trace.error', sanitizeTraceValueForPrivacy(pending.errorRaw), params.cfg, params.maskReq, candidate, params.masking, activePrivacy);
2320
+ evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
2321
+ const errorValueCapture = await maybeCaptureTraceValueAsync({
2322
+ target: 'trace.error',
2323
+ rawValue: pending.errorRaw,
2324
+ previewValue: evt.error,
2325
+ capture: captureContext,
2326
+ metadata: traceValueMetadata,
2327
+ });
2328
+ if (errorValueCapture?.entry) {
2329
+ attachTraceValueEntry(evt, errorValueCapture.entry);
2330
+ }
2331
+ if (errorValueCapture?.captureRef) {
2332
+ evt.errorValueCapture = errorValueCapture.captureRef;
2333
+ }
2310
2334
  }
2311
2335
  }
2312
2336
  }
@@ -2808,7 +2832,22 @@ function traceInlineValueExceedsLimit(value) {
2808
2832
  return boundedSerializedTraceValueLength(value, TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS).exceeded;
2809
2833
  }
2810
2834
  function limitInlinePrivacyValue(target, value) {
2811
- void target;
2835
+ if (value === undefined) {
2836
+ return { value };
2837
+ }
2838
+ const serialized = boundedSerializedTraceValueLength(value, TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS);
2839
+ if (serialized.exceeded) {
2840
+ return {
2841
+ value: undefined,
2842
+ skipped: {
2843
+ status: 'skipped',
2844
+ reason: 'inline-size-limit',
2845
+ target,
2846
+ serializedLength: Math.max(serialized.length, TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS + 1),
2847
+ limit: TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS,
2848
+ },
2849
+ };
2850
+ }
2812
2851
  return { value };
2813
2852
  }
2814
2853
  function limitRawInlinePrivacyValue(target, value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "build": "tsc -p tsconfig.json",
13
13
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
14
14
  "prepublishOnly": "npm run build",
15
- "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js",
15
+ "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js",
16
16
  "test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js"
17
17
  },
18
18
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -1239,7 +1239,7 @@ function nextRequestId(base: number | string): string {
1239
1239
  const SDK_BACKGROUND_IDLE_DELAY_MS = (() => {
1240
1240
  const env = Number(process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS);
1241
1241
  if (Number.isFinite(env) && env >= 0) return env;
1242
- return 25;
1242
+ return 5000;
1243
1243
  })();
1244
1244
 
1245
1245
  const SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = (() => {
@@ -1249,9 +1249,16 @@ const SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = (() => {
1249
1249
  })();
1250
1250
 
1251
1251
  const SDK_BACKGROUND_MAX_DEFER_MS = (() => {
1252
- const env = Number(process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS);
1252
+ const raw = String(process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS ?? '').trim().toLowerCase();
1253
+ if (!raw) {
1254
+ return Number.POSITIVE_INFINITY;
1255
+ }
1256
+ if (raw === 'off' || raw === 'never' || raw === 'infinite' || raw === 'infinity' || raw === '-1') {
1257
+ return Number.POSITIVE_INFINITY;
1258
+ }
1259
+ const env = Number(raw);
1253
1260
  if (Number.isFinite(env) && env >= 0) return Math.trunc(env);
1254
- return 2000;
1261
+ return Number.POSITIVE_INFINITY;
1255
1262
  })();
1256
1263
 
1257
1264
  const SDK_BACKGROUND_WORK_TIMEOUT_MS = (() => {
@@ -1319,7 +1326,11 @@ function noteClientActivity(now = Date.now()): void {
1319
1326
 
1320
1327
  function getSdkBackgroundDelayMs(now = Date.now()): number {
1321
1328
  if (hasActiveClientRequests()) {
1322
- if (oldestActiveClientRequestAt !== null && SDK_BACKGROUND_MAX_DEFER_MS >= 0) {
1329
+ if (
1330
+ oldestActiveClientRequestAt !== null &&
1331
+ Number.isFinite(SDK_BACKGROUND_MAX_DEFER_MS) &&
1332
+ SDK_BACKGROUND_MAX_DEFER_MS >= 0
1333
+ ) {
1323
1334
  const activeForMs = now - oldestActiveClientRequestAt;
1324
1335
  if (activeForMs >= SDK_BACKGROUND_MAX_DEFER_MS) return 0;
1325
1336
  return Math.min(SDK_BACKGROUND_IDLE_DELAY_MS, Math.max(0, SDK_BACKGROUND_MAX_DEFER_MS - activeForMs));
@@ -2704,78 +2715,90 @@ async function materializePendingTraceEvents(
2704
2715
  };
2705
2716
 
2706
2717
  if (!omitJsonBuiltinValues && pending.argsRaw !== undefined) {
2707
- const argsMaterialized = await materializeTracePrivacyValueAsync(
2708
- 'trace.args',
2709
- sanitizeTraceArgsForPrivacy(pending.argsRaw),
2710
- params.cfg,
2711
- params.maskReq,
2712
- candidate,
2713
- params.masking,
2714
- activePrivacy,
2715
- );
2716
- evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
2717
- const argsValueCapture = await maybeCaptureTraceValueAsync({
2718
- target: 'trace.args',
2719
- rawValue: pending.argsRaw,
2720
- previewValue: evt.args,
2721
- capture: captureContext,
2722
- metadata: traceValueMetadata,
2723
- });
2724
- if (argsValueCapture?.entry) {
2725
- attachTraceValueEntry(evt, argsValueCapture.entry);
2726
- }
2727
- if (argsValueCapture?.captureRef) {
2728
- evt.argsValueCapture = argsValueCapture.captureRef;
2718
+ if (!__FULL_VALUE_CAPTURE_ENABLED && traceInlineValueExceedsLimit(pending.argsRaw)) {
2719
+ evt.args = makeOmittedValue('inline-size-limit');
2720
+ } else {
2721
+ const argsMaterialized = await materializeTracePrivacyValueAsync(
2722
+ 'trace.args',
2723
+ sanitizeTraceArgsForPrivacy(pending.argsRaw),
2724
+ params.cfg,
2725
+ params.maskReq,
2726
+ candidate,
2727
+ params.masking,
2728
+ activePrivacy,
2729
+ );
2730
+ evt.args = limitMaterializedTraceInlineValue(argsMaterialized);
2731
+ const argsValueCapture = await maybeCaptureTraceValueAsync({
2732
+ target: 'trace.args',
2733
+ rawValue: pending.argsRaw,
2734
+ previewValue: evt.args,
2735
+ capture: captureContext,
2736
+ metadata: traceValueMetadata,
2737
+ });
2738
+ if (argsValueCapture?.entry) {
2739
+ attachTraceValueEntry(evt, argsValueCapture.entry);
2740
+ }
2741
+ if (argsValueCapture?.captureRef) {
2742
+ evt.argsValueCapture = argsValueCapture.captureRef;
2743
+ }
2729
2744
  }
2730
2745
  }
2731
2746
  if (!omitJsonBuiltinValues && pending.returnValueRaw !== undefined) {
2732
- const returnValueMaterialized = await materializeTracePrivacyValueAsync(
2733
- 'trace.returnValue',
2734
- sanitizeTraceValueForPrivacy(pending.returnValueRaw),
2735
- params.cfg,
2736
- params.maskReq,
2737
- candidate,
2738
- params.masking,
2739
- activePrivacy,
2740
- );
2741
- evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
2742
- const returnValueCapture = await maybeCaptureTraceValueAsync({
2743
- target: 'trace.returnValue',
2744
- rawValue: pending.returnValueRaw,
2745
- previewValue: evt.returnValue,
2746
- capture: captureContext,
2747
- metadata: traceValueMetadata,
2748
- });
2749
- if (returnValueCapture?.entry) {
2750
- attachTraceValueEntry(evt, returnValueCapture.entry);
2751
- }
2752
- if (returnValueCapture?.captureRef) {
2753
- evt.returnValueCapture = returnValueCapture.captureRef;
2747
+ if (!__FULL_VALUE_CAPTURE_ENABLED && traceInlineValueExceedsLimit(pending.returnValueRaw)) {
2748
+ evt.returnValue = makeOmittedValue('inline-size-limit');
2749
+ } else {
2750
+ const returnValueMaterialized = await materializeTracePrivacyValueAsync(
2751
+ 'trace.returnValue',
2752
+ sanitizeTraceValueForPrivacy(pending.returnValueRaw),
2753
+ params.cfg,
2754
+ params.maskReq,
2755
+ candidate,
2756
+ params.masking,
2757
+ activePrivacy,
2758
+ );
2759
+ evt.returnValue = limitMaterializedTraceInlineValue(returnValueMaterialized);
2760
+ const returnValueCapture = await maybeCaptureTraceValueAsync({
2761
+ target: 'trace.returnValue',
2762
+ rawValue: pending.returnValueRaw,
2763
+ previewValue: evt.returnValue,
2764
+ capture: captureContext,
2765
+ metadata: traceValueMetadata,
2766
+ });
2767
+ if (returnValueCapture?.entry) {
2768
+ attachTraceValueEntry(evt, returnValueCapture.entry);
2769
+ }
2770
+ if (returnValueCapture?.captureRef) {
2771
+ evt.returnValueCapture = returnValueCapture.captureRef;
2772
+ }
2754
2773
  }
2755
2774
  }
2756
2775
  if (pending.errorRaw !== undefined) {
2757
- const errorMaterialized = await materializeTracePrivacyValueAsync(
2758
- 'trace.error',
2759
- sanitizeTraceValueForPrivacy(pending.errorRaw),
2760
- params.cfg,
2761
- params.maskReq,
2762
- candidate,
2763
- params.masking,
2764
- activePrivacy,
2765
- );
2766
- evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
2767
- const errorValueCapture = await maybeCaptureTraceValueAsync({
2768
- target: 'trace.error',
2769
- rawValue: pending.errorRaw,
2770
- previewValue: evt.error,
2771
- capture: captureContext,
2772
- metadata: traceValueMetadata,
2773
- });
2774
- if (errorValueCapture?.entry) {
2775
- attachTraceValueEntry(evt, errorValueCapture.entry);
2776
- }
2777
- if (errorValueCapture?.captureRef) {
2778
- evt.errorValueCapture = errorValueCapture.captureRef;
2776
+ if (!__FULL_VALUE_CAPTURE_ENABLED && traceInlineValueExceedsLimit(pending.errorRaw)) {
2777
+ evt.error = makeOmittedValue('inline-size-limit');
2778
+ } else {
2779
+ const errorMaterialized = await materializeTracePrivacyValueAsync(
2780
+ 'trace.error',
2781
+ sanitizeTraceValueForPrivacy(pending.errorRaw),
2782
+ params.cfg,
2783
+ params.maskReq,
2784
+ candidate,
2785
+ params.masking,
2786
+ activePrivacy,
2787
+ );
2788
+ evt.error = limitMaterializedTraceInlineValue(errorMaterialized);
2789
+ const errorValueCapture = await maybeCaptureTraceValueAsync({
2790
+ target: 'trace.error',
2791
+ rawValue: pending.errorRaw,
2792
+ previewValue: evt.error,
2793
+ capture: captureContext,
2794
+ metadata: traceValueMetadata,
2795
+ });
2796
+ if (errorValueCapture?.entry) {
2797
+ attachTraceValueEntry(evt, errorValueCapture.entry);
2798
+ }
2799
+ if (errorValueCapture?.captureRef) {
2800
+ evt.errorValueCapture = errorValueCapture.captureRef;
2801
+ }
2779
2802
  }
2780
2803
  }
2781
2804
  }
@@ -3405,7 +3428,24 @@ function limitInlinePrivacyValue(
3405
3428
  target: RuntimePrivacySurface,
3406
3429
  value: any,
3407
3430
  ): InlinePrivacyMaterializationResult {
3408
- void target;
3431
+ if (value === undefined) {
3432
+ return { value };
3433
+ }
3434
+
3435
+ const serialized = boundedSerializedTraceValueLength(value, TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS);
3436
+ if (serialized.exceeded) {
3437
+ return {
3438
+ value: undefined,
3439
+ skipped: {
3440
+ status: 'skipped',
3441
+ reason: 'inline-size-limit',
3442
+ target,
3443
+ serializedLength: Math.max(serialized.length, TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS + 1),
3444
+ limit: TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS,
3445
+ },
3446
+ };
3447
+ }
3448
+
3409
3449
  return { value };
3410
3450
  }
3411
3451
 
@@ -1,6 +1,7 @@
1
1
  process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
2
2
  process.env.TRACE_IDLE_FLUSH_MS = '25';
3
3
  process.env.SESSION_DRAIN_TIMEOUT_MS = '0';
4
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
4
5
 
5
6
  const assert = require('assert');
6
7
  const { EventEmitter } = require('events');
@@ -0,0 +1,189 @@
1
+ delete process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS;
2
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
3
+ process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = '0';
4
+ process.env.REPRO_SDK_TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = '2048';
5
+ process.env.TRACE_IDLE_FLUSH_MS = '10';
6
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
7
+
8
+ const assert = require('node:assert/strict');
9
+ const { EventEmitter } = require('node:events');
10
+ const { initReproTracing, reproMiddleware, __reproTestHooks } = require('../dist');
11
+ const { flushIngestQueue } = require('../dist/ingest/client');
12
+
13
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14
+
15
+ async function waitFor(predicate, timeoutMs = 2500) {
16
+ const deadline = Date.now() + timeoutMs;
17
+ while (Date.now() < deadline) {
18
+ if (predicate()) return;
19
+ await sleep(20);
20
+ }
21
+ assert(predicate(), 'timed out waiting for expected condition');
22
+ }
23
+
24
+ function makeReqRes(sessionId, actionId, path = '/captured') {
25
+ const req = new EventEmitter();
26
+ req.method = 'GET';
27
+ req.url = path;
28
+ req.headers = sessionId && actionId
29
+ ? {
30
+ 'x-bug-session-id': sessionId,
31
+ 'x-bug-action-id': actionId,
32
+ 'x-bug-request-start': String(Date.now()),
33
+ }
34
+ : {};
35
+ req.body = undefined;
36
+ req.params = {};
37
+ req.query = {};
38
+
39
+ const res = new EventEmitter();
40
+ res.statusCode = 200;
41
+ res.getHeader = () => 'application/json';
42
+ res.setHeader = () => {};
43
+ res.json = function (body) { this.body = body; this.emit('finish'); return body; };
44
+ res.send = function (body) { this.body = body; this.emit('finish'); return body; };
45
+ res.write = () => true;
46
+ res.end = () => { res.emit('finish'); return true; };
47
+
48
+ return { req, res };
49
+ }
50
+
51
+ async function invokeMiddleware(middleware, req, res, handler) {
52
+ await new Promise((resolve, reject) => {
53
+ void middleware(req, res, async (err) => {
54
+ if (err) {
55
+ reject(err);
56
+ return;
57
+ }
58
+ try {
59
+ await handler();
60
+ resolve();
61
+ } catch (error) {
62
+ reject(error);
63
+ }
64
+ });
65
+ });
66
+ }
67
+
68
+ function flattenEvents(posts) {
69
+ return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
70
+ }
71
+
72
+ function eventsOf(posts, eventType) {
73
+ return flattenEvents(posts).filter((event) => event?.event_type === eventType);
74
+ }
75
+
76
+ function largePayload() {
77
+ return {
78
+ rows: Array.from({ length: 400 }, (_, index) => ({
79
+ index,
80
+ depot: `depot-${index}`,
81
+ description: `large response row ${index} `.repeat(4),
82
+ })),
83
+ };
84
+ }
85
+
86
+ async function main() {
87
+ const capturedBodies = [];
88
+ const originalFetch = global.fetch;
89
+ global.fetch = async (_url, init = {}) => {
90
+ capturedBodies.push(JSON.parse(String(init.body || '{}')));
91
+ return {
92
+ ok: true,
93
+ status: 202,
94
+ json: async () => ({ ok: true }),
95
+ text: async () => '{"ok":true}',
96
+ };
97
+ };
98
+
99
+ try {
100
+ const cfg = {
101
+ tenantId: 'TENANT_test',
102
+ appId: 'APP_test',
103
+ appSecret: 'secret',
104
+ captureHeaders: false,
105
+ privacy: { environment: 'dev' },
106
+ ingestBase: 'http://127.0.0.1:65535',
107
+ };
108
+ const middleware = reproMiddleware(cfg);
109
+
110
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
111
+ const held = makeReqRes(null, null, '/held-open');
112
+ await invokeMiddleware(middleware, held.req, held.res, async () => {
113
+ // Keep one app endpoint open. Background capture from other requests must not run.
114
+ });
115
+
116
+ const tagged = makeReqRes('S_perf_regression', 'A_perf_regression');
117
+ await invokeMiddleware(middleware, tagged.req, tagged.res, async () => {
118
+ tagged.res.json({ ok: true });
119
+ });
120
+
121
+ await sleep(2200);
122
+ assert.equal(
123
+ eventsOf(capturedBodies, 'backend_request').length,
124
+ 0,
125
+ `background request capture ran while a client endpoint was still active: ${JSON.stringify(capturedBodies)}`,
126
+ );
127
+
128
+ held.res.emit('finish');
129
+ await waitFor(() => eventsOf(capturedBodies, 'backend_request').length === 1);
130
+
131
+ capturedBodies.length = 0;
132
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
133
+ initReproTracing({ instrument: false, logFunctionCalls: false });
134
+
135
+ function largeTraceReturn() {
136
+ return largePayload();
137
+ }
138
+
139
+ const large = makeReqRes('S_perf_large_payload', 'A_perf_large_payload', '/large-payload');
140
+ await invokeMiddleware(middleware, large.req, large.res, async () => {
141
+ const traced = global.__repro_call(
142
+ largeTraceReturn,
143
+ null,
144
+ [],
145
+ 'app',
146
+ 1,
147
+ 'largeTraceReturn',
148
+ false,
149
+ );
150
+ large.res.json(traced);
151
+ });
152
+
153
+ await waitFor(() => eventsOf(capturedBodies, 'backend_request').length === 1);
154
+ await waitFor(() => eventsOf(capturedBodies, 'trace_batch').length >= 1);
155
+
156
+ const backendRequest = eventsOf(capturedBodies, 'backend_request')[0]?.payload?.request;
157
+ assert(backendRequest, JSON.stringify(capturedBodies));
158
+ assert.equal(backendRequest.respBody, undefined, JSON.stringify(backendRequest));
159
+ assert.equal(
160
+ backendRequest.respBodyMaterialization?.reason,
161
+ 'inline-size-limit',
162
+ JSON.stringify(backendRequest),
163
+ );
164
+
165
+ const traceEvents = eventsOf(capturedBodies, 'trace_batch')
166
+ .flatMap((event) => Array.isArray(event?.payload?.trace) ? event.payload.trace : []);
167
+ const largeReturnExit = traceEvents.find((event) =>
168
+ event?.fn === 'largeTraceReturn' &&
169
+ event?.type === 'exit' &&
170
+ event?.returnValue,
171
+ );
172
+ assert(largeReturnExit, JSON.stringify(traceEvents));
173
+ assert.deepEqual(
174
+ largeReturnExit.returnValue,
175
+ { __kind: 'omitted', reason: 'inline-size-limit' },
176
+ );
177
+
178
+ console.log('client performance regression guards OK');
179
+ } finally {
180
+ await flushIngestQueue();
181
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
182
+ global.fetch = originalFetch;
183
+ }
184
+ }
185
+
186
+ main().catch((error) => {
187
+ console.error(error);
188
+ process.exit(1);
189
+ });
@@ -2,6 +2,7 @@
2
2
  process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
3
3
  process.env.TRACE_IDLE_FLUSH_MS = '25';
4
4
  process.env.SESSION_DRAIN_TIMEOUT_MS = '0';
5
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
5
6
 
6
7
  const assert = require('assert');
7
8
  const { EventEmitter } = require('events');
@@ -1,6 +1,8 @@
1
1
  const assert = require('assert');
2
2
  const http = require('http');
3
3
 
4
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
5
+
4
6
  const originalFetch = global.fetch;
5
7
  const capturedBodies = [];
6
8
 
@@ -1,5 +1,6 @@
1
1
  const assert = require('assert');
2
2
  process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '50';
3
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
3
4
 
4
5
  const { EventEmitter } = require('events');
5
6
  const { normalizeRuntimePrivacyPolicy } = require('../dist/privacy');
@@ -306,7 +307,7 @@ async function testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest() {
306
307
  }
307
308
  }
308
309
 
309
- async function testLargeHttpResponseBodyIsCapturedWithoutDroppingRequest() {
310
+ async function testLargeHttpResponseBodyKeepsRequestMetadataWithoutInlineBody() {
310
311
  const marker = 'OVERSIZED_HTTP_RESPONSE_MARKER';
311
312
  const capturedBodies = [];
312
313
  const originalFetch = global.fetch;
@@ -341,13 +342,15 @@ async function testLargeHttpResponseBodyIsCapturedWithoutDroppingRequest() {
341
342
  await waitFor(() => capturedBodies.some((body) => {
342
343
  const text = JSON.stringify(body);
343
344
  return text.includes('"event_type":"backend_request"') &&
344
- text.includes(marker);
345
+ text.includes('"reason":"inline-size-limit"');
345
346
  }));
346
347
  const text = JSON.stringify(capturedBodies);
347
- assert(text.includes(marker), text);
348
- assert(!text.includes('respBodyMaterialization'), text);
349
- assert(!text.includes('inline_value_omitted_too_large'), text);
350
- assertNoLargeJsonPreviewArtifacts(capturedBodies);
348
+ assert(!text.includes(marker), text);
349
+ assert(text.includes('respBodyMaterialization'), text);
350
+ assert(text.includes('"target":"response.body"'), text);
351
+ assert(!text.includes('__type":"json-string'), text);
352
+ assert(!text.includes('__truncatedKeys'), text);
353
+ assert(!text.includes('more chars'), text);
351
354
  } finally {
352
355
  await flushIngestQueue();
353
356
  global.fetch = originalFetch;
@@ -743,7 +746,7 @@ async function main() {
743
746
  await testKafkaConsumerTraceUsesConsumeTraceId();
744
747
  await testKafkaProducerCapturesFullPublishedMessageValue();
745
748
  await testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest();
746
- await testLargeHttpResponseBodyIsCapturedWithoutDroppingRequest();
749
+ await testLargeHttpResponseBodyKeepsRequestMetadataWithoutInlineBody();
747
750
  await testJsonBuiltinTracePayloadsAreOmitted();
748
751
  await testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
749
752
  await testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
@@ -1,4 +1,5 @@
1
1
  process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '50';
2
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
2
3
 
3
4
  const assert = require('assert');
4
5
  const { EventEmitter } = require('events');