@reproapp/node-sdk 0.0.45 → 0.0.47

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.
@@ -107,6 +107,9 @@ const selectSendBatch = (queue) => {
107
107
  if (first.priority === 'normal') {
108
108
  return { batch: first, queueItems: 1 };
109
109
  }
110
+ if (first.attempts > 0) {
111
+ return { batch: first, queueItems: 1 };
112
+ }
110
113
  const entries = first.entries.slice();
111
114
  let bytes = serializedBytes(entries);
112
115
  let queueItems = 1;
@@ -309,6 +312,14 @@ const drainIngestQueue = async () => {
309
312
  backoffMs = 0;
310
313
  continue;
311
314
  }
315
+ if (batch.priority === 'high' &&
316
+ selected.queueItems === 1 &&
317
+ ingestQueue.length > 1) {
318
+ const [failedItem] = ingestQueue.splice(0, 1);
319
+ if (failedItem) {
320
+ ingestQueue.push(failedItem);
321
+ }
322
+ }
312
323
  backoffMs = Math.min(DEFAULT_QUEUE_BACKOFF_MAX_MS, backoffMs ? backoffMs * 2 : 250);
313
324
  scheduleDrain(backoffMs);
314
325
  return;
package/dist/privacy.d.ts CHANGED
@@ -77,6 +77,7 @@ export type NormalizedRuntimePrivacyPolicy = {
77
77
  strength?: 'weak' | 'medium' | 'strict';
78
78
  statements: NormalizedStatement[];
79
79
  rawTextHints: NormalizedRawTextHint[];
80
+ fingerprint: string;
80
81
  };
81
82
  export declare function clearRuntimePrivacyProvenance(): void;
82
83
  export type RuntimePrivacyFetchResult = {
package/dist/privacy.js CHANGED
@@ -7,7 +7,10 @@ const privacy_redaction_1 = require("./privacy-redaction");
7
7
  const EMPTY_RUNTIME_PRIVACY_POLICY = {
8
8
  statements: [],
9
9
  rawTextHints: [],
10
+ fingerprint: 'empty',
10
11
  };
12
+ const runtimePrivacyStatementIndexes = new WeakMap();
13
+ const RUNTIME_PRIVACY_CANDIDATE_CACHE_MAX_ENTRIES = 2048;
11
14
  const RUNTIME_PRIVACY_PROVENANCE_MAX_ENTRIES = 1000;
12
15
  const RUNTIME_PRIVACY_PROVENANCE_MAX_SERIALIZED_LENGTH = 200000;
13
16
  let objectRuntimePrivacyProvenance = new WeakMap();
@@ -165,21 +168,24 @@ function normalizeRuntimePrivacyPolicy(raw) {
165
168
  const policy = raw;
166
169
  const flattened = flattenStatements(policy);
167
170
  const rawTextHints = normalizeRawTextHints(policy.rawTextHints);
168
- const statements = flattened
171
+ const statements = deduplicateNormalizedStatements(flattened
169
172
  .map((entry, index) => normalizeStatement(entry, index))
170
173
  .filter((entry) => entry !== null)
171
- .sort((left, right) => left.priority - right.priority);
174
+ .sort((left, right) => left.priority - right.priority));
172
175
  if (!statements.length) {
173
176
  return null;
174
177
  }
175
- return {
178
+ const normalizedPolicy = {
176
179
  environment: typeof policy.environment === 'string' ? policy.environment : undefined,
177
180
  strength: policy.strength === 'weak' || policy.strength === 'medium' || policy.strength === 'strict'
178
181
  ? policy.strength
179
182
  : undefined,
180
183
  statements,
181
184
  rawTextHints,
185
+ fingerprint: runtimePrivacyPolicyFingerprint(typeof policy.environment === 'string' ? policy.environment : undefined, typeof policy.strength === 'string' ? policy.strength : undefined, statements, rawTextHints),
182
186
  };
187
+ runtimePrivacyStatementIndexes.set(normalizedPolicy, buildRuntimePrivacyStatementIndex(statements));
188
+ return normalizedPolicy;
183
189
  }
184
190
  exports.normalizeRuntimePrivacyPolicy = normalizeRuntimePrivacyPolicy;
185
191
  function applyRuntimePrivacyPolicy(policy, surface, value, context) {
@@ -267,6 +273,287 @@ function normalizeStatement(input, index) {
267
273
  descendantRawTextMode: normalizeRawTextMode(input?.apply?.descendantRawTextMode ?? input?.descendantRawTextMode),
268
274
  };
269
275
  }
276
+ function deduplicateNormalizedStatements(statements) {
277
+ const seen = new Set();
278
+ const deduplicated = [];
279
+ for (const statement of statements) {
280
+ const key = normalizedStatementSemanticKey(statement);
281
+ if (seen.has(key)) {
282
+ continue;
283
+ }
284
+ seen.add(key);
285
+ deduplicated.push(statement);
286
+ }
287
+ return deduplicated;
288
+ }
289
+ function normalizedStatementSemanticKey(statement) {
290
+ return JSON.stringify({
291
+ scope: statement.scope,
292
+ selectorTokens: statement.selectorTokens,
293
+ selectorLeaves: statement.selectorLeaves,
294
+ action: statement.action,
295
+ pattern: statement.pattern
296
+ ? {
297
+ source: statement.pattern.source,
298
+ flags: statement.pattern.flags,
299
+ }
300
+ : null,
301
+ targetMatchers: statement.targetMatchers.map((matcher) => ({
302
+ surfaces: matcher.surfaces,
303
+ path: matcher.path,
304
+ })),
305
+ textPolicy: statement.textPolicy ?? null,
306
+ rawTextMode: statement.rawTextMode ?? null,
307
+ rawTextHintNames: statement.rawTextHintNames ?? null,
308
+ descendantRawTextMode: statement.descendantRawTextMode ?? null,
309
+ });
310
+ }
311
+ function runtimePrivacyPolicyFingerprint(environment, strength, statements, rawTextHints) {
312
+ const serialized = JSON.stringify({
313
+ environment: environment ?? null,
314
+ strength: strength ?? null,
315
+ statements: statements.map(normalizedStatementSemanticKey),
316
+ rawTextHints: rawTextHints.map((hint) => ({
317
+ name: hint.name,
318
+ action: hint.action,
319
+ pattern: hint.pattern.source,
320
+ flags: hint.pattern.flags,
321
+ tokenLabel: hint.tokenLabel,
322
+ })),
323
+ });
324
+ return (0, crypto_1.createHash)('sha256').update(serialized).digest('hex');
325
+ }
326
+ function createFunctionStatementTrieNode() {
327
+ return {
328
+ children: new Map(),
329
+ terminal: [],
330
+ subtree: [],
331
+ };
332
+ }
333
+ function appendUniqueStatement(target, statement) {
334
+ if (!target.includes(statement)) {
335
+ target.push(statement);
336
+ }
337
+ }
338
+ function functionSelectorSegments(value) {
339
+ return normalizeFunctionSelector(value)
340
+ .split('.')
341
+ .map((segment) => segment.trim())
342
+ .filter(Boolean)
343
+ .reverse();
344
+ }
345
+ function addFunctionStatementToTrie(root, selector, statement) {
346
+ const segments = functionSelectorSegments(selector);
347
+ if (!segments.length) {
348
+ return;
349
+ }
350
+ let node = root;
351
+ for (const segment of segments) {
352
+ let child = node.children.get(segment);
353
+ if (!child) {
354
+ child = createFunctionStatementTrieNode();
355
+ node.children.set(segment, child);
356
+ }
357
+ node = child;
358
+ appendUniqueStatement(node.subtree, statement);
359
+ }
360
+ appendUniqueStatement(node.terminal, statement);
361
+ }
362
+ function createRuntimePrivacySurfaceIndex() {
363
+ return {
364
+ contextualStatements: [],
365
+ fieldStatementsBySuffix: new Map(),
366
+ fieldWildcardTargetStatements: [],
367
+ };
368
+ }
369
+ function appendStatementToMap(target, key, statement) {
370
+ if (!key) {
371
+ return;
372
+ }
373
+ const current = target.get(key);
374
+ if (current) {
375
+ appendUniqueStatement(current, statement);
376
+ return;
377
+ }
378
+ target.set(key, [statement]);
379
+ }
380
+ function explicitTargetSurfaces(statement) {
381
+ if (!statement.targetMatchers.length) {
382
+ return null;
383
+ }
384
+ return new Set(statement.targetMatchers.flatMap((matcher) => matcher.surfaces));
385
+ }
386
+ function scopeCanMatchSurface(statement, surface) {
387
+ if (statement.scope === 'FUNCTION') {
388
+ return surface.startsWith('trace.');
389
+ }
390
+ if (statement.scope === 'ROUTE') {
391
+ return surface.startsWith('request.') || surface === 'response.body';
392
+ }
393
+ if (statement.scope === 'SCHEMA' || statement.scope === 'QUERY') {
394
+ return surface.startsWith('db.');
395
+ }
396
+ return true;
397
+ }
398
+ function addFieldStatementToSurfaceIndex(index, statement) {
399
+ for (const selector of statement.selectorLeaves) {
400
+ appendStatementToMap(index.fieldStatementsBySuffix, normalizeComparableToken(selector), statement);
401
+ }
402
+ for (const matcher of statement.targetMatchers) {
403
+ const terminal = matcher.path?.[matcher.path.length - 1];
404
+ if (!terminal) {
405
+ continue;
406
+ }
407
+ if (terminal === '*') {
408
+ appendUniqueStatement(index.fieldWildcardTargetStatements, statement);
409
+ continue;
410
+ }
411
+ appendStatementToMap(index.fieldStatementsBySuffix, normalizeComparableToken(terminal), statement);
412
+ }
413
+ }
414
+ function buildRuntimePrivacyStatementIndex(statements) {
415
+ const bySurface = new Map(ALL_SURFACES.map((surface) => [surface, createRuntimePrivacySurfaceIndex()]));
416
+ const functionStatements = createFunctionStatementTrieNode();
417
+ let hasNumericTargetSegments = false;
418
+ for (const statement of statements) {
419
+ for (const matcher of statement.targetMatchers) {
420
+ if (matcher.path?.some((segment) => /^\d+$/.test(segment))) {
421
+ hasNumericTargetSegments = true;
422
+ }
423
+ }
424
+ if (statement.scope === 'FUNCTION') {
425
+ for (const selector of statement.selectorTokens) {
426
+ addFunctionStatementToTrie(functionStatements, selector, statement);
427
+ }
428
+ continue;
429
+ }
430
+ const targetSurfaces = explicitTargetSurfaces(statement);
431
+ for (const surface of ALL_SURFACES) {
432
+ if (!scopeCanMatchSurface(statement, surface)) {
433
+ continue;
434
+ }
435
+ // FIELD protections deliberately have cross-surface fallback semantics.
436
+ if (statement.scope !== 'FIELD' &&
437
+ targetSurfaces &&
438
+ !targetSurfaces.has(surface)) {
439
+ continue;
440
+ }
441
+ const surfaceIndex = bySurface.get(surface);
442
+ if (statement.scope === 'FIELD') {
443
+ addFieldStatementToSurfaceIndex(surfaceIndex, statement);
444
+ }
445
+ else {
446
+ appendUniqueStatement(surfaceIndex.contextualStatements, statement);
447
+ }
448
+ }
449
+ }
450
+ return {
451
+ bySurface,
452
+ functionStatements,
453
+ candidateCache: new Map(),
454
+ hasNumericTargetSegments,
455
+ };
456
+ }
457
+ function getRuntimePrivacyStatementIndex(policy) {
458
+ const existing = runtimePrivacyStatementIndexes.get(policy);
459
+ if (existing) {
460
+ return existing;
461
+ }
462
+ const created = buildRuntimePrivacyStatementIndex(policy.statements);
463
+ runtimePrivacyStatementIndexes.set(policy, created);
464
+ return created;
465
+ }
466
+ function matchingFunctionStatements(root, functionName) {
467
+ const segments = functionSelectorSegments(functionName);
468
+ if (!segments.length) {
469
+ return [];
470
+ }
471
+ const matches = new Set();
472
+ let node = root;
473
+ for (let index = 0; index < segments.length; index += 1) {
474
+ node = node.children.get(segments[index]);
475
+ if (!node) {
476
+ break;
477
+ }
478
+ for (const statement of node.terminal) {
479
+ matches.add(statement);
480
+ }
481
+ if (index === segments.length - 1) {
482
+ for (const statement of node.subtree) {
483
+ matches.add(statement);
484
+ }
485
+ }
486
+ }
487
+ return Array.from(matches);
488
+ }
489
+ function normalizedPathSuffixes(path, preserveNumericSegments) {
490
+ const joined = path
491
+ .filter((segment) => segment !== '*' &&
492
+ (preserveNumericSegments || !/^\d+$/.test(segment)))
493
+ .map(normalizeComparableToken)
494
+ .filter(Boolean)
495
+ .join('.');
496
+ if (!joined) {
497
+ return [];
498
+ }
499
+ const segments = joined.split('.').filter(Boolean);
500
+ const suffixes = [];
501
+ for (let index = 0; index < segments.length; index += 1) {
502
+ suffixes.push(segments.slice(index).join('.'));
503
+ }
504
+ return suffixes;
505
+ }
506
+ function runtimePrivacyCandidateCacheKey(index, surface, context, path) {
507
+ const pathKey = path
508
+ .map((segment) => !index.hasNumericTargetSegments && /^\d+$/.test(segment)
509
+ ? '*'
510
+ : segment)
511
+ .join('.');
512
+ return [
513
+ surface,
514
+ normalizeFunctionSelector(context.trace?.fn?.trim() ?? ''),
515
+ context.routeKey?.trim() ?? '',
516
+ String(context.db?.type ?? ''),
517
+ String(context.db?.collection ?? ''),
518
+ String(context.db?.op ?? ''),
519
+ pathKey,
520
+ ].join('\u0000');
521
+ }
522
+ function candidateStatementsForMatch(policy, surface, context, path) {
523
+ const index = getRuntimePrivacyStatementIndex(policy);
524
+ const cacheKey = runtimePrivacyCandidateCacheKey(index, surface, context, path);
525
+ const cached = index.candidateCache.get(cacheKey);
526
+ if (cached) {
527
+ return cached;
528
+ }
529
+ const surfaceIndex = index.bySurface.get(surface);
530
+ const candidateSet = new Set(surfaceIndex?.contextualStatements ?? []);
531
+ if (surfaceIndex && path.length) {
532
+ for (const suffix of normalizedPathSuffixes(path, index.hasNumericTargetSegments)) {
533
+ for (const statement of surfaceIndex.fieldStatementsBySuffix.get(suffix) ?? []) {
534
+ candidateSet.add(statement);
535
+ }
536
+ }
537
+ for (const statement of surfaceIndex.fieldWildcardTargetStatements) {
538
+ candidateSet.add(statement);
539
+ }
540
+ }
541
+ if (surface.startsWith('trace.')) {
542
+ const functionName = context.trace?.fn?.trim() ?? '';
543
+ if (functionName) {
544
+ for (const statement of matchingFunctionStatements(index.functionStatements, functionName)) {
545
+ candidateSet.add(statement);
546
+ }
547
+ }
548
+ }
549
+ const candidates = Array.from(candidateSet)
550
+ .sort((left, right) => left.priority - right.priority);
551
+ if (index.candidateCache.size >= RUNTIME_PRIVACY_CANDIDATE_CACHE_MAX_ENTRIES) {
552
+ index.candidateCache.clear();
553
+ }
554
+ index.candidateCache.set(cacheKey, candidates);
555
+ return candidates;
556
+ }
270
557
  function resolveEffectiveAction(baseAction, value, path) {
271
558
  const safetyFloor = deriveSensitiveLeafFloorAction(value, path);
272
559
  if (!safetyFloor) {
@@ -938,7 +1225,7 @@ async function transformSerializedStructuredStringAsChildrenAsync(policy, surfac
938
1225
  }
939
1226
  function matchStatement(policy, surface, value, context, path) {
940
1227
  let candidates = [];
941
- for (const statement of policy.statements) {
1228
+ for (const statement of candidateStatementsForMatch(policy, surface, context, path)) {
942
1229
  if (shouldIgnoreBroadContainerRuleOnDescendant(statement, surface, path)) {
943
1230
  continue;
944
1231
  }
@@ -1247,14 +1534,26 @@ function querySelectorMatches(selectors, surface, context) {
1247
1534
  function patternMatches(pattern, leaf, joinedPath, value) {
1248
1535
  if (!pattern)
1249
1536
  return false;
1250
- if (leaf && pattern.test(leaf))
1537
+ if (leaf && testRuntimePrivacyPattern(pattern, leaf))
1251
1538
  return true;
1252
- if (joinedPath && pattern.test(joinedPath))
1539
+ if (joinedPath && testRuntimePrivacyPattern(pattern, joinedPath))
1253
1540
  return true;
1254
- if (typeof value === 'string' && pattern.test(value))
1541
+ if (typeof value === 'string' && testRuntimePrivacyPattern(pattern, value))
1255
1542
  return true;
1256
1543
  return false;
1257
1544
  }
1545
+ function testRuntimePrivacyPattern(pattern, value) {
1546
+ // Generated policies may contain global/sticky expressions. RegExp.test mutates
1547
+ // lastIndex for those flags, which otherwise makes identical captures alternate
1548
+ // between matched and unmatched.
1549
+ pattern.lastIndex = 0;
1550
+ try {
1551
+ return pattern.test(value);
1552
+ }
1553
+ finally {
1554
+ pattern.lastIndex = 0;
1555
+ }
1556
+ }
1258
1557
  function functionSelectorMatches(selector, fn) {
1259
1558
  const normalizedSelector = normalizeFunctionSelector(selector);
1260
1559
  const normalizedFn = normalizeFunctionSelector(fn);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.45",
3
+ "version": "0.0.47",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,8 +12,8 @@
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/init-auto-http-server.test.js && node test/backend-live-mode.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/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.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-service-scope.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
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 && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
15
+ "test": "npm run build && node test/trace-source-exclude.test.js && 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/init-auto-http-server.test.js && node test/backend-live-mode.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/sdk-background-timeout-isolation.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-high-priority-fairness.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.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-service-scope.test.js && node test/privacy-policy-index.test.js && node test/policy-capture-attestation.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
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 && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/policy-session-completion.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "express": "^5.1.0",