CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yarnpkg--fslib

A TypeScript library abstracting the Node filesystem APIs with virtual filesystems and cross-platform path handling

Overview
Eval results
Files

filesystem-abstractions.mddocs/

Filesystem Abstractions

Core filesystem interfaces and abstract base classes that define the filesystem API and provide the foundation for all filesystem implementations in @yarnpkg/fslib.

Overview

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.

Core Types and Interfaces

Basic Types

// 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';

Options Types

// 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>;
}

Callback Types

// 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;
}

Abstract Base Classes

FakeFS<P extends Path>

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;
}

BasePortableFakeFS

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
}

Utility Functions

Line Ending Normalization

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)

Usage Patterns

Implementing a Custom Filesystem

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...
}

Working with Directory Entries

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`);
    }
  }
}

Stream Operations

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)
  );
}

Watch Operations

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
  }
}

Error Handling

Common Filesystem Errors

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;
  }
}

Related Documentation

  • Path Handling - Path utilities used by filesystem operations
  • Filesystem Implementations - Concrete implementations of these abstractions
  • Constants and Utilities - Error factories and statistics utilities
  • Advanced Features - Advanced operations building on these abstractions

Install with Tessl CLI

npx tessl i tessl/npm-yarnpkg--fslib

docs

advanced-features.md

constants-and-utilities.md

filesystem-abstractions.md

filesystem-implementations.md

index.md

path-handling.md

tile.json