Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,4 @@ walkthrough.md
# Local planning and inspiration folders (keep tracked files as-is, ignore new ones)
Plan/
prism_inspo_ui/
temp_dir
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

## 2024-03-31 - [Optimize File Stats Fetching]
**Learning:** Sequential `for...of` loops performing I/O operations (like `await stat(absolutePath)`) scale poorly in local disk stores for projects containing many files, leading to performance bottlenecks.
**Action:** Use concurrent `Promise.all` with mapping over files to significantly improve processing speed when listing project files or performing batched independent disk operations.
36 changes: 25 additions & 11 deletions backend/src/store/localStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,33 @@ export class LocalStore {
const root = this.projectFilesDir(projectId);
const files = await listFilesRecursive(root);
const out: ProjectFileEntry[] = [];
for (const absolutePath of files) {
try {
const stats = await stat(absolutePath);
const rel = relative(root, absolutePath).replace(/\\/g, "/");
out.push({
path: rel,
size: stats.size,
modifiedAt: stats.mtime.toISOString()
});
} catch {
continue;

// Performance optimization: Process file stats concurrently in chunks to avoid EMFILE limits
const CHUNK_SIZE = 100;
for (let i = 0; i < files.length; i += CHUNK_SIZE) {
const chunk = files.slice(i, i + CHUNK_SIZE);
const promises = chunk.map(async (absolutePath) => {
try {
const stats = await stat(absolutePath);
const rel = relative(root, absolutePath).replace(/\\/g, "/");
return {
path: rel,
size: stats.size,
modifiedAt: stats.mtime.toISOString()
};
} catch {
return null;
}
});

const results = await Promise.all(promises);
for (const res of results) {
if (res !== null) {
out.push(res);
}
}
}

out.sort((a, b) => a.path.localeCompare(b.path));
return out;
}
Expand Down