jsonion
Version:
A lightweight JSON file-based database with nested data access and manipulation capabilities.
278 lines • 8.17 kB
JavaScript
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