11import  *  as  utils  from  '../../../lib/utils.js' ; 
22import  getAbsolutePathForApp  from  '../utils/getAbsolutePathForApp.js' ; 
33
4+ // Track in-flight requests to avoid duplicate backend calls 
5+ // Each entry stores: { promise, timestamp } 
6+ const  inflightRequests  =  new  Map ( ) ; 
7+ 
8+ // Time window (in ms) to group duplicate requests together 
9+ // Requests made within this window will share the same backend call 
10+ const  DEDUPLICATION_WINDOW_MS  =  2000 ;  // 2 seconds 
11+ 
412const  stat  =  async  function  ( ...args )  { 
513    let  options ; 
614
@@ -24,17 +32,6 @@ const stat = async function (...args) {
2432            options . consistency  =  'strong' ; 
2533        } 
2634
27-         // If auth token is not provided and we are in the web environment,  
28-         // try to authenticate with Puter 
29-         if ( ! puter . authToken  &&  puter . env  ===  'web' ) { 
30-             try { 
31-                 await  puter . ui . authenticateWithPuter ( ) ; 
32-             } catch ( e ) { 
33-                 // if authentication fails, throw an error 
34-                 reject ( 'Authentication failed.' ) ; 
35-             } 
36-         } 
37- 
3835        // Generate cache key based on path or uid 
3936        let  cacheKey ; 
4037        if ( options . path ) { 
@@ -50,41 +47,106 @@ const stat = async function (...args) {
5047            } 
5148        } 
5249
53-         // create xhr object 
54-         const  xhr  =  utils . initXhr ( '/stat' ,  this . APIOrigin ,  undefined ,  "post" ,  "text/plain;actually=json" ) ; 
50+         // Generate deduplication key based on all request parameters 
51+         const  deduplicationKey  =  JSON . stringify ( { 
52+             path : options . path , 
53+             uid : options . uid , 
54+             returnSubdomains : options . returnSubdomains , 
55+             returnPermissions : options . returnPermissions , 
56+             returnVersions : options . returnVersions , 
57+             returnSize : options . returnSize , 
58+             consistency : options . consistency , 
59+         } ) ; 
5560
56-         // set up event handlers for load and error events 
57-         utils . setupXhrEventHandlers ( xhr ,  options . success ,  options . error ,  async  ( result )  =>  { 
58-             // Calculate the size of the result for cache eligibility check 
59-             const  resultSize  =  JSON . stringify ( result ) . length ; 
61+         // Check if there's already an in-flight request for the same parameters 
62+         const  existingEntry  =  inflightRequests . get ( deduplicationKey ) ; 
63+         const  now  =  Date . now ( ) ; 
64+         
65+         if  ( existingEntry )  { 
66+             const  timeSinceRequest  =  now  -  existingEntry . timestamp ; 
6067
61-             // Cache the result if it's not bigger than MAX_CACHE_SIZE 
62-             const  MAX_CACHE_SIZE  =  20  *  1024  *  1024 ; 
68+             // Only reuse the request if it's within the deduplication window 
69+             if  ( timeSinceRequest  <  DEDUPLICATION_WINDOW_MS )  { 
70+                 // Wait for the existing request and return its result 
71+                 try  { 
72+                     const  result  =  await  existingEntry . promise ; 
73+                     resolve ( result ) ; 
74+                 }  catch  ( error )  { 
75+                     reject ( error ) ; 
76+                 } 
77+                 return ; 
78+             }  else  { 
79+                 // Request is too old, remove it from the tracker 
80+                 inflightRequests . delete ( deduplicationKey ) ; 
81+             } 
82+         } 
6383
64-             if ( resultSize  <=  MAX_CACHE_SIZE ) { 
65-                 // UPSERT the cache 
66-                 puter . _cache . set ( cacheKey ,  result ) ; 
84+         // Create a promise for this request and store it to deduplicate concurrent calls 
85+         const  requestPromise  =  new  Promise ( async  ( resolveRequest ,  rejectRequest )  =>  { 
86+             // If auth token is not provided and we are in the web environment,  
87+             // try to authenticate with Puter 
88+             if ( ! puter . authToken  &&  puter . env  ===  'web' ) { 
89+                 try { 
90+                     await  puter . ui . authenticateWithPuter ( ) ; 
91+                 } catch ( e ) { 
92+                     // if authentication fails, throw an error 
93+                     rejectRequest ( 'Authentication failed.' ) ; 
94+                     return ; 
95+                 } 
96+             } 
97+ 
98+             // create xhr object 
99+             const  xhr  =  utils . initXhr ( '/stat' ,  this . APIOrigin ,  undefined ,  "post" ,  "text/plain;actually=json" ) ; 
100+ 
101+             // set up event handlers for load and error events 
102+             utils . setupXhrEventHandlers ( xhr ,  options . success ,  options . error ,  async  ( result )  =>  { 
103+                 // Calculate the size of the result for cache eligibility check 
104+                 const  resultSize  =  JSON . stringify ( result ) . length ; 
105+                 
106+                 // Cache the result if it's not bigger than MAX_CACHE_SIZE 
107+                 const  MAX_CACHE_SIZE  =  20  *  1024  *  1024 ; 
108+ 
109+                 if ( resultSize  <=  MAX_CACHE_SIZE ) { 
110+                     // UPSERT the cache 
111+                     puter . _cache . set ( cacheKey ,  result ) ; 
112+                 } 
113+                 
114+                 resolveRequest ( result ) ; 
115+             } ,  rejectRequest ) ; 
116+ 
117+             let  dataToSend  =  { } ; 
118+             if  ( options . uid  !==  undefined )  { 
119+                 dataToSend . uid  =  options . uid ; 
120+             }  else  if  ( options . path  !==  undefined )  { 
121+                 // If dirPath is not provided or it's not starting with a slash, it means it's a relative path 
122+                 // in that case, we need to prepend the app's root directory to it 
123+                 dataToSend . path  =  getAbsolutePathForApp ( options . path ) ; 
67124            } 
68125
126+             dataToSend . return_subdomains  =  options . returnSubdomains ; 
127+             dataToSend . return_permissions  =  options . returnPermissions ; 
128+             dataToSend . return_versions  =  options . returnVersions ; 
129+             dataToSend . return_size  =  options . returnSize ; 
130+             dataToSend . auth_token  =  this . authToken ; 
131+ 
132+             xhr . send ( JSON . stringify ( dataToSend ) ) ; 
133+         } ) ; 
134+ 
135+         // Store the promise and timestamp in the in-flight tracker 
136+         inflightRequests . set ( deduplicationKey ,  { 
137+             promise : requestPromise , 
138+             timestamp : now , 
139+         } ) ; 
140+ 
141+         // Wait for the request to complete and clean up 
142+         try  { 
143+             const  result  =  await  requestPromise ; 
144+             inflightRequests . delete ( deduplicationKey ) ; 
69145            resolve ( result ) ; 
70-         } ,  reject ) ; 
71- 
72-         let  dataToSend  =  { } ; 
73-         if  ( options . uid  !==  undefined )  { 
74-             dataToSend . uid  =  options . uid ; 
75-         }  else  if  ( options . path  !==  undefined )  { 
76-             // If dirPath is not provided or it's not starting with a slash, it means it's a relative path 
77-             // in that case, we need to prepend the app's root directory to it 
78-             dataToSend . path  =  getAbsolutePathForApp ( options . path ) ; 
146+         }  catch  ( error )  { 
147+             inflightRequests . delete ( deduplicationKey ) ; 
148+             reject ( error ) ; 
79149        } 
80-         
81-         dataToSend . return_subdomains  =  options . returnSubdomains ; 
82-         dataToSend . return_permissions  =  options . returnPermissions ; 
83-         dataToSend . return_versions  =  options . returnVersions ; 
84-         dataToSend . return_size  =  options . returnSize ; 
85-         dataToSend . auth_token  =  this . authToken ; 
86- 
87-         xhr . send ( JSON . stringify ( dataToSend ) ) ; 
88150    } ) 
89151} 
90152
0 commit comments