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.
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;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;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);
}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;
}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[];
}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>;
}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>;
}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>;
}
}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"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"]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); // trueimport { 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();