@@ -16,6 +16,19 @@ export class FilesystemEventManager {
1616 private store : FilesystemStore
1717 private sandbox : Sandbox
1818
19+ // ms delay used when batching rapid load requests
20+ private static readonly LOAD_DEBOUNCE_MS = 300
21+
22+ private loadTimers : Map < string , ReturnType < typeof setTimeout > > = new Map ( )
23+ private pendingLoads : Map <
24+ string ,
25+ {
26+ promise : Promise < void >
27+ resolve : ( ) => void
28+ reject : ( err : unknown ) => void
29+ }
30+ > = new Map ( )
31+
1932 constructor ( store : FilesystemStore , sandbox : Sandbox , rootPath : string ) {
2033 this . store = store
2134 this . sandbox = sandbox
@@ -32,7 +45,7 @@ export class FilesystemEventManager {
3245 this . watchHandle = await this . sandbox . files . watchDir (
3346 this . rootPath ,
3447 ( event ) => this . handleFilesystemEvent ( event ) ,
35- { recursive : true }
48+ { recursive : true , timeout : 0 , timeoutMs : 0 }
3649 )
3750 } catch ( error ) {
3851 console . error ( `Failed to start root watcher on ${ this . rootPath } :` , error )
@@ -120,6 +133,49 @@ export class FilesystemEventManager {
120133 }
121134
122135 async loadDirectory ( path : string ) : Promise < void > {
136+ console . log ( 'LOAD_DIRECTORY' , path )
137+
138+ const normalizedPath = normalizePath ( path )
139+
140+ // if there is already a scheduled load for this path, reset the timer and return its promise
141+ const existingTimer = this . loadTimers . get ( normalizedPath )
142+ if ( existingTimer ) {
143+ clearTimeout ( existingTimer )
144+ }
145+
146+ let pending = this . pendingLoads . get ( normalizedPath )
147+ if ( ! pending ) {
148+ let res ! : ( ) => void
149+ let rej ! : ( err : unknown ) => void
150+ const promise = new Promise < void > ( ( resolve , reject ) => {
151+ res = resolve
152+ rej = reject
153+ } )
154+ pending = { promise, resolve : res , reject : rej }
155+ this . pendingLoads . set ( normalizedPath , pending )
156+ }
157+
158+ const timer = setTimeout ( async ( ) => {
159+ // once the timer fires, perform the actual load then resolve/reject all waiters
160+ this . loadTimers . delete ( normalizedPath )
161+ try {
162+ await this . loadDirectoryImmediate ( normalizedPath )
163+ pending ! . resolve ( )
164+ } catch ( err ) {
165+ pending ! . reject ( err )
166+ } finally {
167+ this . pendingLoads . delete ( normalizedPath )
168+ }
169+ } , FilesystemEventManager . LOAD_DEBOUNCE_MS )
170+
171+ this . loadTimers . set ( normalizedPath , timer )
172+
173+ return pending . promise
174+ }
175+
176+ private async loadDirectoryImmediate ( path : string ) : Promise < void > {
177+ console . log ( 'LOAD_DIRECTORY_IMMEDIATE' , path )
178+
123179 const normalizedPath = normalizePath ( path )
124180 const state = this . store . getState ( )
125181 const node = state . getNode ( normalizedPath )
@@ -160,6 +216,15 @@ export class FilesystemEventManager {
160216 } )
161217
162218 state . addNodes ( normalizedPath , nodes )
219+
220+ const newChildrenSet = new Set ( nodes . map ( ( n ) => normalizePath ( n . path ) ) )
221+
222+ for ( const childPath of [ ...node . children ] ) {
223+ if ( ! newChildrenSet . has ( childPath ) ) {
224+ state . removeNode ( childPath )
225+ }
226+ }
227+
163228 state . updateNode ( normalizedPath , { isLoaded : true } )
164229 } catch ( error ) {
165230 const errorMessage =
@@ -175,16 +240,9 @@ export class FilesystemEventManager {
175240 const normalizedPath = normalizePath ( path )
176241 const state = this . store . getState ( )
177242
243+ // mark directory as stale but keep existing children until fresh data arrives
178244 state . updateNode ( normalizedPath , { isLoaded : false } )
179245
180- const node = state . getNode ( normalizedPath )
181- if ( node && node . type === FileType . DIR ) {
182- const childrenPaths = [ ...node . children ]
183- for ( const childPath of childrenPaths ) {
184- state . removeNode ( childPath )
185- }
186- }
187-
188246 await this . loadDirectory ( normalizedPath )
189247 }
190248}
0 commit comments