README.md 49 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[Источник данных]-->B[Модель A]-->D[Репозиторий А]
A[Источник данных]-->C[Модель Б]-->E[Репозиторий Б]

Настройка

Перед началом работы с коллекцией нужно определить источник и модель данных. Определения хранятся в экземпляре класса 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

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

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

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

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

// объявление модели "city"
schema.defineModel({
  name: 'city', // название новой модели
  properties: { // поля модели
    name: DataType.STRING,
    population: DataType.NUMBER,
  },
});

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

Если модель отражает реальную коллекцию базы, а не является частью другой, то указывая параметр datasource появляется возможность получить репозиторий этой модели (см. Репозиторий), с помощью которого выполняются операции чтения и записи в базу.

schema.defineModel({
  name: 'city',
  datasource: 'myMemory', // выбранный источник
  properties: {
    name: DataType.STRING,
    population: DataType.NUMBER,
  },
});

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

schema.defineModel({
  name: 'city',
  datasource: 'myMemory',
  properties: {
    name: DataType.STRING,
    population: { // расширенные параметры
      type: DataType.NUMBER, // тип значения
      default: 0, // значение по умолчанию
    },
  },
});

Модель может наследовать поля и связи используя параметр base, куда передается название базовой модели. При этом наследуемые настройки можно переопределять не затрагивая родителя.

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

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

Типы данных

  • DataType.ANY разрешено любое значение
  • DataType.STRING только значение типа string
  • DataType.NUMBER только значение типа number
  • DataType.BOOLEAN только значение типа boolean
  • DataType.ARRAY только значение типа array
  • DataType.OBJECT только значение типа object

Параметры поля

  • type: string тип допустимого значения (обязательно)
  • itemType: string тип элемента массива (для type: 'array')
  • model: string модель объекта (для type: 'object')
  • primaryKey: boolean объявить поле первичным ключом
  • columnName: string переопределение названия колонки
  • columnType: string тип колонки (определяется адаптером)
  • required: boolean объявить поле обязательным
  • default: any значение по умолчанию

Репозиторий

Объявление модели имеет необязательный параметр datasource, который указывает на источник хранения данных. Наличие источника позволяет получить репозиторий по названию такой модели. Репозиторий, в свою очередь, предоставляет интерфейс для чтения и записи данных этой модели.

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

const schema = new Schema();

// объявление источника "myMemory"
schema.defineDatasource({
  name: 'myMemory',
  adapter: 'memory',
});

// объявление модели "city"
schema.defineModel({
  name: 'city',
  datasource: 'myMemory', // источник данных
  properties: {
    name: DataType.STRING,
    population: DataType.NUMBER,
  },
});

// получение репозитория модели "city"
const cityRep = schema.getRepository('city');

// записываем новый документ "city"
const city = await cityRep.create({
  name: 'Moscow',
  population: 11980000
});

// выводим результат
console.log(city);
// {
//   "id": 1,
//   "name": "Moscow",
//   "population": 11980000
// }

Методы

  • 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'}} флаги регулярного выражения

filter

Методы репозитория асинхронны и возвращают Promise с некоторым значением. Параметр filter позволяет влиять на это значение более широким образом, чем просто ограничение для затрагиваемых данных. Ниже представлен список опциональных полей объекта filter, одним из которых является where (рассматривался ранее).

  • where объект выборки
  • order указание порядка
  • limit ограничение количества документов
  • skip пропуск документов
  • fields выбор необходимых полей документа
  • include включение связанных данных в результат (см. Связи)

order

Документы могут быть отсортированы по указанным полям и в нужном направлении, где ASC - по умолчанию, и DESC - в обратном порядке.

// на примере метода "find"
const result = await rep.find({
  // упорядочить по полю "featured"
  order: 'featured',
  // по полю "featured" в обратном порядке
  order: 'featured DESC',
  // по нескольким полям в разных направлениях
  order: ['featured ASC', 'publishedAt DESC', 'id'],
});

limit и skip

При использовании метода find может потребоваться комбинация параметров limit и skip для механизма пагинации.

// первый параметр метода `find` принимает
// объект настроек возвращаемого результата
const result = await rep.find({
  limit: 12, // вернуть не более 14и документов
  skip: 24,  // пропустить 24 документа выборки
});

fields

Сократить объем документа можно параметром fields указав необходимый набор полей.

// на примере метода "find"
const result = await rep.find({
  // включить в результат только поле "title"
  fields: 'title',
  // или поле "title", "createdAt" и "featured" 
  fields: ['title', 'createdAt', 'featured'],
  // первичный ключ указывать не обязательно
});

Связи

WIP

Тесты

npm run test

Лицензия

MIT