GreenBean/greenbean.js~
2025-02-17 21:46:57 +03:00

198 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'fs/promises';
import { EventEmitter } from 'events';
import { v4 as uuidv4 } from 'uuid';
class GreenBean extends EventEmitter {
constructor(dbPath = 'data.json') {
super();
this.dbPath = dbPath;
this.data = null;
this.initialized = false;
this.frozen = false;
}
// Базовые операции с бинами
async R(type, id = null) {
if (!this.initialized) await this.init();
if (id === null) {
return this.dispense(type);
}
return this.load(type, id);
}
async dispense(type) {
if (!this.initialized) await this.init();
const bean = {
id: uuidv4(),
__type: type,
__meta: {
created: new Date().toISOString(),
modified: new Date().toISOString()
}
};
return new Proxy(bean, this.createHandler(type));
}
async load(type, id) {
if (!this.initialized) await this.init();
const item = this.data[type]?.find(item => item.id === id);
if (!item) return null;
return new Proxy({...item}, this.createHandler(type));
}
async store(bean) {
if (this.frozen) throw new Error('Cannot modify frozen beans');
if (!this.initialized) await this.init();
const type = bean.__type;
if (!this.data[type]) this.data[type] = [];
bean.__meta.modified = new Date().toISOString();
const index = this.data[type].findIndex(item => item.id === bean.id);
if (index !== -1) {
this.data[type][index] = {...bean};
} else {
this.data[type].push({...bean});
}
await this.save();
return bean.id;
}
async trash(bean) {
if (this.frozen) throw new Error('Cannot modify frozen beans');
if (!this.initialized) await this.init();
const type = bean.__type;
if (!this.data[type]) return;
this.data[type] = this.data[type].filter(item => item.id !== bean.id);
await this.save();
}
// Работа со связями
async own(bean, property, related) {
if (!Array.isArray(related)) related = [related];
bean[property] = related.map(item => ({
id: item.id,
__type: item.__type
}));
await this.store(bean);
}
async link(bean, linkType, attributes = {}) {
const link = await this.dispense(linkType);
Object.assign(link, attributes, {
[`${bean.__type}Id`]: bean.id
});
await this.store(link);
return link;
}
async find(type, criteria = {}) {
if (!this.initialized) await this.init();
if (!this.data[type]) return [];
return this.data[type]
.filter(item => {
return Object.entries(criteria).every(([key, value]) =>
item[key] === value
);
})
.map(item => new Proxy({...item}, this.createHandler(type)));
}
async findOne(type, criteria = {}) {
const results = await this.find(type, criteria);
return results[0] || null;
}
// Вспомогательные методы
async init() {
try {
const exists = await fs.access(this.dbPath)
.then(() => true)
.catch(() => false);
if (exists) {
const content = await fs.readFile(this.dbPath, 'utf-8');
this.data = JSON.parse(content);
} else {
this.data = {};
}
this.initialized = true;
} catch (error) {
throw new Error(`Failed to initialize database: ${error.message}`);
}
}
async save() {
try {
const tempPath = `${this.dbPath}.tmp`;
await fs.writeFile(tempPath, JSON.stringify(this.data, null, 2));
await fs.rename(tempPath, this.dbPath);
this.emit('save');
} catch (error) {
throw new Error(`Failed to save database: ${error.message}`);
}
}
createHandler(type) {
return {
get: (target, prop) => {
if (prop.endsWith('List') && !target[prop]) {
target[prop] = [];
}
return target[prop];
},
set: (target, prop, value) => {
if (this.frozen) throw new Error('Cannot modify frozen beans');
if (prop.endsWith('List') && Array.isArray(value)) {
target[prop] = value.map(item => ({
id: item.id,
__type: item.__type || type
}));
} else {
target[prop] = value;
}
return true;
}
};
}
// Утилиты
freeze(frozen = true) {
this.frozen = frozen;
}
async nuke() {
if (this.frozen) throw new Error('Cannot nuke frozen database');
this.data = {};
await this.save();
}
async transaction(callback) {
const snapshot = JSON.stringify(this.data);
try {
const result = await callback();
await this.save();
return result;
} catch (error) {
this.data = JSON.parse(snapshot);
throw error;
}
}
}
export default GreenBean;