@synet/fs
Version:
Robust, battle-tested filesystem abstraction for Node.js
157 lines (156 loc) • 4.72 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonValidationError = exports.JsonStringifyError = exports.JsonParseError = exports.JsonFileSystem = void 0;
/**
* Type-safe JSON filesystem that automatically handles JSON parsing/stringification
* Wraps any IFileSystem 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
*/
readJsonSync(path) {
const content = this.baseFileSystem.readFileSync(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
*/
writeJsonSync(path, data) {
try {
const content = JSON.stringify(data, this.options.replacer, this.options.space ?? 2);
this.baseFileSystem.writeFileSync(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
*/
readJsonSyncWithDefault(path, defaultValue) {
if (!this.baseFileSystem.existsSync(path)) {
return defaultValue;
}
return this.readJsonSync(path);
}
/**
* Update JSON file with partial data (shallow merge)
* @param path File path
* @param updates Partial data to merge
*/
updateJsonSync(path, updates) {
const current = this.readJsonSync(path);
const updated = { ...current, ...updates };
this.writeJsonSync(path, updated);
}
/**
* Validate JSON structure before writing (if validator provided)
* @param path File path
* @param data Data to validate and write
*/
writeJsonSyncWithValidation(path, data) {
if (this.options.validator) {
const isValid = this.options.validator(data);
if (!isValid) {
throw new JsonValidationError(`Data validation failed for ${path}`);
}
}
this.writeJsonSync(path, data);
}
/**
* Check if JSON file exists and is valid
* @param path File path
* @returns true if file exists and contains valid JSON
*/
isValidJsonSync(path) {
if (!this.baseFileSystem.existsSync(path)) {
return false;
}
try {
this.readJsonSync(path);
return true;
}
catch {
return false;
}
}
// Expose underlying filesystem operations for convenience
get fileSystem() {
return this.baseFileSystem;
}
// Common filesystem operations (delegate to base)
existsSync(path) {
return this.baseFileSystem.existsSync(path);
}
deleteFileSync(path) {
this.baseFileSystem.deleteFileSync(path);
}
deleteDirSync(path) {
this.baseFileSystem.deleteDirSync(path);
}
ensureDirSync(path) {
this.baseFileSystem.ensureDirSync(path);
}
readDirSync(path) {
return this.baseFileSystem.readDirSync(path);
}
chmodSync(path, mode) {
this.baseFileSystem.chmodSync(path, mode);
}
clear(dirPath) {
if (this.baseFileSystem.clear) {
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;