@synet/fs
Version:
Robust, battle-tested filesystem abstraction for Node.js
167 lines (166 loc) • 5.18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonValidationError = exports.JsonStringifyError = exports.JsonParseError = exports.JsonFileSystem = void 0;
/**
* Type-safe async JSON filesystem that automatically handles JSON parsing/stringification
* Wraps any IAsyncFileSystem implementation to provide typed JSON operations
* @template T The type of data stored in JSON files
*/
class JsonFileSystem {
constructor(baseFileSystem, options = {}) {
this.baseFileSystem = baseFileSystem;
this.options = options;
}
/**
* Read and parse JSON file as typed object
* @param path File path
* @returns Parsed object of type T
*/
async readJson(path) {
const content = await this.baseFileSystem.readFile(path);
try {
return JSON.parse(content);
}
catch (error) {
throw new JsonParseError(`Failed to parse JSON from ${path}`, error);
}
}
/**
* Stringify and write typed object as JSON
* @param path File path
* @param data Typed data to write
*/
async writeJson(path, data) {
try {
const content = JSON.stringify(data, this.options.replacer, this.options.space ?? 2);
await this.baseFileSystem.writeFile(path, content);
}
catch (error) {
throw new JsonStringifyError(`Failed to stringify data for ${path}`, error);
}
}
/**
* Read and parse JSON file with default fallback
* @param path File path
* @param defaultValue Default value if file doesn't exist
* @returns Parsed object or default value
*/
async readJsonWithDefault(path, defaultValue) {
if (!(await this.baseFileSystem.exists(path))) {
return defaultValue;
}
return this.readJson(path);
}
/**
* Update JSON file with partial data (shallow merge)
* @param path File path
* @param updates Partial data to merge
*/
async updateJson(path, updates) {
const current = await this.readJson(path);
const updated = { ...current, ...updates };
await this.writeJson(path, updated);
}
/**
* Validate JSON structure before writing (if validator provided)
* @param path File path
* @param data Data to validate and write
*/
async writeJsonWithValidation(path, data) {
if (this.options.validator) {
const isValid = this.options.validator(data);
if (!isValid) {
throw new JsonValidationError(`Data validation failed for ${path}`);
}
}
await this.writeJson(path, data);
}
/**
* Check if JSON file exists and is valid
* @param path File path
* @returns true if file exists and contains valid JSON
*/
async isValidJson(path) {
if (!(await this.baseFileSystem.exists(path))) {
return false;
}
try {
await this.readJson(path);
return true;
}
catch {
return false;
}
}
/**
* Atomically update JSON file (read-modify-write with custom updater function)
* @param path File path
* @param updater Function that receives current data and returns updated data
*/
async atomicUpdate(path, updater) {
const current = await this.readJson(path);
const updated = updater(current);
await this.writeJson(path, updated);
}
// Expose underlying filesystem operations for convenience
get fileSystem() {
return this.baseFileSystem;
}
// Common filesystem operations (delegate to base)
async exists(path) {
return this.baseFileSystem.exists(path);
}
async deleteFile(path) {
return this.baseFileSystem.deleteFile(path);
}
async deleteDir(path) {
return this.baseFileSystem.deleteDir(path);
}
async ensureDir(path) {
return this.baseFileSystem.ensureDir(path);
}
async readDir(path) {
return this.baseFileSystem.readDir(path);
}
async chmod(path, mode) {
return this.baseFileSystem.chmod(path, mode);
}
async clear(dirPath) {
if (this.baseFileSystem.clear) {
return this.baseFileSystem.clear(dirPath);
}
}
}
exports.JsonFileSystem = JsonFileSystem;
/**
* Error thrown when JSON parsing fails
*/
class JsonParseError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "JsonParseError";
}
}
exports.JsonParseError = JsonParseError;
/**
* Error thrown when JSON stringification fails
*/
class JsonStringifyError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "JsonStringifyError";
}
}
exports.JsonStringifyError = JsonStringifyError;
/**
* Error thrown when JSON validation fails
*/
class JsonValidationError extends Error {
constructor(message) {
super(message);
this.name = "JsonValidationError";
}
}
exports.JsonValidationError = JsonValidationError;