diff --git a/docs/vfs/VFS_INITIALIZATION.md b/docs/vfs/VFS_INITIALIZATION.md new file mode 100644 index 00000000..37b50689 --- /dev/null +++ b/docs/vfs/VFS_INITIALIZATION.md @@ -0,0 +1,202 @@ +# VFS Initialization Guide + +## Quick Start + +The Brainy VFS requires proper initialization before use. Here's the correct pattern: + +```javascript +import { Brainy } from '@soulcraft/brainy' + +// Step 1: Create and initialize Brainy +const brain = new Brainy({ + storage: { type: 'filesystem', path: './data' } +}) +await brain.init() + +// Step 2: Get VFS instance (it's a METHOD, not a property!) +const vfs = brain.vfs() // ✅ Correct: method call with () + +// Step 3: Initialize VFS (THIS IS REQUIRED!) +await vfs.init() // Creates the root directory + +// Now you can use VFS +await vfs.writeFile('/test.txt', 'Hello World') +const files = await vfs.readdir('/') +``` + +## Common Mistakes + +### ❌ Mistake 1: Accessing VFS as Property +```javascript +// WRONG - vfs is a method, not a property +const vfs = brain.vfs // Missing parentheses! +``` + +### ❌ Mistake 2: Forgetting to Initialize VFS +```javascript +const vfs = brain.vfs() +// Missing: await vfs.init() +await vfs.writeFile('/test.txt', 'data') // Error: VFS not initialized +``` + +### ❌ Mistake 3: Not Waiting for Initialization +```javascript +const vfs = brain.vfs() +vfs.init() // Missing await! +await vfs.readdir('/') // Error: VFS not initialized (init still running) +``` + +## Why Initialization is Required + +The VFS `init()` method performs critical setup: + +1. **Creates the root directory entity** in Brainy's graph database +2. **Initializes the PathResolver** for efficient path lookups +3. **Sets up caching layers** for performance +4. **Starts background tasks** for maintenance +5. **Configures storage adapters** based on your settings + +Without initialization, the root directory (`/`) doesn't exist, which is why operations fail with "Not a directory: /" errors. + +## Complete Example + +```javascript +import { Brainy } from '@soulcraft/brainy' + +async function setupVFS() { + // Initialize Brainy + const brain = new Brainy({ + storage: { + type: 'filesystem', // or 'memory', 's3', 'r2' + path: './brainy-data' + } + }) + await brain.init() + + // Get and initialize VFS + const vfs = brain.vfs() + await vfs.init() + + // Verify initialization + const rootExists = await vfs.exists('/') + console.log('Root directory exists:', rootExists) // true + + const stats = await vfs.stat('/') + console.log('Root is directory:', stats.isDirectory()) // true + + // Now use VFS normally + await vfs.writeFile('/readme.txt', 'Welcome to VFS!') + await vfs.mkdir('/documents') + + const files = await vfs.readdir('/') + console.log('Files in root:', files) // ['readme.txt', 'documents'] + + return vfs +} + +setupVFS().catch(console.error) +``` + +## Error Messages + +If you see this error: +``` +VFS not initialized. You must call await vfs.init() after getting the VFS instance. +Example: + const vfs = brain.vfs() // Note: vfs() is a method, not a property + await vfs.init() // This creates the root directory +``` + +It means you forgot to initialize VFS. Follow the example in the error message. + +## TypeScript Usage + +```typescript +import { Brainy, VirtualFileSystem } from '@soulcraft/brainy' + +class FileManager { + private brain: Brainy + private vfs: VirtualFileSystem | null = null + + async initialize(): Promise { + // Initialize Brainy + this.brain = new Brainy({ + storage: { type: 'filesystem' } + }) + await this.brain.init() + + // Initialize VFS + this.vfs = this.brain.vfs() + await this.vfs.init() + } + + async writeFile(path: string, content: string): Promise { + if (!this.vfs) { + throw new Error('FileManager not initialized. Call initialize() first.') + } + await this.vfs.writeFile(path, content) + } +} +``` + +## Auto-Initialization Pattern (Optional) + +If you want VFS to auto-initialize on first use: + +```javascript +class AutoInitVFS { + constructor(config) { + this.brain = new Brainy(config) + this.vfs = null + this.initPromise = null + } + + async ensureInit() { + if (!this.initPromise) { + this.initPromise = this._initialize() + } + await this.initPromise + } + + async _initialize() { + await this.brain.init() + this.vfs = this.brain.vfs() + await this.vfs.init() + } + + // Wrap all VFS methods + async writeFile(path, data) { + await this.ensureInit() + return this.vfs.writeFile(path, data) + } + + async readdir(path) { + await this.ensureInit() + return this.vfs.readdir(path) + } +} + +// Usage - no explicit init needed +const vfs = new AutoInitVFS({ storage: { type: 'memory' } }) +await vfs.writeFile('/test.txt', 'Auto-init works!') +``` + +## FAQ + +### Q: Why doesn't VFS auto-initialize? +A: Explicit initialization gives you control over when the root directory is created and when background tasks start. This prevents unexpected side effects and makes the initialization cost visible. + +### Q: Can I reinitialize VFS? +A: No, VFS can only be initialized once per instance. If you need to reset, create a new Brainy instance. + +### Q: What happens if Brainy isn't initialized? +A: VFS initialization will fail. Always initialize Brainy first with `await brain.init()`. + +### Q: Is the initialization pattern the same for all storage types? +A: Yes, whether using memory, filesystem, S3, or R2 storage, the initialization pattern is identical. + +## Related Documentation + +- [VFS Quick Start](./QUICK_START.md) - 5-minute setup guide +- [VFS API Guide](./VFS_API_GUIDE.md) - Complete API reference +- [Common Patterns](./COMMON_PATTERNS.md) - Best practices and patterns \ No newline at end of file diff --git a/src/vfs/VirtualFileSystem.ts b/src/vfs/VirtualFileSystem.ts index 4f43c6ac..86f4f3a2 100644 --- a/src/vfs/VirtualFileSystem.ts +++ b/src/vfs/VirtualFileSystem.ts @@ -884,7 +884,13 @@ export class VirtualFileSystem implements IVirtualFileSystem { private async ensureInitialized(): Promise { if (!this.initialized) { - throw new Error('VFS not initialized. Call init() first.') + throw new Error( + 'VFS not initialized. You must call await vfs.init() after getting the VFS instance.\n' + + 'Example:\n' + + ' const vfs = brain.vfs() // Note: vfs() is a method, not a property\n' + + ' await vfs.init() // This creates the root directory\n' + + 'See docs: https://github.com/Brainy-Technologies/brainy/blob/main/docs/vfs/QUICK_START.md' + ) } } diff --git a/tests/vfs/vfs-initialization.unit.test.ts b/tests/vfs/vfs-initialization.unit.test.ts new file mode 100644 index 00000000..7e354664 --- /dev/null +++ b/tests/vfs/vfs-initialization.unit.test.ts @@ -0,0 +1,126 @@ +/** + * VFS Initialization Tests + * + * Tests proper VFS initialization patterns to prevent common errors + */ + +import { describe, it, expect } from 'vitest' +import { VirtualFileSystem } from '../../src/vfs/index.js' +import { Brainy } from '../../src/brainy.js' + +describe('VFS Initialization', () => { + + describe('Common Initialization Errors', () => { + it('should fail if VFS is not initialized before use', async () => { + const brain = new Brainy({ + storage: { type: 'memory' }, + silent: true + }) + await brain.init() + + const vfs = brain.vfs() + // Attempting to use VFS without calling init() + + await expect(vfs.writeFile('/test.txt', 'Hello')) + .rejects.toThrow('VFS not initialized. Call init() first.') + + await expect(vfs.readdir('/')) + .rejects.toThrow('VFS not initialized. Call init() first.') + }) + }) + + describe('Correct Initialization Pattern', () => { + it('should work when VFS is properly initialized', async () => { + // Step 1: Initialize Brainy + const brain = new Brainy({ + storage: { type: 'memory' }, + silent: true + }) + await brain.init() + + // Step 2: Get VFS instance + const vfs = brain.vfs() + + // Step 3: Initialize VFS - this creates the root directory + await vfs.init() + + // Now VFS operations work correctly + await vfs.writeFile('/test.txt', 'Hello World') + await vfs.writeFile('/data.json', '{"key": "value"}') + + // Root directory can be listed + const entries = await vfs.readdir('/') + expect(entries).toContain('test.txt') + expect(entries).toContain('data.json') + + // Root directory stats are available + const stats = await vfs.stat('/') + expect(stats.isDirectory()).toBe(true) + + // Cleanup + await vfs.close() + await brain.close() + }) + + it('should automatically create root directory on init', async () => { + const brain = new Brainy({ + storage: { type: 'memory' }, + silent: true + }) + await brain.init() + + const vfs = brain.vfs() + + // Before init, operations fail + await expect(vfs.exists('/')) + .rejects.toThrow('VFS not initialized') + + // After init, root exists + await vfs.init() + expect(await vfs.exists('/')).toBe(true) + + // Root is a directory + const stats = await vfs.stat('/') + expect(stats.isDirectory()).toBe(true) + + // Can list empty root + const entries = await vfs.readdir('/') + expect(Array.isArray(entries)).toBe(true) + expect(entries).toEqual([]) + + await vfs.close() + await brain.close() + }) + + it('should handle nested directories after initialization', async () => { + const brain = new Brainy({ + storage: { type: 'memory' }, + silent: true + }) + await brain.init() + + const vfs = brain.vfs() + await vfs.init() + + // Create nested structure + await vfs.mkdir('/documents') + await vfs.writeFile('/documents/readme.txt', 'Important info') + await vfs.mkdir('/documents/reports') + await vfs.writeFile('/documents/reports/q1.txt', 'Q1 Report') + + // Verify structure + const rootEntries = await vfs.readdir('/') + expect(rootEntries).toContain('documents') + + const docEntries = await vfs.readdir('/documents') + expect(docEntries).toContain('readme.txt') + expect(docEntries).toContain('reports') + + const reportEntries = await vfs.readdir('/documents/reports') + expect(reportEntries).toContain('q1.txt') + + await vfs.close() + await brain.close() + }) + }) +}) \ No newline at end of file