node-avo-inspector 1.1.0 → 1.2.0-alpha.1

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 (95) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/.claude/settings.local.json +11 -0
  3. package/.claude/worktrees/release-1.1.1/LICENSE +21 -0
  4. package/.claude/worktrees/release-1.1.1/README.md +69 -0
  5. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +30 -0
  6. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +15 -0
  7. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +6 -0
  8. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +34 -0
  9. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +30 -0
  10. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +18 -0
  11. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +15 -0
  12. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +11 -0
  13. package/.claude/worktrees/release-1.1.1/package-lock.json +4039 -0
  14. package/.claude/worktrees/release-1.1.1/package.json +48 -0
  15. package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +11 -0
  16. package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +27 -0
  17. package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +146 -0
  18. package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +90 -0
  19. package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +9 -0
  20. package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +383 -0
  21. package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +8 -0
  22. package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +267 -0
  23. package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +115 -0
  24. package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +16 -0
  25. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +515 -0
  26. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +242 -0
  27. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +241 -0
  28. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +360 -0
  29. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +72 -0
  30. package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +191 -0
  31. package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +231 -0
  32. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +179 -0
  33. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +208 -0
  34. package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +153 -0
  35. package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +63 -0
  36. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +109 -0
  37. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
  38. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +106 -0
  39. package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +74 -0
  40. package/.claude/worktrees/release-1.1.1/src/index.ts +2 -0
  41. package/.claude/worktrees/release-1.1.1/src/utils.ts +49 -0
  42. package/.claude/worktrees/release-1.1.1/tsconfig.json +13 -0
  43. package/.claude/worktrees/release-1.1.1/yarn.lock +2310 -0
  44. package/.github/workflows/claude.yml +55 -0
  45. package/dist/AvoDeduplicator.d.ts +4 -3
  46. package/dist/AvoDeduplicator.js +25 -24
  47. package/dist/AvoEncryption.d.ts +26 -0
  48. package/dist/AvoEncryption.js +77 -0
  49. package/dist/AvoInspector.d.ts +34 -3
  50. package/dist/AvoInspector.js +196 -40
  51. package/dist/AvoNetworkCallsHandler.d.ts +39 -13
  52. package/dist/AvoNetworkCallsHandler.js +127 -33
  53. package/dist/AvoStreamId.d.ts +5 -0
  54. package/dist/AvoStreamId.js +15 -0
  55. package/dist/__tests__/TestAvoEncryption.d.ts +1 -0
  56. package/dist/__tests__/TestAvoEncryption.js +288 -0
  57. package/dist/__tests__/TestAvoEventSpecCache.d.ts +1 -0
  58. package/dist/__tests__/TestAvoEventSpecCache.js +194 -0
  59. package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +1 -0
  60. package/dist/__tests__/TestAvoEventSpecFetcher.js +219 -0
  61. package/dist/__tests__/TestAvoInspector.js +76 -2
  62. package/dist/__tests__/TestAvoStreamId.d.ts +1 -0
  63. package/dist/__tests__/TestAvoStreamId.js +55 -0
  64. package/dist/__tests__/TestDeduplicator.js +3 -0
  65. package/dist/__tests__/TestEventValidator.d.ts +1 -0
  66. package/dist/__tests__/TestEventValidator.js +196 -0
  67. package/dist/__tests__/TestNetworkCallsHandler.js +37 -10
  68. package/dist/__tests__/TestNetworkCallsHandlerPublic.js +93 -58
  69. package/dist/eventSpec/AvoEventSpecCache.d.ts +19 -0
  70. package/dist/eventSpec/AvoEventSpecCache.js +100 -0
  71. package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +41 -0
  72. package/dist/eventSpec/AvoEventSpecFetchTypes.js +5 -0
  73. package/dist/eventSpec/AvoEventSpecFetcher.d.ts +25 -0
  74. package/dist/eventSpec/AvoEventSpecFetcher.js +159 -0
  75. package/dist/eventSpec/EventValidator.d.ts +15 -0
  76. package/dist/eventSpec/EventValidator.js +53 -0
  77. package/package.json +4 -2
  78. package/src/AvoDeduplicator.ts +31 -27
  79. package/src/AvoEncryption.ts +90 -0
  80. package/src/AvoInspector.ts +278 -54
  81. package/src/AvoNetworkCallsHandler.ts +205 -45
  82. package/src/AvoStreamId.ts +16 -0
  83. package/src/__tests__/TestAvoEncryption.ts +515 -0
  84. package/src/__tests__/TestAvoEventSpecCache.ts +242 -0
  85. package/src/__tests__/TestAvoEventSpecFetcher.ts +279 -0
  86. package/src/__tests__/TestAvoInspector.ts +114 -2
  87. package/src/__tests__/TestAvoStreamId.ts +72 -0
  88. package/src/__tests__/TestDeduplicator.ts +4 -0
  89. package/src/__tests__/TestEventValidator.ts +231 -0
  90. package/src/__tests__/TestNetworkCallsHandler.ts +82 -15
  91. package/src/__tests__/TestNetworkCallsHandlerPublic.ts +134 -63
  92. package/src/eventSpec/AvoEventSpecCache.ts +119 -0
  93. package/src/eventSpec/AvoEventSpecFetchTypes.ts +47 -0
  94. package/src/eventSpec/AvoEventSpecFetcher.ts +186 -0
  95. package/src/eventSpec/EventValidator.ts +74 -0
@@ -2,9 +2,13 @@ import { AvoInspectorEnv, AvoInspectorEnvValueType } from "./AvoInspectorEnv";
2
2
  import { AvoSchemaParser } from "./AvoSchemaParser";
3
3
  import { AvoNetworkCallsHandler } from "./AvoNetworkCallsHandler";
4
4
  import { AvoDeduplicator } from "./AvoDeduplicator";
5
+ import { AvoStreamId } from "./AvoStreamId";
6
+ import { AvoEventSpecFetcher } from "./eventSpec/AvoEventSpecFetcher";
7
+ import { AvoEventSpecCache } from "./eventSpec/AvoEventSpecCache";
8
+ import { EventValidator } from "./eventSpec/EventValidator";
9
+ import { EventSpecResponse } from "./eventSpec/AvoEventSpecFetchTypes";
5
10
 
6
11
  import { isValueEmpty } from "./utils";
7
- import { AvoGuid } from "./AvoGuid";
8
12
 
9
13
  const libVersion = require("../package.json").version;
10
14
 
@@ -14,6 +18,33 @@ export class AvoInspector {
14
18
  avoDeduplicator: AvoDeduplicator;
15
19
  apiKey: string;
16
20
  version: string;
21
+ private publicEncryptionKey?: string;
22
+
23
+ private eventSpecFetcher: AvoEventSpecFetcher | null = null;
24
+ private eventSpecCache: AvoEventSpecCache | null = null;
25
+ private eventValidator: EventValidator | null = null;
26
+ private generatedAnonymousId: string = "";
27
+
28
+ // Keep the Node process alive while there are pending inspector operations.
29
+ // Without this, short-lived processes (e.g. CLIs) can exit before async
30
+ // sends complete, since the caller typically doesn't await the inspector promise.
31
+ private pendingCount = 0;
32
+ private keepAliveTimer: ReturnType<typeof setInterval> | null = null;
33
+
34
+ private trackPending(promise: Promise<any>): void {
35
+ this.pendingCount++;
36
+ if (this.keepAliveTimer === null) {
37
+ this.keepAliveTimer = setInterval(() => {}, 60_000);
38
+ }
39
+ const done = () => {
40
+ this.pendingCount--;
41
+ if (this.pendingCount === 0 && this.keepAliveTimer !== null) {
42
+ clearInterval(this.keepAliveTimer);
43
+ this.keepAliveTimer = null;
44
+ }
45
+ };
46
+ promise.then(done, done);
47
+ }
17
48
 
18
49
  private static _shouldLog = false;
19
50
  static get shouldLog() {
@@ -28,6 +59,7 @@ export class AvoInspector {
28
59
  env: AvoInspectorEnvValueType;
29
60
  version: string;
30
61
  appName?: string;
62
+ publicEncryptionKey?: string;
31
63
  }) {
32
64
  // the constructor does aggressive null/undefined checking because same code paths will be accessible from JS
33
65
  if (isValueEmpty(options.env)) {
@@ -60,6 +92,23 @@ export class AvoInspector {
60
92
  this.version = options.version;
61
93
  }
62
94
 
95
+ this.publicEncryptionKey = options.publicEncryptionKey;
96
+
97
+ if (
98
+ this.publicEncryptionKey &&
99
+ this.environment !== AvoInspectorEnv.Prod
100
+ ) {
101
+ const hexPattern = /^[0-9a-fA-F]+$/;
102
+ const len = this.publicEncryptionKey.length;
103
+ // Accept both compressed (66 hex chars, prefix 02/03) and uncompressed (130 hex chars, prefix 04) P-256 keys
104
+ const isValidLength = len === 66 || len === 130;
105
+ if (!hexPattern.test(this.publicEncryptionKey) || !isValidLength) {
106
+ console.warn(
107
+ "[Avo Inspector] Warning: publicEncryptionKey does not look like a valid P-256 public key (expected 66 or 130 hex characters). Encryption may fail."
108
+ );
109
+ }
110
+ }
111
+
63
112
  if (this.environment === AvoInspectorEnv.Dev) {
64
113
  AvoInspector._shouldLog = true;
65
114
  } else {
@@ -71,14 +120,23 @@ export class AvoInspector {
71
120
  this.environment.toString(),
72
121
  options.appName || "",
73
122
  this.version,
74
- libVersion
123
+ libVersion,
124
+ this.publicEncryptionKey
75
125
  );
76
126
  this.avoDeduplicator = new AvoDeduplicator();
127
+
128
+ // Initialize event spec validation for dev/staging only
129
+ if (this.environment !== AvoInspectorEnv.Prod) {
130
+ this.eventSpecFetcher = new AvoEventSpecFetcher(this.apiKey);
131
+ this.eventSpecCache = new AvoEventSpecCache();
132
+ this.eventValidator = new EventValidator();
133
+ }
77
134
  }
78
135
 
79
136
  trackSchemaFromEvent(
80
137
  eventName: string,
81
- eventProperties: { [propName: string]: any }
138
+ eventProperties: { [propName: string]: any },
139
+ streamId?: string
82
140
  ): Promise<
83
141
  Array<{
84
142
  propertyName: string;
@@ -87,33 +145,40 @@ export class AvoInspector {
87
145
  }>
88
146
  > {
89
147
  try {
148
+ const avoStreamId = new AvoStreamId(streamId);
149
+ const anonymousId = avoStreamId.streamId || this.generatedAnonymousId;
150
+
90
151
  if (
91
152
  this.avoDeduplicator.shouldRegisterEvent(
92
153
  eventName,
93
154
  eventProperties,
94
- false
155
+ false,
156
+ anonymousId
95
157
  )
96
158
  ) {
97
159
  if (AvoInspector.shouldLog) {
98
160
  console.log(
99
- "Avo Inspector: supplied event " +
161
+ "Avo Inspector: Supplied event " +
100
162
  eventName +
101
- " with params " +
163
+ " with params \n" +
102
164
  JSON.stringify(eventProperties)
103
165
  );
104
166
  }
105
167
  let eventSchema = this.extractSchema(eventProperties, false);
106
- return this.trackSchemaInternal(
168
+
169
+ return this.sendEventWithOptionalValidation(
107
170
  eventName,
108
171
  eventSchema,
109
172
  null,
110
- null
173
+ null,
174
+ anonymousId,
175
+ eventProperties
111
176
  ).then(() => {
112
177
  return eventSchema;
113
178
  });
114
179
  } else {
115
180
  if (AvoInspector.shouldLog) {
116
- console.log("Avo Inspector: Deduplicated event: " + eventName);
181
+ console.log("Avo Inspector: Deduplicated event " + eventName);
117
182
  }
118
183
  return Promise.resolve([]);
119
184
  }
@@ -128,11 +193,12 @@ export class AvoInspector {
128
193
  }
129
194
  }
130
195
 
131
- private _avoFunctionTrackSchemaFromEvent(
196
+ _avoFunctionTrackSchemaFromEvent(
132
197
  eventName: string,
133
198
  eventProperties: { [propName: string]: any },
134
199
  eventId: string,
135
- eventHash: string
200
+ eventHash: string,
201
+ streamId?: string
136
202
  ): Promise<
137
203
  Array<{
138
204
  propertyName: string;
@@ -141,33 +207,39 @@ export class AvoInspector {
141
207
  }>
142
208
  > {
143
209
  try {
210
+ const anonymousId = (streamId && streamId.length > 0) ? streamId : this.generatedAnonymousId;
211
+
144
212
  if (
145
213
  this.avoDeduplicator.shouldRegisterEvent(
146
214
  eventName,
147
215
  eventProperties,
148
- true
216
+ true,
217
+ anonymousId
149
218
  )
150
219
  ) {
151
220
  if (AvoInspector.shouldLog) {
152
221
  console.log(
153
- "Avo Inspector: supplied event " +
222
+ "Avo Inspector: Supplied event " +
154
223
  eventName +
155
- " with params " +
224
+ " with params \n" +
156
225
  JSON.stringify(eventProperties)
157
226
  );
158
227
  }
159
228
  let eventSchema = this.extractSchema(eventProperties, false);
160
- return this.trackSchemaInternal(
229
+
230
+ return this.sendEventWithOptionalValidation(
161
231
  eventName,
162
232
  eventSchema,
163
233
  eventId,
164
- eventHash
234
+ eventHash,
235
+ anonymousId,
236
+ eventProperties
165
237
  ).then(() => {
166
238
  return eventSchema;
167
239
  });
168
240
  } else {
169
241
  if (AvoInspector.shouldLog) {
170
- console.log("Avo Inspector: Deduplicated event: " + eventName);
242
+ console.log("Avo Inspector: Deduplicated event " + eventName);
171
243
  }
172
244
  return Promise.resolve([]);
173
245
  }
@@ -182,7 +254,16 @@ export class AvoInspector {
182
254
  }
183
255
  }
184
256
 
185
- private trackSchemaInternal(
257
+ /**
258
+ * Try to fetch event spec and validate, then send a single event call.
259
+ * If validation succeeds, sends a validated event (with results merged in).
260
+ * If unavailable, sends a plain event.
261
+ *
262
+ * Uses trackPending() to keep the Node process alive until the work completes,
263
+ * so short-lived processes (CLIs) don't exit before the send finishes — even
264
+ * when the caller doesn't await the returned promise.
265
+ */
266
+ private sendEventWithOptionalValidation(
186
267
  eventName: string,
187
268
  eventSchema: Array<{
188
269
  propertyName: string;
@@ -190,43 +271,66 @@ export class AvoInspector {
190
271
  children?: any;
191
272
  }>,
192
273
  eventId: string | null,
193
- eventHash: string | null
274
+ eventHash: string | null,
275
+ anonymousId: string,
276
+ rawEventProperties?: { [propName: string]: any }
194
277
  ): Promise<void> {
278
+ const work = this.doSendEventWithOptionalValidation(
279
+ eventName, eventSchema, eventId, eventHash, anonymousId, rawEventProperties
280
+ );
281
+ this.trackPending(work);
282
+ return work;
283
+ }
284
+
285
+ private async doSendEventWithOptionalValidation(
286
+ eventName: string,
287
+ eventSchema: Array<{
288
+ propertyName: string;
289
+ propertyType: string;
290
+ children?: any;
291
+ }>,
292
+ eventId: string | null,
293
+ eventHash: string | null,
294
+ anonymousId: string,
295
+ rawEventProperties?: { [propName: string]: any }
296
+ ): Promise<void> {
297
+ const validationResult = await this.fetchAndValidate(eventName, eventSchema, anonymousId, rawEventProperties, eventId);
298
+
195
299
  try {
196
- const seesionId = AvoGuid.newGuid();
197
- return this.avoNetworkCallsHandler
198
- .callInspectorWithBatchBody([
199
- this.avoNetworkCallsHandler.bodyForSessionStartedCall(seesionId),
200
- ])
201
- .then(() => {
202
- if (AvoInspector.shouldLog) {
203
- console.log("Avo Inspector: session started sent successfully.");
204
- }
205
- this.avoNetworkCallsHandler.callInspectorWithBatchBody([
206
- this.avoNetworkCallsHandler.bodyForEventSchemaCall(
207
- seesionId,
208
- eventName,
209
- eventSchema,
210
- eventId,
211
- eventHash
212
- ),
213
- ]).then(() => {
214
- if (AvoInspector.shouldLog) {
215
- console.log("Avo Inspector: schema sent successfully.");
216
- }
217
- });
218
- })
219
- .catch((err) => {
220
- if (AvoInspector.shouldLog) {
221
- console.log("Avo Inspector: schema sending failed: " + err + ".");
222
- }
223
- });
224
- } catch (e) {
225
- console.error(
226
- "Avo Inspector: something went wrong. Please report to support@avo.app.",
227
- e
228
- );
229
- return Promise.reject();
300
+ let body;
301
+ if (validationResult) {
302
+ if (AvoInspector.shouldLog) {
303
+ console.log("Avo Inspector: Sending validated event " + eventName);
304
+ }
305
+ const eventProps = this.avoNetworkCallsHandler.buildEventProperties(eventSchema, rawEventProperties);
306
+ body = this.avoNetworkCallsHandler.bodyForValidatedEventSchemaCall(
307
+ anonymousId,
308
+ eventName,
309
+ eventProps,
310
+ eventId,
311
+ eventHash,
312
+ validationResult.metadata,
313
+ validationResult.propertyResults
314
+ );
315
+ } else {
316
+ body = this.avoNetworkCallsHandler.bodyForEventSchemaCall(
317
+ anonymousId,
318
+ eventName,
319
+ eventSchema,
320
+ eventId,
321
+ eventHash,
322
+ rawEventProperties
323
+ );
324
+ }
325
+
326
+ await this.avoNetworkCallsHandler.callInspectorWithBatchBody([body]);
327
+
328
+ if (AvoInspector.shouldLog) {
329
+ const schemaString = eventSchema.map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n");
330
+ console.log("Avo Inspector: Saved event " + eventName + " with schema {\n" + schemaString + "\n}");
331
+ }
332
+ } catch (err) {
333
+ console.error("Avo Inspector: schema sending failed: " + err + ".");
230
334
  }
231
335
  }
232
336
 
@@ -262,7 +366,14 @@ export class AvoInspector {
262
366
  );
263
367
  }
264
368
 
265
- return AvoSchemaParser.extractSchema(eventProperties);
369
+ const schema = AvoSchemaParser.extractSchema(eventProperties);
370
+
371
+ if (AvoInspector.shouldLog) {
372
+ const schemaString = schema.map(p => '\t"' + p.propertyName + '": "' + p.propertyType + '"').join(";\n");
373
+ console.log("Avo Inspector: Parsed schema {\n" + schemaString + "\n}");
374
+ }
375
+
376
+ return schema;
266
377
  } catch (e) {
267
378
  console.error(
268
379
  "Avo Inspector: something went wrong. Please report to support@avo.app.",
@@ -271,4 +382,117 @@ export class AvoInspector {
271
382
  return [];
272
383
  }
273
384
  }
385
+
386
+ /**
387
+ * Fetch event spec and validate. Returns validation results + metadata if
388
+ * validation succeeds, or null if unavailable (prod, no spec, fetch error).
389
+ */
390
+ private async fetchAndValidate(
391
+ eventName: string,
392
+ eventSchema: Array<{
393
+ propertyName: string;
394
+ propertyType: string;
395
+ children?: any;
396
+ }>,
397
+ anonymousId: string,
398
+ rawEventProperties?: { [propName: string]: any },
399
+ eventId?: string | null
400
+ ): Promise<{ metadata: import("./eventSpec/AvoEventSpecFetchTypes").EventSpecMetadata; propertyResults: import("./eventSpec/AvoEventSpecFetchTypes").PropertyValidationResult[] } | null> {
401
+ if (!this.eventSpecFetcher || !this.eventSpecCache || !this.eventValidator) {
402
+ if (AvoInspector.shouldLog) {
403
+ console.log("Avo Inspector: Skipping event spec validation for event: " + eventName
404
+ + " (fetcher=" + (this.eventSpecFetcher != null)
405
+ + ", cache=" + (this.eventSpecCache != null)
406
+ + ", env=" + this.environment + ")");
407
+ }
408
+ return null;
409
+ }
410
+
411
+ const cacheKey = AvoEventSpecCache.makeKey(this.apiKey, anonymousId, eventName);
412
+ const cache = this.eventSpecCache;
413
+ const validator = this.eventValidator;
414
+
415
+ const doValidate = (specResponse: EventSpecResponse) => {
416
+ if (specResponse.eventSpec === null) {
417
+ if (AvoInspector.shouldLog) {
418
+ console.log("Avo Inspector: Event spec fetch returned null for event: " + eventName + ". Sending without validation.");
419
+ }
420
+ return null;
421
+ }
422
+
423
+ const eventProperties = eventSchema.map((prop) => ({
424
+ propertyName: prop.propertyName,
425
+ propertyType: prop.propertyType,
426
+ ...(rawEventProperties && rawEventProperties[prop.propertyName] !== undefined
427
+ ? { propertyValue: String(rawEventProperties[prop.propertyName]) }
428
+ : {}),
429
+ }));
430
+
431
+ if (AvoInspector.shouldLog) {
432
+ console.log("Avo Inspector: Validating event: " + eventName
433
+ + " with " + eventProperties.length + " properties"
434
+ + " against " + specResponse.eventSpec.properties.length + " spec properties");
435
+ }
436
+
437
+ const validationId = eventId || eventName;
438
+ const results = validator.validate(
439
+ specResponse.eventSpec,
440
+ eventProperties,
441
+ validationId
442
+ );
443
+
444
+ if (AvoInspector.shouldLog) {
445
+ console.log("Avo Inspector: Validation complete for event: " + eventName
446
+ + " with " + results.length + " property results");
447
+ }
448
+
449
+ return { metadata: specResponse.metadata, propertyResults: results };
450
+ };
451
+
452
+ // Check cache first
453
+ const cached = cache.get(cacheKey);
454
+ if (cached !== undefined) {
455
+ if (AvoInspector.shouldLog) {
456
+ console.log("Avo Inspector: Event spec cache hit for event: " + eventName);
457
+ }
458
+ return doValidate(cached);
459
+ }
460
+
461
+ // Cache miss — fetch spec (process stays alive via trackPending)
462
+ if (AvoInspector.shouldLog) {
463
+ console.log("Avo Inspector: Event spec cache miss for event: " + eventName + ". Fetching before sending.");
464
+ }
465
+
466
+ const fetcher = this.eventSpecFetcher;
467
+ return new Promise((resolve) => {
468
+ fetcher.fetch(eventName, anonymousId, (result) => {
469
+ if (result !== null) {
470
+ cache.set(cacheKey, result);
471
+ resolve(doValidate(result));
472
+ } else {
473
+ if (AvoInspector.shouldLog) {
474
+ console.log("Avo Inspector: Event spec fetch returned null for event: " + eventName + ". Cached empty response. Sending without validation.");
475
+ }
476
+ resolve(null);
477
+ }
478
+ });
479
+ });
480
+ }
481
+
482
+ destroy(): void {
483
+ if (this.eventSpecFetcher) {
484
+ this.eventSpecFetcher.destroy();
485
+ this.eventSpecFetcher = null;
486
+ }
487
+ if (this.eventSpecCache) {
488
+ this.eventSpecCache.flush();
489
+ this.eventSpecCache = null;
490
+ }
491
+ this.eventValidator = null;
492
+ if (this.keepAliveTimer !== null) {
493
+ clearInterval(this.keepAliveTimer);
494
+ this.keepAliveTimer = null;
495
+ }
496
+ this.pendingCount = 0;
497
+ }
274
498
  }