UNPKG

jsonion

Version:

A lightweight JSON file-based database with nested data access and manipulation capabilities.

278 lines 8.17 kB
import * as fs from "fs"; import { validateJsonExtension, sanitizePath, readJSONFile, writeJSONFile, ensureDirectoryExists, } from "./utils.js"; import { dbError, } from "./types.js"; class db { constructor(options, baseDir) { this.isDirty = false; this.AUTO_SAVE_DELAY = 100; this.allowedOperations = [ "set", "get", "del", "has", "keys", "clear", "all", "update", "find", "count", "export", "import", ]; const operation = "validation"; if (typeof options === "string") { this.filePath = options; } else { this.filePath = options.filePath; } validateJsonExtension(this.filePath, operation); const resolvedPath = sanitizePath(this.filePath, baseDir, operation); this.filePath = resolvedPath; this.baseDir = baseDir; if (!this.fileExists()) { ensureDirectoryExists(this.filePath, "create"); writeJSONFile(this.filePath, {}, "create"); } this.load(); } fileExists() { try { return fs.existsSync(this.filePath); } catch (error) { return false; } } validateOperation(operation) { if (!this.allowedOperations.includes(operation)) { throw new dbError("validation", `Operation '${operation}' is not allowed for HarmDB`); } } load() { try { this.data = readJSONFile(this.filePath, "read"); this.isDirty = false; } catch (error) { if (error instanceof dbError) { throw error; } throw new dbError("read", "Failed to load database", error); } } scheduleSave() { if (this.saveTimeout) { clearTimeout(this.saveTimeout); } this.saveTimeout = setTimeout(() => { this.performSave(); }, this.AUTO_SAVE_DELAY); } performSave() { if (!this.isDirty) return; try { writeJSONFile(this.filePath, this.data, "write"); this.isDirty = false; } catch (error) { if (error instanceof dbError) { throw error; } throw new dbError("write", "Failed to save database", error); } } flush() { if (this.saveTimeout) { clearTimeout(this.saveTimeout); this.saveTimeout = undefined; } this.performSave(); } reload() { this.load(); } set(key, value) { const operation = "set"; this.validateOperation(operation); if (typeof key !== "string") { throw new dbError(operation, "Key must be a string"); } if (key.trim() === "") { throw new dbError(operation, "Key cannot be empty"); } try { JSON.stringify(value); } catch (error) { throw new dbError(operation, "Value must be JSON-serializable", error); } this.data[key] = value; this.isDirty = true; this.scheduleSave(); } get(key) { const operation = "get"; this.validateOperation(operation); if (typeof key !== "string") { throw new dbError(operation, "Key must be a string"); } return this.data[key]; } del(key) { const operation = "del"; this.validateOperation(operation); if (typeof key !== "string") { throw new dbError(operation, "Key must be a string"); } if (key in this.data) { delete this.data[key]; this.isDirty = true; this.scheduleSave(); return true; } return false; } has(key) { const operation = "has"; this.validateOperation(operation); if (typeof key !== "string") { throw new dbError(operation, "Key must be a string"); } return key in this.data; } keys() { const operation = "keys"; this.validateOperation(operation); return Object.keys(this.data); } clear() { const operation = "clear"; this.validateOperation(operation); this.data = {}; this.isDirty = true; this.scheduleSave(); } all() { const operation = "all"; this.validateOperation(operation); return JSON.parse(JSON.stringify(this.data)); } update(key, newValue) { const operation = "update"; this.validateOperation(operation); if (typeof key !== "string") { throw new dbError(operation, "Key must be a string"); } if (!(key in this.data)) { return false; } try { JSON.stringify(newValue); } catch (error) { throw new dbError(operation, "Value must be JSON-serializable", error); } this.data[key] = newValue; this.isDirty = true; this.scheduleSave(); return true; } find(predicate) { const operation = "find"; this.validateOperation(operation); if (typeof predicate !== "function") { throw new dbError(operation, "Predicate must be a function"); } try { return Object.entries(this.data) .filter(([key, value]) => predicate(value, key)) .map(([_, value]) => value); } catch (error) { throw new dbError(operation, "Error during find operation", error); } } findEntries(predicate) { const operation = "find"; this.validateOperation(operation); if (typeof predicate !== "function") { throw new dbError(operation, "Predicate must be a function"); } try { return Object.entries(this.data).filter(([key, value]) => predicate(value, key)); } catch (error) { throw new dbError(operation, "Error during findEntries operation", error); } } count() { const operation = "count"; this.validateOperation(operation); return Object.keys(this.data).length; } export() { const operation = "export"; this.validateOperation(operation); try { return JSON.stringify(this.data, null, 2); } catch (error) { throw new dbError(operation, "Failed to export data", error); } } import(jsonData, merge = true) { const operation = "import"; this.validateOperation(operation); let data; if (typeof jsonData === "string") { try { data = JSON.parse(jsonData); } catch (error) { throw new dbError(operation, "Invalid JSON string", error); } } else { data = jsonData; } if (typeof data !== "object" || data === null || Array.isArray(data)) { throw new dbError(operation, "Data must be a valid JSON object"); } this.data = merge ? { ...this.data, ...data } : data; this.isDirty = true; this.scheduleSave(); } size() { return this.count(); } hasAll(...keys) { return keys.every((key) => this.has(key)); } getMany(...keys) { return keys.map((key) => this.get(key)); } deleteMany(...keys) { let deletedCount = 0; for (const key of keys) { if (key in this.data) { delete this.data[key]; deletedCount++; } } if (deletedCount > 0) { this.isDirty = true; this.scheduleSave(); } return deletedCount; } destroy() { if (this.saveTimeout) { clearTimeout(this.saveTimeout); } this.flush(); } } export { db }; //# sourceMappingURL=db.js.map