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,128 +0,0 @@
1
- const isArray = (obj) => {
2
- return Object.prototype.toString.call(obj) === "[object Array]";
3
- };
4
- let _encryptValueFn;
5
- export class AvoSchemaParser {
6
- /**
7
- * Returns true only if we have a valid encryption key and can send encrypted values.
8
- * If no key is present, returns false and no property values will be sent.
9
- */
10
- static canSendEncryptedValues(publicEncryptionKey, env) {
11
- const hasEncryptionKey = publicEncryptionKey != null && publicEncryptionKey !== "";
12
- const isDevOrStaging = env === "dev" || env === "staging";
13
- return hasEncryptionKey && isDevOrStaging;
14
- }
15
- /**
16
- * Returns the encrypted property value if encryption is enabled, otherwise undefined.
17
- * Never returns unencrypted values - only encrypted or nothing.
18
- */
19
- static async getEncryptedPropertyValueIfEnabled(propertyValue, canEncrypt, publicEncryptionKey) {
20
- if (!canEncrypt || !publicEncryptionKey) {
21
- return undefined; // No encryption key: do not send any property values
22
- }
23
- try {
24
- if (!_encryptValueFn) {
25
- const { encryptValue } = await import("./AvoEncryption");
26
- _encryptValueFn = encryptValue;
27
- }
28
- return await _encryptValueFn(propertyValue, publicEncryptionKey); // Only send encrypted values
29
- }
30
- catch (error) {
31
- // If encryption fails, log the error but don't fail the entire schema extraction
32
- console.error("[Avo Inspector] Failed to encrypt property value:", error instanceof Error ? error.message : String(error));
33
- return undefined;
34
- }
35
- }
36
- static async extractSchema(eventProperties, publicEncryptionKey, env) {
37
- if (eventProperties === null || eventProperties === undefined) {
38
- return [];
39
- }
40
- const canSendEncryptedValues = this.canSendEncryptedValues(publicEncryptionKey, env);
41
- const mapping = async (object) => {
42
- if (isArray(object)) {
43
- const list = await Promise.all(object.map(async (x) => {
44
- return await mapping(x);
45
- }));
46
- return this.removeDuplicates(list);
47
- }
48
- else if (typeof object === "object") {
49
- const mappedResult = [];
50
- for (const key in object) {
51
- if (object.hasOwnProperty(key)) {
52
- const val = object[key];
53
- const mappedEntry = {
54
- propertyName: key,
55
- propertyType: this.getPropValueType(val)
56
- };
57
- if (typeof val === "object" && val != null) {
58
- // Object/array properties: children are encrypted individually, no need to encrypt parent
59
- mappedEntry.children = (await mapping(val));
60
- }
61
- else if (val !== undefined) {
62
- // Primitive properties: encrypt the value if encryption is enabled
63
- // Skip undefined values - they can't be encrypted and shouldn't be sent
64
- const encryptedValue = await this.getEncryptedPropertyValueIfEnabled(val, canSendEncryptedValues, publicEncryptionKey);
65
- if (encryptedValue !== undefined) {
66
- mappedEntry.encryptedPropertyValue = encryptedValue;
67
- }
68
- }
69
- mappedResult.push(mappedEntry);
70
- }
71
- }
72
- return mappedResult;
73
- }
74
- else {
75
- return this.getPropValueType(object);
76
- }
77
- };
78
- // eventProperties is always an object (Record), so mapping returns EventProperty[]
79
- const mappedEventProps = (await mapping(eventProperties));
80
- return mappedEventProps;
81
- }
82
- static removeDuplicates(array) {
83
- const primitives = { boolean: {}, number: {}, string: {} };
84
- const objects = [];
85
- return array.filter((item) => {
86
- const type = typeof item;
87
- if (type in primitives && typeof item === "string") {
88
- return primitives[type].hasOwnProperty(item)
89
- ? false
90
- : (primitives[type][item] = true);
91
- }
92
- else {
93
- return objects.includes(item) ? false : objects.push(item);
94
- }
95
- });
96
- }
97
- static getPropValueType(propValue) {
98
- const propType = typeof propValue;
99
- if (propValue == null) {
100
- return "null";
101
- }
102
- else if (propType === "string") {
103
- return "string";
104
- }
105
- else if (propType === "number" || propType === "bigint") {
106
- if ((propValue + "").includes(".")) {
107
- return "float";
108
- }
109
- else {
110
- return "int";
111
- }
112
- }
113
- else if (propType === "boolean") {
114
- return "boolean";
115
- }
116
- else if (propType === "object") {
117
- if (isArray(propValue)) {
118
- return "list";
119
- }
120
- else {
121
- return "object";
122
- }
123
- }
124
- else {
125
- return "unknown";
126
- }
127
- }
128
- }
@@ -1,176 +0,0 @@
1
- class PlatformAvoStorage {
2
- parseJson(maybeItem) {
3
- if (maybeItem !== null && maybeItem !== undefined) {
4
- return JSON.parse(maybeItem);
5
- }
6
- else {
7
- return null;
8
- }
9
- }
10
- }
11
- class BrowserAvoStorage extends PlatformAvoStorage {
12
- constructor() {
13
- super(...arguments);
14
- this.useFallbackStorage = false;
15
- this.fallbackStorage = {};
16
- this.storageInitialized = false;
17
- this.onStorageInitFuncs = [];
18
- this.shouldLog = false;
19
- this.suffix = "";
20
- }
21
- init(shouldLog, suffix) {
22
- this.shouldLog = shouldLog;
23
- this.suffix = suffix;
24
- this.initializeStorageWeb(this.isLocalStorageAvailable());
25
- }
26
- initializeStorageWeb(isLocalStorageAvailable) {
27
- this.storageInitialized = true;
28
- if (!isLocalStorageAvailable) {
29
- this.useFallbackStorage = true;
30
- }
31
- this.onStorageInitFuncs.forEach((func) => {
32
- func();
33
- });
34
- }
35
- isLocalStorageAvailable() {
36
- const uid = new Date().toISOString();
37
- try {
38
- window.localStorage.setItem(uid, uid);
39
- if (window.localStorage.getItem(uid) === uid) {
40
- window.localStorage.removeItem(uid);
41
- return true;
42
- }
43
- else {
44
- return false;
45
- }
46
- }
47
- catch (error) {
48
- return false;
49
- }
50
- }
51
- isInitialized() {
52
- return this.storageInitialized;
53
- }
54
- async getItemAsync(key) {
55
- return await new Promise((resolve, _reject) => {
56
- this.runAfterInit(() => {
57
- if (this.useFallbackStorage) {
58
- const maybeItem = this.fallbackStorage[key + this.suffix];
59
- resolve(this.parseJson(maybeItem));
60
- }
61
- else {
62
- if (typeof window !== "undefined") {
63
- let maybeItem;
64
- try {
65
- maybeItem = window.localStorage.getItem(key + this.suffix);
66
- }
67
- catch (error) {
68
- if (this.shouldLog) {
69
- console.error("Avo Inspector Storage getItemAsync error:", error);
70
- }
71
- resolve(null);
72
- }
73
- resolve(this.parseJson(maybeItem));
74
- }
75
- else {
76
- resolve(null);
77
- }
78
- }
79
- });
80
- });
81
- }
82
- getItem(key) {
83
- let maybeItem;
84
- if (!this.storageInitialized) {
85
- maybeItem = null;
86
- }
87
- else if (this.useFallbackStorage) {
88
- maybeItem = this.fallbackStorage[key + this.suffix];
89
- }
90
- else if (process.env.BROWSER) {
91
- if (typeof window !== "undefined") {
92
- try {
93
- maybeItem = window.localStorage.getItem(key + this.suffix);
94
- }
95
- catch (error) {
96
- if (this.shouldLog) {
97
- console.error("Avo Inspector Storage getItem error:", error);
98
- }
99
- }
100
- }
101
- }
102
- return this.parseJson(maybeItem);
103
- }
104
- setItem(key, value) {
105
- this.runAfterInit(() => {
106
- if (this.useFallbackStorage) {
107
- this.fallbackStorage[key + this.suffix] = JSON.stringify(value);
108
- }
109
- else {
110
- if (typeof window !== "undefined") {
111
- try {
112
- window.localStorage.setItem(key + this.suffix, JSON.stringify(value));
113
- }
114
- catch (error) {
115
- if (this.shouldLog) {
116
- console.error("Avo Inspector Storage setItem error:", error);
117
- }
118
- }
119
- }
120
- }
121
- });
122
- }
123
- removeItem(key) {
124
- this.runAfterInit(() => {
125
- if (this.useFallbackStorage) {
126
- this.fallbackStorage[key + this.suffix] = null;
127
- }
128
- else {
129
- if (typeof window !== "undefined") {
130
- try {
131
- window.localStorage.removeItem(key + this.suffix);
132
- }
133
- catch (error) {
134
- if (this.shouldLog) {
135
- console.error("Avo Inspector Storage removeItem error:", error);
136
- }
137
- }
138
- }
139
- }
140
- });
141
- }
142
- runAfterInit(func) {
143
- if (this.storageInitialized) {
144
- func();
145
- }
146
- else {
147
- this.onStorageInitFuncs.push(func);
148
- }
149
- }
150
- }
151
- export class AvoStorage {
152
- constructor(shouldLog, suffix = "") {
153
- this.Platform = null;
154
- this.Platform = "browser";
155
- this.storageImpl = new BrowserAvoStorage();
156
- this.storageImpl.init(shouldLog, suffix);
157
- }
158
- isInitialized() {
159
- return this.storageImpl.isInitialized();
160
- }
161
- async getItemAsync(key) {
162
- return await this.storageImpl.getItemAsync(key);
163
- }
164
- getItem(key) {
165
- return this.storageImpl.getItem(key);
166
- }
167
- setItem(key, value) {
168
- this.storageImpl.setItem(key, value);
169
- }
170
- removeItem(key) {
171
- this.storageImpl.removeItem(key);
172
- }
173
- runAfterInit(func) {
174
- this.storageImpl.runAfterInit(func);
175
- }
176
- }
@@ -1,140 +0,0 @@
1
- import AvoGuid from "./AvoGuid";
2
- import { AvoInspector } from "./AvoInspector";
3
- /**
4
- * AvoStreamId manages an ephemeral stream identifier.
5
- *
6
- * The stream ID is generated and stored persistently, but resets after:
7
- * - 4 hours of total age AND
8
- * - 2 hours of idle time (no activity)
9
- *
10
- * This ensures the ID is truly anonymous by not persisting indefinitely,
11
- * while still maintaining session continuity during active usage.
12
- */
13
- export class AvoStreamId {
14
- /**
15
- * Get the stream ID. If it doesn't exist or has expired, generates a new one.
16
- * Returns "unknown" if storage is not initialized.
17
- */
18
- static get streamId() {
19
- // Return cached value if available and not expired
20
- if (AvoStreamId._streamId !== null && !AvoStreamId.shouldReset()) {
21
- AvoStreamId.updateLastActivity();
22
- return AvoStreamId._streamId;
23
- }
24
- if (!AvoInspector.avoStorage.isInitialized()) {
25
- return "unknown";
26
- }
27
- // Load from storage
28
- AvoStreamId.loadFromStorage();
29
- // Check if we should reset
30
- if (AvoStreamId._streamId !== null && AvoStreamId.shouldReset()) {
31
- return AvoStreamId.generateAndStoreNew();
32
- }
33
- // If no ID exists, generate new one
34
- if (AvoStreamId._streamId === null) {
35
- return AvoStreamId.generateAndStoreNew();
36
- }
37
- // Update last activity and return existing
38
- AvoStreamId.updateLastActivity();
39
- return AvoStreamId._streamId;
40
- }
41
- /**
42
- * Check if the stream ID should be reset based on age and idle time.
43
- */
44
- static shouldReset() {
45
- if (AvoStreamId._createdAt === null || AvoStreamId._lastActivityAt === null) {
46
- return false;
47
- }
48
- const now = Date.now();
49
- const age = now - AvoStreamId._createdAt;
50
- const idleTime = now - AvoStreamId._lastActivityAt;
51
- // Reset if older than 4 hours AND idle for 2+ hours
52
- return age > AvoStreamId.MAX_AGE_MS && idleTime > AvoStreamId.IDLE_THRESHOLD_MS;
53
- }
54
- /**
55
- * Load stream ID and timestamps from storage into cache.
56
- */
57
- static loadFromStorage() {
58
- try {
59
- const storedId = AvoInspector.avoStorage.getItem(AvoStreamId.STREAM_ID_KEY);
60
- const createdAt = AvoInspector.avoStorage.getItem(AvoStreamId.CREATED_AT_KEY);
61
- const lastActivityAt = AvoInspector.avoStorage.getItem(AvoStreamId.LAST_ACTIVITY_KEY);
62
- if (storedId !== null && storedId !== undefined) {
63
- AvoStreamId._streamId = storedId;
64
- AvoStreamId._createdAt = createdAt ?? Date.now();
65
- AvoStreamId._lastActivityAt = lastActivityAt ?? Date.now();
66
- }
67
- }
68
- catch (e) {
69
- console.error("Avo Inspector: Error reading stream ID from storage. Please report to support@avo.app.", e);
70
- }
71
- }
72
- /**
73
- * Generate a new stream ID and store it with timestamps.
74
- */
75
- static generateAndStoreNew() {
76
- const now = Date.now();
77
- AvoStreamId._streamId = AvoGuid.newGuid();
78
- AvoStreamId._createdAt = now;
79
- AvoStreamId._lastActivityAt = now;
80
- try {
81
- AvoInspector.avoStorage.setItem(AvoStreamId.STREAM_ID_KEY, AvoStreamId._streamId);
82
- AvoInspector.avoStorage.setItem(AvoStreamId.CREATED_AT_KEY, AvoStreamId._createdAt);
83
- AvoInspector.avoStorage.setItem(AvoStreamId.LAST_ACTIVITY_KEY, AvoStreamId._lastActivityAt);
84
- }
85
- catch (e) {
86
- console.error("Avo Inspector: Error saving stream ID to storage. Please report to support@avo.app.", e);
87
- }
88
- return AvoStreamId._streamId;
89
- }
90
- /**
91
- * Update the last activity timestamp.
92
- */
93
- static updateLastActivity() {
94
- const now = Date.now();
95
- AvoStreamId._lastActivityAt = now;
96
- try {
97
- AvoInspector.avoStorage.setItem(AvoStreamId.LAST_ACTIVITY_KEY, now);
98
- }
99
- catch (e) {
100
- // Silently fail - not critical
101
- }
102
- }
103
- /**
104
- * The storage key used to persist the stream ID.
105
- */
106
- static get storageKey() {
107
- return AvoStreamId.STREAM_ID_KEY;
108
- }
109
- /**
110
- * The storage key for created at timestamp.
111
- */
112
- static get createdAtKey() {
113
- return AvoStreamId.CREATED_AT_KEY;
114
- }
115
- /**
116
- * The storage key for last activity timestamp.
117
- */
118
- static get lastActivityKey() {
119
- return AvoStreamId.LAST_ACTIVITY_KEY;
120
- }
121
- /**
122
- * Clear the cached stream ID. The next access will reload from storage.
123
- * This is primarily useful for testing.
124
- */
125
- static clearCache() {
126
- AvoStreamId._streamId = null;
127
- AvoStreamId._createdAt = null;
128
- AvoStreamId._lastActivityAt = null;
129
- }
130
- }
131
- AvoStreamId._streamId = null;
132
- AvoStreamId._createdAt = null;
133
- AvoStreamId._lastActivityAt = null;
134
- // Ephemeral reset thresholds
135
- AvoStreamId.MAX_AGE_MS = 4 * 60 * 60 * 1000; // 4 hours
136
- AvoStreamId.IDLE_THRESHOLD_MS = 2 * 60 * 60 * 1000; // 2 hours
137
- // Storage keys
138
- AvoStreamId.STREAM_ID_KEY = "AvoInspectorStreamId";
139
- AvoStreamId.CREATED_AT_KEY = "AvoInspectorStreamIdCreatedAt";
140
- AvoStreamId.LAST_ACTIVITY_KEY = "AvoInspectorStreamIdLastActivityAt";
@@ -1,165 +0,0 @@
1
- /**
2
- * EventSpecCache implements a dual-condition cache with LRU eviction.
3
- *
4
- * Cache Policy:
5
- * - Entries expire after 5 minutes OR 50 cache hits, whichever comes first
6
- * - When 50 cache hits occur globally, the oldest cached entry is evicted
7
- * - Each cache entry tracks: spec, timestamp, and hit count
8
- *
9
- * Cache Key Format: ${apiKey}:${streamId}:${eventName}
10
- *
11
- * Note: Event count only increments on cache hits, not on every tracked event.
12
- * This ensures the cache evicts based on actual usage, not overall tracking volume.
13
- */
14
- export class EventSpecCache {
15
- constructor(shouldLog = false) {
16
- /** Time-to-live in milliseconds (1 minute) */
17
- this.TTL_MS = 60 * 1000;
18
- /** Maximum cache hit count before rotating cache (50 hits) */
19
- this.MAX_EVENT_COUNT = 50;
20
- /** Global cache hit counter to track when to rotate cache */
21
- this.globalEventCount = 0;
22
- this.cache = new Map();
23
- this.shouldLog = shouldLog;
24
- }
25
- /**
26
- * Generates a cache key from the provided parameters.
27
- */
28
- generateKey(apiKey, streamId, eventName) {
29
- return `${apiKey}:${streamId}:${eventName}`;
30
- }
31
- /**
32
- * Checks if a non-expired cache entry exists for the given key.
33
- * Returns true even for cached null specs (empty backend responses).
34
- * Use this to distinguish between "cache miss" and "cached empty response".
35
- */
36
- contains(apiKey, streamId, eventName) {
37
- const key = this.generateKey(apiKey, streamId, eventName);
38
- const entry = this.cache.get(key);
39
- if (!entry) {
40
- return false;
41
- }
42
- if (this.shouldEvict(entry)) {
43
- this.cache.delete(key);
44
- return false;
45
- }
46
- return true;
47
- }
48
- /**
49
- * Retrieves an event spec response from the cache if it exists and is valid.
50
- * Returns null if the entry is missing, expired, has exceeded event count,
51
- * or if the cached spec itself is null (empty backend response).
52
- *
53
- * On cache hit, increments the hit count for this entry and the global counter.
54
- */
55
- get(apiKey, streamId, eventName) {
56
- const key = this.generateKey(apiKey, streamId, eventName);
57
- const entry = this.cache.get(key);
58
- if (!entry) {
59
- return null;
60
- }
61
- // Check if entry has expired
62
- if (this.shouldEvict(entry)) {
63
- this.cache.delete(key);
64
- return null;
65
- }
66
- if (this.shouldLog) {
67
- console.log(`[Avo Inspector] Cache hit for key: ${key}`);
68
- }
69
- // Update lastAccessed for LRU (Least Recently Used) eviction
70
- entry.lastAccessed = Date.now();
71
- // Increment hit count for this entry
72
- entry.eventCount++;
73
- this.globalEventCount++;
74
- // Check if we need to evict the oldest entry
75
- if (this.globalEventCount >= this.MAX_EVENT_COUNT) {
76
- this.evictOldest();
77
- this.globalEventCount = 0;
78
- }
79
- return entry.spec;
80
- }
81
- /**
82
- * Stores an event spec response in the cache.
83
- * Pass null to cache an empty response (event not found on backend).
84
- */
85
- set(apiKey, streamId, eventName, spec) {
86
- const key = this.generateKey(apiKey, streamId, eventName);
87
- const now = Date.now();
88
- const entry = {
89
- spec,
90
- timestamp: now,
91
- lastAccessed: now,
92
- eventCount: 0
93
- };
94
- this.cache.set(key, entry);
95
- }
96
- /**
97
- * Determines if a cache entry should be evicted based on:
98
- * - Age (older than 5 minutes)
99
- * - Hit count (50 or more cache hits)
100
- */
101
- shouldEvict(entry) {
102
- const age = Date.now() - entry.timestamp;
103
- const ageExpired = age > this.TTL_MS;
104
- const countExpired = entry.eventCount >= this.MAX_EVENT_COUNT;
105
- return ageExpired || countExpired;
106
- }
107
- /**
108
- * Evicts the least recently used cache entry based on lastAccessed timestamp.
109
- * This implements the LRU (Least Recently Used) eviction policy.
110
- */
111
- evictOldest() {
112
- if (this.cache.size === 0) {
113
- return;
114
- }
115
- let lruKey = null;
116
- let oldestAccessTime = Infinity;
117
- // Find the entry with the oldest lastAccessed time (least recently used)
118
- this.cache.forEach((entry, key) => {
119
- if (entry.lastAccessed < oldestAccessTime) {
120
- oldestAccessTime = entry.lastAccessed;
121
- lruKey = key;
122
- }
123
- });
124
- // Remove the least recently used entry
125
- if (lruKey !== null) {
126
- this.cache.delete(lruKey);
127
- }
128
- }
129
- /**
130
- * Clears all cached entries. Useful for testing.
131
- */
132
- clear() {
133
- this.cache.clear();
134
- this.globalEventCount = 0;
135
- if (this.shouldLog) {
136
- console.log("[Avo Inspector] Cache cleared");
137
- }
138
- }
139
- /**
140
- * Returns the current size of the cache.
141
- */
142
- size() {
143
- return this.cache.size;
144
- }
145
- /**
146
- * Returns cache statistics for debugging.
147
- */
148
- getStats() {
149
- const entries = [];
150
- const now = Date.now();
151
- this.cache.forEach((entry, key) => {
152
- entries.push({
153
- key,
154
- age: now - entry.timestamp,
155
- lastAccessedAgo: now - entry.lastAccessed,
156
- eventCount: entry.eventCount
157
- });
158
- });
159
- return {
160
- size: this.cache.size,
161
- globalEventCount: this.globalEventCount,
162
- entries
163
- };
164
- }
165
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * This file is generated. Internal development changes should be made in the generator
3
- * and the file should be re-generated. External contributions are welcome to submit
4
- * changes directly to this file, and we'll apply them to the generator internally.
5
- */
6
- export {};