UNPKG

umbot

Version:

Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф

423 lines (422 loc) 13.1 kB
"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;