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;