A TypeScript library abstracting the Node filesystem APIs with virtual filesystems and cross-platform path handling
Core filesystem interfaces and abstract base classes that define the filesystem API and provide the foundation for all filesystem implementations in @yarnpkg/fslib.
The filesystem abstraction layer provides a unified interface for different filesystem implementations through abstract base classes and comprehensive type definitions. This allows for seamless switching between real filesystems, virtual filesystems, and specialized filesystem implementations.
// Buffer and encoding types
type BufferEncodingOrBuffer = BufferEncoding | 'buffer';
// Enhanced statistics types with optional CRC
interface Stats extends fs.Stats {
crc?: number;
}
interface BigIntStats extends fs.BigIntStats {
crc?: number;
}
// Directory entry types
interface Dirent<T extends Path> extends fs.Dirent {
name: Filename;
path: T;
}
interface DirentNoPath extends fs.Dirent {
name: Filename;
}
// Directory handle interface
interface Dir<P extends Path> extends AsyncIterable<Dirent<P>> {
readonly path: P;
read(): Promise<Dirent<P> | null>;
close(): Promise<void>;
}
// Symlink types
type SymlinkType = 'file' | 'dir' | 'junction';// Directory operations
interface OpendirOptions {
bufferSize?: number;
}
interface ReaddirOptions {
encoding?: BufferEncoding;
withFileTypes?: boolean;
}
interface MkdirOptions {
mode?: number;
recursive?: boolean;
}
interface RmdirOptions {
maxRetries?: number;
recursive?: boolean;
retryDelay?: number;
}
interface RmOptions {
force?: boolean;
maxRetries?: number;
recursive?: boolean;
retryDelay?: number;
}
// File operations
interface WriteFileOptions {
encoding?: BufferEncoding;
mode?: number;
flag?: string;
}
interface StatOptions {
bigint?: false;
}
interface StatSyncOptions {
bigint?: boolean;
throwIfNoEntry?: boolean;
}
// Stream operations
interface CreateReadStreamOptions {
encoding?: BufferEncoding;
fd?: number;
flags?: string;
mode?: number;
autoClose?: boolean;
emitClose?: boolean;
start?: number;
end?: number;
highWaterMark?: number;
}
interface CreateWriteStreamOptions {
encoding?: BufferEncoding;
fd?: number;
flags?: string;
mode?: number;
autoClose?: boolean;
emitClose?: boolean;
start?: number;
highWaterMark?: number;
}
// Watch operations
interface WatchOptions {
encoding?: BufferEncoding;
persistent?: boolean;
recursive?: boolean;
}
interface WatchFileOptions {
bigint?: boolean;
persistent?: boolean;
interval?: number;
}
interface ExtractHintOptions {
relevantExtensions?: Set<string>;
}// Watch callback types
type WatchCallback = (eventType: string, filename: string | null) => void;
type WatchFileCallback = (current: Stats, previous: Stats) => void;
// Watcher interfaces
interface Watcher extends EventEmitter {
close(): void;
}
interface StatWatcher extends EventEmitter {
ref(): StatWatcher;
unref(): StatWatcher;
}The core abstract base class that defines the complete filesystem interface.
import { FakeFS, type Path, type Stats, type Dirent } from '@yarnpkg/fslib';
abstract class FakeFS<P extends Path> {
// File reading operations
abstract readFilePromise(p: FSPath<P>, encoding?: null): Promise<Buffer>;
abstract readFilePromise(p: FSPath<P>, encoding: BufferEncoding): Promise<string>;
abstract readFilePromise(p: FSPath<P>, encoding?: BufferEncoding | null): Promise<Buffer | string>;
abstract readFileSync(p: FSPath<P>, encoding?: null): Buffer;
abstract readFileSync(p: FSPath<P>, encoding: BufferEncoding): string;
abstract readFileSync(p: FSPath<P>, encoding?: BufferEncoding | null): Buffer | string;
// File writing operations
abstract writeFilePromise(p: P, content: string | Buffer, options?: WriteFileOptions): Promise<void>;
abstract writeFileSync(p: P, content: string | Buffer, options?: WriteFileOptions): void;
abstract appendFilePromise(p: P, content: string | Buffer, options?: WriteFileOptions): Promise<void>;
abstract appendFileSync(p: P, content: string | Buffer, options?: WriteFileOptions): void;
// Directory operations
abstract mkdirPromise(p: P, options?: MkdirOptions): Promise<void>;
abstract mkdirSync(p: P, options?: MkdirOptions): void;
abstract readdirPromise(p: P): Promise<Filename[]>;
abstract readdirPromise(p: P, options: ReaddirOptions & { withFileTypes: true }): Promise<Dirent<P>[]>;
abstract readdirPromise(p: P, options?: ReaddirOptions): Promise<Filename[] | Dirent<P>[]>;
abstract readdirSync(p: P): Filename[];
abstract readdirSync(p: P, options: ReaddirOptions & { withFileTypes: true }): Dirent<P>[];
abstract readdirSync(p: P, options?: ReaddirOptions): Filename[] | Dirent<P>[];
// Metadata operations
abstract statPromise(p: FSPath<P>): Promise<Stats>;
abstract statPromise(p: FSPath<P>, options: StatOptions & { bigint: true }): Promise<BigIntStats>;
abstract statPromise(p: FSPath<P>, options?: StatOptions): Promise<Stats | BigIntStats>;
abstract statSync(p: FSPath<P>): Stats;
abstract statSync(p: FSPath<P>, options: StatSyncOptions & { bigint: true }): BigIntStats;
abstract statSync(p: FSPath<P>, options?: StatSyncOptions): Stats | BigIntStats;
abstract lstatPromise(p: P): Promise<Stats>;
abstract lstatPromise(p: P, options: StatOptions & { bigint: true }): Promise<BigIntStats>;
abstract lstatPromise(p: P, options?: StatOptions): Promise<Stats | BigIntStats>;
abstract lstatSync(p: P): Stats;
abstract lstatSync(p: P, options: StatSyncOptions & { bigint: true }): BigIntStats;
abstract lstatSync(p: P, options?: StatSyncOptions): Stats | BigIntStats;
// Permission and ownership
abstract chmodPromise(p: P, mode: number): Promise<void>;
abstract chmodSync(p: P, mode: number): void;
abstract chownPromise(p: P, uid: number, gid: number): Promise<void>;
abstract chownSync(p: P, uid: number, gid: number): void;
// File system operations
abstract copyFilePromise(source: P, destination: P, flags?: number): Promise<void>;
abstract copyFileSync(source: P, destination: P, flags?: number): void;
abstract renamePromise(oldPath: P, newPath: P): Promise<void>;
abstract renameSync(oldPath: P, newPath: P): void;
abstract unlinkPromise(p: P): Promise<void>;
abstract unlinkSync(p: P): void;
// Link operations
abstract linkPromise(existingPath: P, newPath: P): Promise<void>;
abstract linkSync(existingPath: P, newPath: P): void;
abstract symlinkPromise(target: P, p: P, type?: SymlinkType): Promise<void>;
abstract symlinkSync(target: P, p: P, type?: SymlinkType): void;
abstract readlinkPromise(p: P): Promise<P>;
abstract readlinkSync(p: P): P;
// Directory handle operations
abstract opendirPromise(p: P, options?: OpendirOptions): Promise<Dir<P>>;
abstract opendirSync(p: P, options?: OpendirOptions): Dir<P>;
// Stream operations
abstract createReadStream(p: P, options?: CreateReadStreamOptions): fs.ReadStream;
abstract createWriteStream(p: P, options?: CreateWriteStreamOptions): fs.WriteStream;
// Watch operations
abstract watchPromise(p: P, options?: WatchOptions): Promise<AsyncIterable<string>>;
abstract watch(p: P, options?: WatchOptions): Watcher;
abstract watch(p: P, options: WatchOptions, callback: WatchCallback): Watcher;
abstract watch(p: P, callback: WatchCallback): Watcher;
}Specialized abstract base class for filesystems that use portable paths.
import { BasePortableFakeFS, type PortablePath } from '@yarnpkg/fslib';
abstract class BasePortableFakeFS extends FakeFS<PortablePath> {
// Inherits all FakeFS methods with PortablePath as the path type
// Additional portable path specific utilities may be added here
}import { normalizeLineEndings } from '@yarnpkg/fslib';
// Normalize line endings based on original content style
function normalizeLineEndings(originalContent: string, newContent: string): string;
// Usage example
const original = "line1\nline2\nline3"; // Unix line endings
const modified = "line1\r\nmodified\r\nline3"; // Windows line endings
const normalized = normalizeLineEndings(original, modified);
// Result: "line1\nmodified\nline3" (matches original style)import { BasePortableFakeFS, type PortablePath, type Stats } from '@yarnpkg/fslib';
class CustomFileSystem extends BasePortableFakeFS {
private data = new Map<string, Buffer>();
async readFilePromise(p: PortablePath, encoding?: BufferEncoding | null): Promise<Buffer | string> {
const content = this.data.get(p);
if (!content) {
throw new Error(`File not found: ${p}`);
}
return encoding && encoding !== 'buffer' ? content.toString(encoding) : content;
}
readFileSync(p: PortablePath, encoding?: BufferEncoding | null): Buffer | string {
// Synchronous implementation
const content = this.data.get(p);
if (!content) {
throw new Error(`File not found: ${p}`);
}
return encoding && encoding !== 'buffer' ? content.toString(encoding) : content;
}
async writeFilePromise(p: PortablePath, content: string | Buffer): Promise<void> {
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
this.data.set(p, buffer);
}
writeFileSync(p: PortablePath, content: string | Buffer): void {
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
this.data.set(p, buffer);
}
// Implement other required methods...
}import { type Dirent, type PortablePath, type Filename } from '@yarnpkg/fslib';
async function processDirectoryEntries(fs: FakeFS<PortablePath>, dir: PortablePath): Promise<void> {
const entries = await fs.readdirPromise(dir, { withFileTypes: true });
for (const entry of entries) {
console.log(`Processing: ${entry.name} (${entry.isDirectory() ? 'dir' : 'file'})`);
if (entry.isDirectory()) {
// Recursively process subdirectory
const subPath = ppath.join(dir, entry.name);
await processDirectoryEntries(fs, subPath);
} else if (entry.isFile()) {
// Process file
const filePath = ppath.join(dir, entry.name);
const content = await fs.readFilePromise(filePath, 'utf8');
console.log(`File size: ${content.length} characters`);
}
}
}import { type FakeFS, type PortablePath } from '@yarnpkg/fslib';
import { pipeline } from 'stream/promises';
async function copyFileWithStreams(
fs: FakeFS<PortablePath>,
source: PortablePath,
destination: PortablePath
): Promise<void> {
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
await pipeline(readStream, writeStream);
}
// Usage with transform streams
import { Transform } from 'stream';
async function processFileContent(
fs: FakeFS<PortablePath>,
inputPath: PortablePath,
outputPath: PortablePath,
transformer: Transform
): Promise<void> {
await pipeline(
fs.createReadStream(inputPath),
transformer,
fs.createWriteStream(outputPath)
);
}import { type FakeFS, type PortablePath, type WatchCallback } from '@yarnpkg/fslib';
function setupFileWatcher(fs: FakeFS<PortablePath>, path: PortablePath): void {
const callback: WatchCallback = (eventType, filename) => {
console.log(`File ${filename} changed: ${eventType}`);
};
const watcher = fs.watch(path, { recursive: true }, callback);
// Clean up after 10 seconds
setTimeout(() => {
watcher.close();
}, 10000);
}
// Async watch with iteration
async function watchDirectory(fs: FakeFS<PortablePath>, path: PortablePath): Promise<void> {
const watcher = await fs.watchPromise(path, { recursive: true });
for await (const event of watcher) {
console.log(`Directory change detected: ${event}`);
// Handle the event
}
}import { errors, type FakeFS, type PortablePath } from '@yarnpkg/fslib';
async function safeFileOperation(fs: FakeFS<PortablePath>, path: PortablePath): Promise<string | null> {
try {
return await fs.readFilePromise(path, 'utf8');
} catch (error) {
if (error.code === 'ENOENT') {
// File doesn't exist
return null;
} else if (error.code === 'EACCES') {
// Permission denied
throw errors.EACCES(`Cannot access file: ${path}`);
} else {
// Re-throw other errors
throw error;
}
}
}
// Check file existence safely
async function fileExists(fs: FakeFS<PortablePath>, path: PortablePath): Promise<boolean> {
try {
await fs.statPromise(path);
return true;
} catch (error) {
if (error.code === 'ENOENT') {
return false;
}
throw error;
}
}Install with Tessl CLI
npx tessl i tessl/npm-yarnpkg--fslib