avo-inspector 3.2.0 → 3.2.3

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.
Files changed (84) hide show
  1. package/README.md +10 -0
  2. package/dist/AvoBatcher.d.ts +9 -3
  3. package/dist/AvoInspector.d.ts +4 -0
  4. package/dist/AvoNetworkCallsHandler.d.ts +80 -5
  5. package/dist/__tests__/UnboundPublicMethods_test.d.ts +1 -0
  6. package/dist/__tests__/constants.d.ts +2 -1
  7. package/dist/__tests__/helpers/internalGateway.d.ts +23 -0
  8. package/dist/eventSpec/AvoEventSpecCache.d.ts +7 -4
  9. package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +12 -1
  10. package/dist/eventSpec/AvoEventSpecFetcher.d.ts +6 -1
  11. package/dist/index.js +1 -1
  12. package/dist/lite/AvoBatcherLite.d.ts +9 -3
  13. package/dist/lite/AvoBatcherLite.js +46 -7
  14. package/dist/lite/AvoInspectorLite.d.ts +2 -0
  15. package/dist/lite/AvoInspectorLite.js +95 -15
  16. package/dist/lite/AvoNetworkCallsHandlerLite.d.ts +79 -5
  17. package/dist/lite/AvoNetworkCallsHandlerLite.js +300 -20
  18. package/package.json +10 -1
  19. package/dist/AvoBatcher.js +0 -87
  20. package/dist/AvoDeduplicator.js +0 -142
  21. package/dist/AvoEncryption.js +0 -176
  22. package/dist/AvoInspector.js +0 -435
  23. package/dist/AvoNetworkCallsHandler.js +0 -229
  24. package/dist/AvoSchemaParser.js +0 -95
  25. package/dist/AvoStreamId.js +0 -82
  26. package/dist/__mocks__/xhr.js +0 -14
  27. package/dist/__mocks__/xhrError.js +0 -14
  28. package/dist/__tests__/AvoEncryption.js +0 -272
  29. package/dist/__tests__/AvoEncryptionIntegration.js +0 -191
  30. package/dist/__tests__/AvoEventSpecFetcher.js +0 -377
  31. package/dist/__tests__/AvoInspector.js +0 -335
  32. package/dist/__tests__/Batcher.js +0 -214
  33. package/dist/__tests__/BatcherPublic.js +0 -139
  34. package/dist/__tests__/Deduplicator.js +0 -190
  35. package/dist/__tests__/EventSpecCache.js +0 -143
  36. package/dist/__tests__/EventValidator.js +0 -543
  37. package/dist/__tests__/InstallationId.js +0 -60
  38. package/dist/__tests__/NetworkCallsHandler.js +0 -222
  39. package/dist/__tests__/NetworkCallsHandlerModelA.js +0 -206
  40. package/dist/__tests__/Parsing.js +0 -115
  41. package/dist/__tests__/Sessions.d.ts +0 -1
  42. package/dist/__tests__/Sessions.js +0 -10
  43. package/dist/__tests__/Storage.d.ts +0 -1
  44. package/dist/__tests__/Storage.js +0 -173
  45. package/dist/__tests__/StreamId.d.ts +0 -1
  46. package/dist/__tests__/StreamId.js +0 -153
  47. package/dist/__tests__/constants.js +0 -48
  48. package/dist/esm/AvoBatcher.js +0 -75
  49. package/dist/esm/AvoDeduplicator.js +0 -137
  50. package/dist/esm/AvoEncryption.js +0 -277
  51. package/dist/esm/AvoGuid.js +0 -9
  52. package/dist/esm/AvoInspector.js +0 -470
  53. package/dist/esm/AvoInspectorEnv.js +0 -5
  54. package/dist/esm/AvoNetworkCallsHandler.js +0 -181
  55. package/dist/esm/AvoSchemaParser.js +0 -128
  56. package/dist/esm/AvoStorage.js +0 -176
  57. package/dist/esm/AvoStreamId.js +0 -140
  58. package/dist/esm/eventSpec/AvoEventSpecCache.js +0 -165
  59. package/dist/esm/eventSpec/AvoEventSpecFetchTypes.js +0 -6
  60. package/dist/esm/eventSpec/AvoEventSpecFetcher.js +0 -203
  61. package/dist/esm/eventSpec/EventValidator.js +0 -687
  62. package/dist/esm/eventSpec/index.js +0 -4
  63. package/dist/esm/index.js +0 -3
  64. package/dist/esm/utils.js +0 -38
  65. package/dist/esm/version.js +0 -2
  66. package/dist/eventSpec/AvoEventSpecCache.js +0 -181
  67. package/dist/eventSpec/AvoEventSpecFetcher.js +0 -290
  68. package/dist/eventSpec/EventValidator.js +0 -846
  69. package/dist/eventSpec/index.js +0 -20
  70. package/dist/version.d.ts +0 -1
  71. package/dist/version.js +0 -5
  72. /package/dist/__tests__/{AvoEncryption.d.ts → ApiKeyHeaderInjection_test.d.ts} +0 -0
  73. /package/dist/__tests__/{AvoEncryptionIntegration.d.ts → BrowserBootstrap_test.d.ts} +0 -0
  74. /package/dist/__tests__/{AvoEventSpecFetcher.d.ts → EventSpecGatewayCoordinates_test.d.ts} +0 -0
  75. /package/dist/__tests__/{AvoInspector.d.ts → InFlightQueueGaps_test.d.ts} +0 -0
  76. /package/dist/__tests__/{Batcher.d.ts → InFlightQueue_test.d.ts} +0 -0
  77. /package/dist/__tests__/{BatcherPublic.d.ts → NetworkCallsHandlerLite_test.d.ts} +0 -0
  78. /package/dist/__tests__/{Deduplicator.d.ts → PublicSurface_test.d.ts} +0 -0
  79. /package/dist/__tests__/{EventSpecCache.d.ts → TrackOptionsIntegration_test.d.ts} +0 -0
  80. /package/dist/__tests__/{EventValidator.d.ts → TrackOptionsTransport_test.d.ts} +0 -0
  81. /package/dist/__tests__/{InstallationId.d.ts → TrackingHeaders_test.d.ts} +0 -0
  82. /package/dist/__tests__/{NetworkCallsHandler.d.ts → V1WireBaseline_test.d.ts} +0 -0
  83. /package/dist/__tests__/{NetworkCallsHandlerModelA.d.ts → V2QueuedAttribution_test.d.ts} +0 -0
  84. /package/dist/__tests__/{Parsing.d.ts → V2StopSending_test.d.ts} +0 -0
@@ -1,687 +0,0 @@
1
- /**
2
- * EventValidator - Client-side validation of tracking events against the Avo Tracking Plan.
3
- *
4
- * This module validates property values against constraints:
5
- * - Pinned values (exact match required)
6
- * - Allowed values (must be in list)
7
- * - Regex patterns (must match pattern)
8
- * - Min/max ranges (numeric values must be in range)
9
- *
10
- * No schema validation (types/required) is performed - only value constraints.
11
- * Validation runs against ALL events/variants in the response.
12
- */
13
- import safe from 'safe-regex2';
14
- // =============================================================================
15
- // HELPER FUNCTIONS FOR NESTED PROPERTIES
16
- // =============================================================================
17
- /**
18
- * Deep copies a constraint mapping (pinnedValues, allowedValues, etc.),
19
- * including the arrays inside to avoid shared references.
20
- */
21
- function deepCopyConstraintMapping(mapping) {
22
- const result = {};
23
- for (const [key, arr] of Object.entries(mapping)) {
24
- result[key] = [...arr];
25
- }
26
- return result;
27
- }
28
- /**
29
- * Deep copies children constraints recursively.
30
- */
31
- function deepCopyChildren(children) {
32
- const result = {};
33
- for (const [propName, constraints] of Object.entries(children)) {
34
- result[propName] = {
35
- type: constraints.type,
36
- required: constraints.required,
37
- pinnedValues: constraints.pinnedValues
38
- ? deepCopyConstraintMapping(constraints.pinnedValues)
39
- : undefined,
40
- allowedValues: constraints.allowedValues
41
- ? deepCopyConstraintMapping(constraints.allowedValues)
42
- : undefined,
43
- regexPatterns: constraints.regexPatterns
44
- ? deepCopyConstraintMapping(constraints.regexPatterns)
45
- : undefined,
46
- minMaxRanges: constraints.minMaxRanges
47
- ? deepCopyConstraintMapping(constraints.minMaxRanges)
48
- : undefined,
49
- children: constraints.children
50
- ? deepCopyChildren(constraints.children)
51
- : undefined
52
- };
53
- }
54
- return result;
55
- }
56
- /**
57
- * Merges children constraints from source into target recursively.
58
- */
59
- function mergeChildren(target, source) {
60
- for (const [propName, sourceConstraints] of Object.entries(source)) {
61
- if (!target[propName]) {
62
- // New child property - deep copy it
63
- target[propName] = {
64
- type: sourceConstraints.type,
65
- required: sourceConstraints.required,
66
- pinnedValues: sourceConstraints.pinnedValues
67
- ? deepCopyConstraintMapping(sourceConstraints.pinnedValues)
68
- : undefined,
69
- allowedValues: sourceConstraints.allowedValues
70
- ? deepCopyConstraintMapping(sourceConstraints.allowedValues)
71
- : undefined,
72
- regexPatterns: sourceConstraints.regexPatterns
73
- ? deepCopyConstraintMapping(sourceConstraints.regexPatterns)
74
- : undefined,
75
- minMaxRanges: sourceConstraints.minMaxRanges
76
- ? deepCopyConstraintMapping(sourceConstraints.minMaxRanges)
77
- : undefined,
78
- children: sourceConstraints.children
79
- ? deepCopyChildren(sourceConstraints.children)
80
- : undefined
81
- };
82
- }
83
- else {
84
- // Merge into existing child property
85
- const targetConstraints = target[propName];
86
- mergeConstraintMappings(targetConstraints, sourceConstraints);
87
- // Recursively merge nested children
88
- if (sourceConstraints.children) {
89
- if (!targetConstraints.children) {
90
- targetConstraints.children = deepCopyChildren(sourceConstraints.children);
91
- }
92
- else {
93
- mergeChildren(targetConstraints.children, sourceConstraints.children);
94
- }
95
- }
96
- }
97
- }
98
- }
99
- /**
100
- * Merges constraint mappings (pinnedValues, allowedValues, etc.) from source into target.
101
- */
102
- function mergeConstraintMappings(target, source) {
103
- if (source.pinnedValues) {
104
- if (!target.pinnedValues) {
105
- target.pinnedValues = {};
106
- }
107
- for (const key of Object.keys(source.pinnedValues)) {
108
- if (target.pinnedValues[key]) {
109
- const merged = new Set(target.pinnedValues[key]);
110
- for (const id of source.pinnedValues[key]) {
111
- merged.add(id);
112
- }
113
- target.pinnedValues[key] = Array.from(merged);
114
- }
115
- else {
116
- target.pinnedValues[key] = [...source.pinnedValues[key]];
117
- }
118
- }
119
- }
120
- if (source.allowedValues) {
121
- if (!target.allowedValues) {
122
- target.allowedValues = {};
123
- }
124
- for (const key of Object.keys(source.allowedValues)) {
125
- if (target.allowedValues[key]) {
126
- const merged = new Set(target.allowedValues[key]);
127
- for (const id of source.allowedValues[key]) {
128
- merged.add(id);
129
- }
130
- target.allowedValues[key] = Array.from(merged);
131
- }
132
- else {
133
- target.allowedValues[key] = [...source.allowedValues[key]];
134
- }
135
- }
136
- }
137
- if (source.regexPatterns) {
138
- if (!target.regexPatterns) {
139
- target.regexPatterns = {};
140
- }
141
- for (const key of Object.keys(source.regexPatterns)) {
142
- if (target.regexPatterns[key]) {
143
- const merged = new Set(target.regexPatterns[key]);
144
- for (const id of source.regexPatterns[key]) {
145
- merged.add(id);
146
- }
147
- target.regexPatterns[key] = Array.from(merged);
148
- }
149
- else {
150
- target.regexPatterns[key] = [...source.regexPatterns[key]];
151
- }
152
- }
153
- }
154
- if (source.minMaxRanges) {
155
- if (!target.minMaxRanges) {
156
- target.minMaxRanges = {};
157
- }
158
- for (const key of Object.keys(source.minMaxRanges)) {
159
- if (target.minMaxRanges[key]) {
160
- const merged = new Set(target.minMaxRanges[key]);
161
- for (const id of source.minMaxRanges[key]) {
162
- merged.add(id);
163
- }
164
- target.minMaxRanges[key] = Array.from(merged);
165
- }
166
- else {
167
- target.minMaxRanges[key] = [...source.minMaxRanges[key]];
168
- }
169
- }
170
- }
171
- }
172
- // =============================================================================
173
- // CACHES
174
- // =============================================================================
175
- /**
176
- * Cache for compiled regex objects to avoid recompilation on every event.
177
- * Patterns are expected to be stable per session.
178
- * null values indicate patterns that were rejected (unsafe or invalid).
179
- */
180
- const regexCache = new Map();
181
- /**
182
- * Gets a compiled regex from cache or compiles and caches it.
183
- * Validates patterns with safe-regex2 before compilation to prevent ReDoS.
184
- * Returns null if the pattern is unsafe or invalid.
185
- * Null results are cached to avoid retrying bad patterns.
186
- */
187
- function getOrCompileRegex(pattern) {
188
- if (regexCache.has(pattern)) {
189
- return regexCache.get(pattern);
190
- }
191
- if (!safe(pattern)) {
192
- console.warn(`[Avo Inspector] Potentially unsafe regex pattern rejected, skipping constraint: ${pattern}`);
193
- regexCache.set(pattern, null);
194
- return null;
195
- }
196
- try {
197
- const regex = new RegExp(pattern);
198
- regexCache.set(pattern, regex);
199
- return regex;
200
- }
201
- catch (e) {
202
- console.warn(`[Avo Inspector] Invalid regex pattern, skipping constraint: ${pattern}`);
203
- regexCache.set(pattern, null);
204
- return null;
205
- }
206
- }
207
- /**
208
- * Cache for parsed allowed values JSON.
209
- * Key: JSON string, Value: Set of allowed values for O(1) lookup.
210
- */
211
- const allowedValuesCache = new Map();
212
- /**
213
- * Parses allowed values JSON string and returns a Set for O(1) lookup.
214
- * Results are cached to avoid repeated JSON.parse calls.
215
- * @returns Set of allowed values, or null if JSON is invalid
216
- */
217
- function getOrParseAllowedValues(jsonString) {
218
- let allowedSet = allowedValuesCache.get(jsonString);
219
- if (!allowedSet) {
220
- try {
221
- const allowedArray = JSON.parse(jsonString);
222
- allowedSet = new Set(allowedArray);
223
- allowedValuesCache.set(jsonString, allowedSet);
224
- }
225
- catch (e) {
226
- // Invalid JSON - return null
227
- return null;
228
- }
229
- }
230
- return allowedSet;
231
- }
232
- // =============================================================================
233
- // MAIN VALIDATION FUNCTION
234
- // =============================================================================
235
- /**
236
- * Validates runtime properties against all events in the EventSpecResponse.
237
- *
238
- * For each property:
239
- * - If property not in spec: no validation needed (empty result)
240
- * - If property in spec: check constraints and collect failed/passed eventIds
241
- * - Return whichever list is smaller for bandwidth optimization
242
- *
243
- * @param properties - The properties observed at runtime
244
- * @param specResponse - The EventSpecResponse from the backend
245
- * @returns ValidationResult with baseEventId, metadata, and per-property results
246
- */
247
- export function validateEvent(properties, specResponse) {
248
- // Collect all eventIds from all events
249
- const allEventIds = collectAllEventIds(specResponse.events);
250
- // Build lookup table: propertyName -> constraints (with embedded eventId mappings)
251
- const constraintsByProperty = collectConstraintsByPropertyName(specResponse.events);
252
- // Validate each runtime property against its constraints
253
- const propertyResults = {};
254
- for (const propName of Object.keys(properties)) {
255
- const value = properties[propName];
256
- const constraints = constraintsByProperty[propName];
257
- if (!constraints) {
258
- // Property not in spec - no constraints to fail
259
- propertyResults[propName] = {};
260
- }
261
- else {
262
- const result = validatePropertyConstraints(value, constraints, allEventIds);
263
- propertyResults[propName] = result;
264
- }
265
- }
266
- return {
267
- metadata: specResponse.metadata,
268
- propertyResults
269
- };
270
- }
271
- // =============================================================================
272
- // HELPER FUNCTIONS
273
- // =============================================================================
274
- /**
275
- * Collects all eventIds (baseEventId + variantIds) from all events.
276
- */
277
- function collectAllEventIds(events) {
278
- const ids = [];
279
- for (const event of events) {
280
- ids.push(event.baseEventId);
281
- for (let j = 0; j < event.variantIds.length; j++) {
282
- ids.push(event.variantIds[j]);
283
- }
284
- }
285
- return ids;
286
- }
287
- /**
288
- * Collects all property constraints from all events into a single lookup table.
289
- *
290
- * This is purely for lookup efficiency - each constraint already contains
291
- * its applicable eventIds, so no conflict resolution is needed.
292
- *
293
- * Example:
294
- * Event1.props.method = { pinnedValues: { "email": ["evt_1"] } }
295
- * Event2.props.method = { pinnedValues: { "phone": ["evt_2"] } }
296
- *
297
- * Result: { method: { pinnedValues: { "email": ["evt_1"], "phone": ["evt_2"] } } }
298
- */
299
- function collectConstraintsByPropertyName(events) {
300
- // Fast path: no events
301
- if (events.length === 0) {
302
- return {};
303
- }
304
- // Fast path: single event, return props directly (no aggregation needed)
305
- if (events.length === 1) {
306
- return events[0].props;
307
- }
308
- // Multiple events: aggregate constraints from all events
309
- const result = {};
310
- for (const event of events) {
311
- for (const [propName, constraints] of Object.entries(event.props)) {
312
- if (!result[propName]) {
313
- // First time seeing this property - initialize with copies
314
- result[propName] = {
315
- type: constraints.type,
316
- required: constraints.required,
317
- pinnedValues: constraints.pinnedValues
318
- ? { ...constraints.pinnedValues }
319
- : undefined,
320
- allowedValues: constraints.allowedValues
321
- ? { ...constraints.allowedValues }
322
- : undefined,
323
- regexPatterns: constraints.regexPatterns
324
- ? { ...constraints.regexPatterns }
325
- : undefined,
326
- minMaxRanges: constraints.minMaxRanges
327
- ? { ...constraints.minMaxRanges }
328
- : undefined,
329
- children: constraints.children
330
- ? deepCopyChildren(constraints.children)
331
- : undefined
332
- };
333
- }
334
- else {
335
- // Aggregate constraint mappings from additional events
336
- const existing = result[propName];
337
- mergeConstraintMappings(existing, constraints);
338
- // Recursively merge nested children
339
- if (constraints.children) {
340
- if (!existing.children) {
341
- existing.children = deepCopyChildren(constraints.children);
342
- }
343
- else {
344
- mergeChildren(existing.children, constraints.children);
345
- }
346
- }
347
- }
348
- }
349
- }
350
- return result;
351
- }
352
- /**
353
- * Maximum nesting depth for recursive value validation.
354
- * We validate prop (depth 0), prop.child1 (depth 1), but NOT prop.child1.child2 (depth 2+).
355
- * At depth 2+, we know there's an object but don't dive into its children for value validation.
356
- * This matches the behavior of schema validation.
357
- */
358
- const MAX_CHILD_DEPTH = 2;
359
- /**
360
- * Validates a property value against its constraints.
361
- * Returns the validation result with either failedEventIds or passedEventIds
362
- * (whichever is smaller for bandwidth optimization).
363
- *
364
- * For object properties with children:
365
- * - Skip value-level validation (pinned/allowed/regex/minmax)
366
- * - Recursively validate child properties (up to MAX_CHILD_DEPTH)
367
- *
368
- * For list properties (isList=true):
369
- * - If list of objects with children: validate each array item's children
370
- * - If list of primitives: validate each array item against constraints
371
- *
372
- * Depth limiting: We validate prop (depth 0), prop.child1 (depth 1), but stop
373
- * at prop.child1.child2 (depth 2+). This matches schema validation behavior.
374
- *
375
- * @param depth - Current recursion depth (internal use)
376
- */
377
- function validatePropertyConstraints(value, constraints, allEventIds, depth = 0) {
378
- const result = {};
379
- // Stop recursion at depth 2+ - we don't validate child2 and deeper
380
- // This matches schema validation which shows prop.child1.child2: object without diving in
381
- if (depth >= MAX_CHILD_DEPTH) {
382
- return result;
383
- }
384
- // Handle list types (isList=true)
385
- if (constraints.isList) {
386
- return validateListProperty(value, constraints, allEventIds, depth);
387
- }
388
- // Handle nested object properties with children (single object, not list)
389
- if (constraints.children) {
390
- return validateObjectProperty(value, constraints, allEventIds, depth);
391
- }
392
- // For primitive properties only: skip validation for null/undefined on non-required properties.
393
- // For optional properties, null/undefined means "not sent" which is valid.
394
- // Only required properties should fail validation when value is null/undefined.
395
- // Note: We check this AFTER checking for children, because object/list properties
396
- // need to validate their children even when the parent value is null/undefined.
397
- if ((value === null || value === undefined) && !constraints.required) {
398
- return result;
399
- }
400
- // Validate value constraints for primitive properties
401
- return validatePrimitiveProperty(value, constraints, allEventIds);
402
- }
403
- /**
404
- * Validates a list property (array of items).
405
- * For list of objects: validates each item's children.
406
- * For list of primitives: validates each item against constraints.
407
- * Any item failing causes the eventId to fail for that constraint.
408
- */
409
- function validateListProperty(value, constraints, allEventIds, depth) {
410
- const result = {};
411
- // If value is not an array, we can't validate list items
412
- if (!Array.isArray(value)) {
413
- // Non-array value for a list property - return empty result (type mismatch not validated here)
414
- return result;
415
- }
416
- // List of objects with children
417
- if (constraints.children) {
418
- const childrenResults = {};
419
- // For each child property, collect failures across ALL array items
420
- for (const [childName, childConstraints] of Object.entries(constraints.children)) {
421
- const aggregatedFailedIds = new Set();
422
- // Validate this child property in each array item
423
- for (const item of value) {
424
- const itemObj = (typeof item === "object" && item !== null && !Array.isArray(item))
425
- ? item
426
- : {};
427
- const childValue = itemObj[childName];
428
- const childResult = validatePropertyConstraints(childValue, childConstraints, allEventIds, depth + 1);
429
- // Collect failed IDs from this item
430
- if (childResult.failedEventIds) {
431
- for (const id of childResult.failedEventIds) {
432
- aggregatedFailedIds.add(id);
433
- }
434
- }
435
- // If passedEventIds is returned, failed = allEventIds - passedEventIds
436
- // Use Set for O(1) lookup instead of O(n) .includes()
437
- if (childResult.passedEventIds) {
438
- const passedSet = new Set(childResult.passedEventIds);
439
- for (const id of allEventIds) {
440
- if (!passedSet.has(id)) {
441
- aggregatedFailedIds.add(id);
442
- }
443
- }
444
- }
445
- }
446
- // Build result for this child property
447
- if (aggregatedFailedIds.size > 0) {
448
- const failedArray = Array.from(aggregatedFailedIds);
449
- const passedIds = allEventIds.filter((id) => !aggregatedFailedIds.has(id));
450
- if (passedIds.length < failedArray.length && passedIds.length > 0) {
451
- childrenResults[childName] = { passedEventIds: passedIds };
452
- }
453
- else {
454
- childrenResults[childName] = { failedEventIds: failedArray };
455
- }
456
- }
457
- }
458
- if (Object.keys(childrenResults).length > 0) {
459
- result.children = childrenResults;
460
- }
461
- return result;
462
- }
463
- // List of primitives - validate each item against constraints
464
- const failedIds = new Set();
465
- for (const item of value) {
466
- // Check pinned values for this item
467
- if (constraints.pinnedValues) {
468
- checkPinnedValues(item, constraints.pinnedValues, failedIds);
469
- }
470
- // Check allowed values for this item
471
- if (constraints.allowedValues) {
472
- checkAllowedValues(item, constraints.allowedValues, failedIds);
473
- }
474
- // Check regex patterns for this item
475
- if (constraints.regexPatterns) {
476
- checkRegexPatterns(item, constraints.regexPatterns, failedIds);
477
- }
478
- // Check min/max ranges for this item
479
- if (constraints.minMaxRanges) {
480
- checkMinMaxRanges(item, constraints.minMaxRanges, failedIds);
481
- }
482
- }
483
- return buildValidationResult(failedIds, allEventIds);
484
- }
485
- /**
486
- * Validates an object property (single object with children).
487
- */
488
- function validateObjectProperty(value, constraints, allEventIds, depth) {
489
- const result = {};
490
- const childrenResults = {};
491
- const valueObj = (typeof value === "object" && value !== null && !Array.isArray(value))
492
- ? value
493
- : {};
494
- for (const [childName, childConstraints] of Object.entries(constraints.children)) {
495
- const childValue = valueObj[childName];
496
- const childResult = validatePropertyConstraints(childValue, childConstraints, allEventIds, depth + 1);
497
- // Only include non-empty results
498
- if (childResult.failedEventIds || childResult.passedEventIds || childResult.children) {
499
- childrenResults[childName] = childResult;
500
- }
501
- }
502
- if (Object.keys(childrenResults).length > 0) {
503
- result.children = childrenResults;
504
- }
505
- return result;
506
- }
507
- /**
508
- * Validates a primitive property (not list, not object with children).
509
- */
510
- function validatePrimitiveProperty(value, constraints, allEventIds) {
511
- const failedIds = new Set();
512
- // Check pinned values
513
- if (constraints.pinnedValues) {
514
- checkPinnedValues(value, constraints.pinnedValues, failedIds);
515
- }
516
- // Check allowed values
517
- if (constraints.allowedValues) {
518
- checkAllowedValues(value, constraints.allowedValues, failedIds);
519
- }
520
- // Check regex patterns
521
- if (constraints.regexPatterns) {
522
- checkRegexPatterns(value, constraints.regexPatterns, failedIds);
523
- }
524
- // Check min/max ranges
525
- if (constraints.minMaxRanges) {
526
- checkMinMaxRanges(value, constraints.minMaxRanges, failedIds);
527
- }
528
- return buildValidationResult(failedIds, allEventIds);
529
- }
530
- /**
531
- * Builds the validation result from failed IDs, returning whichever list is smaller.
532
- */
533
- function buildValidationResult(failedIds, allEventIds) {
534
- const passedIds = allEventIds.filter((id) => !failedIds.has(id));
535
- const failedArray = Array.from(failedIds);
536
- // Return whichever list is smaller for bandwidth optimization
537
- // If both are empty, return empty object (no constraints)
538
- if (failedArray.length === 0 && passedIds.length === 0) {
539
- return {};
540
- }
541
- // Prefer passedEventIds only when strictly smaller than failedEventIds
542
- // When equal, prefer failedEventIds (more intuitive to see what failed)
543
- if (passedIds.length < failedArray.length && passedIds.length > 0) {
544
- return { passedEventIds: passedIds };
545
- }
546
- else if (failedArray.length > 0) {
547
- return { failedEventIds: failedArray };
548
- }
549
- else {
550
- return {};
551
- }
552
- }
553
- // =============================================================================
554
- // CONSTRAINT VALIDATION FUNCTIONS
555
- // =============================================================================
556
- /**
557
- * Adds all IDs from the array to the set.
558
- * Helper to reduce code duplication in constraint check functions.
559
- */
560
- function addIdsToSet(ids, set) {
561
- for (const id of ids) {
562
- set.add(id);
563
- }
564
- }
565
- /**
566
- * Converts runtime value to string for comparison.
567
- * - Primitives (null, undefined, boolean, number, string) -> String(value)
568
- * - Objects/Arrays -> JSON.stringify(value)
569
- */
570
- function convertValueToString(value) {
571
- if (value === null ||
572
- value === undefined ||
573
- typeof value === "boolean" ||
574
- typeof value === "number" ||
575
- typeof value === "string") {
576
- return String(value);
577
- }
578
- if (Array.isArray(value) || typeof value === "object") {
579
- try {
580
- return JSON.stringify(value);
581
- }
582
- catch (e) {
583
- // Circular reference or other serialization error
584
- console.warn(`[Avo Inspector] Failed to stringify value: ${e}`);
585
- return String(value);
586
- }
587
- }
588
- return String(value);
589
- }
590
- /**
591
- * Checks pinned values constraint.
592
- * For each pinnedValue -> eventIds entry, if runtime value !== pinnedValue, those eventIds FAIL.
593
- */
594
- function checkPinnedValues(value, pinnedValues, failedIds) {
595
- const stringValue = convertValueToString(value);
596
- for (const [pinnedValue, eventIds] of Object.entries(pinnedValues)) {
597
- if (stringValue !== pinnedValue) {
598
- // Value doesn't match this pinned value, so these eventIds fail
599
- addIdsToSet(eventIds, failedIds);
600
- }
601
- }
602
- }
603
- /**
604
- * Checks allowed values constraint.
605
- * For each "[...array]" -> eventIds entry, if runtime value NOT in array, those eventIds FAIL.
606
- * Uses cached Set for O(1) lookup instead of O(n) .includes().
607
- */
608
- function checkAllowedValues(value, allowedValues, failedIds) {
609
- const stringValue = convertValueToString(value);
610
- for (const [allowedArrayJson, eventIds] of Object.entries(allowedValues)) {
611
- const allowedSet = getOrParseAllowedValues(allowedArrayJson);
612
- if (allowedSet === null) {
613
- // Invalid JSON - skip this constraint
614
- console.warn(`[Avo Inspector] Invalid allowed values JSON: ${allowedArrayJson}`);
615
- continue;
616
- }
617
- if (!allowedSet.has(stringValue)) {
618
- // Value not in allowed list, so these eventIds fail
619
- addIdsToSet(eventIds, failedIds);
620
- }
621
- }
622
- }
623
- /**
624
- * Checks regex pattern constraint.
625
- * For each pattern -> eventIds entry, if runtime value doesn't match pattern, those eventIds FAIL.
626
- */
627
- function checkRegexPatterns(value, regexPatterns, failedIds) {
628
- // Only check regex for string values
629
- if (typeof value !== "string") {
630
- // Non-string values fail all regex constraints
631
- for (const eventIds of Object.values(regexPatterns)) {
632
- addIdsToSet(eventIds, failedIds);
633
- }
634
- return;
635
- }
636
- for (const [pattern, eventIds] of Object.entries(regexPatterns)) {
637
- const regex = getOrCompileRegex(pattern);
638
- if (regex === null) {
639
- // Unsafe or invalid pattern - skip constraint (fail-open)
640
- continue;
641
- }
642
- if (!regex.test(value)) {
643
- // Value doesn't match pattern, so these eventIds fail
644
- addIdsToSet(eventIds, failedIds);
645
- }
646
- }
647
- }
648
- /**
649
- * Checks min/max range constraint.
650
- * For each "min,max" -> eventIds entry, if runtime value < min OR > max, those eventIds FAIL.
651
- * Empty bounds are supported: "0," means min=0 with no max, ",100" means no min with max=100.
652
- */
653
- function checkMinMaxRanges(value, minMaxRanges, failedIds) {
654
- // Only check min/max for numeric values
655
- if (typeof value !== "number") {
656
- // Non-numeric values fail all min/max constraints
657
- for (const eventIds of Object.values(minMaxRanges)) {
658
- addIdsToSet(eventIds, failedIds);
659
- }
660
- return;
661
- }
662
- // NaN values fail all min/max constraints (comparisons with NaN are always false)
663
- if (Number.isNaN(value)) {
664
- console.warn(`[Avo Inspector] NaN value fails min/max constraint`);
665
- for (const eventIds of Object.values(minMaxRanges)) {
666
- addIdsToSet(eventIds, failedIds);
667
- }
668
- return;
669
- }
670
- for (const [rangeStr, eventIds] of Object.entries(minMaxRanges)) {
671
- const [minStr, maxStr] = rangeStr.split(",");
672
- // Handle empty bounds: empty string means no constraint on that side
673
- const hasMin = minStr !== "" && minStr !== undefined;
674
- const hasMax = maxStr !== "" && maxStr !== undefined;
675
- const min = hasMin ? parseFloat(minStr) : -Infinity;
676
- const max = hasMax ? parseFloat(maxStr) : Infinity;
677
- // Only check for invalid format if a bound was specified but couldn't be parsed
678
- if ((hasMin && isNaN(min)) || (hasMax && isNaN(max))) {
679
- console.warn(`[Avo Inspector] Invalid min/max range: ${rangeStr}`);
680
- continue;
681
- }
682
- if (value < min || value > max) {
683
- // Value out of range, so these eventIds fail
684
- addIdsToSet(eventIds, failedIds);
685
- }
686
- }
687
- }
@@ -1,4 +0,0 @@
1
- export * from "./AvoEventSpecFetchTypes";
2
- export * from "./AvoEventSpecCache";
3
- export * from "./AvoEventSpecFetcher";
4
- export * from "./EventValidator";
package/dist/esm/index.js DELETED
@@ -1,3 +0,0 @@
1
- export { AvoInspector } from "./AvoInspector";
2
- export { AvoInspectorEnv } from "./AvoInspectorEnv";
3
- export { AvoStreamId } from "./AvoStreamId";