@@ -51,12 +51,75 @@ if (typeof window !== 'undefined') {
5151 window . MOOD_API_BASE = MOOD_API_BASE ;
5252 window . API_BASE = CONFIG . API_BASE ;
5353 window . GoogleBooksClient = {
54+ // ─── Bounded LRU cache (max 100 entries, 10-min TTL) ───────────────
55+ // Using a Map so insertion order is preserved — oldest key is always
56+ // map.keys().next().value, giving us free LRU eviction with no libs.
57+ _cache : new Map ( ) ,
58+ _CACHE_TTL_MS : 10 * 60 * 1000 , // 10 minutes per entry
59+ _CACHE_MAX : 100 , // evict oldest when this is exceeded
60+
61+ // ─── Token-bucket concurrency limiter ──────────────────────────────
62+ // Allows up to _BUCKET_SIZE requests in flight simultaneously.
63+ // Tokens refill at one per _REFILL_MS, preventing 429 bursts while
64+ // not blocking unrelated searches behind each other in a queue.
65+ _tokens : 3 , // max concurrent requests
66+ _BUCKET_SIZE : 3 ,
67+ _REFILL_MS : 300 , // one token back every 300 ms
68+
5469 setKeys ( keys ) {
55- CONFIG . GOOGLE_BOOKS_API_KEYS = Array . from ( new Set ( ( keys || [ ] ) . map ( key => String ( key || '' ) . trim ( ) ) . filter ( Boolean ) ) ) ;
70+ CONFIG . GOOGLE_BOOKS_API_KEYS = Array . from (
71+ new Set ( ( keys || [ ] ) . map ( key => String ( key || '' ) . trim ( ) ) . filter ( Boolean ) )
72+ ) ;
5673 } ,
74+
5775 getKeys ( ) {
5876 return CONFIG . GOOGLE_BOOKS_API_KEYS || [ ] ;
5977 } ,
78+
79+ // ─── Cache helpers ──────────────────────────────────────────────────
80+
81+ /** Returns cached data if still fresh, otherwise null. */
82+ _getCache ( cacheKey ) {
83+ const entry = this . _cache . get ( cacheKey ) ;
84+ if ( ! entry ) return null ;
85+ if ( Date . now ( ) - entry . timestamp > this . _CACHE_TTL_MS ) {
86+ this . _cache . delete ( cacheKey ) ;
87+ return null ;
88+ }
89+ return entry . data ;
90+ } ,
91+
92+ /** Stores a response, evicting the oldest entry when over the cap. */
93+ _setCache ( cacheKey , data ) {
94+ // Evict oldest (first inserted) entry when at capacity
95+ if ( this . _cache . size >= this . _CACHE_MAX ) {
96+ const oldest = this . _cache . keys ( ) . next ( ) . value ;
97+ this . _cache . delete ( oldest ) ;
98+ }
99+ this . _cache . set ( cacheKey , { data, timestamp : Date . now ( ) } ) ;
100+ } ,
101+
102+ // ─── Token-bucket helpers ────────────────────────────────────────────
103+
104+ /** Waits until a token is available, then consumes one. */
105+ async _acquireToken ( ) {
106+ while ( this . _tokens <= 0 ) {
107+ await new Promise ( r => setTimeout ( r , this . _REFILL_MS ) ) ;
108+ }
109+ this . _tokens -= 1 ;
110+ } ,
111+
112+ /** Returns a token after a request finishes. */
113+ _releaseToken ( ) {
114+ if ( this . _tokens < this . _BUCKET_SIZE ) this . _tokens += 1 ;
115+ } ,
116+
117+ /**
118+ * Public entry-point. Checks cache first, then uses the token bucket
119+ * so at most _BUCKET_SIZE unrelated requests run simultaneously —
120+ * preventing 429 bursts without forcing unrelated searches to queue
121+ * behind each other.
122+ */
60123 async fetchVolumes ( query , options = { } ) {
61124 const maxResults = options . maxResults || 5 ;
62125 const extraParams = options . extraParams || '' ;
@@ -80,16 +143,16 @@ if (typeof window !== 'undefined') {
80143
81144 const retryableStatuses = [ 429 , 403 , 503 ] ;
82145 if ( retryableStatuses . includes ( response . status ) && index < candidates . length - 1 ) {
146+ // Brief pause before trying the next key
147+ await new Promise ( r => setTimeout ( r , 500 ) ) ;
83148 lastError = new Error ( `Google Books API returned ${ response . status } ` ) ;
84149 continue ;
85150 }
86151
87152 throw new Error ( `Google Books API returned ${ response . status } ` ) ;
88153 } catch ( error ) {
89154 lastError = error ;
90- if ( index < candidates . length - 1 ) {
91- continue ;
92- }
155+ if ( index < candidates . length - 1 ) continue ;
93156 }
94157 }
95158
@@ -101,5 +164,4 @@ if (typeof window !== 'undefined') {
101164// Export for module systems (if needed)
102165if ( typeof module !== 'undefined' && module . exports ) {
103166 module . exports = CONFIG ;
104- }
105-
167+ }
0 commit comments