brainy/docs/vfs/COMMON_PATTERNS.md
David Snelling 606445cd61 feat(8.0): API simplification — remove neural()/Db.search, one storage path key, integration→0
8.0 RC cleanup toward "one place per thing, zero-config, no deprecation":

- Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead
  legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})`
  / `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate
  entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor,
  NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept.
- Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams).
  Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` →
  now `find({ query, limit })`.
- Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases
  (`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the
  exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver
  feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native
  storage provider resolves the identical root (no split-brain).
- Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now
  applied as a post-filter on `result.score` (the documented way to bound semantic results).
- Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()`
  and failed dimension validation; they are metadata-only updates now.
- Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination
  shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an
  in-place path change that preserves the blob and the entity id, for files and directories.
- Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability.
  Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk
  flushes and emits `progress.queryable`, so imported data is queryable during the import.
- Sweep all docs, comments, and JSDoc for the removed/changed APIs.

Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00

581 lines
No EOL
14 KiB
Markdown

# 🎯 VFS Common Patterns: Do This, Not That
> Learn the correct patterns for using Brainy VFS. Avoid the mistakes that cause crashes, poor performance, and API confusion.
## 🚨 Critical Pattern: Safe Tree Operations
### ❌ **WRONG - Causes Infinite Recursion**
```typescript
// DON'T DO THIS - Directory appears as its own child!
function buildFileTree(allItems, parentPath) {
return allItems.filter(item => {
// This includes the parent directory itself!
return item.path.startsWith(parentPath)
})
}
// Result: /dir -> /dir -> /dir -> ∞ (crashes browser/server)
```
### ✅ **CORRECT - Tree-Aware Methods**
```typescript
// ✅ Pattern 1: Direct children for UI trees
async function loadDirectoryUI(path: string) {
const children = await vfs.getDirectChildren(path)
// Guaranteed: No self-inclusion, no recursion
return children.map(child => ({
name: child.metadata.name,
path: child.metadata.path,
type: child.metadata.vfsType,
hasChildren: child.metadata.vfsType === 'directory'
}))
}
// ✅ Pattern 2: Complete tree structure
async function buildCompleteTree(path: string) {
return await vfs.getTreeStructure(path, {
maxDepth: 5, // Prevent deep recursion
includeHidden: false, // Skip .hidden files
sort: 'name' // Organized output
})
}
// ✅ Pattern 3: Detailed inspection
async function inspectPath(path: string) {
const info = await vfs.inspect(path)
return {
current: info.node,
children: info.children, // Direct children only
parent: info.parent, // Parent directory
stats: info.stats // Size, permissions, etc.
}
}
```
## 🗃️ Storage Configuration Patterns
### ❌ **WRONG - Memory Storage for Files**
```typescript
// DON'T DO THIS - Data disappears when process exits!
const brain = new Brainy({
storage: { type: 'memory' } // ❌ Temporary only
})
// Files written here are lost forever on restart
await vfs.writeFile('/important.doc', content)
```
### ✅ **CORRECT - Persistent Storage**
```typescript
// ✅ Pattern 1: Filesystem storage (development)
const brain = new Brainy({
storage: {
type: 'filesystem',
path: './brainy-data' // Persisted to disk
}
})
// ✅ Pattern 2: Filesystem (production default)
const brain = new Brainy({
storage: {
type: 'filesystem',
path: '/var/lib/brainy'
}
})
// ✅ Pattern 3: Auto-detection (recommended)
const brain = new Brainy() // Picks filesystem on Node, memory in browser
```
## 🔍 Search Patterns
### ❌ **WRONG - Manual String Matching**
```typescript
// DON'T DO THIS - Missing semantic understanding
async function findFilesOldWay(query: string) {
const allFiles = await vfs.readdir('/', { recursive: true })
return allFiles.filter(file =>
file.includes(query.toLowerCase()) // ❌ Basic string match
)
}
```
### ✅ **CORRECT - Semantic Search**
```typescript
// ✅ Pattern 1: Content-aware search
async function findFilesByContent(query: string) {
return await vfs.search(query, {
type: 'file', // Only search files
limit: 50, // Reasonable limit
threshold: 0.7 // Minimum relevance
})
}
// ✅ Pattern 2: Filtered search
async function findInDirectory(query: string, basePath: string) {
return await vfs.search(query, {
path: basePath, // Limit to specific directory
includeContent: true, // Include file content in results
sort: 'relevance' // Best matches first
})
}
// ✅ Pattern 3: Metadata-based search
async function findByAttributes(criteria: any) {
return await vfs.find({
where: criteria, // MongoDB-style queries
orderBy: 'modified', // Sort by last modified
limit: 100
})
}
```
## 🏗️ Initialization Patterns
### ❌ **WRONG - Race Conditions**
```typescript
// DON'T DO THIS - Not waiting for initialization
const brain = new Brainy()
const vfs = brain.vfs()
// ❌ Using VFS before it's ready
await vfs.writeFile('/file.txt', 'content') // May fail!
```
### ✅ **CORRECT - Proper Initialization**
```typescript
// ✅ Pattern 1: Sequential initialization
async function initializeVFS() {
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
})
// Wait for brain to be ready
await brain.init()
// Then initialize VFS
const vfs = brain.vfs()
await vfs.init()
// Now safe to use
return vfs
}
// ✅ Pattern 2: With error handling
async function robustVFSInit() {
try {
const brain = new Brainy()
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Verify it's working
await vfs.stat('/') // Should not throw
return vfs
} catch (error) {
console.error('VFS initialization failed:', error)
throw new Error(`Cannot initialize VFS: ${error.message}`)
}
}
// ✅ Pattern 3: Singleton pattern for apps
class VFSManager {
private static instance: any = null
static async getInstance() {
if (!this.instance) {
const brain = new Brainy()
await brain.init()
this.instance = brain.vfs()
await this.instance.init()
}
return this.instance
}
}
```
## 📝 File Operation Patterns
### ❌ **WRONG - Blocking Operations**
```typescript
// DON'T DO THIS - Blocking the main thread
async function badFileProcessing(files: string[]) {
for (const file of files) {
const content = await vfs.readFile(file) // ❌ Sequential
await processContent(content) // ❌ Blocking
await vfs.writeFile(file + '.processed', result)
}
}
```
### ✅ **CORRECT - Efficient Operations**
```typescript
// ✅ Pattern 1: Parallel processing
async function efficientProcessing(files: string[]) {
const operations = files.map(async (file) => {
try {
const content = await vfs.readFile(file)
const result = await processContent(content)
await vfs.writeFile(file + '.processed', result)
return { file, success: true }
} catch (error) {
return { file, success: false, error: error.message }
}
})
return await Promise.allSettled(operations)
}
// ✅ Pattern 2: Batch operations with limits
async function batchProcessFiles(files: string[], batchSize = 10) {
const results = []
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize)
const batchResults = await Promise.all(
batch.map(file => processFile(file))
)
results.push(...batchResults)
// Optional: Add delay between batches
if (i + batchSize < files.length) {
await new Promise(resolve => setTimeout(resolve, 100))
}
}
return results
}
// ✅ Pattern 3: Streaming for large files
async function streamLargeFile(filePath: string) {
const stream = await vfs.createReadStream(filePath, {
highWaterMark: 64 * 1024 // 64KB chunks
})
return new Promise((resolve, reject) => {
let content = ''
stream.on('data', (chunk) => {
content += chunk.toString()
})
stream.on('end', () => resolve(content))
stream.on('error', reject)
})
}
```
## 🔗 Relationship Patterns
### ❌ **WRONG - Manual Relationship Tracking**
```typescript
// DON'T DO THIS - Reinventing the graph
const fileRelationships = new Map() // ❌ Manual tracking
function linkFiles(sourceFile: string, targetFile: string, relationship: string) {
if (!fileRelationships.has(sourceFile)) {
fileRelationships.set(sourceFile, [])
}
fileRelationships.get(sourceFile).push({ target: targetFile, type: relationship })
}
```
### ✅ **CORRECT - Use Built-in Relationships**
```typescript
// ✅ Pattern 1: Semantic relationships
async function createFileRelationships() {
// Link test to source file
await vfs.addRelationship(
'/src/auth.ts',
'/tests/auth.test.ts',
'tested-by'
)
// Link documentation to implementation
await vfs.addRelationship(
'/docs/api.md',
'/src/api.ts',
'documents'
)
// Link dependency relationships
await vfs.addRelationship(
'/src/index.ts',
'/src/utils.ts',
'imports'
)
}
// ✅ Pattern 2: Query relationships
async function findRelatedFiles(filePath: string) {
// Find all files related to this one
const related = await vfs.getRelated(filePath, {
depth: 2, // Include relationships of relationships
types: ['tests', 'documents', 'imports'], // Filter relationship types
direction: 'both' // Both incoming and outgoing
})
return related
}
// ✅ Pattern 3: Relationship-based search
async function findTestFiles(sourceFile: string) {
return await vfs.search('', {
connected: {
to: sourceFile,
via: 'tested-by',
direction: 'incoming'
}
})
}
```
## 🚀 Performance Patterns
### ❌ **WRONG - Loading Everything**
```typescript
// DON'T DO THIS - Loading massive directories
async function loadEntireProject() {
const allFiles = await vfs.getTreeStructure('/', {
// ❌ No limits, could be millions of files
})
return allFiles // ❌ Crashes on large projects
}
```
### ✅ **CORRECT - Smart Loading**
```typescript
// ✅ Pattern 1: Paginated loading
async function loadDirectoryPage(path: string, page = 0, size = 50) {
const children = await vfs.getDirectChildren(path, {
limit: size,
offset: page * size,
sort: 'name'
})
const total = await vfs.getChildrenCount(path)
return {
items: children,
page,
size,
total,
hasMore: (page + 1) * size < total
}
}
// ✅ Pattern 2: Lazy loading with caching
class FileTreeCache {
private cache = new Map()
async getDirectory(path: string) {
if (this.cache.has(path)) {
return this.cache.get(path)
}
const children = await vfs.getDirectChildren(path)
this.cache.set(path, children)
// Auto-expire cache after 5 minutes
setTimeout(() => this.cache.delete(path), 5 * 60 * 1000)
return children
}
}
// ✅ Pattern 3: Progressive disclosure
async function buildLazyTree(rootPath: string) {
const tree = await vfs.getTreeStructure(rootPath, {
maxDepth: 1, // Only immediate children
lazy: true // Enable lazy loading for subdirectories
})
// Expand directories on demand
tree.expandDirectory = async (path: string) => {
const subtree = await vfs.getTreeStructure(path, {
maxDepth: 1
})
return subtree.children
}
return tree
}
```
## 🔧 Error Handling Patterns
### ❌ **WRONG - Silent Failures**
```typescript
// DON'T DO THIS - Ignoring errors
async function badErrorHandling(path: string) {
try {
return await vfs.readFile(path)
} catch (error) {
return null // ❌ Silent failure
}
}
```
### ✅ **CORRECT - Robust Error Handling**
```typescript
// ✅ Pattern 1: Specific error handling
async function robustFileRead(path: string) {
try {
return await vfs.readFile(path)
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`File not found: ${path}`)
} else if (error.code === 'EACCES') {
throw new Error(`Permission denied: ${path}`)
} else if (error.code === 'EISDIR') {
throw new Error(`Path is a directory, not a file: ${path}`)
} else {
throw new Error(`Failed to read ${path}: ${error.message}`)
}
}
}
// ✅ Pattern 2: Retry with backoff
async function resilientOperation(operation: () => Promise<any>, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation()
} catch (error) {
if (attempt === maxRetries) {
throw error
}
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms...`)
}
}
}
// ✅ Pattern 3: Graceful degradation
async function gracefulFileExplorer(path: string) {
try {
// Try the optimal method first
return await vfs.getDirectChildren(path)
} catch (error) {
console.warn('Direct children failed, trying basic readdir:', error.message)
try {
// Fallback to basic directory listing
const entries = await vfs.readdir(path)
return entries.map(name => ({
metadata: { name, path: `${path}/${name}` },
// Missing detailed metadata, but functional
}))
} catch (fallbackError) {
console.error('All methods failed:', fallbackError.message)
return [] // Empty result rather than crash
}
}
}
```
## 📚 Integration Patterns
### ✅ **React Hook Pattern**
```typescript
function useVFS() {
const [vfs, setVFS] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function initVFS() {
try {
const brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
await brain.init()
const vfsInstance = brain.vfs()
await vfsInstance.init()
setVFS(vfsInstance)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
initVFS()
}, [])
return { vfs, loading, error }
}
```
### ✅ **Express.js Middleware Pattern**
```typescript
function vfsMiddleware() {
let vfsInstance: any = null
return async (req: any, res: any, next: any) => {
if (!vfsInstance) {
try {
const brain = new Brainy()
await brain.init()
vfsInstance = brain.vfs()
await vfsInstance.init()
} catch (error) {
return res.status(500).json({ error: 'VFS initialization failed' })
}
}
req.vfs = vfsInstance
next()
}
}
```
## 🎯 Summary: Do This, Not That
| ❌ **Avoid These Patterns** | ✅ **Use These Instead** |
|---------------------------|------------------------|
| Manual tree filtering | `vfs.getDirectChildren()` |
| Memory storage for files | Filesystem (snapshot off-site for backup) |
| Sequential file operations | Parallel processing with limits |
| Manual relationship tracking | Built-in `vfs.addRelationship()` |
| Loading entire directories | Paginated/lazy loading |
| Silent error handling | Specific error types and fallbacks |
| Blocking synchronous calls | Async/await with proper error handling |
---
**🎉 Following these patterns will give you:**
- 🚫 **Zero infinite recursion** in file explorers
-**Fast performance** even with large directories
- 🔄 **Reliable error recovery** and graceful degradation
- 🧠 **Semantic intelligence** for powerful file search
- 📈 **Scalable architecture** that grows with your needs
**Next Steps:** Check out the [VFS API Guide](./VFS_API_GUIDE.md) for complete method documentation.