In-memory file-system with Node's fs API providing virtual file systems for testing, mocking, and development purposes.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
File statistics, directory entries, and utility classes available through the fs object for working with file system metadata and operations.
import { fs } from "memfs";Comprehensive file and directory statistics matching Node.js Stats interface. Accessible via the fs.Stats constructor or returned by stat operations.
/**
* File statistics class providing metadata about files and directories
* Accessible through fs.Stats constructor or returned by fs.statSync(), etc.
*/
interface Stats<T = number | bigint> {
// File size and identification
size: T;
ino: T;
mode: T;
nlink: T;
// Ownership and permissions
uid: T;
gid: T;
rdev: T;
// Timestamps
atime: Date;
mtime: Date;
ctime: Date;
birthtime: Date;
// Timestamp numbers (for compatibility)
atimeMs: T;
mtimeMs: T;
ctimeMs: T;
birthtimeMs: T;
// File type detection methods
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
isSocket(): boolean;
isFIFO(): boolean;
isCharacterDevice(): boolean;
isBlockDevice(): boolean;
// BigInt support
isBigIntStats(): this is Stats<bigint>;
}
/**
* File system statistics - returned by fs.statfsSync()
*/
interface StatFs<T = number | bigint> {
type: T;
bsize: T;
blocks: T;
bfree: T;
bavail: T;
files: T;
ffree: T;
}Usage Examples:
import { fs } from "memfs";
// Create test files
fs.writeFileSync('/data.txt', 'Hello World!');
fs.mkdirSync('/folder');
fs.symlinkSync('./data.txt', '/link.txt');
// Get file statistics
const fileStats = fs.statSync('/data.txt');
console.log('File size:', fileStats.size);
console.log('Is file:', fileStats.isFile()); // true
console.log('Is directory:', fileStats.isDirectory()); // false
console.log('Mode:', fileStats.mode.toString(8)); // octal permissions
console.log('Modified:', fileStats.mtime);
// Directory statistics
const dirStats = fs.statSync('/folder');
console.log('Directory size:', dirStats.size);
console.log('Is directory:', dirStats.isDirectory()); // true
// Symlink statistics (lstat vs stat)
const linkStats = fs.lstatSync('/link.txt'); // stats of the link itself
const targetStats = fs.statSync('/link.txt'); // stats of the target file
console.log('Link is symlink:', linkStats.isSymbolicLink()); // true
console.log('Target is file:', targetStats.isFile()); // true
// BigInt statistics
const bigintStats = fs.statSync('/data.txt', { bigint: true });
console.log('BigInt size:', typeof bigintStats.size); // 'bigint'
console.log('Is BigInt stats:', bigintStats.isBigIntStats()); // true
// File system statistics
const fsStats = fs.statfsSync('/');
console.log('Block size:', fsStats.bsize);
console.log('Total blocks:', fsStats.blocks);
console.log('Free blocks:', fsStats.bfree);Directory entry objects providing file type and name information.
/**
* Directory entry interface representing items found in directory listings
* Returned by fs.readdirSync() with withFileTypes: true option
*/
interface Dirent {
// Entry name and path
name: string;
path?: string;
// File type detection methods (same as Stats)
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
isSocket(): boolean;
isFIFO(): boolean;
isCharacterDevice(): boolean;
isBlockDevice(): boolean;
}Usage Examples:
import { fs } from "memfs";
// Set up directory structure
fs.mkdirSync('/app');
fs.writeFileSync('/app/index.js', 'console.log("Hello");');
fs.writeFileSync('/app/package.json', '{"name": "app"}');
fs.mkdirSync('/app/src');
fs.symlinkSync('./index.js', '/app/main.js');
// Get directory entries with file types
const entries = fs.readdirSync('/app', { withFileTypes: true });
entries.forEach(entry => {
console.log(`${entry.name}:`);
if (entry.isFile()) {
console.log(' Type: File');
} else if (entry.isDirectory()) {
console.log(' Type: Directory');
} else if (entry.isSymbolicLink()) {
console.log(' Type: Symbolic Link');
}
// Optional: check path property if available
if (entry.path) {
console.log(' Path:', entry.path);
}
});
// Filter entries by type
const files = entries.filter(entry => entry.isFile());
const directories = entries.filter(entry => entry.isDirectory());
const symlinks = entries.filter(entry => entry.isSymbolicLink());
console.log('Files:', files.map(f => f.name));
console.log('Directories:', directories.map(d => d.name));
console.log('Symlinks:', symlinks.map(s => s.name));
// Recursive directory traversal
function listAllFiles(dirPath: string, prefix = '') {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
entries.forEach(entry => {
const fullPath = `${dirPath}/${entry.name}`;
console.log(`${prefix}${entry.name}`);
if (entry.isDirectory()) {
listAllFiles(fullPath, prefix + ' ');
}
});
}
listAllFiles('/app');Promise-based file handle interface for advanced file operations.
/**
* File handle class for promise-based file operations
*/
export class FileHandle extends EventEmitter {
// File descriptor
readonly fd: number;
// File operations
appendFile(data: string | Buffer, options?: { encoding?: BufferEncoding; mode?: number; flag?: string }): Promise<void>;
chmod(mode: string | number): Promise<void>;
chown(uid: number, gid: number): Promise<void>;
close(): Promise<void>;
datasync(): Promise<void>;
sync(): Promise<void>;
// Reading operations
read<T extends ArrayBufferView>(
buffer: T,
offset?: number,
length?: number,
position?: number
): Promise<{ bytesRead: number; buffer: T }>;
readFile(options?: { encoding?: BufferEncoding | null; flag?: string }): Promise<string | Buffer>;
readv(buffers: readonly ArrayBufferView[], position?: number): Promise<ReadVResult>;
// File metadata
stat(options?: { bigint?: boolean }): Promise<Stats>;
truncate(len?: number): Promise<void>;
utimes(atime: string | number | Date, mtime: string | number | Date): Promise<void>;
// Writing operations
write<T extends ArrayBufferView>(
buffer: T,
offset?: number,
length?: number,
position?: number
): Promise<{ bytesWritten: number; buffer: T }>;
write(data: string, position?: number, encoding?: BufferEncoding): Promise<{ bytesWritten: number; buffer: string }>;
writeFile(data: string | Buffer, options?: { encoding?: BufferEncoding; mode?: number; flag?: string }): Promise<void>;
writev(buffers: readonly ArrayBufferView[], position?: number): Promise<WriteVResult>;
}
// Result types for vectored I/O
export interface ReadVResult {
bytesRead: number;
buffers: ArrayBufferView[];
}
export interface WriteVResult {
bytesWritten: number;
buffers: ArrayBufferView[];
}Usage Examples:
import { fs } from "memfs";
async function fileHandleOperations() {
// Open file handle
const fileHandle = await fs.promises.open('/data.txt', 'w+');
try {
// Write data
await fileHandle.writeFile('Hello World!\nSecond line\n');
// Read data
const content = await fileHandle.readFile('utf8');
console.log('Content:', content);
// Buffer-based operations
const buffer = Buffer.alloc(5);
const readResult = await fileHandle.read(buffer, 0, 5, 0);
console.log('Read:', readResult.bytesRead, 'bytes:', buffer.toString());
// Write buffer
const writeBuffer = Buffer.from('New content');
const writeResult = await fileHandle.write(writeBuffer, 0, writeBuffer.length, 0);
console.log('Wrote:', writeResult.bytesWritten, 'bytes');
// File metadata operations
await fileHandle.chmod(0o644);
await fileHandle.utimes(new Date(), new Date());
const stats = await fileHandle.stat();
console.log('File size:', stats.size);
// Vectored I/O
const buffers = [Buffer.from('Hello '), Buffer.from('World!')];
const writevResult = await fileHandle.writev(buffers);
console.log('Vectored write:', writevResult.bytesWritten, 'bytes');
// Sync operations
await fileHandle.datasync(); // Sync data only
await fileHandle.sync(); // Sync data and metadata
} finally {
// Always close file handle
await fileHandle.close();
}
}
// File handle with error handling
async function safeFileOperations() {
let fileHandle;
try {
fileHandle = await fs.promises.open('/safe-file.txt', 'w');
await fileHandle.writeFile('Safe content');
} catch (error) {
console.error('File operation failed:', error);
} finally {
if (fileHandle) {
await fileHandle.close();
}
}
}Directory class for iterating over directory contents.
/**
* Directory iterator class for asynchronous directory traversal
*/
export class Dir {
readonly path: string;
/**
* Read next directory entry asynchronously
* @returns Promise resolving to next Dirent or null if end reached
*/
read(): Promise<Dirent | null>;
/**
* Read next directory entry synchronously
* @returns Next Dirent or null if end reached
*/
readSync(): Dirent | null;
/**
* Close directory iterator asynchronously
* @returns Promise that resolves when closed
*/
close(): Promise<void>;
/**
* Close directory iterator synchronously
*/
closeSync(): void;
/**
* Async iterator interface
*/
[Symbol.asyncIterator](): AsyncIterableIterator<Dirent>;
}Usage Examples:
import { fs } from "memfs";
async function directoryIteration() {
// Set up directory
fs.mkdirSync('/items');
fs.writeFileSync('/items/file1.txt', 'content1');
fs.writeFileSync('/items/file2.txt', 'content2');
fs.mkdirSync('/items/subdir');
// Open directory
const dir = await fs.promises.opendir('/items');
try {
// Manual iteration
let entry;
while ((entry = await dir.read()) !== null) {
console.log(`${entry.name}: ${entry.isFile() ? 'file' : 'directory'}`);
}
} finally {
await dir.close();
}
// Async iterator (recommended)
const dir2 = await fs.promises.opendir('/items');
try {
for await (const entry of dir2) {
console.log(`Async: ${entry.name}`);
if (entry.isFile()) {
const content = fs.readFileSync(`/items/${entry.name}`, 'utf8');
console.log(` Content: ${content}`);
}
}
} finally {
await dir2.close();
}
// Synchronous directory iteration
const dirSync = fs.opendirSync('/items');
try {
let entry;
while ((entry = dirSync.readSync()) !== null) {
console.log(`Sync: ${entry.name}`);
}
} finally {
dirSync.closeSync();
}
}
// Directory traversal with filtering
async function findFiles(dirPath: string, extension: string): Promise<string[]> {
const results: string[] = [];
const dir = await fs.promises.opendir(dirPath);
try {
for await (const entry of dir) {
const fullPath = `${dirPath}/${entry.name}`;
if (entry.isFile() && entry.name.endsWith(extension)) {
results.push(fullPath);
} else if (entry.isDirectory()) {
// Recursive search
const subdirResults = await findFiles(fullPath, extension);
results.push(...subdirResults);
}
}
} finally {
await dir.close();
}
return results;
}Utility functions for generating human-readable tree representations of file systems.
/**
* Generate a tree representation of the file system
* @param fs File system API to use
* @param opts Tree generation options
* @returns String representation of the file system tree
*/
export function toTreeSync(fs: FsSynchronousApi, opts?: ToTreeOptions): string;
/**
* Options for tree generation
*/
export interface ToTreeOptions {
/** Root directory to start from (default: '/') */
dir?: string;
/** Tab character or string for indentation (default: '') */
tab?: string;
/** Maximum depth to traverse (default: 10) */
depth?: number;
/** Path separator character (default: '/') */
separator?: '/' | '\\';
}Usage Examples:
import { fs, toTreeSync } from "memfs";
// Create sample file system
fs.mkdirSync('/project', { recursive: true });
fs.writeFileSync('/project/README.md', '# My Project');
fs.writeFileSync('/project/package.json', '{"name": "my-project"}');
fs.mkdirSync('/project/src');
fs.writeFileSync('/project/src/index.js', 'console.log("Hello");');
fs.writeFileSync('/project/src/utils.js', 'module.exports = {};');
fs.mkdirSync('/project/tests');
fs.writeFileSync('/project/tests/index.test.js', 'test("works", () => {});');
fs.symlinkSync('../README.md', '/project/src/README.md');
// Generate tree with default options
const tree = toTreeSync(fs);
console.log('Full file system tree:');
console.log(tree);
// Generate tree for specific directory
const projectTree = toTreeSync(fs, { dir: '/project' });
console.log('Project tree:');
console.log(projectTree);
// Customize tree appearance
const customTree = toTreeSync(fs, {
dir: '/project',
tab: ' ', // Use 2 spaces for indentation
depth: 2, // Limit depth to 2 levels
separator: '/' // Use forward slash
});
console.log('Custom tree:');
console.log(customTree);
// Tree with different indentation
const fancyTree = toTreeSync(fs, {
dir: '/project',
tab: '│ ', // Use box drawing characters
depth: 3
});
console.log('Fancy tree:');
console.log(fancyTree);
// Utility function to print directory structure
function printDirectoryStructure(path: string, maxDepth = 5) {
console.log(`Directory structure for ${path}:`);
console.log(toTreeSync(fs, {
dir: path,
tab: '├─ ',
depth: maxDepth
}));
}
printDirectoryStructure('/project');
// Compare two directory structures
function compareDirectories(path1: string, path2: string) {
const tree1 = toTreeSync(fs, { dir: path1, tab: ' ' });
const tree2 = toTreeSync(fs, { dir: path2, tab: ' ' });
console.log(`${path1}:`);
console.log(tree1);
console.log(`\n${path2}:`);
console.log(tree2);
}Stream implementations for reading and writing files.
/**
* Readable stream for file reading
*/
export class ReadStream extends Readable {
readonly path: string;
readonly fd: number;
readonly flags: string;
readonly mode: number;
readonly start?: number;
readonly end?: number;
readonly autoClose: boolean;
bytesRead: number;
pending: boolean;
// Readable stream interface
_read(size: number): void;
_destroy(error: Error | null, callback: (error?: Error | null) => void): void;
}
/**
* Writable stream for file writing
*/
export class WriteStream extends Writable {
readonly path: string;
readonly fd: number;
readonly flags: string;
readonly mode: number;
readonly start?: number;
readonly autoClose: boolean;
bytesWritten: number;
pending: boolean;
// Writable stream interface
_write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
_destroy(error: Error | null, callback: (error?: Error | null) => void): void;
}Usage Examples:
import { fs } from "memfs";
function streamOperations() {
// Create readable stream
fs.writeFileSync('/source.txt', 'Hello\nWorld\nFrom\nStream!');
const readStream = fs.createReadStream('/source.txt', { encoding: 'utf8' });
// Create writable stream
const writeStream = fs.createWriteStream('/destination.txt');
// Stream events
readStream.on('data', (chunk) => {
console.log('Read chunk:', chunk);
});
readStream.on('end', () => {
console.log('Reading finished');
});
writeStream.on('finish', () => {
console.log('Writing finished');
console.log('Bytes written:', writeStream.bytesWritten);
});
// Pipe streams
readStream.pipe(writeStream);
// Manual stream writing
const manualWriteStream = fs.createWriteStream('/manual.txt');
manualWriteStream.write('Line 1\n');
manualWriteStream.write('Line 2\n');
manualWriteStream.end('Final line\n');
// Stream with options
const rangeReadStream = fs.createReadStream('/source.txt', {
start: 5, // Start at byte 5
end: 10, // End at byte 10
encoding: 'utf8'
});
rangeReadStream.on('data', (chunk) => {
console.log('Range chunk:', chunk);
});
}// File system API interface for utilities
export interface FsSynchronousApi {
readdirSync(path: string, options?: any): string[] | Dirent[];
statSync(path: string, options?: any): Stats;
lstatSync(path: string, options?: any): Stats;
readFileSync(path: string, options?: any): string | Buffer;
readlinkSync(path: string, options?: any): string;
// ... other synchronous methods
}
// Event types for file handles and streams
export interface FileHandleEventMap {
'close': () => void;
'error': (error: Error) => void;
}
export interface StreamEventMap {
'data': (chunk: any) => void;
'end': () => void;
'error': (error: Error) => void;
'close': () => void;
'finish': () => void;
}
// Generic buffer types
export type BufferLike = ArrayBufferView | ArrayBuffer;
export type StringOrBuffer = string | Buffer;