README.md 44 KB

@e22m4u/js-repository

Абстракция для работы с базами данных для Node.js

Установка

npm install @e22m4u/js-repository

Опционально устанавливаем адаптер. Например, если используется MongoDB, то для подключения потребуется установить адаптер mongodb как отдельную зависимость.

npm install @e22m4u/js-repository-mongodb-adapter

Список доступных адаптеров:

адаптер описание
memory виртуальная база в памяти процесса (для разработки и тестирования)
mongodb MongoDB - система управления NoSQL базами (требует установки)

Концепция

Модуль позволяет спроектировать систему связанных данных, доступ к которым осуществляется посредством репозиториев. Каждый репозиторий имеет собственную модель данных, которая описывает структуру документа коллекции, а так же определяет связи к другим моделям.

flowchart LR

A[Datasource]-->B[Data Model]-->С[Repository];

Точка входа

Определения источников и моделей хранятся в экземпляре класса Schema, и первым шагом будет создание данного экземпляра.

import {Schema} from '@e22m4u/js-repository';

const schema = new Schema();

Интерфейс Schema содержит три основных метода:

  • defineDatasource(datasourceDef: object): this - добавить источник
  • defineModel(modelDef: object): this - добавить модель
  • getRepository(modelName: string): Repository - получить репозиторий

Источник данных

Источник описывает способ подключения к базе и используемый адаптер. Если адаптер имеет настройки, то они передаются вместе с объектом определения источника методом defineDatasource, как это показано ниже.

schema.defineDatasource({
  name: 'myMongo', // название нового источника
  adapter: 'mongodb', // название выбранного адаптера
  // настройки адаптера mongodb
  host: '127.0.0.1',
  port: 27017,
  database: 'data'
});

Параметры источника:

  • name: string уникальное название
  • adapter: string выбранный адаптер

При желании можно использовать встроенный адаптер memory, который хранит данные в памяти процесса. У него нет специальных настроек, и он отлично подходит для тестов и прототипирования.

schema.defineDatasource({
  name: 'myMemory', // название источника
  adapter: 'memory', // выбранный адаптер
});

Модель данных

Когда источники определены, можно перейти к описанию моделей данных. Модель может определять как структуру какого-либо объекта, так и являться отражением реальной коллекции базы.

Представьте себе коллекцию торговых точек, у каждой из которых имеются координаты lat и lng. Мы можем заранее определить модель для объекта координат методом defineModel и использовать ее в других коллекциях.

schema.defineModel({
  name: 'latLng', // название новой модели
  properties: { // поля модели
    lat: DataType.NUMBER, // поле широты
    lng: DataType.NUMBER, // поле долготы
  },
});

Параметры модели:

  • name: string уникальное название (обязательно)
  • datasource: string выбранный источник данных
  • properties: object определения полей модели
  • relations: object определения связанных коллекций

Параметр properties принимает объект, ключи которого являются именами полей, а значением тип поля или объект с дополнительными параметрами.

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

schema.defineModel({
  name: 'place',
  datasource: 'myMemory', // выбранный источник данных
  properties: {
    name: DataType.STRING, // поле для названия торговой точки
    location: { // поле для объекта координат
      type: DataType.OBJECT, // допускать только объекты
      model: 'latLng', // определение модели объекта координат
    },
  },
});

В примере выше мы использовали модель latLng как структуру допустимого значения поля location. Возможный документ данной коллекции может выглядеть так:

{
  "id": 1,
  "name": "Burger King",
  "location": {
    "lat": 32.412891,
    "lng": 34.7660061
  }
}

Стоит обратить внимание, что мы могли бы не объявлять параметр properties, при этом теряя возможность проверки данных перед записью в базу.

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, который был объявлен ранее. Наличие источника позволяет получить репозиторий по названию модели.

const rep = schema.getRepository('place');

Методы:

  • create(data, filter = undefined)
  • replaceById(id, data, filter = undefined)
  • patchById(id, data, filter = undefined)
  • patch(data, where = undefined)
  • find(filter = undefined)
  • findOne(filter = undefined)
  • findById(id, filter = undefined)
  • delete(where = undefined)
  • deleteById(id)
  • exists(id)
  • count(where = undefined)

Параметры:

  • id идентификатор документа (первичный ключ)
  • data объект отражающий состав документа
  • where параметры выборки (см. Фильтрация)
  • filter параметры возвращаемого результата (см. Фильтрация)

Фильтрация

Некоторые методы репозитория опционально принимают параметр where для условий выборки и/или параметр filter управляющий возвращаемым значением.

where

Рассмотрим применение параметра where на примере метода patch, который выполняет поиск и обновление документов по определенному условию.

Сигнатура метода patch(data, where = undefined) указывает на два параметра, где первый принимает объект данных, а второй является опциональным объектом выборки.

// вызов метода `patch`
await rep.patch(
  // обновить значения полей
  // "hidden" и "updatedAt"
  {
    hidden: true,
    updatedAt: new Date().toISOString(),
  },
  // только в тех документах, где поле "type"
  // равно значению "article", а "publishedAt"
  // содержит дату более раннюю, чем указана
  // в операторе "lte"
  {
    type: 'article',
    publishedAt: {
      lte: '2023-12-02T14:00:00.000Z',
    }
  },
);

Второй аргумент метода patch предыдущего листинга содержит условия выборки по которым будет выполнен поиск и обновление документов. Полный список возможных операторов сравнения приводится ниже.

{foo: 'bar'} поиск по значению поля foo
{foo: {eq: 'bar'}} оператор равенства eq
{foo: {neq: 'bar'}} оператор неравенства neq
{foo: {gt: 5}} оператор "больше" gt
{foo: {lt: 10}} оператор "меньше" lt
{foo: {gte: 5}} оператор "больше или равно" gte
{foo: {lte: 10}} оператор "меньше или равно" lte
{foo: {inq: ['bar', 'baz']}} равенство одного из значений inq
{foo: {nin: ['bar', 'baz']}} исключение значений массива nin
{foo: {between: [5, 10]}} оператор диапазона between
{foo: {exists: true}} оператор наличия значения exists
{foo: {like: 'bar'}} оператор поиска подстроки like
{foo: {ilike: 'BaR'}} регистронезависимая версия ilike
{foo: {nlike: 'bar'}} оператор исключения подстроки nlike
{foo: {nilike: 'BaR'}} регистронезависимая версия nilike
{foo: {regexp: 'ba.+'}} оператор регулярного выражения regexp
{foo: {regexp: 'ba.+', flags: 'i'}} флаги регулярного выражения

Тесты

npm run test

Лицензия

MIT