asta.db
Version:
Enhanced database solution with QuickDB-like features
404 lines (357 loc) • 10 kB
JavaScript
const fs = require("fs");
const path = require("path");
class AstaDB {
constructor(options = {}) {
this.dataPath = options.dataPath || "./astadb.json";
this.data = this.loadData();
this.tables = new Map();
}
loadData() {
try {
if (fs.existsSync(this.dataPath)) {
const rawData = fs.readFileSync(this.dataPath, "utf8");
return JSON.parse(rawData) || {};
}
} catch (error) {
console.error("Error loading data:", error);
}
return {};
}
saveData() {
try {
fs.writeFileSync(
this.dataPath,
JSON.stringify(this.data, null, 2),
"utf8"
);
} catch (error) {
console.error("Error saving data:", error);
}
}
setDataPath(newPath) {
this.dataPath = newPath;
this.data = this.loadData();
return this;
}
all() {
const entries = [];
const flatten = (obj, prefix = "") => {
for (const key in obj) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (
typeof obj[key] === "object" &&
obj[key] !== null &&
!Array.isArray(obj[key])
) {
flatten(obj[key], fullKey);
} else {
entries.push({ id: fullKey, value: obj[key] });
}
}
};
flatten(this.data);
return entries;
}
set(key, value) {
const keys = key.split(".");
let current = this.data;
for (let i = 0; i < keys.length - 1; i++) {
const part = keys[i];
if (!current[part] || typeof current[part] !== "object") {
current[part] = {};
}
current = current[part];
}
current[keys[keys.length - 1]] = value;
this.saveData();
return this;
}
get(key, defaultValue = null) {
if (!key) return this.data;
const keys = key.split(".");
let current = this.data;
for (const part of keys) {
if (current[part] === undefined) {
return defaultValue;
}
current = current[part];
}
return current;
}
fetch(key, defaultValue = null) {
return this.get(key, defaultValue);
}
push(key, value) {
const array = this.get(key, []);
if (!Array.isArray(array)) {
throw new Error(`Value at ${key} is not an array`);
}
array.push(value);
this.set(key, array);
return array.length;
}
delete(key) {
const keys = key.split(".");
let current = this.data;
for (let i = 0; i < keys.length - 1; i++) {
const part = keys[i];
if (current[part] === undefined) {
return false;
}
current = current[part];
}
const lastKey = keys[keys.length - 1];
const exists = current[lastKey] !== undefined;
delete current[lastKey];
this.saveData();
return exists;
}
deleteAll() {
this.data = {};
this.saveData();
return true;
}
has(key) {
return this.get(key) !== null;
}
add(key, amount = 1) {
const value = this.get(key, 0);
if (typeof value !== "number") {
throw new Error(`Value at ${key} is not a number`);
}
const newValue = value + amount;
this.set(key, newValue);
return newValue;
}
subtract(key, amount = 1) {
return this.add(key, -amount);
}
startsWith(prefix) {
const results = [];
const entries = this.all();
for (const entry of entries) {
if (entry.id.startsWith(prefix)) {
results.push(entry);
}
}
return results;
}
count(key) {
const value = this.get(key);
if (Array.isArray(value)) {
return value.length;
} else if (typeof value === "object" && value !== null) {
return Object.keys(value).length;
}
return 0;
}
ensure(key, defaultValue) {
if (!this.has(key)) {
this.set(key, defaultValue);
}
return this.get(key);
}
includes(key, value) {
const data = this.get(key);
if (!Array.isArray(data)) {
throw new Error(`Value at ${key} is not an array`);
}
return data.includes(value);
}
filter(key, condition) {
const data = this.get(key);
if (!Array.isArray(data)) {
throw new Error(`Value at ${key} is not an array`);
}
if (typeof condition === "function") {
return data.filter(condition);
} else if (typeof condition === "object") {
return data.filter((item) => {
for (const prop in condition) {
if (item[prop] !== condition[prop]) {
return false;
}
}
return true;
});
}
throw new Error("Condition must be a function or object");
}
find(key, condition) {
const data = this.get(key);
if (!Array.isArray(data)) {
throw new Error(`Value at ${key} is not an array`);
}
if (typeof condition === "function") {
return data.find(condition);
} else if (typeof condition === "object") {
return data.find((item) => {
for (const prop in condition) {
if (item[prop] !== condition[prop]) {
return false;
}
}
return true;
});
}
throw new Error("Condition must be a function or object");
}
isArray(key) {
return Array.isArray(this.get(key));
}
ensureArray(key) {
const value = this.get(key);
if (!Array.isArray(value)) {
this.set(key, []);
return [];
}
return value;
}
remove(key, callback) {
const data = this.get(key);
if (!Array.isArray(data)) {
throw new Error(`Value at ${key} is not an array`);
}
const removed = data.filter(callback);
this.set(
key,
data.filter((item) => !callback(item))
);
return removed;
}
table(name) {
if (this.tables.has(name)) {
return this.tables.get(name);
}
const table = {
set: (key, value) => this.set(`${name}.${key}`, value),
get: (key, defaultValue) => this.get(`${name}.${key}`, defaultValue),
fetch: (key, defaultValue) => this.get(`${name}.${key}`, defaultValue),
delete: (key) => this.delete(`${name}.${key}`),
all: () => {
const allData = this.all();
return allData.filter((entry) => entry.id.startsWith(`${name}.`))
.map((entry) => ({
...entry,
id: entry.id.replace(`${name}.`, '')
}));
},
push: (key, value) => this.push(`${name}.${key}`, value),
add: (key, amount) => this.add(`${name}.${key}`, amount),
subtract: (key, amount) => this.subtract(`${name}.${key}`, amount),
has: (key) => this.has(`${name}.${key}`),
startsWith: (prefix) => {
const results = this.startsWith(`${name}.${prefix}`);
return results.map(entry => ({
...entry,
id: entry.id.replace(`${name}.`, '')
}));
},
deleteAll: () => {
const entries = this.all().filter((entry) =>
entry.id.startsWith(`${name}.`)
);
entries.forEach((entry) => this.delete(entry.id));
return entries.length;
},
count: (key) => this.count(`${name}.${key}`),
ensure: (key, defaultValue) => this.ensure(`${name}.${key}`, defaultValue),
includes: (key, value) => this.includes(`${name}.${key}`, value),
filter: (key, condition) => this.filter(`${name}.${key}`, condition),
find: (key, condition) => this.find(`${name}.${key}`, condition),
isArray: (key) => this.isArray(`${name}.${key}`),
ensureArray: (key) => this.ensureArray(`${name}.${key}`),
remove: (key, callback) => this.remove(`${name}.${key}`, callback),
array: (key) => this.array(`${name}.${key}`)
};
this.tables.set(name, table);
return table;
}
array(key) {
const array = this.get(key, []);
if (!Array.isArray(array)) {
throw new Error(`Value at ${key} is not an array`);
}
return {
push: (...items) => {
array.push(...items);
this.set(key, array);
return array.length;
},
pop: () => {
const item = array.pop();
this.set(key, array);
return item;
},
shift: () => {
const item = array.shift();
this.set(key, array);
return item;
},
unshift: (...items) => {
array.unshift(...items);
this.set(key, array);
return array.length;
},
splice: (...args) => {
const result = array.splice(...args);
this.set(key, array);
return result;
},
slice: (...args) => array.slice(...args),
find: (...args) => array.find(...args),
filter: (...args) => array.filter(...args),
map: (...args) => array.map(...args),
reduce: (...args) => array.reduce(...args),
forEach: (...args) => array.forEach(...args),
includes: (...args) => array.includes(...args),
indexOf: (...args) => array.indexOf(...args),
join: (...args) => array.join(...args),
length: array.length,
get: () => array,
set: (newArray) => {
if (!Array.isArray(newArray)) {
throw new Error("Value must be an array");
}
this.set(key, newArray);
return newArray;
},
};
}
// QuickDB compatibility aliases
static create() {
return new AstaDB();
}
pull(key, callback) {
return this.remove(key, callback);
}
math(key, operation, amount) {
const value = this.get(key, 0);
if (typeof value !== "number") {
throw new Error(`Value at ${key} is not a number`);
}
let newValue;
switch (operation) {
case 'add':
newValue = value + amount;
break;
case 'subtract':
newValue = value - amount;
break;
case 'multiply':
newValue = value * amount;
break;
case 'divide':
newValue = value / amount;
break;
case 'mod':
newValue = value % amount;
break;
default:
throw new Error(`Invalid math operation: ${operation}`);
}
this.set(key, newValue);
return newValue;
}
}
module.exports = AstaDB;