@reproapp/node-sdk 0.0.8 → 0.0.9

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.d.ts CHANGED
@@ -283,6 +283,8 @@ type InlinePrivacyMaterializationResult = {
283
283
  };
284
284
  };
285
285
  declare function materializeInlinePrivacyValueAsync(target: RuntimePrivacySurface, value: any, cfg: ReproMiddlewareConfig, req: MaskRequestContext, trace: TraceEventForFilter | null, masking: NormalizedMaskingConfig | null, privacy: NormalizedRuntimePrivacyPolicy | null, db?: RuntimePrivacyDbContext | null): Promise<InlinePrivacyMaterializationResult>;
286
+ declare function estimateTraceBatchSerializedBytes(batch: TraceEventRecord[], requestRid: string, actionId: string | null | undefined, batchIndex: number): number;
287
+ declare function chunkTraceEventsForTransport(events: TraceEventRecord[], requestRid: string, actionId: string | null | undefined): TraceEventRecord[][];
286
288
  export type ReproMiddlewareConfig = IngestClientConfig & {
287
289
  /** Configure header capture/masking. Defaults to capturing with sensitive headers masked. */
288
290
  captureHeaders?: boolean | HeaderCaptureOptions;
@@ -348,5 +350,7 @@ export declare const __reproTestHooks: {
348
350
  materializeInlinePrivacyValueAsyncForTest: typeof materializeInlinePrivacyValueAsync;
349
351
  patchKafkaProducerInstanceForTest: typeof patchKafkaProducerInstance;
350
352
  wrapKafkaEachMessageHandlerForTest: typeof wrapKafkaEachMessageHandler;
353
+ chunkTraceEventsForTransportForTest: typeof chunkTraceEventsForTransport;
354
+ estimateTraceBatchSerializedBytesForTest: typeof estimateTraceBatchSerializedBytes;
351
355
  };
352
356
  export {};
package/dist/index.js CHANGED
@@ -1657,6 +1657,12 @@ const TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = (() => {
1657
1657
  })();
1658
1658
  const TRACE_VALUE_SIZE_EXCEEDED = Symbol('trace-value-size-exceeded');
1659
1659
  const TRACE_BATCH_SIZE = 100;
1660
+ const TRACE_BATCH_MAX_SERIALIZED_BYTES = (() => {
1661
+ const env = Number(process.env.REPRO_TRACE_BATCH_MAX_SERIALIZED_BYTES);
1662
+ if (Number.isFinite(env) && env > 0)
1663
+ return Math.trunc(env);
1664
+ return 512 * 1024;
1665
+ })();
1660
1666
  const TRACE_FLUSH_DELAY_MS = 20;
1661
1667
  // Choose how to order trace events in payloads.
1662
1668
  // - "chronological" (default): preserve event arrival order (no reshuffle).
@@ -3526,6 +3532,51 @@ function collectBatchTraceValueEntries(batch, batchIndex) {
3526
3532
  });
3527
3533
  return collected;
3528
3534
  }
3535
+ function serializedByteLength(value) {
3536
+ try {
3537
+ return Buffer.byteLength(JSON.stringify(value), 'utf8');
3538
+ }
3539
+ catch {
3540
+ return Number.MAX_SAFE_INTEGER;
3541
+ }
3542
+ }
3543
+ function estimateTraceBatchSerializedBytes(batch, requestRid, actionId, batchIndex) {
3544
+ const traceValues = collectBatchTraceValueEntries(batch, batchIndex);
3545
+ return serializedByteLength({
3546
+ actionId: actionId ?? null,
3547
+ trace: batch,
3548
+ traceValues: traceValues.length ? traceValues : undefined,
3549
+ traceBatch: {
3550
+ rid: requestRid,
3551
+ index: batchIndex,
3552
+ total: 0,
3553
+ },
3554
+ t: 0,
3555
+ });
3556
+ }
3557
+ function chunkTraceEventsForTransport(events, requestRid, actionId) {
3558
+ if (!Array.isArray(events) || events.length === 0)
3559
+ return [];
3560
+ const countBatches = chunkArray(events, TRACE_BATCH_SIZE);
3561
+ const sizedBatches = [];
3562
+ let current = [];
3563
+ for (const event of countBatches.flat()) {
3564
+ const candidate = current.concat(event);
3565
+ const batchIndex = sizedBatches.length;
3566
+ const estimatedBytes = estimateTraceBatchSerializedBytes(candidate, requestRid, actionId, batchIndex);
3567
+ if (current.length > 0 &&
3568
+ estimatedBytes > TRACE_BATCH_MAX_SERIALIZED_BYTES) {
3569
+ sizedBatches.push(current);
3570
+ current = [event];
3571
+ continue;
3572
+ }
3573
+ current = candidate;
3574
+ }
3575
+ if (current.length > 0) {
3576
+ sizedBatches.push(current);
3577
+ }
3578
+ return sizedBatches;
3579
+ }
3529
3580
  function createCapturedValueEntry(params) {
3530
3581
  if (!__FULL_VALUE_CAPTURE_ENABLED)
3531
3582
  return undefined;
@@ -4109,7 +4160,7 @@ function reproMiddleware(cfg) {
4109
4160
  const orderedEvents = TRACE_ORDER_MODE === 'tree'
4110
4161
  ? reorderTraceEvents(baseEvents)
4111
4162
  : sortTraceEventsChronologically(baseEvents);
4112
- const traceBatches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
4163
+ const traceBatches = chunkTraceEventsForTransport(orderedEvents, rid, aid);
4113
4164
  if (traceBatches.length) {
4114
4165
  for (let i = 0; i < traceBatches.length; i++) {
4115
4166
  const batch = traceBatches[i];
@@ -5312,7 +5363,7 @@ function buildKafkaTraceEntries(actionId, requestRid, events) {
5312
5363
  const orderedEvents = TRACE_ORDER_MODE === 'tree'
5313
5364
  ? reorderTraceEvents(baseEvents)
5314
5365
  : sortTraceEventsChronologically(baseEvents);
5315
- const batches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
5366
+ const batches = chunkTraceEventsForTransport(orderedEvents, requestRid, actionId);
5316
5367
  return batches.map((batch, index) => ({
5317
5368
  actionId: actionId ?? null,
5318
5369
  trace: batch,
@@ -6626,4 +6677,6 @@ exports.__reproTestHooks = {
6626
6677
  materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
6627
6678
  patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
6628
6679
  wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
6680
+ chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
6681
+ estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
6629
6682
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
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/express-trace-http-args.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/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/express-trace-http-args.test.js && node test/trace-batch-size.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/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
16
16
  },
17
17
  "peerDependencies": {
18
18
  "express": "^5.1.0",
package/src/index.ts CHANGED
@@ -2028,6 +2028,11 @@ const TRACE_INLINE_VALUE_MAX_SERIALIZED_CHARS = (() => {
2028
2028
  })();
2029
2029
  const TRACE_VALUE_SIZE_EXCEEDED = Symbol('trace-value-size-exceeded');
2030
2030
  const TRACE_BATCH_SIZE = 100;
2031
+ const TRACE_BATCH_MAX_SERIALIZED_BYTES = (() => {
2032
+ const env = Number(process.env.REPRO_TRACE_BATCH_MAX_SERIALIZED_BYTES);
2033
+ if (Number.isFinite(env) && env > 0) return Math.trunc(env);
2034
+ return 512 * 1024;
2035
+ })();
2031
2036
  const TRACE_FLUSH_DELAY_MS = 20;
2032
2037
  // Choose how to order trace events in payloads.
2033
2038
  // - "chronological" (default): preserve event arrival order (no reshuffle).
@@ -4215,6 +4220,74 @@ function collectBatchTraceValueEntries(batch: TraceEventRecord[], batchIndex: nu
4215
4220
  return collected;
4216
4221
  }
4217
4222
 
4223
+ function serializedByteLength(value: any): number {
4224
+ try {
4225
+ return Buffer.byteLength(JSON.stringify(value), 'utf8');
4226
+ } catch {
4227
+ return Number.MAX_SAFE_INTEGER;
4228
+ }
4229
+ }
4230
+
4231
+ function estimateTraceBatchSerializedBytes(
4232
+ batch: TraceEventRecord[],
4233
+ requestRid: string,
4234
+ actionId: string | null | undefined,
4235
+ batchIndex: number,
4236
+ ): number {
4237
+ const traceValues = collectBatchTraceValueEntries(batch, batchIndex);
4238
+ return serializedByteLength({
4239
+ actionId: actionId ?? null,
4240
+ trace: batch,
4241
+ traceValues: traceValues.length ? traceValues : undefined,
4242
+ traceBatch: {
4243
+ rid: requestRid,
4244
+ index: batchIndex,
4245
+ total: 0,
4246
+ },
4247
+ t: 0,
4248
+ });
4249
+ }
4250
+
4251
+ function chunkTraceEventsForTransport(
4252
+ events: TraceEventRecord[],
4253
+ requestRid: string,
4254
+ actionId: string | null | undefined,
4255
+ ): TraceEventRecord[][] {
4256
+ if (!Array.isArray(events) || events.length === 0) return [];
4257
+
4258
+ const countBatches = chunkArray(events, TRACE_BATCH_SIZE);
4259
+ const sizedBatches: TraceEventRecord[][] = [];
4260
+ let current: TraceEventRecord[] = [];
4261
+
4262
+ for (const event of countBatches.flat()) {
4263
+ const candidate = current.concat(event);
4264
+ const batchIndex = sizedBatches.length;
4265
+ const estimatedBytes = estimateTraceBatchSerializedBytes(
4266
+ candidate,
4267
+ requestRid,
4268
+ actionId,
4269
+ batchIndex,
4270
+ );
4271
+
4272
+ if (
4273
+ current.length > 0 &&
4274
+ estimatedBytes > TRACE_BATCH_MAX_SERIALIZED_BYTES
4275
+ ) {
4276
+ sizedBatches.push(current);
4277
+ current = [event];
4278
+ continue;
4279
+ }
4280
+
4281
+ current = candidate;
4282
+ }
4283
+
4284
+ if (current.length > 0) {
4285
+ sizedBatches.push(current);
4286
+ }
4287
+
4288
+ return sizedBatches;
4289
+ }
4290
+
4218
4291
  function createCapturedValueEntry(
4219
4292
  params: {
4220
4293
  target:
@@ -4953,7 +5026,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
4953
5026
  const orderedEvents = TRACE_ORDER_MODE === 'tree'
4954
5027
  ? reorderTraceEvents(baseEvents)
4955
5028
  : sortTraceEventsChronologically(baseEvents);
4956
- const traceBatches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
5029
+ const traceBatches = chunkTraceEventsForTransport(
5030
+ orderedEvents,
5031
+ rid,
5032
+ aid,
5033
+ );
4957
5034
 
4958
5035
  if (traceBatches.length) {
4959
5036
  for (let i = 0; i < traceBatches.length; i++) {
@@ -6253,7 +6330,11 @@ function buildKafkaTraceEntries(
6253
6330
  TRACE_ORDER_MODE === 'tree'
6254
6331
  ? reorderTraceEvents(baseEvents)
6255
6332
  : sortTraceEventsChronologically(baseEvents);
6256
- const batches = chunkArray(orderedEvents, TRACE_BATCH_SIZE);
6333
+ const batches = chunkTraceEventsForTransport(
6334
+ orderedEvents,
6335
+ requestRid,
6336
+ actionId,
6337
+ );
6257
6338
  return batches.map((batch, index) => ({
6258
6339
  actionId: actionId ?? null,
6259
6340
  trace: batch,
@@ -7618,4 +7699,6 @@ export const __reproTestHooks = {
7618
7699
  materializeInlinePrivacyValueAsyncForTest: materializeInlinePrivacyValueAsync,
7619
7700
  patchKafkaProducerInstanceForTest: patchKafkaProducerInstance,
7620
7701
  wrapKafkaEachMessageHandlerForTest: wrapKafkaEachMessageHandler,
7702
+ chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
7703
+ estimateTraceBatchSerializedBytesForTest: estimateTraceBatchSerializedBytes,
7621
7704
  };
@@ -0,0 +1,58 @@
1
+ const assert = require('assert');
2
+ const { __reproTestHooks } = require('../dist');
3
+
4
+ function makeEvent(index, payload) {
5
+ return {
6
+ t: index,
7
+ type: 'enter',
8
+ fn: `fn${index}`,
9
+ file: '/app/src/controllers/subjects/index.ts',
10
+ line: index + 1,
11
+ depth: 1,
12
+ spanId: index + 1,
13
+ parentSpanId: null,
14
+ args: [payload],
15
+ };
16
+ }
17
+
18
+ function main() {
19
+ const payload = {
20
+ __kind: 'http-request',
21
+ method: 'POST',
22
+ url: '/api/v1/subject_visits/697001aaac70cc3d60f21273/subjects/createNewSubject?tenantId=tgtherapeutics',
23
+ headers: {
24
+ x_http_user: 'x'.repeat(12000),
25
+ authorization: '[dropped]',
26
+ },
27
+ body: {
28
+ value: 'y'.repeat(12000),
29
+ },
30
+ };
31
+
32
+ const events = Array.from({ length: 120 }, (_, index) => makeEvent(index, payload));
33
+ const batches = __reproTestHooks.chunkTraceEventsForTransportForTest(
34
+ events,
35
+ 'RID_test_trace_batch_size',
36
+ 'A_test_trace_batch_size',
37
+ );
38
+
39
+ assert(batches.length > 1, `expected more than one batch, got ${batches.length}`);
40
+
41
+ batches.forEach((batch, index) => {
42
+ const size = __reproTestHooks.estimateTraceBatchSerializedBytesForTest(
43
+ batch,
44
+ 'RID_test_trace_batch_size',
45
+ 'A_test_trace_batch_size',
46
+ index,
47
+ );
48
+ assert(
49
+ size <= 512 * 1024,
50
+ `batch ${index} too large: ${size}`,
51
+ );
52
+ });
53
+
54
+ // eslint-disable-next-line no-console
55
+ console.log('trace batch size chunking OK');
56
+ }
57
+
58
+ main();