@reproapp/node-sdk 0.0.54 → 0.0.56

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.yieldCaptureWork = void 0;
3
+ exports.yieldCaptureWork = yieldCaptureWork;
4
4
  // Promise-only recursion never yields to HTTP sockets. Share a small time slice
5
5
  // across asynchronous capture walkers, including the worker-unavailable fallback.
6
6
  let deadline = 0;
@@ -22,4 +22,3 @@ function yieldCaptureWork() {
22
22
  }));
23
23
  return pendingYield;
24
24
  }
25
- exports.yieldCaptureWork = yieldCaptureWork;
package/dist/index.d.ts CHANGED
@@ -1,28 +1,3 @@
1
- /// <reference types="mongoose/types/aggregate" />
2
- /// <reference types="mongoose/types/callback" />
3
- /// <reference types="mongoose/types/collection" />
4
- /// <reference types="mongoose/types/connection" />
5
- /// <reference types="mongoose/types/cursor" />
6
- /// <reference types="mongoose/types/document" />
7
- /// <reference types="mongoose/types/error" />
8
- /// <reference types="mongoose/types/expressions" />
9
- /// <reference types="mongoose/types/helpers" />
10
- /// <reference types="mongoose/types/middlewares" />
11
- /// <reference types="mongoose/types/indexes" />
12
- /// <reference types="mongoose/types/models" />
13
- /// <reference types="mongoose/types/mongooseoptions" />
14
- /// <reference types="mongoose/types/pipelinestage" />
15
- /// <reference types="mongoose/types/populate" />
16
- /// <reference types="mongoose/types/query" />
17
- /// <reference types="mongoose/types/schemaoptions" />
18
- /// <reference types="mongoose/types/session" />
19
- /// <reference types="mongoose/types/types" />
20
- /// <reference types="mongoose/types/utility" />
21
- /// <reference types="mongoose/types/validation" />
22
- /// <reference types="mongoose/types/virtuals" />
23
- /// <reference types="mongoose/types/schematypes" />
24
- /// <reference types="mongoose/types/inferschematype" />
25
- /// <reference types="mongoose/types/inferrawdoctype" />
26
1
  import type { Request, Response, NextFunction } from 'express';
27
2
  import type { Schema } from 'mongoose';
28
3
  import { type IngestClientConfig } from './ingest/client';
@@ -485,7 +460,7 @@ export declare const __reproTestHooks: {
485
460
  shareRuntimePrivacyStateForTest: typeof shareRuntimePrivacyState;
486
461
  setFullValueCaptureEnabledForTest(enabled: boolean): void;
487
462
  scheduleSdkBackgroundWorkForTest(work: () => Promise<void> | void, options?: {
488
- priority?: 'high' | 'normal';
463
+ priority?: "high" | "normal";
489
464
  }): void;
490
465
  drainSdkBackgroundQueueForTest(): Promise<void>;
491
466
  resetSdkBackgroundQueuesForTest(): void;
package/dist/index.js CHANGED
@@ -1,6 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.__reproTestHooks = exports.initRepro = exports.patchSendgridMail = exports.patchMongoDb = exports.patchPostgreSql = exports.patchRedis = exports.patchAmqplib = exports.patchKafkaJs = exports.reproMongoosePlugin = exports.reproMiddleware = exports.__materializePendingTraceEventsForWorker = exports.isReproTracingEnabled = exports.initReproTracing = exports.disableReproTraceLogs = exports.enableReproTraceLogs = exports.setReproTraceLogsEnabled = exports.setDisabledTraceFiles = exports.setDisabledFunctionTypes = exports.setDisabledFunctionTraces = void 0;
3
+ exports.__reproTestHooks = void 0;
4
+ exports.setDisabledFunctionTraces = setDisabledFunctionTraces;
5
+ exports.setDisabledFunctionTypes = setDisabledFunctionTypes;
6
+ exports.setDisabledTraceFiles = setDisabledTraceFiles;
7
+ exports.setReproTraceLogsEnabled = setReproTraceLogsEnabled;
8
+ exports.enableReproTraceLogs = enableReproTraceLogs;
9
+ exports.disableReproTraceLogs = disableReproTraceLogs;
10
+ exports.initReproTracing = initReproTracing;
11
+ exports.isReproTracingEnabled = isReproTracingEnabled;
12
+ exports.__materializePendingTraceEventsForWorker = __materializePendingTraceEventsForWorker;
13
+ exports.reproMiddleware = reproMiddleware;
14
+ exports.reproMongoosePlugin = reproMongoosePlugin;
15
+ exports.patchKafkaJs = patchKafkaJs;
16
+ exports.patchAmqplib = patchAmqplib;
17
+ exports.patchRedis = patchRedis;
18
+ exports.patchPostgreSql = patchPostgreSql;
19
+ exports.patchMongoDb = patchMongoDb;
20
+ exports.patchSendgridMail = patchSendgridMail;
21
+ exports.initRepro = initRepro;
4
22
  const mongoose = require("mongoose");
5
23
  const async_hooks_1 = require("async_hooks");
6
24
  const crypto_1 = require("crypto");
@@ -514,11 +532,9 @@ function setDisabledFunctionTraces(rules) {
514
532
  }
515
533
  refreshDisabledFunctionTraceRules();
516
534
  }
517
- exports.setDisabledFunctionTraces = setDisabledFunctionTraces;
518
535
  function setDisabledFunctionTypes(patterns) {
519
536
  disabledFunctionTypePatterns = normalizePatternArray(patterns);
520
537
  }
521
- exports.setDisabledFunctionTypes = setDisabledFunctionTypes;
522
538
  function flattenTraceFilePatterns(config) {
523
539
  if (config === null || config === undefined)
524
540
  return [];
@@ -541,7 +557,6 @@ function setDisabledTraceFiles(config) {
541
557
  const items = Array.isArray(config) ? config : [config];
542
558
  disabledTraceFilePatterns = items.flatMap(entry => flattenTraceFilePatterns(entry)).filter(Boolean);
543
559
  }
544
- exports.setDisabledTraceFiles = setDisabledTraceFiles;
545
560
  function applyTraceLogPreference(tracer) {
546
561
  if (__TRACE_LOG_PREF === null)
547
562
  return;
@@ -554,11 +569,8 @@ function setReproTraceLogsEnabled(enabled) {
554
569
  __TRACE_LOG_PREF = !!enabled;
555
570
  applyTraceLogPreference(__TRACER__);
556
571
  }
557
- exports.setReproTraceLogsEnabled = setReproTraceLogsEnabled;
558
572
  function enableReproTraceLogs() { setReproTraceLogsEnabled(true); }
559
- exports.enableReproTraceLogs = enableReproTraceLogs;
560
573
  function disableReproTraceLogs() { setReproTraceLogsEnabled(false); }
561
- exports.disableReproTraceLogs = disableReproTraceLogs;
562
574
  function summarizeEndpointFromEvents(events) {
563
575
  let endpointTrace = null;
564
576
  let preferredAppTrace = null;
@@ -665,10 +677,8 @@ function initReproTracing(opts) {
665
677
  }
666
678
  return __TRACER__;
667
679
  }
668
- exports.initReproTracing = initReproTracing;
669
680
  /** Optional helper if users want to check it. */
670
681
  function isReproTracingEnabled() { return __TRACER_READY; }
671
- exports.isReproTracingEnabled = isReproTracingEnabled;
672
682
  function captureSpanContextFromTracer(source) {
673
683
  try {
674
684
  // Prefer a preserved store captured at the call-site for thenables (e.g., Mongoose Query),
@@ -3127,7 +3137,6 @@ async function __materializePendingTraceEventsForWorker(payload) {
3127
3137
  __FULL_VALUE_CAPTURE_ENABLED = prevFullValueCapture;
3128
3138
  }
3129
3139
  }
3130
- exports.__materializePendingTraceEventsForWorker = __materializePendingTraceEventsForWorker;
3131
3140
  function sanitizeRequestSnapshot(value) {
3132
3141
  if (value === undefined)
3133
3142
  return undefined;
@@ -5527,7 +5536,6 @@ function reproMiddleware(cfg) {
5527
5536
  }));
5528
5537
  };
5529
5538
  }
5530
- exports.reproMiddleware = reproMiddleware;
5531
5539
  // ===================================================================
5532
5540
  // reproMongoosePlugin — stable + NON-intrusive query logs
5533
5541
  // - NO prototype monkey-patching of Mongoose
@@ -5937,7 +5945,6 @@ function reproMongoosePlugin(cfg) {
5937
5945
  });
5938
5946
  };
5939
5947
  }
5940
- exports.reproMongoosePlugin = reproMongoosePlugin;
5941
5948
  function summarizeQueryResult(op, res) {
5942
5949
  const resultPreview = sanitizeResultForMeta(res, { preserveLongStrings: true });
5943
5950
  if (op === 'find' ||
@@ -7504,7 +7511,6 @@ function patchKafkaJs(cfg) {
7504
7511
  }
7505
7512
  }
7506
7513
  }
7507
- exports.patchKafkaJs = patchKafkaJs;
7508
7514
  const AMQP_INTERNAL_PUBLISH_DEPTH = Symbol.for('repro.amqplib.internalPublishDepth');
7509
7515
  function decodeAmqpHeaderValue(value) {
7510
7516
  if (value === null || value === undefined)
@@ -7870,7 +7876,7 @@ function scheduleAmqpPublishCapture(params) {
7870
7876
  url: params.path,
7871
7877
  path: params.path,
7872
7878
  status: params.status,
7873
- durMs: Date.now() - params.t0,
7879
+ durMs: params.durationMs,
7874
7880
  key: params.key,
7875
7881
  headers: {
7876
7882
  transport: 'amqp',
@@ -7890,7 +7896,7 @@ function scheduleAmqpPublishCapture(params) {
7890
7896
  entries: [{
7891
7897
  actionId: params.aid ?? null,
7892
7898
  request: requestPayload,
7893
- t: alignedNow(),
7899
+ t: params.completedAt,
7894
7900
  }],
7895
7901
  });
7896
7902
  post(params.cfg, params.sid, {
@@ -7952,6 +7958,9 @@ function patchAmqplibPublishMethod(channel, cfg, methodName) {
7952
7958
  if (finished)
7953
7959
  return;
7954
7960
  finished = true;
7961
+ // Capture queue and privacy processing time are not broker latency.
7962
+ const durationMs = Math.max(0, Date.now() - t0);
7963
+ const completedAt = alignedNow();
7955
7964
  scheduleAmqpPublishCapture({
7956
7965
  cfg,
7957
7966
  sid,
@@ -7967,7 +7976,8 @@ function patchAmqplibPublishMethod(channel, cfg, methodName) {
7967
7976
  content,
7968
7977
  options: nextOptions,
7969
7978
  span,
7970
- t0,
7979
+ durationMs,
7980
+ completedAt,
7971
7981
  status,
7972
7982
  error,
7973
7983
  result,
@@ -8100,6 +8110,8 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
8100
8110
  finished = true;
8101
8111
  if (!sid)
8102
8112
  return;
8113
+ const durationMs = Math.max(0, Date.now() - t0);
8114
+ const completedAt = alignedNow();
8103
8115
  const fields = message?.fields ?? {};
8104
8116
  const messagePayload = buildAmqpMessagePayload(message?.content, properties);
8105
8117
  const body = {
@@ -8129,7 +8141,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
8129
8141
  url: `amqp://queue/${queueName}`,
8130
8142
  path: `amqp://queue/${queueName}`,
8131
8143
  status: threw ? 500 : 200,
8132
- durMs: Date.now() - t0,
8144
+ durMs: durationMs,
8133
8145
  key: `AMQP_CONSUME ${queueName}`,
8134
8146
  headers: normalizeAmqpHeadersForPayload(headers),
8135
8147
  body: bodyMaterialization.value,
@@ -8140,7 +8152,7 @@ function wrapAmqplibConsumeHandler(onMessage, cfg, queue) {
8140
8152
  entries: [{
8141
8153
  actionId: aid ?? null,
8142
8154
  request: requestPayload,
8143
- t: alignedNow(),
8155
+ t: completedAt,
8144
8156
  }],
8145
8157
  });
8146
8158
  scheduleAmqpTraceFlush(cfg, amqpCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
@@ -8304,7 +8316,6 @@ function patchAmqplib(cfg) {
8304
8316
  patchAmqplibModule(amqpModule, cfg);
8305
8317
  }
8306
8318
  }
8307
- exports.patchAmqplib = patchAmqplib;
8308
8319
  const redisPatchConfigs = [];
8309
8320
  let redisRequireHookInstalled = false;
8310
8321
  const REDIS_KEYLESS_COMMANDS = new Set([
@@ -8594,7 +8605,6 @@ function patchRedis(cfg) {
8594
8605
  patchRedisFromModuleExports(redisModule, cfg);
8595
8606
  }
8596
8607
  }
8597
- exports.patchRedis = patchRedis;
8598
8608
  const postgresSqlPatchConfigs = [];
8599
8609
  const postgresSqlSourceAls = new async_hooks_1.AsyncLocalStorage();
8600
8610
  const pgPoolQueryAls = new async_hooks_1.AsyncLocalStorage();
@@ -9152,7 +9162,6 @@ function patchPostgreSql(cfg) {
9152
9162
  for (const loaded of typeOrmModules)
9153
9163
  patchTypeOrmFromModuleExports(loaded);
9154
9164
  }
9155
- exports.patchPostgreSql = patchPostgreSql;
9156
9165
  const mongoDbPatchConfigs = [];
9157
9166
  let mongoDbRequireHookInstalled = false;
9158
9167
  let mongoDbImportHookInstalled = false;
@@ -9735,7 +9744,6 @@ function patchMongoDb(cfg) {
9735
9744
  patchMongoDbFromModuleExports(mongoModule, cfg);
9736
9745
  }
9737
9746
  }
9738
- exports.patchMongoDb = patchMongoDb;
9739
9747
  // ===================================================================
9740
9748
  // Auto init helpers
9741
9749
  // ===================================================================
@@ -10203,7 +10211,6 @@ function patchSendgridMail(cfg) {
10203
10211
  }
10204
10212
  }
10205
10213
  }
10206
- exports.patchSendgridMail = patchSendgridMail;
10207
10214
  /**
10208
10215
  * Datadog-style one-time init.
10209
10216
  * Enables tracing, auto-patches integrations, auto-registers mongoose plugin,
@@ -10251,7 +10258,6 @@ async function initRepro(cfg) {
10251
10258
  enqueueRuntimeServiceRegistration(reproAutoMiddlewareConfig);
10252
10259
  await initializeRuntimePrivacyPolicyAtStartup(reproAutoMiddlewareConfig);
10253
10260
  }
10254
- exports.initRepro = initRepro;
10255
10261
  /** @internal Test hooks for runtime privacy policy wiring. */
10256
10262
  exports.__reproTestHooks = {
10257
10263
  shareRuntimePrivacyStateForTest: shareRuntimePrivacyState,
@@ -3,8 +3,8 @@ export type EnqueueIngestOptions = {
3
3
  priority?: 'normal' | 'high' | 'critical';
4
4
  };
5
5
  export declare const configureIngestQueueDrain: (options: {
6
- shouldDefer?: ((enqueuedAt?: number) => boolean) | null | undefined;
7
- deferMs?: number | undefined;
6
+ shouldDefer?: ((enqueuedAt?: number) => boolean) | null;
7
+ deferMs?: number;
8
8
  }) => void;
9
9
  export declare const kickIngestQueueDrain: () => void;
10
10
  export declare const postIngestEntries: (config: IngestClientConfig, sessionId: string, entries: LegacyEntry[]) => Promise<void>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.patchSendgridMail = exports.getCtx = void 0;
3
+ exports.getCtx = void 0;
4
+ exports.patchSendgridMail = patchSendgridMail;
4
5
  // repro-node/src/integrations/sendgrid.ts
5
6
  const async_hooks_1 = require("async_hooks");
6
7
  const client_1 = require("../ingest/client");
@@ -164,4 +165,3 @@ function patchSendgridMail(cfg) {
164
165
  return undefined;
165
166
  }
166
167
  }
167
- exports.patchSendgridMail = patchSendgridMail;
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.limitLiveArguments = exports.collectCaptureOmissions = exports.limitLiveCapture = exports.LIVE_CAPTURE_LIMITS = void 0;
3
+ exports.LIVE_CAPTURE_LIMITS = void 0;
4
+ exports.limitLiveCapture = limitLiveCapture;
5
+ exports.collectCaptureOmissions = collectCaptureOmissions;
6
+ exports.limitLiveArguments = limitLiveArguments;
4
7
  const http_1 = require("http");
5
8
  const semantic_serialization_1 = require("./semantic-serialization");
6
9
  exports.LIVE_CAPTURE_LIMITS = Object.freeze({ maxDepth: 10, maxKeys: 100, maxEntries: 100 });
@@ -170,7 +173,6 @@ function limitLiveCapture(value, rootPath = '$') {
170
173
  return { value: omit({ reason: 'uninspectable', path: rootPath, capturedCount: 0 }), omissions };
171
174
  }
172
175
  }
173
- exports.limitLiveCapture = limitLiveCapture;
174
176
  function collectCaptureOmissions(value) {
175
177
  const result = [];
176
178
  const seen = new WeakSet();
@@ -193,7 +195,6 @@ function collectCaptureOmissions(value) {
193
195
  }
194
196
  return result;
195
197
  }
196
- exports.collectCaptureOmissions = collectCaptureOmissions;
197
198
  function limitLiveArguments(args) {
198
199
  const values = args.slice(0, 100).map((arg, index) => limitLiveCapture(arg, `$[${index}]`).value);
199
200
  if (args.length > 100) {
@@ -204,4 +205,3 @@ function limitLiveArguments(args) {
204
205
  }
205
206
  return values;
206
207
  }
207
- exports.limitLiveArguments = limitLiveArguments;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sanitizeUnknownBoundaryTextWithDetectors = void 0;
3
+ exports.sanitizeUnknownBoundaryTextWithDetectors = sanitizeUnknownBoundaryTextWithDetectors;
4
4
  const SANITIZED_TEXT_CACHE = new Map();
5
5
  const SANITIZED_TEXT_CACHE_MAX = 500;
6
6
  async function sanitizeUnknownBoundaryTextWithDetectors(value) {
@@ -14,7 +14,6 @@ async function sanitizeUnknownBoundaryTextWithDetectors(value) {
14
14
  rememberSanitizedText(value, value);
15
15
  return value;
16
16
  }
17
- exports.sanitizeUnknownBoundaryTextWithDetectors = sanitizeUnknownBoundaryTextWithDetectors;
18
17
  function rememberSanitizedText(input, output) {
19
18
  SANITIZED_TEXT_CACHE.set(input, output);
20
19
  if (SANITIZED_TEXT_CACHE.size <= SANITIZED_TEXT_CACHE_MAX) {
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.collectEmailsInText = exports.redactEmailsInText = exports.redactEmailDeterministic = void 0;
3
+ exports.redactEmailDeterministic = redactEmailDeterministic;
4
+ exports.redactEmailsInText = redactEmailsInText;
5
+ exports.collectEmailsInText = collectEmailsInText;
4
6
  const crypto_1 = require("crypto");
5
7
  const EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
6
8
  const EMAIL_TOKEN_PREFIX = 'email_tok_';
@@ -13,14 +15,12 @@ function redactEmailDeterministic(value) {
13
15
  const token = (0, crypto_1.createHash)('sha256').update(normalized).digest('hex').slice(0, EMAIL_TOKEN_LENGTH);
14
16
  return `${EMAIL_TOKEN_PREFIX}${token}`;
15
17
  }
16
- exports.redactEmailDeterministic = redactEmailDeterministic;
17
18
  function redactEmailsInText(value) {
18
19
  if (!value) {
19
20
  return value;
20
21
  }
21
22
  return value.replace(EMAIL_PATTERN, (match) => isAlreadyMaskedEmail(match) ? match : redactEmailDeterministic(match));
22
23
  }
23
- exports.redactEmailsInText = redactEmailsInText;
24
24
  function collectEmailsInText(value) {
25
25
  if (!value) {
26
26
  return [];
@@ -28,7 +28,6 @@ function collectEmailsInText(value) {
28
28
  const matches = value.match(EMAIL_PATTERN);
29
29
  return Array.isArray(matches) ? matches : [];
30
30
  }
31
- exports.collectEmailsInText = collectEmailsInText;
32
31
  function isAlreadyMaskedEmail(value) {
33
32
  if (/^email_tok_[a-z0-9]+$/i.test(value)) {
34
33
  return true;
package/dist/privacy.js CHANGED
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.applyRuntimePrivacyPolicyAsync = exports.applyRuntimePrivacyPolicy = exports.normalizeRuntimePrivacyPolicy = exports.fetchApprovedRuntimePrivacyPolicy = exports.resolvePrivacyApiBase = exports.normalizePrivacyEnvironment = exports.clearRuntimePrivacyProvenance = void 0;
3
+ exports.clearRuntimePrivacyProvenance = clearRuntimePrivacyProvenance;
4
+ exports.normalizePrivacyEnvironment = normalizePrivacyEnvironment;
5
+ exports.resolvePrivacyApiBase = resolvePrivacyApiBase;
6
+ exports.fetchApprovedRuntimePrivacyPolicy = fetchApprovedRuntimePrivacyPolicy;
7
+ exports.normalizeRuntimePrivacyPolicy = normalizeRuntimePrivacyPolicy;
8
+ exports.applyRuntimePrivacyPolicy = applyRuntimePrivacyPolicy;
9
+ exports.applyRuntimePrivacyPolicyAsync = applyRuntimePrivacyPolicyAsync;
4
10
  const crypto_1 = require("crypto");
5
11
  const capture_work_budget_1 = require("./capture-work-budget");
6
12
  const privacy_fallback_1 = require("./privacy-fallback");
@@ -21,7 +27,6 @@ function clearRuntimePrivacyProvenance() {
21
27
  objectRuntimePrivacyProvenance = new WeakMap();
22
28
  serializedRuntimePrivacyProvenance.clear();
23
29
  }
24
- exports.clearRuntimePrivacyProvenance = clearRuntimePrivacyProvenance;
25
30
  const ALL_SURFACES = [
26
31
  'db.pk',
27
32
  'db.before',
@@ -79,7 +84,6 @@ function normalizePrivacyEnvironment(input) {
79
84
  return 'qa';
80
85
  return normalized || 'dev';
81
86
  }
82
- exports.normalizePrivacyEnvironment = normalizePrivacyEnvironment;
83
87
  function resolvePrivacyApiBase(cfg) {
84
88
  const value = cfg?.apiBase ||
85
89
  process.env.REPRO_PRIVACY_API_BASE ||
@@ -90,7 +94,6 @@ function resolvePrivacyApiBase(cfg) {
90
94
  }
91
95
  return String(value).replace(/\/+$/, '');
92
96
  }
93
- exports.resolvePrivacyApiBase = resolvePrivacyApiBase;
94
97
  const DEFAULT_RUNTIME_PRIVACY_FETCH_TIMEOUT_MS = 10000;
95
98
  function resolveRuntimePrivacyFetchTimeoutMs(value) {
96
99
  if (!Number.isFinite(value)) {
@@ -162,7 +165,6 @@ async function fetchApprovedRuntimePrivacyPolicy(params) {
162
165
  }
163
166
  }
164
167
  }
165
- exports.fetchApprovedRuntimePrivacyPolicy = fetchApprovedRuntimePrivacyPolicy;
166
168
  function normalizeRuntimePrivacyPolicy(raw) {
167
169
  if (!raw || typeof raw !== 'object') {
168
170
  return null;
@@ -189,21 +191,18 @@ function normalizeRuntimePrivacyPolicy(raw) {
189
191
  runtimePrivacyStatementIndexes.set(normalizedPolicy, buildRuntimePrivacyStatementIndex(statements));
190
192
  return normalizedPolicy;
191
193
  }
192
- exports.normalizeRuntimePrivacyPolicy = normalizeRuntimePrivacyPolicy;
193
194
  function applyRuntimePrivacyPolicy(policy, surface, value, context) {
194
195
  if (value === undefined) {
195
196
  return value;
196
197
  }
197
198
  return transformValue(policy ?? EMPTY_RUNTIME_PRIVACY_POLICY, surface, value, context, []);
198
199
  }
199
- exports.applyRuntimePrivacyPolicy = applyRuntimePrivacyPolicy;
200
200
  async function applyRuntimePrivacyPolicyAsync(policy, surface, value, context) {
201
201
  if (value === undefined) {
202
202
  return value;
203
203
  }
204
204
  return transformValueAsync(policy ?? EMPTY_RUNTIME_PRIVACY_POLICY, surface, value, context, []);
205
205
  }
206
- exports.applyRuntimePrivacyPolicyAsync = applyRuntimePrivacyPolicyAsync;
207
206
  function flattenStatements(policy) {
208
207
  const fromGroups = Array.isArray(policy.groups)
209
208
  ? policy.groups.flatMap((group) => Array.isArray(group?.statements) ? group.statements : [])
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.projectSemanticInfrastructureValue = exports.mongooseDocumentData = void 0;
3
+ exports.mongooseDocumentData = mongooseDocumentData;
4
+ exports.projectSemanticInfrastructureValue = projectSemanticInfrastructureValue;
4
5
  function constructorName(value) {
5
6
  if (!value || typeof value !== 'object')
6
7
  return '';
@@ -55,7 +56,6 @@ function mongooseDocumentData(value) {
55
56
  return null;
56
57
  return method(value, '$__init') && method(value, '$toObject') ? doc : null;
57
58
  }
58
- exports.mongooseDocumentData = mongooseDocumentData;
59
59
  function primitiveStateValue(value) {
60
60
  if (value === null)
61
61
  return null;
@@ -200,4 +200,3 @@ function projectSemanticInfrastructureValue(value) {
200
200
  projectPostgres(value, className) ||
201
201
  projectTypeOrm(value, className));
202
202
  }
203
- exports.projectSemanticInfrastructureValue = projectSemanticInfrastructureValue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,6 +13,7 @@
13
13
  "test:latency": "npm run build && node test/privacy-fetch-timeout.test.js && node test/capture-latency.testcontainers.test.js",
14
14
  "test:live-capture": "npm run build && node test/twelve-element-capture.testcontainers.test.js && node test/live-capture-limits.test.js && node test/ingest-throttle-retention.test.js && node test/live-capture-limits.testcontainers.test.js",
15
15
  "test:live-brokers": "npm run build && node test/live-broker-limits.testcontainers.test.js",
16
+ "test:amqp-timing": "npm run build && node test/amqplib-integration.test.js && node test/amqplib-timing.testcontainers.test.js",
16
17
  "build": "tsc -p tsconfig.json",
17
18
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
18
19
  "prepublishOnly": "npm run build",
@@ -24,6 +25,7 @@
24
25
  "mongoose": ">=6"
25
26
  },
26
27
  "devDependencies": {
28
+ "typescript": "^5.7.3",
27
29
  "@nestjs/typeorm": "^11.0.0",
28
30
  "@testcontainers/kafka": "^11.8.1",
29
31
  "@types/express": "^5.0.3",
package/src/index.ts CHANGED
@@ -9498,7 +9498,8 @@ function scheduleAmqpPublishCapture(params: {
9498
9498
  content: any;
9499
9499
  options: any;
9500
9500
  span: SpanContext | null;
9501
- t0: number;
9501
+ durationMs: number;
9502
+ completedAt: number;
9502
9503
  status: number;
9503
9504
  error?: any;
9504
9505
  result?: any;
@@ -9540,7 +9541,7 @@ function scheduleAmqpPublishCapture(params: {
9540
9541
  url: params.path,
9541
9542
  path: params.path,
9542
9543
  status: params.status,
9543
- durMs: Date.now() - params.t0,
9544
+ durMs: params.durationMs,
9544
9545
  key: params.key,
9545
9546
  headers: {
9546
9547
  transport: 'amqp',
@@ -9561,7 +9562,7 @@ function scheduleAmqpPublishCapture(params: {
9561
9562
  entries: [{
9562
9563
  actionId: params.aid ?? null,
9563
9564
  request: requestPayload,
9564
- t: alignedNow(),
9565
+ t: params.completedAt,
9565
9566
  }],
9566
9567
  });
9567
9568
  post(params.cfg, params.sid!, {
@@ -9634,6 +9635,9 @@ function patchAmqplibPublishMethod(
9634
9635
  const finish = (status: number, error?: any, result?: any) => {
9635
9636
  if (finished) return;
9636
9637
  finished = true;
9638
+ // Capture queue and privacy processing time are not broker latency.
9639
+ const durationMs = Math.max(0, Date.now() - t0);
9640
+ const completedAt = alignedNow();
9637
9641
  scheduleAmqpPublishCapture({
9638
9642
  cfg,
9639
9643
  sid,
@@ -9649,7 +9653,8 @@ function patchAmqplibPublishMethod(
9649
9653
  content,
9650
9654
  options: nextOptions,
9651
9655
  span,
9652
- t0,
9656
+ durationMs,
9657
+ completedAt,
9653
9658
  status,
9654
9659
  error,
9655
9660
  result,
@@ -9779,6 +9784,8 @@ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queu
9779
9784
  if (finished) return;
9780
9785
  finished = true;
9781
9786
  if (!sid) return;
9787
+ const durationMs = Math.max(0, Date.now() - t0);
9788
+ const completedAt = alignedNow();
9782
9789
  const fields = message?.fields ?? {};
9783
9790
  const messagePayload = buildAmqpMessagePayload(message?.content, properties);
9784
9791
  const body = {
@@ -9814,7 +9821,7 @@ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queu
9814
9821
  url: `amqp://queue/${queueName}`,
9815
9822
  path: `amqp://queue/${queueName}`,
9816
9823
  status: threw ? 500 : 200,
9817
- durMs: Date.now() - t0,
9824
+ durMs: durationMs,
9818
9825
  key: `AMQP_CONSUME ${queueName}`,
9819
9826
  headers: normalizeAmqpHeadersForPayload(headers),
9820
9827
  body: bodyMaterialization.value,
@@ -9826,7 +9833,7 @@ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queu
9826
9833
  entries: [{
9827
9834
  actionId: aid ?? null,
9828
9835
  request: requestPayload,
9829
- t: alignedNow(),
9836
+ t: completedAt,
9830
9837
  }],
9831
9838
  });
9832
9839
  scheduleAmqpTraceFlush(cfg, amqpCtx, aid, consumeRid, rawTraceEvents, maskReq, captureScope);
@@ -0,0 +1,198 @@
1
+ process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '0';
2
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '0';
3
+ process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = '0';
4
+ process.env.TRACE_IDLE_FLUSH_MS = '0';
5
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
6
+
7
+ const assert = require('node:assert/strict');
8
+ const http = require('node:http');
9
+ const { once } = require('node:events');
10
+ const { GenericContainer, Wait } = require('testcontainers');
11
+ const amqp = require('amqplib');
12
+ const { __reproTestHooks: hooks } = require('../dist');
13
+ const { flushIngestQueue } = require('../dist/ingest/client');
14
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
15
+ const deferred = () => {
16
+ let resolve;
17
+ const promise = new Promise(done => { resolve = done; });
18
+ return { promise, resolve };
19
+ };
20
+ async function within(promise, label) {
21
+ let timer;
22
+ try {
23
+ return await Promise.race([promise, new Promise((_, reject) => {
24
+ timer = setTimeout(() => reject(new Error(`Timed out: ${label}`)), 10000);
25
+ })]);
26
+ } finally { clearTimeout(timer); }
27
+ }
28
+
29
+ async function main() {
30
+ const events = [], channels = [];
31
+ let rabbit, connection, release;
32
+ const sink = http.createServer(async (req, res) => {
33
+ const buffers = [];
34
+ for await (const chunk of req) buffers.push(chunk);
35
+ events.push(...(JSON.parse(Buffer.concat(buffers)).events || []));
36
+ res.writeHead(202); res.end('{}');
37
+ });
38
+ sink.listen(0, '127.0.0.1'); await once(sink, 'listening');
39
+ try {
40
+ rabbit = await new GenericContainer('rabbitmq:3.13-alpine')
41
+ .withEnvironment({ RABBITMQ_DEFAULT_USER: 'repro', RABBITMQ_DEFAULT_PASS: 'test' })
42
+ .withExposedPorts(5672).withWaitStrategy(Wait.forLogMessage('Server startup complete')).start();
43
+ connection = await amqp.connect(`amqp://repro:test@${rabbit.getHost()}:${rabbit.getMappedPort(5672)}`);
44
+ for (const mode of ['live', 'staging']) {
45
+ const sid = `S_amqp_timing_${mode}`;
46
+ const cfg = { tenantId: 'TENANT_timing', appId: 'APP_timing', appSecret: 'test',
47
+ privacy: { captureRaw: true, environment: 'dev' },
48
+ ingestBase: `http://127.0.0.1:${sink.address().port}`,
49
+ resolveContext: () => ({ sid, aid: `A_${mode}` }) };
50
+ const channel = await connection.createConfirmChannel(); channels.push(channel);
51
+ hooks.patchAmqplibChannelForTest(channel, cfg);
52
+ const queue = `timing-${mode}`;
53
+ await channel.assertQueue(queue, { durable: false });
54
+ const handled = deferred(), acknowledged = deferred(), gate = deferred(), started = deferred();
55
+ release = gate.resolve;
56
+ // Hold the real capture queue, not the broker or application callback.
57
+ hooks.scheduleSdkBackgroundWorkForTest(async () => {
58
+ started.resolve(); await gate.promise;
59
+ }, { priority: 'high' });
60
+ await within(started.promise, 'capture queue blocker');
61
+ let handlerStart, handlerEnd, publishEnd;
62
+ const input = { orderId: `order-${mode}`, lines: [{ sku: 'item', quantity: 3 }] };
63
+ const consumer = await channel.consume(queue, async message => {
64
+ handlerStart = Date.now();
65
+ assert.deepEqual(JSON.parse(message.content.toString()), input);
66
+ assert.equal(message.properties.headers['x-bug-session-id'], sid);
67
+ assert.equal(message.properties.headers['x-repro-capture-mode'], mode);
68
+ await sleep(60);
69
+ channel.ack(message);
70
+ handlerEnd = Date.now(); handled.resolve();
71
+ });
72
+ const publishStart = Date.now();
73
+ const options = { headers: { 'x-repro-capture-mode': mode }, persistent: false };
74
+ const result = hooks.runWithRequestContextForTest({ sid, aid: `A_${mode}`, captureScope: { liveCapture: mode === 'live' } },
75
+ () => channel.sendToQueue(queue, Buffer.from(JSON.stringify(input)), options, error => {
76
+ assert.ifError(error); publishEnd = Date.now(); acknowledged.resolve();
77
+ }));
78
+ assert.equal(typeof result, 'boolean', 'publish must preserve amqplib backpressure contract');
79
+ await within(Promise.all([handled.promise, acknowledged.promise]), 'broker confirm and consumer acknowledgement');
80
+ assert.equal(options.headers['x-bug-session-id'], undefined, 'caller options stay unchanged');
81
+ assert.equal(events.filter(e => e.session_id === sid && e.event_type === 'backend_request').length, 0);
82
+ await sleep(450);
83
+ const releasedAt = Date.now(); gate.resolve(); release = null;
84
+ const deadline = Date.now() + 10000;
85
+ let requests;
86
+ do {
87
+ await hooks.drainSdkBackgroundQueueForTest(); await flushIngestQueue();
88
+ requests = events.filter(e => e.session_id === sid && e.event_type === 'backend_request');
89
+ if (requests.length === 2) break;
90
+ await sleep(20);
91
+ } while (Date.now() < deadline);
92
+ assert.equal(requests.length, 2, 'exactly one publish and one consume, despite sendToQueue delegating to publish');
93
+ for (const [method, start, end] of [
94
+ ['AMQP_SEND_TO_QUEUE', publishStart, publishEnd],
95
+ ['AMQP_CONSUME', handlerStart, handlerEnd],
96
+ ]) {
97
+ const event = requests.find(e => e.payload.request.method === method);
98
+ assert(event, `${mode}: missing ${method}`);
99
+ const request = event.payload.request;
100
+ assert(request.durMs <= end - start + 30,
101
+ `${mode} ${method}: ${request.durMs}ms includes capture queue time; business took ${end - start}ms`);
102
+ assert(request.durMs >= end - start - 10, 'duration must still include the async business handler');
103
+ const timestamp = Date.parse(event.event_ts);
104
+ assert(timestamp <= end + 30 && timestamp >= start,
105
+ `event time must be operation completion, not background delivery: ${timestamp}, ${end}`);
106
+ assert(timestamp < releasedAt - 400);
107
+ assert.deepEqual(request.body.value, input, 'timing correction must not lose payload');
108
+ }
109
+ await channel.cancel(consumer.consumerTag);
110
+ console.log(`${mode}: broker confirms and async handler finish before capture drain; timings and payload exact`);
111
+ }
112
+ // Exercise failure/return contracts with real delivered envelopes. The
113
+ // adapter catches deliberate handler failures just as an application would.
114
+ const sid = 'S_amqp_failures';
115
+ const cfg = { tenantId: 'TENANT_timing', appId: 'APP_timing', appSecret: 'test',
116
+ privacy: { captureRaw: true, environment: 'dev' },
117
+ ingestBase: `http://127.0.0.1:${sink.address().port}`,
118
+ resolveContext: () => ({ sid, aid: 'A_failures' }) };
119
+ const raw = await connection.createChannel(); channels.push(raw);
120
+ await raw.assertQueue('timing-contracts', { durable: false });
121
+ const gate = deferred(), started = deferred(); release = gate.resolve;
122
+ hooks.scheduleSdkBackgroundWorkForTest(async () => { started.resolve(); await gate.promise; }, { priority: 'high' });
123
+ await within(started.promise, 'failure capture queue blocker');
124
+ const observations = new Map(), completed = deferred();
125
+ const identity = { result: 'unchanged' }, failure = new Error('application failure');
126
+ const wrapped = hooks.wrapAmqplibConsumeHandlerForTest(message => {
127
+ const { scenario } = JSON.parse(message.content.toString());
128
+ const observation = { start: Date.now(), calls: 1, scenario };
129
+ observations.set(scenario, observation);
130
+ const finish = () => {
131
+ observation.end = Date.now();
132
+ if (scenario.endsWith('error')) throw failure;
133
+ return identity;
134
+ };
135
+ return scenario.startsWith('async') ? sleep(60).then(finish) : finish();
136
+ }, cfg, 'timing-contracts');
137
+ let handled = 0;
138
+ const consumer = await raw.consume('timing-contracts', message => {
139
+ const { scenario } = JSON.parse(message.content.toString());
140
+ const complete = () => { raw.ack(message); if (++handled === 4) completed.resolve(); };
141
+ try {
142
+ const value = wrapped(message);
143
+ if (scenario.startsWith('async')) {
144
+ assert.equal(typeof value.then, 'function');
145
+ value.then(result => {
146
+ assert.strictEqual(result, identity); complete();
147
+ }, error => { assert.strictEqual(error, failure); complete(); });
148
+ } else { assert.strictEqual(value, identity); complete(); }
149
+ } catch (error) { assert.strictEqual(error, failure); complete(); }
150
+ });
151
+ for (const scenario of ['sync-success', 'async-success', 'sync-error', 'async-error']) {
152
+ raw.sendToQueue('timing-contracts', Buffer.from(JSON.stringify({ scenario })), { headers: {
153
+ 'x-bug-session-id': sid, 'x-bug-action-id': 'A_failures', 'x-repro-capture-mode': 'live',
154
+ } });
155
+ }
156
+ await within(completed.promise, 'handler return and error contracts');
157
+ const invalid = await connection.createConfirmChannel(); channels.push(invalid);
158
+ invalid.on('error', () => {});
159
+ hooks.patchAmqplibChannelForTest(invalid, cfg);
160
+ const rejected = deferred();
161
+ const rejectStart = Date.now(); let rejectEnd;
162
+ invalid.publish('missing-exchange-timing', 'route', Buffer.from('message'), {}, error => {
163
+ assert(error, 'broker must reject the nonexistent exchange');
164
+ rejectEnd = Date.now(); rejected.resolve();
165
+ });
166
+ await within(rejected.promise, 'broker rejection');
167
+ await sleep(450); gate.resolve(); release = null;
168
+ const deadline = Date.now() + 10000;
169
+ let requests;
170
+ do {
171
+ await hooks.drainSdkBackgroundQueueForTest(); await flushIngestQueue();
172
+ requests = events.filter(e => e.session_id === sid && e.event_type === 'backend_request');
173
+ if (requests.length === 5) break;
174
+ await sleep(20);
175
+ } while (Date.now() < deadline);
176
+ assert.equal(requests.length, 5, 'no retries/double captures on exceptions or broker channel close');
177
+ for (const event of requests) {
178
+ const request = event.payload.request;
179
+ const observation = request.method === 'AMQP_PUBLISH'
180
+ ? { start: rejectStart, end: rejectEnd, scenario: 'broker-error' }
181
+ : observations.get(request.body.value.scenario);
182
+ assert(observation);
183
+ assert.equal(request.status, observation.scenario.endsWith('error') ? 500 : 200);
184
+ assert(request.durMs <= observation.end - observation.start + 30);
185
+ assert(Date.parse(event.event_ts) <= observation.end + 30);
186
+ }
187
+ await raw.cancel(consumer.consumerTag);
188
+ console.log('sync/async return and error identity preserved; broker rejection and failure timestamps exclude capture drain');
189
+ } finally {
190
+ release?.();
191
+ for (const channel of channels) await channel.close().catch(() => {});
192
+ if (connection) await connection.close();
193
+ await hooks.drainSdkBackgroundQueueForTest(); await flushIngestQueue();
194
+ sink.closeAllConnections(); await new Promise(resolve => sink.close(resolve));
195
+ if (rabbit) await rabbit.stop();
196
+ }
197
+ }
198
+ main().catch(error => { console.error(error); process.exitCode = 1; });
@@ -25,7 +25,9 @@ async function main() {
25
25
  const results = [];
26
26
  try {
27
27
  mongo = await new GenericContainer('mongo:7').withExposedPorts(27017).withWaitStrategy(Wait.forListeningPorts()).start();
28
- for (const variant of ['off', 'live-raw', 'staging-raw', 'live-policy', 'staging-policy', 'live-policy-fallback', 'staging-policy-fallback', 'staging-policy-fault']) {
28
+ const variants = ['off', 'live-raw', 'staging-raw', 'live-policy', 'staging-policy', 'live-policy-fallback', 'staging-policy-fallback', 'staging-policy-fault']
29
+ .filter(variant => !process.argv.includes('--raw-only') || !variant.includes('policy'));
30
+ for (const variant of variants) {
29
31
  const mode = variant.split('-')[0];
30
32
  child = fork(require.resolve('./fixtures/capture-latency-server'), [], {
31
33
  env: { ...process.env, CAPTURE_TEST_MODE: mode,