## @e22m4u/js-repository Абстракция для работы с базами данных для Node.js ## Установка ```bash npm install @e22m4u/js-repository ``` Опционально устанавливаем адаптер. Например, если используется *MongoDB*, то для подключения потребуется установить [адаптер mongodb](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter) как отдельную зависимость. ```bash npm install @e22m4u/js-repository-mongodb-adapter ``` **Список доступных адаптеров:** | адаптер | описание | |-----------|-------------------------------------------------------------------------------------------------------------------------------------------------| | `memory` | виртуальная база в памяти процесса (для разработки и тестирования) | | `mongodb` | MongoDB - система управления NoSQL базами (*[требует установки](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter))* | ## Концепция Модуль позволяет спроектировать систему связанных данных, доступ к которым осуществляется посредством репозиториев. Каждый репозиторий имеет собственную модель данных, которая описывает структуру определенной коллекции в базе, а так же определяет связи к другим коллекциям. ```mermaid flowchart LR A[Datasource]-->B[Data Model]-->С[Repository]; ``` ## Использование Определения источников и моделей хранятся в экземпляре класса `Schema`, и первым шагом будет создание данного экземпляра. ```js import {Schema} from '@e22m4u/js-repository'; const schema = new Schema(); ``` Интерфейс `Schema` содержит три основных метода: - `defineDatasource(datasourceDef: object): this` - добавить источник - `defineModel(modelDef: object): this` - добавить модель - `getRepository(modelName: string): Repository` - получить репозиторий ### Источник данных Источник описывает способ подключения к базе и используемый адаптер. Если адаптер имеет настройки, то они передаются вместе с объектом определения источника методом `defineDatasource`, как это показано ниже. ```js schema.defineDatasource({ name: 'myMongo', // название нового источника adapter: 'mongodb', // название выбранного адаптера // настройки адаптера mongodb host: '127.0.0.1', port: 27017, database: 'data' }); ``` **Параметры источника:** - `name: string` уникальное название - `adapter: string` выбранный адаптер При желании можно использовать встроенный адаптер `memory`, который хранит данные в памяти процесса. У него нет специальных настроек, и он отлично подходит для тестов и прототипирования. ```js schema.defineDatasource({ name: 'myMemory', // название источника adapter: 'memory', // выбранный адаптер }); ``` ### Модель данных Когда источники определены, можно перейти к описанию моделей данных. Модель может определять как структуру какого-либо объекта, так и являться отражением реальной коллекции базы. Представьте себе коллекцию торговых точек, у каждой из которых имеются координаты `lat` и `lng`. Мы можем заранее определить модель для объекта координат методом `defineModel` и использовать ее в других коллекциях. ```js schema.defineModel({ name: 'latLng', // название новой модели properties: { // поля модели lat: DataType.NUMBER, // поле широты lng: DataType.NUMBER, // поле долготы }, }); ``` **Параметры модели:** - `name: string` уникальное название (обязательно) - `datasource: string` выбранный источник данных - `properties: object` определения полей модели - `relations: object` определения связей модели Параметр `properties` принимает объект, ключи которого являются именами полей, а значением тип поля или объект с дополнительными параметрами. ```js schema.defineModel({ name: 'latLng', properties: { lat: DataType.NUMBER, // краткое определение поля "lat" lng: { // расширенное определение поля "lng" type: DataType.NUMBER, // тип допустимого значения required: true, // исключает null и undefined }, }, }); ``` **Типы данных:** - `DataType.ANY` - `DataType.STRING` - `DataType.NUMBER` - `DataType.BOOLEAN` - `DataType.ARRAY` - `DataType.OBJECT` Модель `latLng` всего лишь описывает структуру объекта координат, тогда как торговая точка должна иметь реальную таблицу в базе. По аналогии с предыдущим примером, добавим модель `place`, но дополнительно укажем источник данных в параметре `datasource` ```js schema.defineModel({ name: 'place', datasource: 'myMemory', // выбранный источник данных properties: { name: DataType.STRING, // поле для названия торговой точки location: { // поле объекта координат type: DataType.OBJECT, // допускать только объекты model: 'latLng', // определение структуры объекта }, }, }); ``` В примере выше мы использовали модель `latLng` как структуру допустимого значения поля `location`. Возможный документ данной коллекции может выглядеть так: ```json { "id": 1, "name": "Burger King", "location": { "lat": 32.412891, "lng": 34.7660061 } } ``` Стоит обратить внимание, что мы могли бы не объявлять параметр `properties`, при этом теряя возможность проверки данных перед записью в базу. ```js schema.defineModel({ name: 'place', adapter: 'myMemory', // параметр "properties" не указан }); ``` **Параметры поля:** - `type: string` тип допустимого значения (обязательно) - `itemType: string` тип элемента массива (для `type: 'array'`) - `model: string` модель объекта (для `type: 'object'`) - `primaryKey: boolean` объявить поле первичным ключом - `columnName: string` переопределение названия колонки - `columnType: string` тип колонки (определяется адаптером) - `required: boolean` объявить поле обязательным - `default: any` значение по умолчанию ### Репозиторий В отличие от `latLng`, модель `place` имеет источник данных с названием `myMemory`, который был объявлен ранее. Наличие источника позволяет получить репозиторий по названию модели. ```js const rep = schema.getRepository('place'); ``` **Методы:** - `create(data, filter = undefined)` - `replaceById(id, data, filter = undefined)` - `patch(data, where = undefined)` - `patchById(id, data, filter = undefined)` - `find(filter = undefined)` - `findOne(filter = undefined)` - `findById(id, filter = undefined)` - `delete(where = undefined)` - `deleteById(id)` - `exists(id)` - `count(where = undefined)` #### create(data, filter = undefined) Метод `create` добавляет новый документ в коллекцию и возвращает записанные данные. Первый аргумент должен являться объектом и соответствовать модели используемого репозитория. ````js const rick = await rep.create({ name: 'Rick Sanchez', dimension: 'C-137', age: 67, }); console.log(rick); // { // id: 1, // name: 'Rick Sanchez', // dimension: 'C-137', // age: 67, // } ```` #### replaceById(id, data, filter = undefined) Метод `replaceById` находит документ по идентификатору и перезаписывает его данными из второго аргумента. Если указанный идентификатор не найден, то будет выброшено исключение, в противном случае возвращает затронутый документ. ```js // { // id: 1, // name: 'Rick Sanchez', // dimension: 'C-137', // age: 67, // } const morty = await rep.replaceById(1, { name: 'Morty Smith', age: 14, }); console.log(morty); // { // id: 1, // name: 'Morty Smith', // age: 14, // } ``` #### patchById(id, data, filter = undefined) Метод `patchById` находит документ по идентификатору и выполняет его частичное обновление данными из второго аргумента. В отличие от `replaceById`, данный метод не удаляет поля, которые не были переданы. ```js // { // "id": 2, // "type": "airport", // "name": "Domodedovo Airport", // "code": "DME" // } const airport = await rep.patchById(2, { name: 'Sheremetyevo Airport', code: 'SVO' }); console.log(airport); // { // "id": 2, // "type": "airport", // "name": "Sheremetyevo Airport", // "code": "SVO" // } ``` ## Расширение При использовании метода `getRepository` выполняется проверка на наличие существующего экземпляра репозитория для нужной модели. При первичном запросе создается новый экземпляр, который будет сохранен для повторных обращений к методу. ```js import {Schema} from '@e22m4u/js-repository'; import {Repository} from '@e22m4u/js-repository'; // const schema = new Schema(); // schema.defineDatasource ... // schema.defineModel ... const rep1 = schema.getRepository('myModel'); const rep2 = schema.getRepository('myModel'); console.log(rep1 === rep2); // true ``` Если требуется изменить или расширить поведение репозитория, то перед его созданием можно переопределить его конструктор методом `setRepositoryCtor`. После чего, все создаваемые репозитории будут использовать уже пользовательский конструктор вместо стандартного. ```js import {Schema} from '@e22m4u/js-repository'; import {Repository} from '@e22m4u/js-repository'; import {RepositoryRegistry} from '@e22m4u/js-repository'; class MyRepository extends Repository { /*...*/ } const schema = new Schema(); schema.get(RepositoryRegistry).setRepositoryCtor(MyRepository); // теперь schema.getRepository(modelName) будет возвращать // экземпляр класса MyRepository ``` ## Тесты ```bash npm run test ``` ## Лицензия MIT