/** * fs-Compatible Interface for VFS * * Provides a drop-in replacement for Node's fs module * that uses VFS for storage instead of the real filesystem. * * Usage: * import { FSCompat } from '@soulcraft/brainy/vfs' * const fs = new FSCompat(brain.vfs) * * // Now use like Node's fs * await fs.promises.readFile('/path') * fs.createReadStream('/path').pipe(output) */ import { VirtualFileSystem } from './VirtualFileSystem.js' import type { ReadOptions, WriteOptions, MkdirOptions, VFSStats, VFSDirent } from './types.js' export class FSCompat { /** * Promise-based API (like fs.promises) */ public readonly promises: FSPromises constructor(private vfs: VirtualFileSystem) { this.promises = new FSPromises(vfs) } // ============= Callback-style methods (for compatibility) ============= readFile(path: string, callback: (err: Error | null, data?: Buffer) => void): void readFile(path: string, encoding: BufferEncoding, callback: (err: Error | null, data?: string) => void): void readFile(path: string, options: any, callback?: any): void { if (typeof options === 'function') { callback = options options = {} } this.vfs.readFile(path, options) .then(data => { if (options?.encoding) { callback(null, data.toString(options.encoding)) } else { callback(null, data) } }) .catch(err => callback(err)) } writeFile(path: string, data: Buffer | string, callback: (err: Error | null) => void): void writeFile(path: string, data: Buffer | string, options: any, callback?: any): void { if (typeof options === 'function') { callback = options options = {} } const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, options?.encoding || 'utf8') this.vfs.writeFile(path, buffer, options) .then(() => callback(null)) .catch(err => callback(err)) } mkdir(path: string, callback: (err: Error | null) => void): void mkdir(path: string, options: any, callback?: any): void { if (typeof options === 'function') { callback = options options = {} } this.vfs.mkdir(path, options) .then(() => callback(null)) .catch(err => callback(err)) } rmdir(path: string, callback: (err: Error | null) => void): void rmdir(path: string, options: any, callback?: any): void { if (typeof options === 'function') { callback = options options = {} } this.vfs.rmdir(path, options) .then(() => callback(null)) .catch(err => callback(err)) } readdir(path: string, callback: (err: Error | null, files?: string[]) => void): void readdir(path: string, options: any, callback?: any): void { if (typeof options === 'function') { callback = options options = {} } this.vfs.readdir(path, options) .then(files => callback(null, files)) .catch(err => callback(err)) } stat(path: string, callback: (err: Error | null, stats?: VFSStats) => void): void { this.vfs.stat(path) .then(stats => callback(null, stats)) .catch(err => callback(err)) } lstat(path: string, callback: (err: Error | null, stats?: VFSStats) => void): void { this.vfs.lstat(path) .then(stats => callback(null, stats)) .catch(err => callback(err)) } unlink(path: string, callback: (err: Error | null) => void): void { this.vfs.unlink(path) .then(() => callback(null)) .catch(err => callback(err)) } rename(oldPath: string, newPath: string, callback: (err: Error | null) => void): void { this.vfs.rename(oldPath, newPath) .then(() => callback(null)) .catch(err => callback(err)) } copyFile(src: string, dest: string, callback: (err: Error | null) => void): void copyFile(src: string, dest: string, flags: number, callback: (err: Error | null) => void): void copyFile(src: string, dest: string, flagsOrCallback: any, callback?: any): void { if (typeof flagsOrCallback === 'function') { callback = flagsOrCallback } else { // flags provided but not used } this.vfs.copy(src, dest) .then(() => callback(null)) .catch(err => callback(err)) } exists(path: string, callback: (exists: boolean) => void): void { this.vfs.exists(path) .then(exists => callback(exists)) .catch(() => callback(false)) } access(path: string, callback: (err: Error | null) => void): void access(path: string, mode: number, callback: (err: Error | null) => void): void access(path: string, modeOrCallback: any, callback?: any): void { if (typeof modeOrCallback === 'function') { callback = modeOrCallback } else { // mode provided but not used } this.vfs.exists(path) .then(exists => { if (exists) { callback(null) } else { const err: any = new Error('ENOENT: no such file or directory') err.code = 'ENOENT' callback(err) } }) .catch(err => callback(err)) } appendFile(path: string, data: Buffer | string, callback: (err: Error | null) => void): void appendFile(path: string, data: Buffer | string, options: any, callback?: any): void { if (typeof options === 'function') { callback = options options = {} } const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, options?.encoding || 'utf8') this.vfs.appendFile(path, buffer, options) .then(() => callback(null)) .catch(err => callback(err)) } // ============= Stream methods ============= createReadStream(path: string, options?: any) { return this.vfs.createReadStream(path, options) } createWriteStream(path: string, options?: any) { return this.vfs.createWriteStream(path, options) } // ============= Watch methods ============= watch(path: string, listener?: any) { return this.vfs.watch(path, listener) } watchFile(path: string, listener: any) { return this.vfs.watchFile(path, listener) } unwatchFile(path: string) { return this.vfs.unwatchFile(path) } // ============= Additional methods ============= /** * Import a directory from real filesystem (VFS extension) */ async importDirectory(sourcePath: string, options?: any) { return this.vfs.importDirectory(sourcePath, options) } /** * Search files semantically (VFS extension) */ async search(query: string, options?: any) { return this.vfs.search(query, options) } } /** * Promise-based fs API (like fs.promises) */ class FSPromises { constructor(private vfs: VirtualFileSystem) {} async readFile(path: string, options?: any): Promise { const buffer = await this.vfs.readFile(path, options) if (options?.encoding) { return buffer.toString(options.encoding) } return buffer } async writeFile(path: string, data: Buffer | string, options?: any): Promise { const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, options?.encoding || 'utf8') return this.vfs.writeFile(path, buffer, options) } async mkdir(path: string, options?: any): Promise { return this.vfs.mkdir(path, options) } async rmdir(path: string, options?: any): Promise { return this.vfs.rmdir(path, options) } async readdir(path: string, options?: any): Promise { return this.vfs.readdir(path, options) } async stat(path: string): Promise { return this.vfs.stat(path) } async lstat(path: string): Promise { return this.vfs.lstat(path) } async unlink(path: string): Promise { return this.vfs.unlink(path) } async rename(oldPath: string, newPath: string): Promise { return this.vfs.rename(oldPath, newPath) } async copyFile(src: string, dest: string, flags?: number): Promise { return this.vfs.copy(src, dest) } async access(path: string, mode?: number): Promise { const exists = await this.vfs.exists(path) if (!exists) { const err: any = new Error('ENOENT: no such file or directory') err.code = 'ENOENT' throw err } } async appendFile(path: string, data: Buffer | string, options?: any): Promise { const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, options?.encoding || 'utf8') return this.vfs.appendFile(path, buffer, options) } async realpath(path: string): Promise { return this.vfs.realpath(path) } async chmod(path: string, mode: number): Promise { return this.vfs.chmod(path, mode) } async chown(path: string, uid: number, gid: number): Promise { return this.vfs.chown(path, uid, gid) } async utimes(path: string, atime: Date, mtime: Date): Promise { return this.vfs.utimes(path, atime, mtime) } async symlink(target: string, path: string): Promise { return this.vfs.symlink(target, path) } async readlink(path: string): Promise { return this.vfs.readlink(path) } // VFS Extensions async search(query: string, options?: any) { return this.vfs.search(query, options) } async findSimilar(path: string, options?: any) { return this.vfs.findSimilar(path, options) } async importDirectory(sourcePath: string, options?: any) { return this.vfs.importDirectory(sourcePath, options) } } // Export a convenience function to create fs replacement export function createFS(vfs: VirtualFileSystem): FSCompat { return new FSCompat(vfs) }