/** * Rebuild Counts Utility * * Scans storage and rebuilds counts.json from actual data * Use this to fix databases affected by the v4.1.1 count synchronization bug * * NO MOCKS - Production-ready implementation */ import type { BaseStorage } from '../storage/baseStorage.js' export interface RebuildCountsResult { /** Total number of entities (nouns) found */ nounCount: number /** Total number of relationships (verbs) found */ verbCount: number /** Entity counts by type */ entityCounts: Map /** Verb counts by type */ verbCounts: Map /** Processing time in milliseconds */ duration: number } /** * Rebuild counts.json from actual storage data * * This scans all entities and relationships in storage and reconstructs * the counts index from scratch. Use this to fix count desynchronization. * * @param storage - The storage adapter to rebuild counts for * @returns Promise that resolves to rebuild statistics * * @example * ```typescript * const brain = new Brainy({ storage: { type: 'filesystem', path: './brainy-data' } }) * await brain.init() * * const result = await rebuildCounts(brain.storage) * console.log(`Rebuilt counts: ${result.nounCount} nouns, ${result.verbCount} verbs`) * ``` */ export async function rebuildCounts(storage: BaseStorage): Promise { const startTime = Date.now() console.log('🔧 Rebuilding counts from storage...') const entityCounts = new Map() const verbCounts = new Map() let totalNouns = 0 let totalVerbs = 0 // Scan all nouns using pagination console.log('📊 Scanning entities...') // Check if pagination method exists const storageWithPagination = storage as any if (typeof storageWithPagination.getNounsWithPagination !== 'function') { throw new Error('Storage adapter does not support getNounsWithPagination') } let hasMore = true let cursor: string | undefined while (hasMore) { const result: any = await storageWithPagination.getNounsWithPagination({ limit: 100, cursor }) for (const noun of result.items) { const metadata = await storage.getNounMetadata(noun.id) if (metadata?.noun) { const entityType = metadata.noun entityCounts.set(entityType, (entityCounts.get(entityType) || 0) + 1) totalNouns++ } } hasMore = result.hasMore cursor = result.nextCursor } console.log(` Found ${totalNouns} entities across ${entityCounts.size} types`) // Scan all verbs using pagination console.log('🔗 Scanning relationships...') if (typeof storageWithPagination.getVerbsWithPagination !== 'function') { throw new Error('Storage adapter does not support getVerbsWithPagination') } hasMore = true cursor = undefined while (hasMore) { const result: any = await storageWithPagination.getVerbsWithPagination({ limit: 100, cursor }) for (const verb of result.items) { if (verb.verb) { const verbType = verb.verb verbCounts.set(verbType, (verbCounts.get(verbType) || 0) + 1) totalVerbs++ } } hasMore = result.hasMore cursor = result.nextCursor } console.log(` Found ${totalVerbs} relationships across ${verbCounts.size} types`) // Update storage adapter's in-memory counts FIRST storageWithPagination.totalNounCount = totalNouns storageWithPagination.totalVerbCount = totalVerbs storageWithPagination.entityCounts = entityCounts storageWithPagination.verbCounts = verbCounts // Mark counts as pending persist (required for flushCounts to actually persist) storageWithPagination.pendingCountPersist = true storageWithPagination.pendingCountOperations = 1 // Persist counts using storage adapter's own persist method // This ensures counts.json is written correctly (compressed or uncompressed) await storageWithPagination.flushCounts() const duration = Date.now() - startTime console.log(`✅ Counts rebuilt successfully in ${duration}ms`) console.log(` Entities: ${totalNouns}`) console.log(` Relationships: ${totalVerbs}`) console.log('') console.log('Entity breakdown:') entityCounts.forEach((count, entityType) => { console.log(` ${entityType}: ${count}`) }) if (verbCounts.size > 0) { console.log('') console.log('Relationship breakdown:') verbCounts.forEach((count, verbType) => { console.log(` ${verbType}: ${count}`) }) } return { nounCount: totalNouns, verbCount: totalVerbs, entityCounts, verbCounts, duration } }