From e66a8a2c69e5aff86769e61b769816fe5a3b773e Mon Sep 17 00:00:00 2001 From: David Snelling Date: Mon, 27 Oct 2025 11:25:55 -0700 Subject: [PATCH] fix(vfs): add sourceId+verbType optimization to fix readdir() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL VFS BUG FIX - Root Cause Analysis: The issue was that baseStorage.getVerbs() had optimizations for: - sourceId only - targetId only - verbType only But VFS PathResolver.getChildren() queries with BOTH sourceId AND verbType: getRelations({ from: dirId, type: VerbType.Contains }) This combination wasn't optimized, so it fell through to the getVerbsWithPagination() path, which MemoryStorage doesn't implement, causing it to return empty results. Fix: Added optimization for sourceId + verbType combination - Uses O(1) graph index lookup for sourceId - Filters results by verbType in O(n) - Enables VFS readdir() to work correctly This was the real bug preventing Workshop team from seeing their 567 markdown files in vfs.readdir('/'). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/storage/baseStorage.ts | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/storage/baseStorage.ts b/src/storage/baseStorage.ts index 533fa8a8..b9a1c416 100644 --- a/src/storage/baseStorage.ts +++ b/src/storage/baseStorage.ts @@ -631,6 +631,46 @@ export abstract class BaseStorage extends BaseStorageAdapter { // Optimize for common filter cases to avoid loading all verbs if (options?.filter) { + // CRITICAL VFS FIX: If filtering by sourceId + verbType (most common VFS pattern!) + // This is the query PathResolver.getChildren() uses: getRelations({ from: dirId, type: VerbType.Contains }) + if ( + options.filter.sourceId && + options.filter.verbType && + !options.filter.targetId && + !options.filter.service && + !options.filter.metadata + ) { + const sourceId = Array.isArray(options.filter.sourceId) + ? options.filter.sourceId[0] + : options.filter.sourceId + + const verbType = Array.isArray(options.filter.verbType) + ? options.filter.verbType[0] + : options.filter.verbType + + // Get verbs by source, then filter by type (O(1) graph lookup + O(n) type filter) + const verbsBySource = await this.getVerbsBySource_internal(sourceId) + const filteredVerbs = verbsBySource.filter(v => v.verb === verbType) + + // Apply pagination + const paginatedVerbs = filteredVerbs.slice(offset, offset + limit) + const hasMore = offset + limit < filteredVerbs.length + + // Set next cursor if there are more items + let nextCursor: string | undefined = undefined + if (hasMore && paginatedVerbs.length > 0) { + const lastItem = paginatedVerbs[paginatedVerbs.length - 1] + nextCursor = lastItem.id + } + + return { + items: paginatedVerbs, + totalCount: filteredVerbs.length, + hasMore, + nextCursor + } + } + // If filtering by sourceId only, use the optimized method if ( options.filter.sourceId &&