umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
423 lines (422 loc) • 13.1 kB
JavaScript
"use strict";
/**
* Модуль базовой модели для работы с данными
*
* Предоставляет абстрактный класс для:
* - Взаимодействия с базой данных
* - Валидации данных
* - Управления состоянием модели
* - Выполнения CRUD операций
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Model = void 0;
const QueryData_1 = require("./QueryData");
/**
* Абстрактный класс для создания моделей данных
* Предоставляет базовую функциональность для работы с данными в базе данных
*
* @example
* ```ts
* class UserModel extends Model<UserState> {
* // Определение правил валидации
* rules(): IModelRules[] {
* return [
* { name: ['username'], type: 'string', max: 50 },
* { name: ['age'], type: 'integer' }
* ];
* }
*
* // Определение меток атрибутов
* attributeLabels(): UserState {
* return {
* id: 'ID',
* username: 'Имя пользователя',
* age: 'Возраст'
* };
* }
*
* // Определение имени таблицы
* tableName(): string {
* return 'users';
* }
* }
*
* // Использование модели
* const user = new UserModel();
* user.state.username = 'John';
* user.state.age = 25;
* await user.save();
* ```
*
* @template TState - Тип состояния модели
* @class Model
*/
class Model {
/**
* Объект для хранения параметров запроса.
* Содержит условия поиска и данные для обновления
*/
queryData;
/**
* Начальный индекс для итерации по данным.
* Используется при инициализации модели из массива
*/
startIndex = 0;
/**
* Состояние модели.
* Содержит текущие значения всех атрибутов
*
* @example
* ```ts
* // Установка значений
* this.state.username = 'John';
* this.state.age = 25;
*
* // Получение значений
* console.log(this.state.username);
* ```
*/
state = {};
/**
* Контекст приложения.
*/
_appContext;
/**
* Создает новый экземпляр модели.
* Инициализирует контроллер базы данных и состояние модели
*
* @example
* ```ts
* const user = new UserModel();
* ```
*/
constructor(appContext) {
this._appContext = appContext;
this.queryData = {
query: null,
data: null,
tableName: this.tableName(),
primaryKeyName: this.getId(),
rules: this.rules(),
};
}
/**
* Проверяет состояние подключения к базе данных
*
* @example
* ```ts
* const isConnected = await model.isConnected();
* if (isConnected) {
* // Выполнение операций с базой данных
* }
* ```
*
* @returns Promise<boolean> - true если подключение активно
*/
isConnected() {
if (this._appContext.database.adapter) {
return this._appContext.database.adapter.isConnected();
}
return false;
}
/**
* Экранирует специальные символы в строке
*
* @example
* ```ts
* const safe = model.escapeString("O'Connor");
* ```
*
* @param text - Текст для экранирования
* @returns Экранированная строка
*/
escapeString(text) {
if (this._appContext.database.adapter) {
return this._appContext.database.adapter.escapeString(text);
}
if (text) {
return text.toString();
}
return '';
}
/**
* Выполняет валидацию данных модели
* Может быть переопределен в дочерних классах
*
* @example
* ```ts
* validate(): void {
* if (!this.state.username) {
* throw new Error('Username is required');
* }
* }
* ```
*/
validate() {
// TODO document why this method 'validate' is empty
}
/**
* Определяет имя первичного ключа таблицы.
* Ищет поле с меткой 'id' или 'ID'
*
* @returns Имя первичного ключа или null
*/
getId() {
const labels = this.attributeLabels();
for (const index in labels) {
if (labels[index] === 'id' || labels[index] === 'ID') {
return index;
}
}
return null;
}
/**
* Инициализирует модель данными
*
* @example
* ```ts
* model.init({
* id: 1,
* username: 'John',
* age: 25
* });
* ```
*
* @param data - Данные для инициализации
*/
init(data) {
if (data === null) {
return;
}
let i = this.startIndex;
const labels = this.attributeLabels();
for (const index in labels) {
if (data) {
if (data[index] !== undefined) {
this.state[index] = data[index];
}
else if (data[i] === undefined) {
this.state[index] = undefined;
}
else {
this.state[index] = data[i];
}
}
else {
this.state[index] = undefined;
}
i++;
}
}
/**
* Выполняет поиск записи по первичному ключу
*
* @example
* ```ts
* const result = await model.selectOne();
* if (result.status) {
* model.init(result.data);
* }
* ```
*
* @returns Promise с результатом запроса
*/
async selectOne() {
const idName = this.queryData.primaryKeyName;
if (idName) {
this.queryData.query = {
[idName]: this.state[idName],
};
}
this.queryData.data = null;
if (this._appContext.database.adapter) {
return (await this._appContext.database.adapter.select(this.queryData, this.queryData.query, true));
}
return {
status: false,
error: 'Не указан источник для базы данных',
};
}
/**
* Инициализирует параметры запроса.
* Подготавливает данные для сохранения или обновления
*/
#initData() {
// Не назвать через "#", так как есть proxy
this.validate();
const idName = this.queryData.primaryKeyName;
if (idName) {
this.queryData.query = {
[idName]: this.state[idName],
};
}
const data = {};
for (const index in this.attributeLabels()) {
if (index !== idName) {
data[index] = this.state[index];
}
}
this.queryData.data = data;
}
/**
* Сохраняет данные модели в базу данных
* Если запись существует - обновляет, иначе создает новую
*
* @example
* ```ts
* model.state.name = 'John';
* await model.save(); // Обновление существующей записи
* await model.save(true); // Создание новой записи
* ```
*
* @param isNew - Флаг создания новой записи
* @returns Promise с результатом операции
*/
async save(isNew = false) {
this.#initData();
if (this._appContext.database.adapter) {
return await this._appContext.database.adapter.save(this.queryData, isNew);
}
return false;
}
/**
* Обновляет существующую запись в базе данных
*
* @example
* ```ts
* model.state.name = 'John';
* await model.update();
* ```
*
* @returns Promise с результатом операции
*/
async update() {
this.#initData();
if (this._appContext.database.adapter) {
return await this._appContext.database.adapter.update(this.queryData);
}
return false;
}
/**
* Добавляет новую запись в базу данных
*
* @example
* ```ts
* model.state.name = 'John';
* await model.add();
* ```
*
* @returns Promise с результатом операции
*/
async add() {
this.validate();
this.queryData.query = null;
const data = {};
for (const index in this.attributeLabels()) {
data[index] = this.state[index];
}
this.queryData.data = data;
if (this._appContext.database.adapter) {
return await this._appContext.database.adapter.insert(this.queryData);
}
return false;
}
/**
* Удаляет запись из базы данных
*
* @example
* ```ts
* await model.remove();
* ```
*
* @returns Promise<boolean> - true если удаление успешно
*/
async remove() {
this.validate();
const idName = this.queryData.primaryKeyName;
if (idName) {
this.queryData.query = {
[idName]: this.state[idName],
};
}
this.queryData.data = null;
if (this._appContext.database.adapter) {
return await this._appContext.database.adapter.remove(this.queryData);
}
return false;
}
/**
* Выполняет произвольный запрос к базе данных
*
* @param where - Условия запроса
* @param isOne - Флаг выборки одной записи
* @returns Promise с результатом запроса
*/
async where(where = '1', isOne = false) {
const select = typeof where === 'string' ? (0, QueryData_1.getQueryData)(where) : where;
if (this._appContext.database.adapter) {
return await this._appContext.database.adapter.select(this.queryData, select, isOne);
}
return {
status: false,
error: 'Не указан источник для базы данных',
};
}
/**
* Выполняет запрос с выборкой одной записи
*
* @example
* ```ts
* const found = await model.whereOne({ id: 1 });
* if (found) {
* console.log('Record found');
* }
* ```
*
* @param where - Условия запроса
* @returns Promise<boolean> - true если запись найдена
*/
async whereOne(where = '1') {
const res = await this.where(where, true);
if (res && res.status && this._appContext.database.adapter) {
this.init(this._appContext.database.adapter.getValue(res));
return true;
}
return false;
}
/**
* Выполняет произвольный запрос к базе данных
*
* @example
* ```ts
* const result = await model.query(async (client, db) => {
* const collection = db.collection('users');
* return await collection.aggregate([
* { $match: { age: { $gt: 18 } } },
* { $group: { _id: '$city', count: { $sum: 1 } } }
* ]).toArray();
* });
* ```
*
* @param callback - Функция обратного вызова для выполнения запроса
* @returns Результат выполнения запроса
*/
query(callback) {
if (this._appContext.database.adapter) {
return this._appContext.database.adapter.query(callback);
}
return null;
}
/**
* Закрывает соединение с базой данных
*
* @example
* ```ts
* model.destroy();
* ```
*/
destroy() {
return this._appContext.database.adapter?.destroy();
}
}
exports.Model = Model;