@reproapp/node-sdk 0.0.46 → 0.0.48
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 +16 -3
- package/dist/index.js +327 -159
- package/dist/ingest/client.js +11 -0
- package/dist/privacy.d.ts +1 -0
- package/dist/privacy.js +306 -7
- package/dist/semantic-serialization.d.ts +9 -0
- package/dist/semantic-serialization.js +171 -0
- package/package.json +3 -3
- package/src/index.ts +513 -144
- package/src/ingest/client.ts +13 -0
- package/src/privacy.ts +409 -7
- package/src/semantic-serialization.ts +182 -0
- package/test/content-addressed-mongodb.testcontainers.test.js +348 -0
- package/test/content-addressed-trace-values.test.js +264 -0
- package/test/fixtures/semantic-serialization-trace-controller.js +85 -0
- package/test/fixtures/semantic-serialization-trace-server.js +19 -0
- package/test/ingest-high-priority-fairness.test.js +76 -0
- package/test/policy-capture-attestation.test.js +234 -0
- package/test/policy-session-completion.testcontainers.test.js +271 -0
- package/test/privacy-policy-index.test.js +194 -0
- package/test/sdk-background-timeout-isolation.test.js +39 -0
- package/test/semantic-serialization-trace.test.js +152 -0
- package/test/semantic-serialization.test.js +229 -0
- package/test/session-storage-budget.test.js +298 -0
- package/test/trace-batch-size.test.js +37 -0
package/dist/ingest/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
1537
|
+
if (leaf && testRuntimePrivacyPattern(pattern, leaf))
|
|
1251
1538
|
return true;
|
|
1252
|
-
if (joinedPath && pattern
|
|
1539
|
+
if (joinedPath && testRuntimePrivacyPattern(pattern, joinedPath))
|
|
1253
1540
|
return true;
|
|
1254
|
-
if (typeof value === 'string' && pattern
|
|
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);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type SemanticProjection = {
|
|
2
|
+
matched: true;
|
|
3
|
+
value: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Projects known runtime machinery into stable, non-recursive descriptions.
|
|
7
|
+
* Unknown application values deliberately return null and remain fully captured.
|
|
8
|
+
*/
|
|
9
|
+
export declare function projectSemanticInfrastructureValue(value: unknown): SemanticProjection | null;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectSemanticInfrastructureValue = void 0;
|
|
4
|
+
function constructorName(value) {
|
|
5
|
+
if (!value || typeof value !== 'object')
|
|
6
|
+
return '';
|
|
7
|
+
try {
|
|
8
|
+
const prototype = Object.getPrototypeOf(value);
|
|
9
|
+
const constructor = prototype
|
|
10
|
+
? Object.getOwnPropertyDescriptor(prototype, 'constructor')?.value
|
|
11
|
+
: undefined;
|
|
12
|
+
const name = constructor?.name;
|
|
13
|
+
return typeof name === 'string' ? name : '';
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function ownDataDescriptors(value) {
|
|
20
|
+
const out = {};
|
|
21
|
+
let descriptors;
|
|
22
|
+
try {
|
|
23
|
+
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
|
|
30
|
+
out[key] = descriptor;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function method(value, key) {
|
|
36
|
+
try {
|
|
37
|
+
let current = value;
|
|
38
|
+
while (current) {
|
|
39
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
40
|
+
if (descriptor) {
|
|
41
|
+
return Object.prototype.hasOwnProperty.call(descriptor, 'value') &&
|
|
42
|
+
typeof descriptor.value === 'function';
|
|
43
|
+
}
|
|
44
|
+
current = Object.getPrototypeOf(current);
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function primitiveStateValue(value) {
|
|
53
|
+
if (value === null)
|
|
54
|
+
return null;
|
|
55
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === 'bigint')
|
|
59
|
+
return value.toString();
|
|
60
|
+
if (value instanceof Date)
|
|
61
|
+
return value.toISOString();
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function semanticValue(category, className, details = {}) {
|
|
65
|
+
return {
|
|
66
|
+
matched: true,
|
|
67
|
+
value: {
|
|
68
|
+
__kind: 'repro-semantic',
|
|
69
|
+
category,
|
|
70
|
+
...(className ? { className } : {}),
|
|
71
|
+
...details,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function projectConfigurationService(value, className) {
|
|
76
|
+
if (!/(^|Api|App|Runtime|Environment)ConfigService$/i.test(className))
|
|
77
|
+
return null;
|
|
78
|
+
if (!method(value, 'get') && !method(value, 'getOrThrow'))
|
|
79
|
+
return null;
|
|
80
|
+
return semanticValue('configuration-service', className);
|
|
81
|
+
}
|
|
82
|
+
function projectLogger(value, className) {
|
|
83
|
+
if (!/(Logger|LoggerService)$/i.test(className))
|
|
84
|
+
return null;
|
|
85
|
+
const descriptors = ownDataDescriptors(value);
|
|
86
|
+
const context = primitiveStateValue(descriptors.context?.value);
|
|
87
|
+
return semanticValue('logger', className, context !== undefined ? { context } : {});
|
|
88
|
+
}
|
|
89
|
+
function projectNestInfrastructure(value, className) {
|
|
90
|
+
if (!/(ModuleRef|ModulesContainer|InstanceWrapper|NestContainer|Injector|DiscoveryService|ExecutionContextHost|ArgumentsHost|HttpAdapterHost)$/i.test(className)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return semanticValue('nestjs-infrastructure', className);
|
|
94
|
+
}
|
|
95
|
+
function projectRedis(value, className) {
|
|
96
|
+
const looksLikeRedis = /Redis|Commander|Cluster/i.test(className) && (method(value, 'get') || method(value, 'set') || method(value, 'pipeline') || method(value, 'multi'));
|
|
97
|
+
if (!looksLikeRedis)
|
|
98
|
+
return null;
|
|
99
|
+
const descriptors = ownDataDescriptors(value);
|
|
100
|
+
const status = primitiveStateValue(descriptors.status?.value);
|
|
101
|
+
return semanticValue('redis-client', className, status !== undefined ? { status } : {});
|
|
102
|
+
}
|
|
103
|
+
function projectKafka(value, className) {
|
|
104
|
+
if (!/Kafka/i.test(className))
|
|
105
|
+
return null;
|
|
106
|
+
if (method(value, 'send') && method(value, 'connect') && method(value, 'disconnect')) {
|
|
107
|
+
return semanticValue('kafka-producer', className || 'KafkaProducer');
|
|
108
|
+
}
|
|
109
|
+
if (method(value, 'subscribe') && method(value, 'run') && method(value, 'connect')) {
|
|
110
|
+
return semanticValue('kafka-consumer', className || 'KafkaConsumer');
|
|
111
|
+
}
|
|
112
|
+
if (method(value, 'listTopics') && method(value, 'connect') && method(value, 'disconnect')) {
|
|
113
|
+
return semanticValue('kafka-admin', className || 'KafkaAdmin');
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function projectAmqp(value, className) {
|
|
118
|
+
if (!/(Amqp|Channel|ChannelModel|ConfirmChannel)/i.test(className))
|
|
119
|
+
return null;
|
|
120
|
+
if (method(value, 'publish') && method(value, 'sendToQueue') && method(value, 'ack')) {
|
|
121
|
+
return semanticValue('amqp-channel', className || 'AmqpChannel');
|
|
122
|
+
}
|
|
123
|
+
if (method(value, 'createChannel') && method(value, 'close')) {
|
|
124
|
+
return semanticValue('amqp-connection', className || 'AmqpConnection');
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function projectPostgres(value, className) {
|
|
129
|
+
if (/^(Pool|BoundPool)$/i.test(className) && method(value, 'query') && method(value, 'connect')) {
|
|
130
|
+
return semanticValue('postgres-pool', className);
|
|
131
|
+
}
|
|
132
|
+
if (/^(Client|PoolClient)$/i.test(className) && method(value, 'query')) {
|
|
133
|
+
return semanticValue('postgres-client', className);
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function projectTypeOrm(value, className) {
|
|
138
|
+
if (/^DataSource$/i.test(className) && method(value, 'createQueryRunner')) {
|
|
139
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
140
|
+
}
|
|
141
|
+
if (/^EntityManager$/i.test(className) && method(value, 'getRepository') && method(value, 'query')) {
|
|
142
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
143
|
+
}
|
|
144
|
+
if (/^QueryRunner$/i.test(className) && method(value, 'query') && method(value, 'release')) {
|
|
145
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
146
|
+
}
|
|
147
|
+
if (/^Repository$/i.test(className) && method(value, 'find') && method(value, 'save')) {
|
|
148
|
+
return semanticValue('typeorm-infrastructure', className);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Projects known runtime machinery into stable, non-recursive descriptions.
|
|
154
|
+
* Unknown application values deliberately return null and remain fully captured.
|
|
155
|
+
*/
|
|
156
|
+
function projectSemanticInfrastructureValue(value) {
|
|
157
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
158
|
+
return null;
|
|
159
|
+
const className = constructorName(value);
|
|
160
|
+
if (!className || className === 'Object')
|
|
161
|
+
return null;
|
|
162
|
+
return (projectConfigurationService(value, className) ||
|
|
163
|
+
projectLogger(value, className) ||
|
|
164
|
+
projectNestInfrastructure(value, className) ||
|
|
165
|
+
projectRedis(value, className) ||
|
|
166
|
+
projectKafka(value, className) ||
|
|
167
|
+
projectAmqp(value, className) ||
|
|
168
|
+
projectPostgres(value, className) ||
|
|
169
|
+
projectTypeOrm(value, className));
|
|
170
|
+
}
|
|
171
|
+
exports.projectSemanticInfrastructureValue = projectSemanticInfrastructureValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
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/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/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/semantic-serialization.test.js && node test/semantic-serialization-trace.test.js && node test/content-addressed-trace-values.test.js && node test/session-storage-budget.test.js && 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/content-addressed-mongodb.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",
|