GreenBean/README.md
2025-02-17 21:46:57 +03:00

16 KiB
Raw Blame History

GreenBean

GreenBean - это легковесная JavaScript библиотека для работы с данными, вдохновленная RedBeanPHP. Она предоставляет простой и гибкий способ хранения данных в JSON-формате с поддержкой связей между объектами.

📖 Содержание

Введение

Что такое GreenBean?

GreenBean - это JavaScript библиотека, которая позволяет работать с данными в простом и гибком стиле. Вместо того чтобы заранее определять структуру данных (схему), GreenBean позволяет создавать и изменять объекты "на лету".

Для кого это?

  • 👨‍💻 Разработчикам, которые хотят быстро прототипировать приложения
  • 🎓 Начинающим, которым нужен простой способ работы с данными
  • 🚀 Проектам, где структура данных часто меняется
  • 🛠️ Небольшим и средним приложениям

Почему GreenBean?

  • Простота: Нет необходимости писать схемы и миграции
  • Гибкость: Структура данных может меняться в процессе разработки
  • Удобство: Автоматическое создание связей между объектами
  • Надежность: Поддержка транзакций и защита от одновременных изменений
  • Производительность: Данные хранятся в JSON-файле, что идеально для небольших проектов

Установка

Прямое подключение

  1. Скопируйте файл greenbean.js в ваш проект
  2. Установите зависимость uuid:
npm install uuid
  1. Импортируйте GreenBean в ваш проект:
import GreenBean from './greenbean.js';

Убедитесь, что в вашем package.json установлен тип модуля:

{
  "type": "module"
}

Концепции

Бины (Beans)

В GreenBean все объекты называются "бинами". Бин - это простой JavaScript объект, который автоматически получает:

  • Уникальный идентификатор (id)
  • Тип (__type)
  • Метаданные (__meta)
// Создание бина
const user = await gb.dispense('user');

// Бин автоматически получает следующую структуру:
{
    id: "550e8400-e29b-41d4-a716-446655440000",
    __type: "user",
    __meta: {
        created: "2025-02-17T18:44:08.000Z",
        modified: "2025-02-17T18:44:08.000Z"
    }
}

Типы бинов

Тип бина - это просто строка, которая определяет "категорию" объекта. Например:

  • user для пользователей
  • post для постов
  • comment для комментариев
// Создание бинов разных типов
const user = await gb.dispense('user');
const post = await gb.dispense('post');
const comment = await gb.dispense('comment');

Динамические свойства

Вы можете добавлять любые свойства к бину в любое время:

const user = await gb.dispense('user');

// Добавляем свойства
user.name = 'Иван';
user.email = 'ivan@example.com';

// Позже можем добавить новые свойства
user.age = 25;
user.city = 'Москва';

await gb.store(user);

Быстрый старт

Создание базы данных

import GreenBean from 'greenbean';

// Создаем экземпляр GreenBean
const gb = new GreenBean('data.json');

// База данных создастся автоматически

Пример простого блога

// Создаем автора
const author = await gb.dispense('author');
author.name = 'Иван Петров';
author.email = 'ivan@example.com';
await gb.store(author);

// Создаем пост
const post = await gb.dispense('post');
post.title = 'Мой первый пост';
post.content = 'Привет, мир!';
post.created = new Date().toISOString();
await gb.store(post);

// Связываем автора и пост
await gb.xown(post, 'author', author);

// Создаем комментарии
const comment1 = await gb.dispense('comment');
comment1.text = 'Отличный пост!';
comment1.author = 'Мария';
await gb.store(comment1);

const comment2 = await gb.dispense('comment');
comment2.text = 'Спасибо за статью';
comment2.author = 'Петр';
await gb.store(comment2);

// Связываем пост и комментарии
await gb.xown(post, 'comments', [comment1, comment2]);

// Получаем пост с автором и комментариями
const loadedPost = await gb.load('post', post.id);
const postAuthor = await gb.getXown(loadedPost, 'author');
const comments = await gb.getXown(loadedPost, 'comments');

console.log(`Пост: ${loadedPost.title}`);
console.log(`Автор: ${postAuthor.name}`);
console.log('Комментарии:');
comments.forEach(comment => {
    console.log(`- ${comment.author}: ${comment.text}`);
});

Основные операции

Создание объектов

// Простое создание
const user = await gb.dispense('user');
user.name = 'Иван';
await gb.store(user);

// Создание с начальными данными
const post = await gb.dispense('post');
Object.assign(post, {
    title: 'Заголовок',
    content: 'Содержание',
    tags: ['javascript', 'tutorial']
});
await gb.store(post);

Поиск объектов

// Поиск всех пользователей
const allUsers = await gb.find('user');

// Поиск по критерию
const activeUsers = await gb.find('user', { status: 'active' });

// Поиск одного объекта
const ivan = await gb.findOne('user', { name: 'Иван' });

// Загрузка по ID
const user = await gb.load('user', 'some-uuid');

Обновление объектов

// Загружаем объект
const user = await gb.load('user', userId);

// Обновляем свойства
user.name = 'Новое имя';
user.age = 26;

// Сохраняем изменения
await gb.store(user);

Удаление объектов

// Удаление одного объекта
await gb.trash(user);

// Удаление нескольких объектов
const inactiveUsers = await gb.find('user', { status: 'inactive' });
for (const user of inactiveUsers) {
    await gb.trash(user);
}

Работа со связями

Типы связей

GreenBean поддерживает три типа связей:

  1. Один-к-одному (one-to-one)

    • Один объект связан с другим объектом
    • Пример: пользователь и профиль
  2. Один-ко-многим (one-to-many)

    • Один объект связан с несколькими объектами
    • Пример: пост и комментарии
  3. Многие-ко-многим (many-to-many)

    • Объекты могут быть связаны с множеством других объектов
    • Пример: пользователи и группы

Связь один-к-одному

// Пример: пользователь и профиль
const user = await gb.dispense('user');
user.name = 'Иван';
await gb.store(user);

const profile = await gb.dispense('profile');
profile.bio = 'Программист JavaScript';
profile.avatar = 'avatar.jpg';
await gb.store(profile);

// Связываем пользователя и профиль
await gb.xown(user, 'profile', profile);

// Получаем профиль пользователя
const userProfile = await gb.getXown(user, 'profile');
console.log(userProfile.bio); // "Программист JavaScript"

// Удаляем связь
await gb.xown(user, 'profile', null);

Связь один-ко-многим

// Пример: категория и товары
const category = await gb.dispense('category');
category.name = 'Электроника';
await gb.store(category);

// Создаем несколько товаров
const products = [];
for (const name of ['Телефон', 'Ноутбук', 'Планшет']) {
    const product = await gb.dispense('product');
    product.name = name;
    await gb.store(product);
    products.push(product);
}

// Связываем категорию и товары
await gb.xown(category, 'products', products);

// Получаем все товары категории
const categoryProducts = await gb.getXown(category, 'products');
categoryProducts.forEach(product => {
    console.log(`- ${product.name}`);
});

// Добавляем новый товар к существующим
const newProduct = await gb.dispense('product');
newProduct.name = 'Смарт-часы';
await gb.store(newProduct);

const updatedProducts = [...products, newProduct];
await gb.xown(category, 'products', updatedProducts);

Связь многие-ко-многим

// Пример: студенты и курсы
const student = await gb.dispense('student');
student.name = 'Мария';
await gb.store(student);

// Создаем несколько курсов
const courses = [];
for (const name of ['JavaScript', 'Python', 'Web Design']) {
    const course = await gb.dispense('course');
    course.name = name;
    await gb.store(course);
    courses.push(course);
}

// Записываем студента на курсы
await gb.shared(student, 'course', courses);

// Получаем все курсы студента
const studentCourses = await gb.getShared(student, 'course');
console.log('Курсы студента:');
studentCourses.forEach(course => {
    console.log(`- ${course.name}`);
});

// Отписываем от одного курса
const remainingCourses = courses.slice(1);
await gb.shared(student, 'course', remainingCourses);

Продвинутые возможности

Транзакции

Транзакции позволяют выполнять несколько операций как единое целое:

try {
    await gb.transaction(async () => {
        // Создаем заказ
        const order = await gb.dispense('order');
        order.total = 1000;
        await gb.store(order);

        // Создаем элементы заказа
        const item1 = await gb.dispense('orderItem');
        item1.name = 'Товар 1';
        item1.price = 600;
        await gb.store(item1);

        const item2 = await gb.dispense('orderItem');
        item2.name = 'Товар 2';
        item2.price = 400;
        await gb.store(item2);

        // Связываем заказ и элементы
        await gb.xown(order, 'items', [item1, item2]);

        // Если произойдет ошибка, все изменения будут отменены
        throw new Error('Что-то пошло не так');
    });
} catch (error) {
    console.error('Транзакция отменена:', error.message);
}

Заморозка данных

Заморозка предотвращает случайные изменения данных:

// Замораживаем базу данных
gb.freeze();

try {
    const user = await gb.dispense('user');
    user.name = 'Иван';
    await gb.store(user); // Выбросит ошибку
} catch (error) {
    console.error('Нельзя изменять замороженные данные');
}

// Размораживаем для внесения изменений
gb.freeze(false);

События

GreenBean позволяет отслеживать различные события:

// Подписываемся на событие сохранения
gb.on('save', () => {
    console.log('База данных сохранена');
});

// Можно использовать для логирования
gb.on('save', () => {
    const now = new Date().toISOString();
    console.log(`[${now}] Данные сохранены`);
});

Лучшие практики

Организация кода

// Создайте класс для работы с определенным типом объектов
class UserService {
    constructor(gb) {
        this.gb = gb;
    }

    async create(userData) {
        const user = await this.gb.dispense('user');
        Object.assign(user, userData);
        await this.gb.store(user);
        return user;
    }

    async findByEmail(email) {
        return await this.gb.findOne('user', { email });
    }

    async addToGroup(user, group) {
        await this.gb.shared(user, 'group', group);
    }
}

// Использование
const userService = new UserService(gb);
const user = await userService.create({
    name: 'Иван',
    email: 'ivan@example.com'
});

Обработка ошибок

async function safeOperation(callback) {
    try {
        return await callback();
    } catch (error) {
        console.error('Ошибка:', error.message);
        // Можно добавить логирование или обработку ошибок
        throw error;
    }
}

// Использование
await safeOperation(async () => {
    const user = await gb.dispense('user');
    user.name = 'Иван';
    await gb.store(user);
});

Валидация данных

function validateUser(user) {
    const errors = [];
    
    if (!user.name) {
        errors.push('Имя обязательно');
    }
    
    if (!user.email?.includes('@')) {
        errors.push('Некорректный email');
    }
    
    return errors;
}

// Использование
const user = await gb.dispense('user');
user.name = 'Иван';
user.email = 'invalid-email';

const errors = validateUser(user);
if (errors.length > 0) {
    console.error('Ошибки валидации:', errors);
} else {
    await gb.store(user);
}

API Reference

Основные методы

dispense(type)

Создает новый объект указанного типа.

store(bean)

Сохраняет объект в базе данных.

load(type, id)

Загружает объект по типу и ID.

find(type, criteria)

Ищет объекты по критериям.

findOne(type, criteria)

Ищет один объект по критериям.

trash(bean)

Удаляет объект из базы данных.

Методы для работы со связями

Устанавливает связь один-к-одному или один-ко-многим.

getXown(bean, property)

Получает связанные объекты.

Устанавливает связь многие-ко-многим.

getShared(bean, property)

Получает объекты из связи многие-ко-многим.

Служебные методы

freeze(state = true)

Замораживает или размораживает базу данных.

transaction(callback)

Выполняет операции в транзакции.

nuke()

Очищает всю базу данных.

Лицензия

MIT