or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdjson-schema.mdlogging.mdnode-integration.mdutilities.mdvirtual-filesystem.mdworkspace.md
tile.json

virtual-filesystem.mddocs/

Virtual File System

Abstract file system API with path management utilities and multiple host implementations, enabling consistent file operations across different environments including memory, Node.js, and testing scenarios.

Capabilities

Path Management

Cross-platform path normalization and manipulation utilities with branded types for path safety.

/**
 * Branded string type representing a normalized absolute path
 */
type Path = string & { __PRIVATE_DEVKIT_PATH: void };

/**
 * Branded string type representing a path fragment (filename or directory name)
 */
type PathFragment = string & { __PRIVATE_DEVKIT_PATH_FRAGMENT: void };

/**
 * Branded string type for Windows-style paths
 */
type WindowsPath = string & { __PRIVATE_DEVKIT_WINDOWS_PATH: void };

/**
 * Branded string type for POSIX-style paths
 */
type PosixPath = string & { __PRIVATE_DEVKIT_POSIX_PATH: void };

/**
 * Path separator for normalized paths
 */
const NormalizedSep: '/';

/**
 * Root path for normalized paths
 */
const NormalizedRoot: Path;

Path Utility Functions

Core functions for path manipulation and normalization.

/**
 * Normalize a string path to a Path with caching
 * @param path - String path to normalize
 * @returns Normalized Path
 */
function normalize(path: string): Path;

/**
 * Normalize a string path without caching
 * @param path - String path to normalize
 * @returns Normalized Path
 */
function noCacheNormalize(path: string): Path;

/**
 * Join path segments together
 * @param p1 - Base path
 * @param others - Additional path fragments to join
 * @returns Joined path
 */
function join(p1: Path, ...others: PathFragment[]): Path;

/**
 * Split a path into its component fragments
 * @param path - Path to split
 * @returns Array of path fragments
 */
function split(path: Path): PathFragment[];

/**
 * Get the directory name of a path
 * @param path - Path to get directory from
 * @returns Directory path
 */
function dirname(path: Path): Path;

/**
 * Get the base filename from a path
 * @param path - Path to get basename from
 * @param ext - Optional extension to remove
 * @returns Base filename
 */
function basename(path: Path, ext?: string): PathFragment;

/**
 * Get the file extension from a path
 * @param path - Path to get extension from
 * @returns File extension including the dot
 */
function extname(path: Path): string;

/**
 * Get relative path from one path to another
 * @param from - Starting path
 * @param to - Target path
 * @returns Relative path
 */
function relative(from: Path, to: Path): Path;

/**
 * Resolve a path against a base path
 * @param base - Base path to resolve against
 * @param path - Path to resolve
 * @returns Resolved absolute path
 */
function resolve(base: Path, path: Path): Path;

/**
 * Check if a path is absolute
 * @param path - Path to check
 * @returns True if path is absolute
 */
function isAbsolute(path: Path): boolean;

/**
 * Create a path fragment from a string
 * @param name - String to convert to fragment
 * @returns Path fragment
 */
function fragment(name: string): PathFragment;

/**
 * Reset the path normalization cache
 */
function resetNormalizeCache(): void;

/**
 * Template tag for creating paths with proper escaping
 */
function path(strings: TemplateStringsArray, ...values: any[]): Path;

/**
 * Convert a Path to Windows-style format
 * @param path - Path to convert
 * @returns Windows-style path
 */
function asWindowsPath(path: Path): WindowsPath;

/**
 * Convert a Path to POSIX-style format
 * @param path - Path to convert
 * @returns POSIX-style path
 */
function asPosixPath(path: Path): PosixPath;

/**
 * Convert a Path to system-specific format
 * @param path - Path to convert
 * @returns System-specific path string
 */
function getSystemPath(path: Path): string;

Path Exception Classes

Exception types for path-related errors.

/**
 * Exception thrown for invalid path formats
 */
class InvalidPathException extends BaseException {
  constructor(path: string);
}

/**
 * Exception thrown when an absolute path is required but not provided
 */
class PathMustBeAbsoluteException extends BaseException {
  constructor(path: string);
}

/**
 * Exception thrown when a path cannot be a fragment
 */
class PathCannotBeFragmentException extends BaseException {
  constructor(path: string);
}

Virtual File System Core Types

Core interfaces and types for the virtual file system.

/**
 * ArrayBuffer type for file content
 */
type FileBuffer = ArrayBuffer;

/**
 * ArrayBufferLike type for file content input
 */
type FileBufferLike = ArrayBufferLike;

/**
 * File statistics interface with generic extension support
 */
interface Stats<T extends object = {}> {
  /** Whether the path represents a file */
  readonly isFile: boolean;
  /** Whether the path represents a directory */
  readonly isDirectory: boolean;
  /** File size in bytes */
  readonly size: number;
  /** Access time */
  readonly atime: Date;
  /** Modification time */
  readonly mtime: Date;
  /** Creation time */
  readonly ctime: Date;
  /** Birth time */
  readonly birthtime: Date;
  /** Additional statistics specific to host implementation */
  readonly content: T;
}

/**
 * Host capability flags describing what operations are supported
 */
interface HostCapabilities {
  /** Whether the host supports synchronous operations */
  readonly synchronous: boolean;
}

File System Watch System

Types and interfaces for watching file system changes.

/**
 * Types of file system events that can be watched
 */
enum HostWatchEventType {
  Changed = 0,
  Created = 1,
  Deleted = 2,
  Renamed = 3
}

/**
 * File system watch event
 */
interface HostWatchEvent {
  /** Type of the event */
  readonly type: HostWatchEventType;
  /** Path that triggered the event */
  readonly path: Path;
  /** New path for rename events */
  readonly to?: Path;
  /** Event timestamp */
  readonly time: Date;
}

/**
 * Options for configuring file system watching
 */
interface HostWatchOptions {
  /** Whether to watch subdirectories recursively */
  readonly recursive?: boolean;
  /** File patterns to exclude from watching */
  readonly exclude?: string[];
  /** File patterns to include in watching */
  readonly include?: string[];
}

Host Interfaces

Core host interfaces for file system operations.

/**
 * Read-only file system host interface
 */
interface ReadonlyHost<StatsT extends object = {}> {
  /** Host capabilities */
  readonly capabilities: HostCapabilities;
  
  /**
   * Read file content as buffer
   * @param path - File path to read
   * @returns Observable emitting file content
   */
  read(path: Path): Observable<FileBuffer>;
  
  /**
   * List directory contents
   * @param path - Directory path to list
   * @returns Observable emitting array of path fragments
   */
  list(path: Path): Observable<PathFragment[]>;
  
  /**
   * Check if path exists
   * @param path - Path to check
   * @returns Observable emitting boolean
   */
  exists(path: Path): Observable<boolean>;
  
  /**
   * Check if path is a directory
   * @param path - Path to check
   * @returns Observable emitting boolean
   */
  isDirectory(path: Path): Observable<boolean>;
  
  /**
   * Check if path is a file
   * @param path - Path to check
   * @returns Observable emitting boolean
   */
  isFile(path: Path): Observable<boolean>;
  
  /**
   * Get file/directory statistics
   * @param path - Path to get stats for
   * @returns Observable emitting stats object
   */
  stat(path: Path): Observable<Stats<StatsT>>;
}

/**
 * Full file system host interface extending ReadonlyHost
 */
interface Host<StatsT extends object = {}> extends ReadonlyHost<StatsT> {
  /**
   * Write content to file
   * @param path - File path to write
   * @param content - Content to write
   * @returns Observable completing when write is done
   */
  write(path: Path, content: FileBufferLike): Observable<void>;
  
  /**
   * Delete file or directory
   * @param path - Path to delete
   * @returns Observable completing when deletion is done
   */
  delete(path: Path): Observable<void>;
  
  /**
   * Rename/move file or directory
   * @param from - Source path
   * @param to - Destination path
   * @returns Observable completing when rename is done
   */
  rename(from: Path, to: Path): Observable<void>;
  
  /**
   * Watch path for changes
   * @param path - Path to watch
   * @param options - Watch configuration options
   * @returns Observable emitting watch events
   */
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

Host Implementations

Various host implementations for different use cases.

/**
 * In-memory file system host for testing and temporary operations
 */
class MemoryHost implements Host<{}> {
  readonly capabilities: HostCapabilities;
  constructor();
  
  read(path: Path): Observable<FileBuffer>;
  write(path: Path, content: FileBufferLike): Observable<void>;
  delete(path: Path): Observable<void>;
  rename(from: Path, to: Path): Observable<void>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<{}>>;
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

/**
 * Empty/no-op host implementation that provides no files
 */
class EmptyHost implements ReadonlyHost<{}> {
  readonly capabilities: HostCapabilities;
  
  read(path: Path): Observable<FileBuffer>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<{}>>;
}

/**
 * Host that scopes operations to a subdirectory
 */
class ScopedHost<T extends object> implements Host<T> {
  readonly capabilities: HostCapabilities;
  
  constructor(delegate: Host<T>, root?: Path);
  
  read(path: Path): Observable<FileBuffer>;
  write(path: Path, content: FileBufferLike): Observable<void>;
  delete(path: Path): Observable<void>;
  rename(from: Path, to: Path): Observable<void>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<T>>;
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

/**
 * Host that provides synchronous operations via delegation
 */
class SyncDelegateHost<T extends object> {
  constructor(delegate: Host<T>);
  
  read(path: Path): FileBuffer;
  write(path: Path, content: FileBufferLike): void;
  delete(path: Path): void;
  rename(from: Path, to: Path): void;
  list(path: Path): PathFragment[];
  exists(path: Path): boolean;
  isDirectory(path: Path): boolean;
  isFile(path: Path): boolean;
  stat(path: Path): Stats<T>;
}

/**
 * Host with path aliasing capabilities for redirecting paths
 */
class AliasHost<StatsT extends object = {}> implements Host<StatsT> {
  readonly capabilities: HostCapabilities;
  
  constructor(host: Host<StatsT>, aliases?: Map<Path, Path>);
  
  read(path: Path): Observable<FileBuffer>;
  write(path: Path, content: FileBufferLike): Observable<void>;
  delete(path: Path): Observable<void>;
  rename(from: Path, to: Path): Observable<void>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<StatsT>>;
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

/**
 * Host that records all operations for debugging and testing
 */
class RecordHost<T extends object = {}> implements Host<T> {
  readonly capabilities: HostCapabilities;
  readonly records: Array<{ kind: string; path: Path; [key: string]: any }>;
  
  constructor(host: Host<T>);
  
  read(path: Path): Observable<FileBuffer>;
  write(path: Path, content: FileBufferLike): Observable<void>;
  delete(path: Path): Observable<void>;
  rename(from: Path, to: Path): Observable<void>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<T>>;
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

/**
 * Host with safety checks to prevent dangerous operations
 */
class SafeHost<T extends object = {}> implements Host<T> {
  readonly capabilities: HostCapabilities;
  
  constructor(host: Host<T>);
  
  read(path: Path): Observable<FileBuffer>;
  write(path: Path, content: FileBufferLike): Observable<void>;
  delete(path: Path): Observable<void>;
  rename(from: Path, to: Path): Observable<void>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<T>>;
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

/**
 * Host with pattern-based filtering for file operations
 */
class PatternHost<StatsT extends object = {}> implements Host<StatsT> {
  readonly capabilities: HostCapabilities;
  
  constructor(host: Host<StatsT>, patterns: string[]);
  
  read(path: Path): Observable<FileBuffer>;
  write(path: Path, content: FileBufferLike): Observable<void>;
  delete(path: Path): Observable<void>;
  rename(from: Path, to: Path): Observable<void>;
  list(path: Path): Observable<PathFragment[]>;
  exists(path: Path): Observable<boolean>;
  isDirectory(path: Path): Observable<boolean>;
  isFile(path: Path): Observable<boolean>;
  stat(path: Path): Observable<Stats<StatsT>>;
  watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
}

Testing Utilities

Utilities and hosts specifically designed for testing scenarios.

namespace test {
  /**
   * Create a test host with predefined file structure
   * @param files - Map of paths to file contents
   * @returns Host instance for testing
   */
  function createHost(files: { [path: string]: string }): Host<{}>;
  
  /**
   * Host implementation that simulates various error conditions for testing
   */
  class TestHost implements Host<{}> {
    readonly capabilities: HostCapabilities;
    
    constructor(files?: { [path: string]: string });
    
    read(path: Path): Observable<FileBuffer>;
    write(path: Path, content: FileBufferLike): Observable<void>;
    delete(path: Path): Observable<void>;
    rename(from: Path, to: Path): Observable<void>;
    list(path: Path): Observable<PathFragment[]>;
    exists(path: Path): Observable<boolean>;
    isDirectory(path: Path): Observable<boolean>;
    isFile(path: Path): Observable<boolean>;
    stat(path: Path): Observable<Stats<{}>>;
    watch(path: Path, options?: HostWatchOptions): Observable<HostWatchEvent>;
  }
}

Usage Examples

Basic Path Operations

import { normalize, join, dirname, basename, extname } from "@angular-devkit/core";

// Normalize paths
const path = normalize("/my/project/src/app.ts");
console.log(path); // "/my/project/src/app.ts"

// Join paths
const fullPath = join(normalize("/my/project"), fragment("src"), fragment("app.ts"));

// Path manipulation
const dir = dirname(path);        // "/my/project/src"
const file = basename(path);      // "app.ts"
const name = basename(path, ".ts"); // "app"
const ext = extname(path);        // ".ts"

Memory Host Usage

import { virtualFs, normalize } from "@angular-devkit/core";

const host = new virtualFs.MemoryHost();
const path = normalize("/test.txt");
const content = new TextEncoder().encode("Hello, World!");

// Write file
await host.write(path, content.buffer).toPromise();

// Read file
const buffer = await host.read(path).toPromise();
const text = new TextDecoder().decode(buffer);
console.log(text); // "Hello, World!"

// List directory contents
const files = await host.list(normalize("/")).toPromise();
console.log(files); // ["test.txt"]

Scoped Host Usage

import { virtualFs, normalize } from "@angular-devkit/core";

const baseHost = new virtualFs.MemoryHost();
const scopedHost = new virtualFs.ScopedHost(baseHost, normalize("/project"));

// Operations on scoped host are relative to /project
const filePath = normalize("/src/app.ts");
await scopedHost.write(filePath, content.buffer).toPromise();

// This actually writes to /project/src/app.ts in the base host
const exists = await baseHost.exists(normalize("/project/src/app.ts")).toPromise();
console.log(exists); // true

File System Watching

import { virtualFs, normalize } from "@angular-devkit/core";

const host = new virtualFs.MemoryHost();
const watchPath = normalize("/src");

// Watch for changes
const subscription = host.watch(watchPath, { 
  recursive: true,
  exclude: ["node_modules/**"]
}).subscribe(event => {
  console.log(`File ${event.type}: ${event.path}`);
  if (event.type === virtualFs.HostWatchEventType.Renamed && event.to) {
    console.log(`Renamed to: ${event.to}`);
  }
});

// Make changes to trigger events
await host.write(normalize("/src/new-file.ts"), buffer).toPromise();

// Clean up
subscription.unsubscribe();