node-avo-inspector 1.2.0 → 2.0.0-beta.2

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 (132) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +270 -4
  3. package/dist/AvoBatchQueue.d.ts +73 -0
  4. package/dist/AvoBatchQueue.js +239 -0
  5. package/dist/AvoDeduplicator.d.ts +15 -6
  6. package/dist/AvoDeduplicator.js +46 -24
  7. package/dist/AvoInspector.d.ts +136 -23
  8. package/dist/AvoInspector.js +777 -109
  9. package/dist/AvoInspectorVersion.d.ts +3 -0
  10. package/dist/AvoInspectorVersion.js +10 -0
  11. package/dist/AvoLog.d.ts +52 -0
  12. package/dist/AvoLog.js +246 -0
  13. package/dist/AvoNetworkCallsHandler.d.ts +55 -8
  14. package/dist/AvoNetworkCallsHandler.js +266 -92
  15. package/dist/AvoSchemaParser.d.ts +6 -0
  16. package/dist/AvoSchemaParser.js +138 -33
  17. package/dist/AvoStreamId.d.ts +2 -1
  18. package/dist/AvoStreamId.js +21 -2
  19. package/dist/eventSpec/AvoEventSpecCache.d.ts +28 -5
  20. package/dist/eventSpec/AvoEventSpecCache.js +66 -11
  21. package/dist/eventSpec/AvoEventSpecFetchTypes.d.ts +50 -18
  22. package/dist/eventSpec/AvoEventSpecFetcher.d.ts +29 -6
  23. package/dist/eventSpec/AvoEventSpecFetcher.js +279 -69
  24. package/dist/eventSpec/EventValidator.d.ts +16 -10
  25. package/dist/eventSpec/EventValidator.js +351 -38
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +4 -1
  28. package/dist/utils.d.ts +29 -1
  29. package/dist/utils.js +86 -10
  30. package/package.json +18 -1
  31. package/.claude/scheduled_tasks.lock +0 -1
  32. package/.claude/settings.local.json +0 -11
  33. package/.claude/worktrees/release-1.1.1/LICENSE +0 -21
  34. package/.claude/worktrees/release-1.1.1/README.md +0 -69
  35. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/index.js +0 -30
  36. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/package.json +0 -15
  37. package/.claude/worktrees/release-1.1.1/examples/node-inspector-js/yarn.lock +0 -6
  38. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/dist/index.js +0 -34
  39. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/index.ts +0 -30
  40. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/package.json +0 -18
  41. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/tsconfig.json +0 -15
  42. package/.claude/worktrees/release-1.1.1/examples/node-inspector-ts/yarn.lock +0 -11
  43. package/.claude/worktrees/release-1.1.1/package-lock.json +0 -4039
  44. package/.claude/worktrees/release-1.1.1/package.json +0 -48
  45. package/.claude/worktrees/release-1.1.1/reason/AvoInspector.rei +0 -11
  46. package/.claude/worktrees/release-1.1.1/reason/NodeAvoInspector.re +0 -27
  47. package/.claude/worktrees/release-1.1.1/src/AvoDeduplicator.ts +0 -146
  48. package/.claude/worktrees/release-1.1.1/src/AvoEncryption.ts +0 -90
  49. package/.claude/worktrees/release-1.1.1/src/AvoGuid.ts +0 -9
  50. package/.claude/worktrees/release-1.1.1/src/AvoInspector.ts +0 -383
  51. package/.claude/worktrees/release-1.1.1/src/AvoInspectorEnv.ts +0 -8
  52. package/.claude/worktrees/release-1.1.1/src/AvoNetworkCallsHandler.ts +0 -267
  53. package/.claude/worktrees/release-1.1.1/src/AvoSchemaParser.ts +0 -115
  54. package/.claude/worktrees/release-1.1.1/src/AvoStreamId.ts +0 -16
  55. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEncryption.ts +0 -515
  56. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecCache.ts +0 -242
  57. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoEventSpecFetcher.ts +0 -241
  58. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoInspector.ts +0 -360
  59. package/.claude/worktrees/release-1.1.1/src/__tests__/TestAvoStreamId.ts +0 -72
  60. package/.claude/worktrees/release-1.1.1/src/__tests__/TestDeduplicator.ts +0 -191
  61. package/.claude/worktrees/release-1.1.1/src/__tests__/TestEventValidator.ts +0 -231
  62. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandler.ts +0 -179
  63. package/.claude/worktrees/release-1.1.1/src/__tests__/TestNetworkCallsHandlerPublic.ts +0 -208
  64. package/.claude/worktrees/release-1.1.1/src/__tests__/TestParsing.ts +0 -153
  65. package/.claude/worktrees/release-1.1.1/src/__tests__/constants.ts +0 -63
  66. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecCache.ts +0 -109
  67. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetchTypes.ts +0 -47
  68. package/.claude/worktrees/release-1.1.1/src/eventSpec/AvoEventSpecFetcher.ts +0 -106
  69. package/.claude/worktrees/release-1.1.1/src/eventSpec/EventValidator.ts +0 -74
  70. package/.claude/worktrees/release-1.1.1/src/index.ts +0 -2
  71. package/.claude/worktrees/release-1.1.1/src/utils.ts +0 -49
  72. package/.claude/worktrees/release-1.1.1/tsconfig.json +0 -13
  73. package/.claude/worktrees/release-1.1.1/yarn.lock +0 -2310
  74. package/.github/workflows/claude.yml +0 -55
  75. package/dist/__tests__/TestAvoEncryption.d.ts +0 -1
  76. package/dist/__tests__/TestAvoEncryption.js +0 -288
  77. package/dist/__tests__/TestAvoEventSpecCache.d.ts +0 -1
  78. package/dist/__tests__/TestAvoEventSpecCache.js +0 -194
  79. package/dist/__tests__/TestAvoEventSpecFetcher.d.ts +0 -1
  80. package/dist/__tests__/TestAvoEventSpecFetcher.js +0 -219
  81. package/dist/__tests__/TestAvoInspector.d.ts +0 -1
  82. package/dist/__tests__/TestAvoInspector.js +0 -291
  83. package/dist/__tests__/TestAvoStreamId.d.ts +0 -1
  84. package/dist/__tests__/TestAvoStreamId.js +0 -55
  85. package/dist/__tests__/TestDeduplicator.d.ts +0 -1
  86. package/dist/__tests__/TestDeduplicator.js +0 -102
  87. package/dist/__tests__/TestEventValidator.d.ts +0 -1
  88. package/dist/__tests__/TestEventValidator.js +0 -196
  89. package/dist/__tests__/TestNetworkCallsHandler.d.ts +0 -1
  90. package/dist/__tests__/TestNetworkCallsHandler.js +0 -86
  91. package/dist/__tests__/TestNetworkCallsHandlerPublic.d.ts +0 -1
  92. package/dist/__tests__/TestNetworkCallsHandlerPublic.js +0 -160
  93. package/dist/__tests__/TestParsing.d.ts +0 -1
  94. package/dist/__tests__/TestParsing.js +0 -128
  95. package/dist/__tests__/constants.d.ts +0 -42
  96. package/dist/__tests__/constants.js +0 -53
  97. package/examples/node-inspector-js/index.js +0 -30
  98. package/examples/node-inspector-js/package.json +0 -15
  99. package/examples/node-inspector-js/yarn.lock +0 -6
  100. package/examples/node-inspector-ts/dist/index.js +0 -34
  101. package/examples/node-inspector-ts/index.ts +0 -30
  102. package/examples/node-inspector-ts/package.json +0 -18
  103. package/examples/node-inspector-ts/tsconfig.json +0 -15
  104. package/examples/node-inspector-ts/yarn.lock +0 -11
  105. package/reason/AvoInspector.rei +0 -11
  106. package/reason/NodeAvoInspector.re +0 -27
  107. package/src/AvoDeduplicator.ts +0 -150
  108. package/src/AvoEncryption.ts +0 -90
  109. package/src/AvoGuid.ts +0 -9
  110. package/src/AvoInspector.ts +0 -498
  111. package/src/AvoInspectorEnv.ts +0 -8
  112. package/src/AvoNetworkCallsHandler.ts +0 -340
  113. package/src/AvoSchemaParser.ts +0 -115
  114. package/src/AvoStreamId.ts +0 -16
  115. package/src/__tests__/TestAvoEncryption.ts +0 -515
  116. package/src/__tests__/TestAvoEventSpecCache.ts +0 -242
  117. package/src/__tests__/TestAvoEventSpecFetcher.ts +0 -279
  118. package/src/__tests__/TestAvoInspector.ts +0 -376
  119. package/src/__tests__/TestAvoStreamId.ts +0 -72
  120. package/src/__tests__/TestDeduplicator.ts +0 -191
  121. package/src/__tests__/TestEventValidator.ts +0 -231
  122. package/src/__tests__/TestNetworkCallsHandler.ts +0 -181
  123. package/src/__tests__/TestNetworkCallsHandlerPublic.ts +0 -208
  124. package/src/__tests__/TestParsing.ts +0 -153
  125. package/src/__tests__/constants.ts +0 -63
  126. package/src/eventSpec/AvoEventSpecCache.ts +0 -119
  127. package/src/eventSpec/AvoEventSpecFetchTypes.ts +0 -47
  128. package/src/eventSpec/AvoEventSpecFetcher.ts +0 -186
  129. package/src/eventSpec/EventValidator.ts +0 -74
  130. package/src/index.ts +0 -2
  131. package/src/utils.ts +0 -49
  132. package/tsconfig.json +0 -13
@@ -1,153 +0,0 @@
1
- import { AvoInspector } from "../AvoInspector";
2
- import { defaultOptions, type } from "./constants";
3
-
4
- describe("Schema Parsing", () => {
5
- const inspector = new AvoInspector(defaultOptions);
6
-
7
- beforeAll(() => {
8
- inspector.enableLogging(false);
9
- });
10
-
11
- test("Empty array returned if eventProperties are not set", () => {
12
- // @ts-ignore
13
- const schema = inspector.extractSchema();
14
-
15
- expect(schema).toEqual([]);
16
- });
17
-
18
- test("Property types and names are set", () => {
19
- // Given
20
- const eventProperties = {
21
- prop0: true,
22
- prop1: 1,
23
- prop2: "str",
24
- prop3: 0.5,
25
- prop4: undefined,
26
- prop5: null,
27
- prop6: { an: "object" },
28
- prop7: [
29
- "a",
30
- "list",
31
- {
32
- "obj in list": true,
33
- "int field": 1,
34
- },
35
- ["another", "list"],
36
- [1, 2],
37
- ],
38
- };
39
-
40
- // When
41
- const res = inspector.extractSchema(eventProperties);
42
-
43
- // Then
44
- res.forEach(({ propertyName }, index) => {
45
- expect(propertyName).toBe(`prop${index}`);
46
- });
47
-
48
- expect(res[0].propertyType).toBe(type.BOOL);
49
- expect(res[1].propertyType).toBe(type.INT);
50
- expect(res[2].propertyType).toBe(type.STRING);
51
- expect(res[3].propertyType).toBe(type.FLOAT);
52
- expect(res[4].propertyType).toBe(type.NULL);
53
- expect(res[5].propertyType).toBe(type.NULL);
54
-
55
- expect(res[6].propertyType).toBe(type.OBJECT);
56
- expect(res[6].children).toMatchObject([
57
- {
58
- propertyName: "an",
59
- propertyType: type.STRING,
60
- },
61
- ]);
62
-
63
- expect(res[7].propertyType).toBe(type.STRINGLIST);
64
- expect(res[7].children).toMatchObject([
65
- type.STRING,
66
- [
67
- {
68
- propertyName: "obj in list",
69
- propertyType: type.BOOL,
70
- },
71
- {
72
- propertyName: "int field",
73
- propertyType: type.INT,
74
- },
75
- ],
76
- [type.STRING],
77
- [type.INT],
78
- ]);
79
- });
80
-
81
- test("Duplicated values are removed", () => {
82
- // Given
83
- const eventProperties = {
84
- prop0: ["true", "false", true, 10, "true", true, 11, 10, 0.1, 0.1],
85
- };
86
-
87
- // When
88
- const res = inspector.extractSchema(eventProperties);
89
-
90
- // Then
91
- expect(res[0].propertyType).toBe(type.STRINGLIST);
92
- });
93
-
94
- test("Empty and falsy values are set correctly", () => {
95
- // Given
96
- const eventProperties = {
97
- prop0: false,
98
- prop1: 0,
99
- prop2: "",
100
- prop3: 0.0,
101
- prop4: undefined,
102
- prop5: null,
103
- prop6: {},
104
- prop7: [],
105
- };
106
-
107
- // When
108
- const res = inspector.extractSchema(eventProperties);
109
-
110
- // Then
111
- expect(res[0].propertyType).toBe(type.BOOL);
112
- expect(res[1].propertyType).toBe(type.INT);
113
- expect(res[2].propertyType).toBe(type.STRING);
114
- expect(res[3].propertyType).toBe(type.INT);
115
- expect(res[4].propertyType).toBe(type.NULL);
116
- expect(res[5].propertyType).toBe(type.NULL);
117
-
118
- expect(res[6].propertyType).toBe(type.OBJECT);
119
- expect(res[6].children).toMatchObject([]);
120
-
121
- expect(res[7].propertyType).toBe(type.STRINGLIST);
122
- expect(res[7].children).toMatchObject([]);
123
- });
124
-
125
- test("List of string returns list(string)", () => {
126
- // Given
127
- const eventProperties = {
128
- prop0: ["a", "b", "c"],
129
- };
130
-
131
- // When
132
- const res = inspector.extractSchema(eventProperties);
133
-
134
- // Then
135
- expect(res[0].propertyType).toBe(type.STRINGLIST);
136
- });
137
-
138
-
139
- test("List of multiple types returns list(`firstType`)", () => {
140
- // Given
141
- const eventProperties = {
142
- prop0: [1.2, "two", {"three": 3}],
143
- };
144
-
145
- // When
146
- const res = inspector.extractSchema(eventProperties);
147
-
148
- // Then
149
- expect(res[0].propertyType).toBe(type.FLOATLIST);
150
- });
151
-
152
-
153
- });
@@ -1,63 +0,0 @@
1
- import { AvoInspectorEnv } from "../AvoInspectorEnv";
2
-
3
- const defaultOptions = {
4
- apiKey: "api-key-xxx",
5
- env: AvoInspectorEnv.Dev,
6
- version: "1",
7
- shouldLog: true,
8
- appName: "my-test-app",
9
- };
10
-
11
- const error = {
12
- API_KEY:
13
- "[Avo Inspector] No API key provided. Inspector can't operate without API key.",
14
- VERSION:
15
- "[Avo Inspector] No version provided. Many features of Inspector rely on versioning. Please provide comparable string version, i.e. integer or semantic.",
16
- };
17
-
18
- const mockedReturns = {
19
- INSTALLATION_ID: "avo-instalation-id",
20
- GUID: "generated-guid",
21
- SESSION_ID: "session-id",
22
- };
23
-
24
- const networkCallType = {
25
- EVENT: "event",
26
- SESSION_STARTED: "sessionStarted",
27
- };
28
-
29
- const requestMsg = {
30
- ERROR: "Request failed",
31
- TIMEOUT: "Request timed out",
32
- };
33
-
34
- const trackingEndpoint = "https://api.avo.app/inspector/v1/track";
35
-
36
- const sessionTimeMs = 5 * 60 * 1000;
37
-
38
- const type = {
39
- STRING: "string",
40
- STRINGLIST: "list(string)",
41
- INT: "int",
42
- INTLIST: "list(int)",
43
- OBJECT: "object",
44
- OBJECTLIST: "list(object)",
45
- FLOAT: "float",
46
- FLOATLIST: "list(float)",
47
- BOOL: "boolean",
48
- BOOLLIST: "list(boolean)",
49
- NULL: "null",
50
- UNKNOWN: "unknown",
51
- UNKNOWNLIST: "list(unknown)",
52
- };
53
-
54
- export {
55
- defaultOptions,
56
- error,
57
- mockedReturns,
58
- networkCallType,
59
- requestMsg,
60
- sessionTimeMs,
61
- type,
62
- trackingEndpoint,
63
- };
@@ -1,119 +0,0 @@
1
- import { EventSpecResponse } from "./AvoEventSpecFetchTypes";
2
-
3
- interface CacheEntry {
4
- value: EventSpecResponse;
5
- timestamp: number;
6
- eventCount: number;
7
- lastAccessed: number;
8
- }
9
-
10
- const MAX_EVENT_COUNT = 50;
11
- const TTL_MS = 60_000;
12
-
13
- export class AvoEventSpecCache {
14
- private cache: Map<string, CacheEntry> = new Map();
15
- private globalEventCount = 0;
16
-
17
- static makeKey(apiKey: string, streamId: string, eventName: string): string {
18
- return `${apiKey}\0${streamId}\0${eventName}`;
19
- }
20
-
21
- /**
22
- * Returns the cached EventSpecResponse, or undefined on cache miss.
23
- * A cached response with eventSpec: null means "no spec exists for this event".
24
- */
25
- get(key: string): EventSpecResponse | undefined {
26
- const entry = this.cache.get(key);
27
- if (entry === undefined) {
28
- return undefined;
29
- }
30
-
31
- // TTL check
32
- if (Date.now() - entry.timestamp > TTL_MS) {
33
- this.cache.delete(key);
34
- return undefined;
35
- }
36
-
37
- // Per-entry access count eviction
38
- entry.eventCount++;
39
- if (entry.eventCount >= MAX_EVENT_COUNT) {
40
- this.cache.delete(key);
41
- return undefined;
42
- }
43
-
44
- entry.lastAccessed = Date.now();
45
- return entry.value;
46
- }
47
-
48
- /**
49
- * Returns true if the key exists in the cache and has not expired.
50
- */
51
- contains(key: string): boolean {
52
- const entry = this.cache.get(key);
53
- if (entry === undefined) {
54
- return false;
55
- }
56
- if (Date.now() - entry.timestamp > TTL_MS) {
57
- this.cache.delete(key);
58
- return false;
59
- }
60
- if (entry.eventCount >= MAX_EVENT_COUNT) {
61
- this.cache.delete(key);
62
- return false;
63
- }
64
- return true;
65
- }
66
-
67
- set(key: string, value: EventSpecResponse): void {
68
- this.globalEventCount++;
69
-
70
- // Global rotation: evict LRU entry every MAX_EVENT_COUNT operations
71
- if (this.globalEventCount >= MAX_EVENT_COUNT) {
72
- this.purgeExpired();
73
- this.evictLRU();
74
- this.globalEventCount = 0;
75
- }
76
-
77
- this.cache.set(key, {
78
- value,
79
- timestamp: Date.now(),
80
- eventCount: 0,
81
- lastAccessed: Date.now(),
82
- });
83
-
84
- // Size cap: evict oldest entries when exceeding max
85
- while (this.cache.size > MAX_EVENT_COUNT) {
86
- this.evictLRU();
87
- }
88
- }
89
-
90
- flush(): void {
91
- this.cache.clear();
92
- this.globalEventCount = 0;
93
- }
94
-
95
- private purgeExpired(): void {
96
- const now = Date.now();
97
- for (const [key, entry] of this.cache.entries()) {
98
- if (now - entry.timestamp > TTL_MS) {
99
- this.cache.delete(key);
100
- }
101
- }
102
- }
103
-
104
- private evictLRU(): void {
105
- let oldestKey: string | null = null;
106
- let oldestTime = Infinity;
107
-
108
- for (const [key, entry] of this.cache.entries()) {
109
- if (entry.lastAccessed < oldestTime) {
110
- oldestTime = entry.lastAccessed;
111
- oldestKey = key;
112
- }
113
- }
114
-
115
- if (oldestKey !== null) {
116
- this.cache.delete(oldestKey);
117
- }
118
- }
119
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * Type definitions for Event Spec validation responses.
3
- */
4
-
5
- /** A single property constraint from the spec. */
6
- export interface PropertyConstraint {
7
- propertyName: string;
8
- /** Expected type, e.g. "string", "int", "float", "boolean", "object", "list(string)" */
9
- propertyType: string;
10
- /** Optional regex pattern that the value must match */
11
- regex?: string;
12
- }
13
-
14
- /** The spec for a single event, as returned by the API. */
15
- export interface EventSpec {
16
- eventName: string;
17
- properties: PropertyConstraint[];
18
- }
19
-
20
- /** Metadata identifying the spec source. */
21
- export interface EventSpecMetadata {
22
- schemaId: string;
23
- branchId: string;
24
- latestActionId: string;
25
- sourceId: string;
26
- }
27
-
28
- /** Full response from the spec endpoint. */
29
- export interface EventSpecResponse {
30
- eventSpec: EventSpec | null;
31
- metadata: EventSpecMetadata;
32
- }
33
-
34
- /** Per-property validation result. */
35
- export interface PropertyValidationResult {
36
- propertyName: string;
37
- failedEventIds: string[];
38
- passedEventIds: string[];
39
- }
40
-
41
- /** Payload sent to reportValidatedEvent. */
42
- export interface ValidatedEventPayload {
43
- streamId: string;
44
- eventName: string;
45
- eventSpecMetadata: EventSpecMetadata;
46
- propertyResults: PropertyValidationResult[];
47
- }
@@ -1,186 +0,0 @@
1
- import { request, Agent } from "https";
2
- import { EventSpecResponse, EventSpec, EventSpecMetadata, PropertyConstraint } from "./AvoEventSpecFetchTypes";
3
- import { AvoInspector } from "../AvoInspector";
4
-
5
- type FetchCallback = (result: EventSpecResponse | null) => void;
6
-
7
- export class AvoEventSpecFetcher {
8
- private apiKey: string;
9
- private inFlight: Map<string, FetchCallback[]> = new Map();
10
- private agent: Agent;
11
-
12
- private static specEndpoint = "/trackingPlan/eventSpec";
13
-
14
- constructor(apiKey: string) {
15
- this.apiKey = apiKey;
16
- this.agent = new Agent({ keepAlive: true });
17
- }
18
-
19
- fetch(
20
- eventName: string,
21
- streamId: string,
22
- callback: FetchCallback
23
- ): void {
24
- const dedupeKey = `${this.apiKey}:${streamId}:${eventName}`;
25
-
26
- // In-flight dedup: if there's already a request in flight for this key,
27
- // queue the callback
28
- const existingCallbacks = this.inFlight.get(dedupeKey);
29
- if (existingCallbacks) {
30
- existingCallbacks.push(callback);
31
- return;
32
- }
33
-
34
- // Register the callback and start the request
35
- this.inFlight.set(dedupeKey, [callback]);
36
-
37
- const queryParams = new URLSearchParams({
38
- apiKey: this.apiKey,
39
- eventName,
40
- streamId,
41
- });
42
-
43
- const options = {
44
- hostname: "api.avo.app",
45
- port: 443,
46
- path: `${AvoEventSpecFetcher.specEndpoint}?${queryParams.toString()}`,
47
- method: "GET",
48
- agent: this.agent,
49
- headers: {
50
- Accept: "application/json",
51
- },
52
- };
53
-
54
- if (AvoInspector.shouldLog) {
55
- console.log("Avo Inspector: [network] GET https://" + options.hostname + AvoEventSpecFetcher.specEndpoint + "?eventName=" + encodeURIComponent(eventName));
56
- }
57
-
58
- const req = request(options, (res) => {
59
- if (AvoInspector.shouldLog) {
60
- console.log("Avo Inspector: [network] Spec response status: " + res.statusCode + " " + res.statusMessage);
61
- }
62
- const chunks: Buffer[] = [];
63
- res.on("data", (data: Buffer) => chunks.push(data));
64
- res.on("end", () => {
65
- let result: EventSpecResponse | null = null;
66
- if (res.statusCode !== 200) {
67
- if (AvoInspector.shouldLog) {
68
- const body = Buffer.concat(chunks).toString();
69
- console.warn("Avo Inspector: [network] Spec fetch failed with status " + res.statusCode + ": " + body);
70
- }
71
- this.resolveCallbacks(dedupeKey, null);
72
- return;
73
- }
74
- try {
75
- const body = Buffer.concat(chunks).toString();
76
- if (AvoInspector.shouldLog) {
77
- console.log("Avo Inspector: [network] Spec response body: " + body);
78
- }
79
- const wire = JSON.parse(body);
80
- result = AvoEventSpecFetcher.parseWireResponse(wire, eventName);
81
- if (AvoInspector.shouldLog) {
82
- console.log("Avo Inspector: [network] Parsed spec: " + JSON.stringify(result));
83
- }
84
- } catch (e) {
85
- if (AvoInspector.shouldLog) {
86
- console.warn("Avo Inspector: [network] Failed to parse spec response: " + e);
87
- }
88
- }
89
- this.resolveCallbacks(dedupeKey, result);
90
- });
91
- });
92
-
93
- req.on("error", (err) => {
94
- if (AvoInspector.shouldLog) {
95
- console.error("Avo Inspector: [network] Spec fetch error: " + err);
96
- }
97
- this.resolveCallbacks(dedupeKey, null);
98
- });
99
-
100
- req.setTimeout(10_000);
101
- req.on("timeout", () => {
102
- if (AvoInspector.shouldLog) {
103
- console.error("Avo Inspector: [network] Spec fetch timed out after 10s");
104
- }
105
- req.destroy();
106
- this.resolveCallbacks(dedupeKey, null);
107
- });
108
-
109
- req.end();
110
- }
111
-
112
- private resolveCallbacks(
113
- key: string,
114
- result: EventSpecResponse | null
115
- ): void {
116
- const callbacks = this.inFlight.get(key);
117
- this.inFlight.delete(key);
118
-
119
- if (callbacks) {
120
- for (const cb of callbacks) {
121
- try {
122
- cb(result);
123
- } catch (e) {
124
- // Don't let one callback failure affect others
125
- }
126
- }
127
- }
128
- }
129
-
130
- /**
131
- * Parse the wire format from /trackingPlan/eventSpec into our internal types.
132
- *
133
- * Wire format:
134
- * { events: [{ b, id, vids, p: { "PropName": { t: "string", r: true, v: {...}, rx: {...} } } }],
135
- * metadata: { schemaId, branchId, latestActionId, sourceId } }
136
- *
137
- * Internal format:
138
- * { eventSpec: { eventName, properties: [{ propertyName, propertyType, regex? }] } | null,
139
- * metadata: { schemaId, branchId, latestActionId, sourceId } }
140
- */
141
- static parseWireResponse(wire: any, eventName: string): EventSpecResponse {
142
- const metadata: EventSpecMetadata = {
143
- schemaId: wire.metadata?.schemaId ?? "",
144
- branchId: wire.metadata?.branchId ?? "",
145
- latestActionId: wire.metadata?.latestActionId ?? "",
146
- sourceId: wire.metadata?.sourceId ?? "",
147
- };
148
-
149
- if (!wire.events || !Array.isArray(wire.events) || wire.events.length === 0) {
150
- return { eventSpec: null, metadata };
151
- }
152
-
153
- // Use the first event entry (the endpoint returns specs for the requested event)
154
- const entry = wire.events[0];
155
- const properties: PropertyConstraint[] = [];
156
-
157
- if (entry.p && typeof entry.p === "object") {
158
- for (const propName of Object.keys(entry.p)) {
159
- const constraint = entry.p[propName];
160
- const prop: PropertyConstraint = {
161
- propertyName: propName,
162
- propertyType: constraint?.t ?? "unknown",
163
- };
164
- // Extract regex pattern if present (rx field maps regex patterns to event IDs)
165
- if (constraint?.rx && typeof constraint.rx === "object") {
166
- const patterns = Object.keys(constraint.rx);
167
- if (patterns.length > 0) {
168
- prop.regex = patterns[0];
169
- }
170
- }
171
- properties.push(prop);
172
- }
173
- }
174
-
175
- const eventSpec: EventSpec = {
176
- eventName,
177
- properties,
178
- };
179
-
180
- return { eventSpec, metadata };
181
- }
182
-
183
- destroy(): void {
184
- this.agent.destroy();
185
- }
186
- }
@@ -1,74 +0,0 @@
1
- import safeRegex from "safe-regex2";
2
- import {
3
- EventSpec,
4
- PropertyValidationResult,
5
- } from "./AvoEventSpecFetchTypes";
6
-
7
- interface EventProperty {
8
- propertyName: string;
9
- propertyType: string;
10
- propertyValue?: string;
11
- }
12
-
13
- export class EventValidator {
14
- /**
15
- * Validates event properties against the spec.
16
- * Returns per-property validation results with bandwidth optimization:
17
- * passedEventIds is returned only when strictly smaller than failedEventIds.
18
- */
19
- validate(
20
- spec: EventSpec,
21
- eventProperties: EventProperty[],
22
- eventId: string
23
- ): PropertyValidationResult[] {
24
- const results: PropertyValidationResult[] = [];
25
- const eventPropMap = new Map<string, EventProperty>();
26
-
27
- for (const prop of eventProperties) {
28
- eventPropMap.set(prop.propertyName, prop);
29
- }
30
-
31
- for (const specProp of spec.properties) {
32
- const actualProp = eventPropMap.get(specProp.propertyName);
33
- let passed = false;
34
-
35
- if (actualProp) {
36
- // Type check
37
- passed = actualProp.propertyType === specProp.propertyType;
38
-
39
- // Regex check (only if type passed, regex is defined, and value is available)
40
- if (passed && specProp.regex && actualProp.propertyValue !== undefined) {
41
- if (!safeRegex(specProp.regex)) {
42
- console.warn(
43
- `[Avo Inspector] Warning: Unsafe regex pattern skipped for property "${specProp.propertyName}": ${specProp.regex}`
44
- );
45
- } else {
46
- try {
47
- const re = new RegExp(specProp.regex);
48
- passed = re.test(actualProp.propertyValue);
49
- } catch (e) {
50
- console.warn(
51
- `[Avo Inspector] Warning: Invalid regex pattern for property "${specProp.propertyName}": ${specProp.regex}`
52
- );
53
- }
54
- }
55
- }
56
- }
57
-
58
- const result: PropertyValidationResult = {
59
- propertyName: specProp.propertyName,
60
- failedEventIds: passed ? [] : [eventId],
61
- passedEventIds: passed ? [eventId] : [],
62
- };
63
-
64
- // Bandwidth optimization: return passedEventIds only when strictly smaller than failedEventIds
65
- if (result.passedEventIds.length >= result.failedEventIds.length) {
66
- result.passedEventIds = [];
67
- }
68
-
69
- results.push(result);
70
- }
71
-
72
- return results;
73
- }
74
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { AvoInspector } from "./AvoInspector";
2
- export { AvoInspectorEnv } from "./AvoInspectorEnv";
package/src/utils.ts DELETED
@@ -1,49 +0,0 @@
1
- const isValueEmpty = (value: string | null | undefined): boolean => {
2
- return value === null || value === undefined || value.trim().length == 0;
3
- };
4
-
5
- function deepEquals(x: any, y: any) {
6
-
7
- if (x === y) {
8
- return true;
9
- }
10
-
11
- if (!(x instanceof Object) || !(y instanceof Object)) {
12
- return false;
13
- }
14
-
15
- if (x.constructor !== y.constructor) {
16
- return false;
17
- }
18
-
19
- for (var p in x) {
20
- if (!x.hasOwnProperty(p)) {
21
- continue;
22
- }
23
-
24
- if (!y.hasOwnProperty(p)) {
25
- return false;
26
- }
27
-
28
- if (x[p] === y[p]) {
29
- continue;
30
- }
31
-
32
- if (typeof x[p] !== "object") {
33
- return false;
34
- }
35
-
36
- if (!deepEquals(x[p], y[p])) {
37
- return false;
38
- }
39
- }
40
-
41
- for (p in y) {
42
- if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
43
- return false;
44
- }
45
- }
46
- return true;
47
- }
48
-
49
- export { isValueEmpty, deepEquals };
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "CommonJS",
4
- "target": "ES6",
5
- "declaration": true,
6
- "strict": true,
7
- "noImplicitAny": true,
8
- "baseUrl": "./src",
9
- "outDir": "./dist"
10
- },
11
- "include": ["src/**/*"],
12
- "exclude": ["src/tests"]
13
- }