- Implemented async utilities including delay, retryWithBackoff, withTimeout, parallelLimit, debounceAsync, AsyncMutex, and CircuitBreaker. - Created tests for async utilities to ensure functionality and reliability. - Developed AsyncFileSystem class with methods for file and directory operations, including ensureDir, readFile, writeFile, remove, and more. - Added tests for filesystem utilities to validate file operations and error handling.
270 lines
8.1 KiB
TypeScript
270 lines
8.1 KiB
TypeScript
/**
|
|
* Async filesystem utilities for SmartProxy
|
|
* Provides non-blocking alternatives to synchronous filesystem operations
|
|
*/
|
|
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
export class AsyncFileSystem {
|
|
/**
|
|
* Check if a file or directory exists
|
|
* @param path - Path to check
|
|
* @returns Promise resolving to true if exists, false otherwise
|
|
*/
|
|
static async exists(path: string): Promise<boolean> {
|
|
try {
|
|
await plugins.fs.promises.access(path);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure a directory exists, creating it if necessary
|
|
* @param dirPath - Directory path to ensure
|
|
* @returns Promise that resolves when directory is ensured
|
|
*/
|
|
static async ensureDir(dirPath: string): Promise<void> {
|
|
await plugins.fs.promises.mkdir(dirPath, { recursive: true });
|
|
}
|
|
|
|
/**
|
|
* Read a file as string
|
|
* @param filePath - Path to the file
|
|
* @param encoding - File encoding (default: utf8)
|
|
* @returns Promise resolving to file contents
|
|
*/
|
|
static async readFile(filePath: string, encoding: BufferEncoding = 'utf8'): Promise<string> {
|
|
return plugins.fs.promises.readFile(filePath, encoding);
|
|
}
|
|
|
|
/**
|
|
* Read a file as buffer
|
|
* @param filePath - Path to the file
|
|
* @returns Promise resolving to file buffer
|
|
*/
|
|
static async readFileBuffer(filePath: string): Promise<Buffer> {
|
|
return plugins.fs.promises.readFile(filePath);
|
|
}
|
|
|
|
/**
|
|
* Write string data to a file
|
|
* @param filePath - Path to the file
|
|
* @param data - String data to write
|
|
* @param encoding - File encoding (default: utf8)
|
|
* @returns Promise that resolves when file is written
|
|
*/
|
|
static async writeFile(filePath: string, data: string, encoding: BufferEncoding = 'utf8'): Promise<void> {
|
|
// Ensure directory exists
|
|
const dir = plugins.path.dirname(filePath);
|
|
await this.ensureDir(dir);
|
|
await plugins.fs.promises.writeFile(filePath, data, encoding);
|
|
}
|
|
|
|
/**
|
|
* Write buffer data to a file
|
|
* @param filePath - Path to the file
|
|
* @param data - Buffer data to write
|
|
* @returns Promise that resolves when file is written
|
|
*/
|
|
static async writeFileBuffer(filePath: string, data: Buffer): Promise<void> {
|
|
const dir = plugins.path.dirname(filePath);
|
|
await this.ensureDir(dir);
|
|
await plugins.fs.promises.writeFile(filePath, data);
|
|
}
|
|
|
|
/**
|
|
* Remove a file
|
|
* @param filePath - Path to the file
|
|
* @returns Promise that resolves when file is removed
|
|
*/
|
|
static async remove(filePath: string): Promise<void> {
|
|
try {
|
|
await plugins.fs.promises.unlink(filePath);
|
|
} catch (error: any) {
|
|
if (error.code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
// File doesn't exist, which is fine
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a directory and all its contents
|
|
* @param dirPath - Path to the directory
|
|
* @returns Promise that resolves when directory is removed
|
|
*/
|
|
static async removeDir(dirPath: string): Promise<void> {
|
|
try {
|
|
await plugins.fs.promises.rm(dirPath, { recursive: true, force: true });
|
|
} catch (error: any) {
|
|
if (error.code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read JSON from a file
|
|
* @param filePath - Path to the JSON file
|
|
* @returns Promise resolving to parsed JSON
|
|
*/
|
|
static async readJSON<T = any>(filePath: string): Promise<T> {
|
|
const content = await this.readFile(filePath);
|
|
return JSON.parse(content);
|
|
}
|
|
|
|
/**
|
|
* Write JSON to a file
|
|
* @param filePath - Path to the file
|
|
* @param data - Data to write as JSON
|
|
* @param pretty - Whether to pretty-print JSON (default: true)
|
|
* @returns Promise that resolves when file is written
|
|
*/
|
|
static async writeJSON(filePath: string, data: any, pretty = true): Promise<void> {
|
|
const jsonString = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
await this.writeFile(filePath, jsonString);
|
|
}
|
|
|
|
/**
|
|
* Copy a file from source to destination
|
|
* @param source - Source file path
|
|
* @param destination - Destination file path
|
|
* @returns Promise that resolves when file is copied
|
|
*/
|
|
static async copyFile(source: string, destination: string): Promise<void> {
|
|
const destDir = plugins.path.dirname(destination);
|
|
await this.ensureDir(destDir);
|
|
await plugins.fs.promises.copyFile(source, destination);
|
|
}
|
|
|
|
/**
|
|
* Move/rename a file
|
|
* @param source - Source file path
|
|
* @param destination - Destination file path
|
|
* @returns Promise that resolves when file is moved
|
|
*/
|
|
static async moveFile(source: string, destination: string): Promise<void> {
|
|
const destDir = plugins.path.dirname(destination);
|
|
await this.ensureDir(destDir);
|
|
await plugins.fs.promises.rename(source, destination);
|
|
}
|
|
|
|
/**
|
|
* Get file stats
|
|
* @param filePath - Path to the file
|
|
* @returns Promise resolving to file stats or null if doesn't exist
|
|
*/
|
|
static async getStats(filePath: string): Promise<plugins.fs.Stats | null> {
|
|
try {
|
|
return await plugins.fs.promises.stat(filePath);
|
|
} catch (error: any) {
|
|
if (error.code === 'ENOENT') {
|
|
return null;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List files in a directory
|
|
* @param dirPath - Directory path
|
|
* @returns Promise resolving to array of filenames
|
|
*/
|
|
static async listFiles(dirPath: string): Promise<string[]> {
|
|
try {
|
|
return await plugins.fs.promises.readdir(dirPath);
|
|
} catch (error: any) {
|
|
if (error.code === 'ENOENT') {
|
|
return [];
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List files in a directory with full paths
|
|
* @param dirPath - Directory path
|
|
* @returns Promise resolving to array of full file paths
|
|
*/
|
|
static async listFilesFullPath(dirPath: string): Promise<string[]> {
|
|
const files = await this.listFiles(dirPath);
|
|
return files.map(file => plugins.path.join(dirPath, file));
|
|
}
|
|
|
|
/**
|
|
* Recursively list all files in a directory
|
|
* @param dirPath - Directory path
|
|
* @param fileList - Accumulator for file list (used internally)
|
|
* @returns Promise resolving to array of all file paths
|
|
*/
|
|
static async listFilesRecursive(dirPath: string, fileList: string[] = []): Promise<string[]> {
|
|
const files = await this.listFiles(dirPath);
|
|
|
|
for (const file of files) {
|
|
const filePath = plugins.path.join(dirPath, file);
|
|
const stats = await this.getStats(filePath);
|
|
|
|
if (stats?.isDirectory()) {
|
|
await this.listFilesRecursive(filePath, fileList);
|
|
} else if (stats?.isFile()) {
|
|
fileList.push(filePath);
|
|
}
|
|
}
|
|
|
|
return fileList;
|
|
}
|
|
|
|
/**
|
|
* Create a read stream for a file
|
|
* @param filePath - Path to the file
|
|
* @param options - Stream options
|
|
* @returns Read stream
|
|
*/
|
|
static createReadStream(filePath: string, options?: Parameters<typeof plugins.fs.createReadStream>[1]): plugins.fs.ReadStream {
|
|
return plugins.fs.createReadStream(filePath, options);
|
|
}
|
|
|
|
/**
|
|
* Create a write stream for a file
|
|
* @param filePath - Path to the file
|
|
* @param options - Stream options
|
|
* @returns Write stream
|
|
*/
|
|
static createWriteStream(filePath: string, options?: Parameters<typeof plugins.fs.createWriteStream>[1]): plugins.fs.WriteStream {
|
|
return plugins.fs.createWriteStream(filePath, options);
|
|
}
|
|
|
|
/**
|
|
* Ensure a file exists, creating an empty file if necessary
|
|
* @param filePath - Path to the file
|
|
* @returns Promise that resolves when file is ensured
|
|
*/
|
|
static async ensureFile(filePath: string): Promise<void> {
|
|
const exists = await this.exists(filePath);
|
|
if (!exists) {
|
|
await this.writeFile(filePath, '');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a path is a directory
|
|
* @param path - Path to check
|
|
* @returns Promise resolving to true if directory, false otherwise
|
|
*/
|
|
static async isDirectory(path: string): Promise<boolean> {
|
|
const stats = await this.getStats(path);
|
|
return stats?.isDirectory() ?? false;
|
|
}
|
|
|
|
/**
|
|
* Check if a path is a file
|
|
* @param path - Path to check
|
|
* @returns Promise resolving to true if file, false otherwise
|
|
*/
|
|
static async isFile(path: string): Promise<boolean> {
|
|
const stats = await this.getStats(path);
|
|
return stats?.isFile() ?? false;
|
|
}
|
|
} |