1- const cache = new Map ( ) ;
21const logger = require ( './logger' ) . default ;
32
43module . exports = class CacheHandler {
@@ -9,109 +8,95 @@ module.exports = class CacheHandler {
98 * @param {number } [options.maxSize=100] - Maximum number of cache entries.
109 */
1110 constructor ( options = { } ) {
11+ this . cache = new Map ( ) ; // Stores cache entries
12+ this . cacheKeys = [ ] ; // Tracks cache keys for LRU eviction
1213 this . options = {
1314 ttl : options . ttl || 60 * 1000 , // Default: 1 minute
14- maxSize : options . maxSize || 100 , // Limit max items
15+ maxSize : options . maxSize || 100 , // Default max cache size
1516 ...options ,
1617 } ;
18+
19+ // Periodically clean expired cache entries
20+ setInterval ( ( ) => this . cleanupExpiredEntries ( ) , this . options . ttl ) ;
1721 }
1822
1923 /**
2024 * Retrieves a value from the cache.
2125 * @param {string } key - The cache key.
2226 * @returns {Promise<any> } The cached value or null if not found or expired.
27+ * @throws {TypeError } If the key is not a string.
2328 */
2429 async get ( key ) {
25- const entry = cache . get ( key ) ;
30+ if ( typeof key !== 'string' ) {
31+ throw new TypeError ( 'Cache key must be a string' ) ;
32+ }
2633
34+ const entry = this . cache . get ( key ) ;
2735 if ( ! entry ) {
28- logger . cache ( {
29- message : `Unable to find cache for key: ${ key } ` ,
30- service : 'CACHE' ,
31- method : 'get' ,
32- cacheState : 'MISS' ,
33- level : 'warn'
34- } ) ;
36+ logger . cache ( { message : `Cache miss: ${ key } ` , service : 'CACHE' , method : 'get' , cacheState : 'MISS' , level : 'warn' } ) ;
3537 return null ;
3638 }
3739
3840 // Check expiration
3941 if ( Date . now ( ) - entry . lastModified > this . options . ttl ) {
40- cache . delete ( key ) ;
41- logger . cache ( {
42- message : `The cache for key: ${ key } has expired` ,
43- service : 'CACHE' ,
44- method : 'get' ,
45- cacheState : 'EXPIRED' ,
46- level : 'error'
47- } ) ;
42+ this . cache . delete ( key ) ;
43+ this . cacheKeys . splice ( this . cacheKeys . indexOf ( key ) , 1 ) ;
44+ logger . cache ( { message : `Cache expired: ${ key } ` , service : 'CACHE' , method : 'get' , cacheState : 'EXPIRED' , level : 'error' } ) ;
4845 return null ;
4946 }
5047
51- logger . cache ( {
52- message : `Located cache for key: ${ key } ` ,
53- service : 'CACHE' ,
54- method : 'get' ,
55- cacheState : 'HIT' ,
56- level : 'info'
57- } ) ;
48+ // Move key to end (most recently used)
49+ this . cacheKeys . splice ( this . cacheKeys . indexOf ( key ) , 1 ) ;
50+ this . cacheKeys . push ( key ) ;
5851
52+ logger . cache ( { message : `Cache hit: ${ key } ` , service : 'CACHE' , method : 'get' , cacheState : 'HIT' , level : 'info' } ) ;
5953 return entry . value ;
6054 }
6155
6256 /**
63- * Sets a value in the cache.
57+ * Stores a value in the cache.
58+ * If the cache exceeds its max size, it removes the least recently used (LRU) entry.
6459 * @param {string } key - The cache key.
6560 * @param {any } data - The data to cache.
6661 * @param {Object } [ctx={}] - The context object.
6762 * @param {string[] } [ctx.tags=[]] - Tags associated with the cache entry.
63+ * @throws {TypeError } If the key is not a string.
64+ * @throws {Error } If the data is undefined.
6865 */
6966 async set ( key , data , ctx = { } ) {
70- if ( cache . size >= this . options . maxSize ) {
71- // Evict oldest entry
72- const oldestKey = [ ...cache . keys ( ) ] [ 0 ] ;
73- cache . delete ( oldestKey ) ;
74- logger . cache ( {
75- message : `Evicted cache for key: ${ oldestKey } ` ,
76- service : 'CACHE' ,
77- method : 'set' ,
78- cacheState : 'EVICTED' ,
79- level : 'warn'
80- } ) ;
67+ if ( typeof key !== 'string' ) {
68+ throw new TypeError ( 'Cache key must be a string' ) ;
69+ }
70+ if ( data === undefined ) {
71+ throw new Error ( 'Cannot cache undefined value' ) ;
8172 }
8273
83- cache . set ( key , {
84- value : data ,
85- lastModified : Date . now ( ) ,
86- tags : ctx . tags || [ ] ,
87- } ) ;
74+ // If cache reaches max size, remove least recently used (LRU) entry
75+ if ( this . cache . size >= this . options . maxSize ) {
76+ const oldestKey = this . cacheKeys . shift ( ) ; // Remove LRU entry
77+ this . cache . delete ( oldestKey ) ;
78+ logger . cache ( { message : `Evicted LRU cache: ${ oldestKey } ` , service : 'CACHE' , method : 'set' , cacheState : 'EVICTED' , level : 'warn' } ) ;
79+ }
80+
81+ // Store new entry
82+ this . cache . set ( key , { value : data , lastModified : Date . now ( ) , tags : ctx . tags || [ ] } ) ;
83+ this . cacheKeys . push ( key ) ; // Track for LRU
8884
89- logger . cache ( {
90- message : `Set cache for key: ${ key } ` ,
91- service : 'CACHE' ,
92- method : 'set' ,
93- cacheState : 'SET' ,
94- level : 'info'
95- } ) ;
85+ logger . cache ( { message : `Cache set: ${ key } ` , service : 'CACHE' , method : 'set' , cacheState : 'SET' , level : 'info' } ) ;
9686 }
9787
9888 /**
99- * Revalidates cache entries by tags.
100- * @param {string|string[] } tags - The tags to revalidate .
89+ * Invalidates cache entries associated with a specific tag or multiple tags.
90+ * @param {string|string[] } tags - The tag(s) to invalidate .
10191 */
10292 async revalidateTag ( tags ) {
10393 tags = Array . isArray ( tags ) ? tags : [ tags ] ;
10494
105- for ( const [ key , value ] of cache ) {
95+ for ( const [ key , value ] of this . cache ) {
10696 if ( value . tags . some ( tag => tags . includes ( tag ) ) ) {
107- cache . delete ( key ) ;
108- logger . cache ( {
109- message : `Invalidated cache for key: ${ key } ` ,
110- service : 'CACHE' ,
111- method : 'revalidateTag' ,
112- cacheState : 'INVALIDATE' ,
113- level : 'info'
114- } ) ;
97+ this . cache . delete ( key ) ;
98+ this . cacheKeys . splice ( this . cacheKeys . indexOf ( key ) , 1 ) ;
99+ logger . cache ( { message : `Cache invalidated: ${ key } ` , service : 'CACHE' , method : 'revalidateTag' , cacheState : 'INVALIDATE' , level : 'info' } ) ;
115100 }
116101 }
117102 }
@@ -120,13 +105,21 @@ module.exports = class CacheHandler {
120105 * Clears all cache entries.
121106 */
122107 async clear ( ) {
123- cache . clear ( ) ;
124- logger . cache ( {
125- message : `Cleared all cache entries` ,
126- service : 'CACHE' ,
127- method : 'clear' ,
128- cacheState : 'CLEAR' ,
129- level : 'info'
130- } ) ;
108+ this . cache . clear ( ) ;
109+ this . cacheKeys = [ ] ;
110+ logger . cache ( { message : `All cache cleared` , service : 'CACHE' , method : 'clear' , cacheState : 'CLEAR' , level : 'info' } ) ;
111+ }
112+
113+ /**
114+ * Periodically removes expired cache entries.
115+ */
116+ cleanupExpiredEntries ( ) {
117+ for ( const [ key , entry ] of this . cache ) {
118+ if ( Date . now ( ) - entry . lastModified > this . options . ttl ) {
119+ this . cache . delete ( key ) ;
120+ this . cacheKeys . splice ( this . cacheKeys . indexOf ( key ) , 1 ) ;
121+ logger . cache ( { message : `Auto-cleaned expired cache: ${ key } ` , service : 'CACHE' , method : 'cleanup' , cacheState : 'EXPIRED' , level : 'info' } ) ;
122+ }
123+ }
131124 }
132125} ;
0 commit comments