@nasriya/cachify
Version:
A lightweight, extensible in-memory caching library for storing anything, with built-in TTL and customizable cache types.
118 lines (117 loc) • 5.25 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const PersistanceService_1 = __importDefault(require("../../../core/persistence/PersistanceService"));
/**
* Resolve the absolute filesystem path for a backup file inside the cachify/backups directory.
*
* Validates the provided file name and resolves it against `configs.path` (or the current working directory)
* to produce an absolute path like `<base>/cachify/backups/<fileName>`.
*
* @param fileName - The backup file name to resolve; must be a valid backup file name
* @param configs - Local configuration object whose `path` property (if present) is used as the base directory
* @returns The absolute path to the backup file
* @throws If `fileName` is not a valid backup file name
*/
function getFilePath(fileName, configs) {
assertValidBackupFileName(fileName);
return path_1.default.resolve((configs.path || process.cwd()), 'cachify', 'backups', fileName);
}
/**
* Validate a candidate backup file name and throw if it is invalid.
*
* @param name - The candidate backup file name to validate
* @throws Error - if `name` is not a non-empty string, equals `.` or `..`, contains slashes or backslashes, contains illegal/control characters (`< > : " / \ | ? *` or control chars 0x00–0x1F), or contains `..` sequences. The thrown error's message is prefixed with `Invalid backup file name: `.
*/
function assertValidBackupFileName(name) {
try {
if (typeof name !== 'string' || !name.trim()) {
throw new Error(`Backup file name must be a non-empty string`);
}
const trimmed = name.trim();
if (trimmed === '.' || trimmed === '..') {
throw new Error(`Invalid backup file name: "${trimmed}" is not allowed`);
}
if (/[\\/]/.test(trimmed)) {
throw new Error(`Backup file name (${name}) must not contain slashes or backslashes`);
}
if (/[<>:"/\\|?*\x00-\x1F]/.test(trimmed)) {
throw new Error(`Backup file name (${name}) contains illegal or control characters`);
}
// Optional: prevent directory traversal via sneaky names
if (trimmed.includes('..')) {
throw new Error(`Backup file name (${name}) must not be a relative path and must not contain ".."`);
}
}
catch (error) {
if (error instanceof Error) {
error.message = `Invalid backup file name: ${error.message}`;
}
throw error;
}
}
class LocalStorageDriver extends PersistanceService_1.default {
#_manager;
constructor(persistenceManager, configs) {
super('local', configs);
this.#_manager = persistenceManager;
}
/**
* Backs up all records to the specified file path.
*
* @returns {Promise<void>} Resolves when the backup completes.
* @throws {Error} If an error occurs while writing to the file.
*/
async backup(...args) {
const [flavor, backupStream, fileNameRaw] = args;
try {
const ext = path_1.default.extname(fileNameRaw);
const baseName = path_1.default.basename(fileNameRaw, ext); // filename without extension
const backupName = `${flavor}-${baseName}.backup`;
const fileName = getFilePath(backupName, this.configs);
const destDir = path_1.default.dirname(fileName);
fs_1.default.mkdirSync(destDir, { recursive: true });
const destStream = fs_1.default.createWriteStream(fileName);
return await backupStream.streamTo(destStream);
}
catch (error) {
if (error instanceof Error) {
error.message = `Failed to backup to "${fileNameRaw}": ${error.message}`;
}
throw error;
}
}
/**
* Restores records from a specified file path.
*
* @returns {Promise<void>} A promise that resolves when the restore operation completes.
* @throws {Error} If an error occurs while reading from the file.
*/
async restore(...args) {
const [flavor, fileNameRaw] = args;
try {
const ext = path_1.default.extname(fileNameRaw);
const baseName = path_1.default.basename(fileNameRaw, ext); // filename without extension
const backupName = `${flavor}-${baseName}.backup`;
const finalPath = getFilePath(backupName, this.configs);
const exist = fs_1.default.existsSync(finalPath);
if (!exist) {
return;
}
const srcStream = fs_1.default.createReadStream(finalPath);
const restoreStream = this.#_manager.createRestoreStream();
return await restoreStream.streamFrom(srcStream);
}
catch (error) {
if (error instanceof Error) {
error.message = `Failed to restore from "${fileNameRaw}": ${error.message}`;
}
throw error;
}
}
}
exports.default = LocalStorageDriver;